merge with plugins
This commit is contained in:
parent
b2fa484bb3
commit
a214ab12c7
12 changed files with 579 additions and 12 deletions
|
@ -8,7 +8,7 @@ from profile import Profile
|
|||
from toxcore_enums_and_consts import *
|
||||
from toxav_enums import *
|
||||
from tox import bin_to_string
|
||||
from ctypes import c_char_p, cast, pointer
|
||||
from plugin_support import PluginLoader
|
||||
|
||||
|
||||
class InvokeEvent(QtCore.QEvent):
|
||||
|
@ -85,6 +85,7 @@ def friend_connection_status(tox, friend_num, new_status, user_data):
|
|||
sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
|
||||
elif friend.status is None:
|
||||
invoke_in_main_thread(profile.send_avatar, friend_num)
|
||||
PluginLoader.get_instance().friend_online(friend_num)
|
||||
|
||||
|
||||
def friend_name(tox, friend_num, name, size, user_data):
|
||||
|
@ -221,13 +222,35 @@ def file_recv_control(tox, friend_number, file_number, file_control, user_data):
|
|||
elif file_control == TOX_FILE_CONTROL['RESUME']:
|
||||
Profile.get_instance().resume_transfer(friend_number, file_number, True)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Callbacks - custom packets
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
def lossless_packet(tox, friend_number, data, length, user_data):
|
||||
"""
|
||||
Incoming lossless packet
|
||||
"""
|
||||
plugin = PluginLoader.get_instance()
|
||||
invoke_in_main_thread(plugin.callback_lossless, friend_number, data, length)
|
||||
|
||||
|
||||
def lossy_packet(tox, friend_number, data, length, user_data):
|
||||
"""
|
||||
Incoming lossy packet
|
||||
"""
|
||||
plugin = PluginLoader.get_instance()
|
||||
invoke_in_main_thread(plugin.callback_lossy, friend_number, data, length)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Callbacks - audio
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def call_state(toxav, friend_number, mask, user_data):
|
||||
"""New call state"""
|
||||
"""
|
||||
New call state
|
||||
"""
|
||||
print friend_number, mask
|
||||
if mask == TOXAV_FRIEND_CALL_STATE['FINISHED'] or mask == TOXAV_FRIEND_CALL_STATE['ERROR']:
|
||||
invoke_in_main_thread(Profile.get_instance().stop_call, friend_number, True)
|
||||
|
@ -236,13 +259,17 @@ def call_state(toxav, friend_number, mask, user_data):
|
|||
|
||||
|
||||
def call(toxav, friend_number, audio, video, user_data):
|
||||
"""Incoming call from friend"""
|
||||
"""
|
||||
Incoming call from friend
|
||||
"""
|
||||
print friend_number, audio, video
|
||||
invoke_in_main_thread(Profile.get_instance().incoming_call, audio, video, friend_number)
|
||||
|
||||
|
||||
def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, audio_channels_count, rate, user_data):
|
||||
"""New audio chunk"""
|
||||
"""
|
||||
New audio chunk
|
||||
"""
|
||||
print audio_samples_per_channel, audio_channels_count, rate
|
||||
Profile.get_instance().call.chunk(
|
||||
''.join(chr(x) for x in samples[:audio_samples_per_channel * 2 * audio_channels_count]),
|
||||
|
@ -282,3 +309,6 @@ def init_callbacks(tox, window, tray):
|
|||
toxav.callback_call(call, 0)
|
||||
toxav.callback_audio_receive_frame(callback_audio, 0)
|
||||
|
||||
tox.callback_friend_lossless_packet(lossless_packet, 0)
|
||||
tox.callback_friend_lossy_packet(lossy_packet, 0)
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ class LoginScreen(CenteredWidget):
|
|||
def update_select(self, data):
|
||||
list_of_profiles = []
|
||||
for elem in data:
|
||||
list_of_profiles.append(self.tr(elem))
|
||||
list_of_profiles.append(elem)
|
||||
self.comboBox.addItems(list_of_profiles)
|
||||
self.create_only = not list_of_profiles
|
||||
|
||||
|
|
10
src/main.py
10
src/main.py
|
@ -15,6 +15,7 @@ import locale
|
|||
import toxencryptsave
|
||||
from passwordscreen import PasswordScreen
|
||||
import profile
|
||||
from plugin_support import PluginLoader
|
||||
|
||||
|
||||
class Toxygen(object):
|
||||
|
@ -191,6 +192,9 @@ class Toxygen(object):
|
|||
self.ms.show()
|
||||
QtGui.QApplication.setStyle(get_style(settings['theme'])) # set application style
|
||||
|
||||
plugin_helper = PluginLoader(self.tox, settings) # plugin support
|
||||
plugin_helper.load()
|
||||
|
||||
# init thread
|
||||
self.init = self.InitThread(self.tox, self.ms, self.tray)
|
||||
self.init.start()
|
||||
|
@ -200,11 +204,13 @@ class Toxygen(object):
|
|||
self.mainloop.start()
|
||||
self.avloop = self.ToxAVIterateThread(self.tox.AV)
|
||||
self.avloop.start()
|
||||
|
||||
app.connect(app, QtCore.SIGNAL("lastWindowClosed()"), app, QtCore.SLOT("quit()"))
|
||||
app.exec_()
|
||||
self.init.stop = True
|
||||
self.mainloop.stop = True
|
||||
self.avloop.stop = True
|
||||
plugin_helper.stop()
|
||||
self.mainloop.wait()
|
||||
self.init.wait()
|
||||
self.avloop.wait()
|
||||
|
@ -239,6 +245,10 @@ class Toxygen(object):
|
|||
|
||||
self.avloop = self.ToxAVIterateThread(self.tox.AV)
|
||||
self.avloop.start()
|
||||
|
||||
plugin_helper = PluginLoader.get_instance()
|
||||
plugin_helper.set_tox(self.tox)
|
||||
|
||||
return self.tox
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
|
|
@ -4,6 +4,7 @@ from menu import *
|
|||
from profile import *
|
||||
from list_items import *
|
||||
from widgets import QRightClickButton
|
||||
import plugin_support
|
||||
|
||||
|
||||
class MessageArea(QtGui.QPlainTextEdit):
|
||||
|
@ -48,12 +49,16 @@ class MainWindow(QtGui.QMainWindow):
|
|||
self.menubar.setMinimumSize(self.width(), 25)
|
||||
self.menubar.setMaximumSize(self.width(), 25)
|
||||
self.menubar.setBaseSize(self.width(), 25)
|
||||
|
||||
self.menuProfile = QtGui.QMenu(self.menubar)
|
||||
self.menuProfile.setObjectName("menuProfile")
|
||||
self.menuSettings = QtGui.QMenu(self.menubar)
|
||||
self.menuSettings.setObjectName("menuSettings")
|
||||
self.menuPlugins = QtGui.QMenu(self.menubar)
|
||||
self.menuPlugins.setObjectName("menuPlugins")
|
||||
self.menuAbout = QtGui.QMenu(self.menubar)
|
||||
self.menuAbout.setObjectName("menuAbout")
|
||||
|
||||
self.actionAdd_friend = QtGui.QAction(MainWindow)
|
||||
self.actionAdd_friend.setObjectName("actionAdd_friend")
|
||||
self.actionProfile_settings = QtGui.QAction(MainWindow)
|
||||
|
@ -71,6 +76,7 @@ class MainWindow(QtGui.QMainWindow):
|
|||
self.actionSettings = QtGui.QAction(MainWindow)
|
||||
self.actionSettings.setObjectName("actionSettings")
|
||||
self.audioSettings = QtGui.QAction(MainWindow)
|
||||
self.pluginData = QtGui.QAction(MainWindow)
|
||||
self.menuProfile.addAction(self.actionAdd_friend)
|
||||
self.menuProfile.addAction(self.actionSettings)
|
||||
self.menuSettings.addAction(self.actionPrivacy_settings)
|
||||
|
@ -78,9 +84,11 @@ class MainWindow(QtGui.QMainWindow):
|
|||
self.menuSettings.addAction(self.actionNotifications)
|
||||
self.menuSettings.addAction(self.actionNetwork)
|
||||
self.menuSettings.addAction(self.audioSettings)
|
||||
self.menuPlugins.addAction(self.pluginData)
|
||||
self.menuAbout.addAction(self.actionAbout_program)
|
||||
self.menubar.addAction(self.menuProfile.menuAction())
|
||||
self.menubar.addAction(self.menuSettings.menuAction())
|
||||
self.menubar.addAction(self.menuPlugins.menuAction())
|
||||
self.menubar.addAction(self.menuAbout.menuAction())
|
||||
|
||||
self.actionAbout_program.triggered.connect(self.about_program)
|
||||
|
@ -91,12 +99,15 @@ class MainWindow(QtGui.QMainWindow):
|
|||
self.actionInterface_settings.triggered.connect(self.interface_settings)
|
||||
self.actionNotifications.triggered.connect(self.notification_settings)
|
||||
self.audioSettings.triggered.connect(self.audio_settings)
|
||||
self.pluginData.triggered.connect(self.plugins_menu)
|
||||
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
||||
|
||||
def languageChange(self, *args, **kwargs):
|
||||
self.retranslateUi()
|
||||
|
||||
def retranslateUi(self):
|
||||
self.menuPlugins.setTitle(QtGui.QApplication.translate("MainWindow", "Plugins", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.pluginData.setText(QtGui.QApplication.translate("MainWindow", "List of plugins", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.menuProfile.setTitle(QtGui.QApplication.translate("MainWindow", "Profile", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.menuSettings.setTitle(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.menuAbout.setTitle(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
@ -358,6 +369,10 @@ class MainWindow(QtGui.QMainWindow):
|
|||
self.n_s = NetworkSettings(self.reset)
|
||||
self.n_s.show()
|
||||
|
||||
def plugins_menu(self):
|
||||
self.p_s = PluginsSettings()
|
||||
self.p_s.show()
|
||||
|
||||
def add_contact(self):
|
||||
self.a_c = AddContact()
|
||||
self.a_c.show()
|
||||
|
@ -431,7 +446,7 @@ class MainWindow(QtGui.QMainWindow):
|
|||
def friend_right_click(self, pos):
|
||||
item = self.friends_list.itemAt(pos)
|
||||
num = self.friends_list.indexFromItem(item).row()
|
||||
friend = Profile.get_instance().get_friend_by_number(num)
|
||||
friend = Profile.get_instance().get_friend(num)
|
||||
settings = Settings.get_instance()
|
||||
allowed = friend.tox_id in settings['auto_accept_from_friends']
|
||||
auto = QtGui.QApplication.translate("MainWindow", 'Disallow auto accept', None, QtGui.QApplication.UnicodeUTF8) if allowed else QtGui.QApplication.translate("MainWindow", 'Allow auto accept', None, QtGui.QApplication.UnicodeUTF8)
|
||||
|
@ -442,6 +457,10 @@ class MainWindow(QtGui.QMainWindow):
|
|||
copy_key_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Copy public key', None, QtGui.QApplication.UnicodeUTF8))
|
||||
auto_accept_item = self.listMenu.addAction(auto)
|
||||
remove_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Remove friend', None, QtGui.QApplication.UnicodeUTF8))
|
||||
submenu = plugin_support.PluginLoader.get_instance().get_menu(self.listMenu, num)
|
||||
if len(submenu):
|
||||
plug = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8))
|
||||
plug.addActions(submenu)
|
||||
self.connect(set_alias_item, QtCore.SIGNAL("triggered()"), lambda: self.set_alias(num))
|
||||
self.connect(remove_item, QtCore.SIGNAL("triggered()"), lambda: self.remove_friend(num))
|
||||
self.connect(copy_key_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_friend_key(num))
|
||||
|
|
78
src/menu.py
78
src/menu.py
|
@ -8,6 +8,7 @@ from util import get_style, curr_directory
|
|||
from widgets import CenteredWidget, DataLabel
|
||||
import pyaudio
|
||||
import toxencryptsave
|
||||
import plugin_support
|
||||
|
||||
|
||||
class AddContact(CenteredWidget):
|
||||
|
@ -311,8 +312,8 @@ class NetworkSettings(CenteredWidget):
|
|||
# recreate tox instance
|
||||
Profile.get_instance().reset(self.reset)
|
||||
self.close()
|
||||
except:
|
||||
pass
|
||||
except Exception as ex:
|
||||
log('Exception in restart: ' + str(ex))
|
||||
|
||||
|
||||
class PrivacySettings(CenteredWidget):
|
||||
|
@ -589,3 +590,76 @@ class AudioSettings(CenteredWidget):
|
|||
settings.audio['input'] = self.in_indexes[self.input.currentIndex()]
|
||||
settings.audio['output'] = self.out_indexes[self.output.currentIndex()]
|
||||
settings.save()
|
||||
|
||||
|
||||
class PluginsSettings(CenteredWidget):
|
||||
|
||||
def __init__(self):
|
||||
super(PluginsSettings, self).__init__()
|
||||
self.initUI()
|
||||
self.center()
|
||||
self.retranslateUi()
|
||||
|
||||
def initUI(self):
|
||||
self.resize(400, 210)
|
||||
self.setMinimumSize(QtCore.QSize(400, 210))
|
||||
self.setMaximumSize(QtCore.QSize(400, 210))
|
||||
self.comboBox = QtGui.QComboBox(self)
|
||||
self.comboBox.setGeometry(QtCore.QRect(30, 10, 340, 30))
|
||||
self.label = QtGui.QLabel(self)
|
||||
self.label.setGeometry(QtCore.QRect(30, 40, 340, 90))
|
||||
self.label.setWordWrap(True)
|
||||
self.button = QtGui.QPushButton(self)
|
||||
self.button.setGeometry(QtCore.QRect(30, 130, 340, 30))
|
||||
self.button.clicked.connect(self.button_click)
|
||||
self.open = QtGui.QPushButton(self)
|
||||
self.open.setGeometry(QtCore.QRect(30, 170, 340, 30))
|
||||
self.open.clicked.connect(self.open_plugin)
|
||||
self.pl_loader = plugin_support.PluginLoader.get_instance()
|
||||
self.update_list()
|
||||
self.comboBox.currentIndexChanged.connect(self.show_data)
|
||||
self.show_data()
|
||||
|
||||
def retranslateUi(self):
|
||||
self.setWindowTitle(QtGui.QApplication.translate('PluginsForm', "Plugins", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.open.setText(QtGui.QApplication.translate('PluginsForm', "Open selected plugin", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
def open_plugin(self):
|
||||
ind = self.comboBox.currentIndex()
|
||||
plugin = self.data[ind]
|
||||
window = self.pl_loader.plugin_window(plugin[-1])
|
||||
if window is not None:
|
||||
self.window = window
|
||||
self.window.show()
|
||||
else:
|
||||
msgBox = QtGui.QMessageBox()
|
||||
text = (QtGui.QApplication.translate("PluginsForm", 'No GUI found for this plugin', None,
|
||||
QtGui.QApplication.UnicodeUTF8))
|
||||
msgBox.setText(text)
|
||||
msgBox.exec_()
|
||||
|
||||
def update_list(self):
|
||||
self.comboBox.clear()
|
||||
data = self.pl_loader.get_plugins_list()
|
||||
self.comboBox.addItems(map(lambda x: x[0], data))
|
||||
self.data = data
|
||||
|
||||
def show_data(self):
|
||||
ind = self.comboBox.currentIndex()
|
||||
plugin = self.data[ind]
|
||||
descr = plugin[2] or QtGui.QApplication.translate("PluginsForm", "No description available", None, QtGui.QApplication.UnicodeUTF8)
|
||||
self.label.setText(descr)
|
||||
if plugin[1]:
|
||||
self.button.setText(QtGui.QApplication.translate("PluginsForm", "Disable plugin", None, QtGui.QApplication.UnicodeUTF8))
|
||||
else:
|
||||
self.button.setText(QtGui.QApplication.translate("PluginsForm", "Enable plugin", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
def button_click(self):
|
||||
ind = self.comboBox.currentIndex()
|
||||
plugin = self.data[ind]
|
||||
self.pl_loader.toggle_plugin(plugin[-1])
|
||||
plugin[1] = not plugin[1]
|
||||
if plugin[1]:
|
||||
self.button.setText(QtGui.QApplication.translate("PluginsForm", "Disable plugin", None, QtGui.QApplication.UnicodeUTF8))
|
||||
else:
|
||||
self.button.setText(QtGui.QApplication.translate("PluginsForm", "Enable plugin", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
|
146
src/plugin_support.py
Normal file
146
src/plugin_support.py
Normal file
|
@ -0,0 +1,146 @@
|
|||
import util
|
||||
import profile
|
||||
import os
|
||||
import imp
|
||||
import inspect
|
||||
import plugins.plugin_super_class as pl
|
||||
import toxencryptsave
|
||||
|
||||
|
||||
class PluginLoader(util.Singleton):
|
||||
|
||||
def __init__(self, tox, settings):
|
||||
self._profile = profile.Profile.get_instance()
|
||||
self._settings = settings
|
||||
self._plugins = {} # dict. key - plugin unique short name, value - tuple (plugin instance, is active)
|
||||
self._tox = tox
|
||||
self._encr = toxencryptsave.LibToxEncryptSave.get_instance()
|
||||
|
||||
def set_tox(self, tox):
|
||||
"""
|
||||
New tox instance
|
||||
"""
|
||||
self._tox = tox
|
||||
for value in self._plugins.values():
|
||||
value[0].set_tox(tox)
|
||||
|
||||
def load(self):
|
||||
"""
|
||||
Load all plugins in plugins folder
|
||||
"""
|
||||
path = util.curr_directory() + '/plugins/'
|
||||
files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))]
|
||||
for fl in files:
|
||||
if fl in ('plugin_super_class.py', '__init__.py') or not fl.endswith('.py'):
|
||||
continue
|
||||
name = fl[:-3] # module name without .py
|
||||
try:
|
||||
module = imp.load_source('plugins.' + name, path + fl) # import plugin
|
||||
except ImportError:
|
||||
util.log('Import error in module ' + name)
|
||||
continue
|
||||
except Exception as ex:
|
||||
util.log('Exception in module ' + name + ' Exception: ' + str(ex))
|
||||
continue
|
||||
for elem in dir(module):
|
||||
obj = getattr(module, elem)
|
||||
if inspect.isclass(obj) and issubclass(obj, pl.PluginSuperClass): # looking for plugin class in module
|
||||
print 'Plugin', elem
|
||||
try: # create instance of plugin class
|
||||
inst = obj(self._tox, self._profile, self._settings, self._encr)
|
||||
autostart = inst.get_short_name() in self._settings['plugins']
|
||||
if autostart:
|
||||
inst.start()
|
||||
except Exception as ex:
|
||||
util.log('Exception in module ' + name + ' Exception: ' + str(ex))
|
||||
continue
|
||||
self._plugins[inst.get_short_name()] = [inst, autostart] # (inst, is active)
|
||||
break
|
||||
|
||||
def callback_lossless(self, friend_number, data, length):
|
||||
"""
|
||||
New incoming custom lossless packet (callback)
|
||||
"""
|
||||
l = data[0] - pl.LOSSLESS_FIRST_BYTE
|
||||
name = ''.join(chr(x) for x in data[1:l + 1])
|
||||
if name in self._plugins and self._plugins[name][1]:
|
||||
self._plugins[name][0].lossless_packet(''.join(chr(x) for x in data[l + 1:length]), friend_number)
|
||||
|
||||
def callback_lossy(self, friend_number, data, length):
|
||||
"""
|
||||
New incoming custom lossy packet (callback)
|
||||
"""
|
||||
l = data[0] - pl.LOSSY_FIRST_BYTE
|
||||
name = ''.join(chr(x) for x in data[1:l + 1])
|
||||
if name in self._plugins and self._plugins[name][1]:
|
||||
self._plugins[name][0].lossy_packet(''.join(chr(x) for x in data[l + 1:length]), friend_number)
|
||||
|
||||
def friend_online(self, friend_number):
|
||||
for elem in self._plugins.values():
|
||||
if elem[1]:
|
||||
elem[0].friend_connected(friend_number)
|
||||
|
||||
def get_plugins_list(self):
|
||||
"""
|
||||
Returns list of all plugins
|
||||
"""
|
||||
result = []
|
||||
for data in self._plugins.values():
|
||||
result.append([data[0].get_name(), # plugin full name
|
||||
data[1], # is enabled
|
||||
data[0].get_description(), # plugin description
|
||||
data[0].get_short_name()]) # key - short unique name
|
||||
return result
|
||||
|
||||
def plugin_window(self, key):
|
||||
"""
|
||||
Return window or None for specified plugin
|
||||
"""
|
||||
return self._plugins[key][0].get_window()
|
||||
|
||||
def toggle_plugin(self, key):
|
||||
"""
|
||||
Enable/disable plugin
|
||||
:param key: plugin short name
|
||||
"""
|
||||
plugin = self._plugins[key]
|
||||
if plugin[1]:
|
||||
plugin[0].stop()
|
||||
else:
|
||||
plugin[0].start()
|
||||
plugin[1] = not plugin[1]
|
||||
if plugin[1]:
|
||||
self._settings['plugins'].append(key)
|
||||
else:
|
||||
self._settings['plugins'].remove(key)
|
||||
self._settings.save()
|
||||
|
||||
def command(self, text):
|
||||
"""
|
||||
New command for plugin
|
||||
"""
|
||||
text = text.strip()
|
||||
name = text.split()[0]
|
||||
if name in self._plugins and self._plugins[name][1]:
|
||||
self._plugins[name][0].command(text[len(name) + 1:])
|
||||
|
||||
def get_menu(self, menu, num):
|
||||
"""
|
||||
Return list of items for menu
|
||||
"""
|
||||
result = []
|
||||
for elem in self._plugins.values():
|
||||
if elem[1]:
|
||||
try:
|
||||
result.extend(elem[0].get_menu(menu, num))
|
||||
except:
|
||||
continue
|
||||
return result
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
App is closing, stop all plugins
|
||||
"""
|
||||
for key in self._plugins.keys():
|
||||
self._plugins[key][0].close()
|
||||
del self._plugins[key]
|
0
src/plugins/__init__.py
Normal file
0
src/plugins/__init__.py
Normal file
224
src/plugins/plugin_super_class.py
Normal file
224
src/plugins/plugin_super_class.py
Normal file
|
@ -0,0 +1,224 @@
|
|||
import os
|
||||
try:
|
||||
from PySide import QtCore, QtGui
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
|
||||
MAX_SHORT_NAME_LENGTH = 5
|
||||
|
||||
LOSSY_FIRST_BYTE = 200
|
||||
|
||||
LOSSLESS_FIRST_BYTE = 160
|
||||
|
||||
|
||||
def path_to_data(name):
|
||||
"""
|
||||
:param name: plugin unique name
|
||||
:return path do plugin's directory
|
||||
"""
|
||||
return os.path.dirname(os.path.realpath(__file__)) + '/' + name + '/'
|
||||
|
||||
|
||||
class PluginSuperClass(object):
|
||||
"""
|
||||
Superclass for all plugins. Plugin is python module with at least one class derived from PluginSuperClass.
|
||||
"""
|
||||
|
||||
def __init__(self, name, short_name, tox=None, profile=None, settings=None, encrypt_save=None):
|
||||
"""
|
||||
:param name: plugin full name
|
||||
:param short_name: plugin unique short name (length of short name should not exceed MAX_SHORT_NAME_LENGTH)
|
||||
:param tox: tox instance
|
||||
:param profile: profile instance
|
||||
:param settings: profile settings
|
||||
:param encrypt_save: LibToxEncryptSave instance.
|
||||
"""
|
||||
self._settings = settings
|
||||
self._profile = profile
|
||||
self._tox = tox
|
||||
name = name.strip()
|
||||
short_name = short_name.strip()
|
||||
if not name or not short_name:
|
||||
raise NameError('Wrong name')
|
||||
self._name = name
|
||||
self._short_name = short_name[:MAX_SHORT_NAME_LENGTH]
|
||||
self._translator = None # translator for plugin's GUI
|
||||
self._encrypt_save = encrypt_save
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Get methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_name(self):
|
||||
"""
|
||||
:return plugin full name
|
||||
"""
|
||||
return self._name
|
||||
|
||||
def get_short_name(self):
|
||||
"""
|
||||
:return plugin unique (short) name
|
||||
"""
|
||||
return self._short_name
|
||||
|
||||
def get_description(self):
|
||||
"""
|
||||
Should return plugin description
|
||||
"""
|
||||
return self.__doc__
|
||||
|
||||
def get_menu(self, menu, row_number):
|
||||
"""
|
||||
This method creates items for menu which called on right click in list of friends
|
||||
:param menu: menu instance
|
||||
:param row_number: number of selected row in list of contacts
|
||||
:return list of QAction's
|
||||
"""
|
||||
return []
|
||||
|
||||
def get_window(self):
|
||||
"""
|
||||
This method should return window for plugins with GUI or None
|
||||
"""
|
||||
return None
|
||||
|
||||
def set_tox(self, tox):
|
||||
"""
|
||||
New tox instance
|
||||
"""
|
||||
self._tox = tox
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Plugin was stopped, started or new command received
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
This method called when plugin was started
|
||||
"""
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
This method called when plugin was stopped
|
||||
"""
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
App is closing
|
||||
"""
|
||||
pass
|
||||
|
||||
def command(self, command):
|
||||
"""
|
||||
New command. On 'help' this method should provide user list of available commands
|
||||
:param command: string with command
|
||||
"""
|
||||
msgbox = QtGui.QMessageBox()
|
||||
title = QtGui.QApplication.translate("PluginWindow", "List of commands for plugin {}", None, QtGui.QApplication.UnicodeUTF8)
|
||||
msgbox.setWindowTitle(title.format(self._name))
|
||||
msgbox.setText(QtGui.QApplication.translate("PluginWindow", "No commands available", None, QtGui.QApplication.UnicodeUTF8))
|
||||
msgbox.exec_()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Translations support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def load_translator(self):
|
||||
"""
|
||||
This method loads translations for GUI
|
||||
"""
|
||||
app = QtGui.QApplication.instance()
|
||||
langs = self._settings.supported_languages()
|
||||
curr_lang = self._settings['language']
|
||||
if curr_lang in map(lambda x: x[0], langs):
|
||||
if self._translator is not None:
|
||||
app.removeTranslator(self._translator)
|
||||
self._translator = QtCore.QTranslator()
|
||||
lang_path = filter(lambda x: x[0] == curr_lang, langs)[0][1]
|
||||
self._translator.load(path_to_data(self._short_name) + lang_path)
|
||||
app.installTranslator(self._translator)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Settings loading and saving
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def load_settings(self):
|
||||
"""
|
||||
This method loads settings of plugin and returns raw data
|
||||
"""
|
||||
with open(path_to_data(self._short_name) + 'settings.json') as fl:
|
||||
data = fl.read()
|
||||
return data
|
||||
|
||||
def save_settings(self, data):
|
||||
"""
|
||||
This method saves plugin's settings to file
|
||||
:param data: string with data
|
||||
"""
|
||||
with open(path_to_data(self._short_name) + 'settings.json', 'wb') as fl:
|
||||
fl.write(data)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Callbacks
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def lossless_packet(self, data, friend_number):
|
||||
"""
|
||||
Incoming lossless packet
|
||||
:param data: string with data
|
||||
:param friend_number: number of friend who sent packet
|
||||
"""
|
||||
pass
|
||||
|
||||
def lossy_packet(self, data, friend_number):
|
||||
"""
|
||||
Incoming lossy packet
|
||||
:param data: string with data
|
||||
:param friend_number: number of friend who sent packet
|
||||
"""
|
||||
pass
|
||||
|
||||
def friend_connected(self, friend_number):
|
||||
"""
|
||||
Friend with specified number is online now
|
||||
"""
|
||||
pass
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Custom packets sending
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def send_lossless(self, data, friend_number):
|
||||
"""
|
||||
This method sends lossless packet to friend
|
||||
Wrapper for self._tox.friend_send_lossless_packet
|
||||
Use it instead of direct using self._tox.friend_send_lossless_packet
|
||||
:return True on success
|
||||
"""
|
||||
if data is None:
|
||||
data = ''
|
||||
try:
|
||||
return self._tox.friend_send_lossless_packet(friend_number,
|
||||
chr(len(self._short_name) + LOSSLESS_FIRST_BYTE) +
|
||||
self._short_name + str(data))
|
||||
except:
|
||||
return False
|
||||
|
||||
def send_lossy(self, data, friend_number):
|
||||
"""
|
||||
This method sends lossy packet to friend
|
||||
Wrapper for self._tox.friend_send_lossy_packet
|
||||
Use it instead of direct using self._tox.friend_send_lossy_packet
|
||||
:return True on success
|
||||
"""
|
||||
if data is None:
|
||||
data = ''
|
||||
try:
|
||||
return self._tox.friend_send_lossy_packet(friend_number,
|
||||
chr(len(self._short_name) + LOSSY_FIRST_BYTE) +
|
||||
self._short_name + str(data))
|
||||
except:
|
||||
return False
|
|
@ -16,6 +16,7 @@ from file_transfers import *
|
|||
import time
|
||||
import calls
|
||||
import avwidgets
|
||||
import plugin_support
|
||||
|
||||
|
||||
class Contact(object):
|
||||
|
@ -383,6 +384,9 @@ class Profile(Contact, Singleton):
|
|||
def get_friend_by_number(self, num):
|
||||
return filter(lambda x: x.number == num, self._friends)[0]
|
||||
|
||||
def get_friend(self, num):
|
||||
return self._friends[num]
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Work with active friend
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
@ -557,7 +561,10 @@ class Profile(Contact, Singleton):
|
|||
Send message to active friend
|
||||
:param text: message text
|
||||
"""
|
||||
if self.is_active_online() and text:
|
||||
if text.startswith('/plugin '):
|
||||
plugin_support.PluginLoader.get_instance().command(text[8:])
|
||||
self._screen.messageEdit.clear()
|
||||
elif self.is_active_online() and text:
|
||||
if text.startswith('/me '):
|
||||
message_type = TOX_MESSAGE_TYPE['ACTION']
|
||||
text = text[4:]
|
||||
|
@ -1024,12 +1031,15 @@ class Profile(Contact, Singleton):
|
|||
st.set_state_changed_handler(item.update)
|
||||
self._messages.scrollToBottom()
|
||||
|
||||
def send_file(self, path):
|
||||
def send_file(self, path, number=None):
|
||||
"""
|
||||
Send file to current active friend
|
||||
:param path: file path
|
||||
:param number: friend_number
|
||||
"""
|
||||
friend_number = self.get_active_number()
|
||||
friend_number = number or self.get_active_number()
|
||||
if self.get_friend_by_number(friend_number).status is None:
|
||||
return
|
||||
st = SendTransfer(path, self._tox, friend_number)
|
||||
self._file_transfers[(friend_number, st.get_file_number())] = st
|
||||
tm = TransferMessage(MESSAGE_OWNER['ME'],
|
||||
|
|
|
@ -79,7 +79,8 @@ class Settings(Singleton, dict):
|
|||
'friends_aliases': [],
|
||||
'typing_notifications': False,
|
||||
'calls_sound': True,
|
||||
'blocked': []
|
||||
'blocked': [],
|
||||
'plugins': []
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue