class AbstractWebView(QWebEngineView): contentLoaded = False loadChanged = Signal(usertypes.LoadEvent) def __init__(self, parent): super().__init__(parent) self.loadStarted.connect(self._loadStarted) self.loadFinished.connect(self._loadFinished) def _loadStarted(self): self.contentLoaded = False self.loadChanged.emit(usertypes.LoadEvent.STARTED) def _loadFinished(self): self.contentLoaded = True self.loadChanged.emit(usertypes.LoadEvent.FINISHED) def load(self, url): self.setUrl(url) def setUrl(self, url): self.loadChanged.emit(usertypes.LoadEvent.BEFORE_LOAD) return super().setUrl(url) def loadProgress(self, progress): return super().loadProgress(progress)
class LWidget(QWidget): onClose = Signal() onDestroyed = Signal() def __init__(self, parent=None): super().__init__(parent) self.setAttribute(Qt.WA_DeleteOnClose, True) self.destroyed.connect(LWidget._onDestroy) @classmethod def _onDestroy(cls): cls.onDestroyed.emit() def closeEvent(self, e): self.onClose.emit(e) super().closeEvent(e)
class Controller(object): mainWindowChanged = Signal() app = None defaultUrl = None _mainWindow: Window = None @classmethod def setMainWindow(cls, window) -> None: cls._mainWindow = window cls.mainWindowChanged.emit(window) @classmethod def setDefaultUrl(cls, url: str) -> None: cls.defaultUrl = url def _onMainWindowChanged(self, window: Window): if self.defaultUrl is not None: window.loadUrl(self.defaultUrl) window.restoreState("main") window.onClose.connect(self._onMainWindowClosed) def _onMainWindowClosed(self): self._mainWindow.saveState("main") def _onWindowAdded(self, window: Window): if self._mainWindow is None and isinstance(window, Window): self.setMainWindow(window) def __init__(self, app): super().__init__() log.controller.debug("Initializing controller") self.app = app # listen for window added signal in case user is late to register their window app.windowAdded.connect(self._onWindowAdded) self.mainWindowChanged.connect(self._onMainWindowChanged) w: list = app.windows if self._mainWindow is not None and len(w) > 0: self.setMainWindow(w[0]) elif self._mainWindow is not None: self._mainWindow.restoreState("main") if getattr(self, "defaultUrl") is not None: self._mainWindow.loadUrl(self.defaultUrl)
class AbstractWebPage(QWebEnginePage): shuttingDown = Signal() _isShuttingDown = False def __init__(self, profile, parent): super().__init__(profile, parent) def shutdown(self): self._isShuttingDown = True self.shuttingDown.emit() def javaScriptAlert(self, securityOrigin, js_msg): """Override javaScriptAlert to use luminos prompts.""" if self._isShuttingDown: return escape_msg = qtutils.version_check('5.11', compiled=False) try: Shared.javascript_alert(securityOrigin, js_msg, abort_on=[self.loadStarted, self.shuttingDown], escape_msg=escape_msg) except Shared.CallSuper: super().javaScriptAlert(securityOrigin, js_msg) def javaScriptConsoleMessage(self, level, message, lineNumber, source): level_map = { QWebEnginePage.InfoMessageLevel: usertypes.JsLogLevel.info, QWebEnginePage.WarningMessageLevel: usertypes.JsLogLevel.warning, QWebEnginePage.ErrorMessageLevel: usertypes.JsLogLevel.error, } Shared.javascript_log_message(level_map[level], source, lineNumber, message) def javaScriptPrompt(self, url, js_msg, defaultValue): """Override javaScriptPrompt to use luminos prompts.""" escape_msg = qtutils.version_check('5.11', compiled=False) if self._isShuttingDown: return (False, "") try: return Shared.javascript_prompt(url, js_msg, defaultValue, abort_on=[self.loadStarted, self.shuttingDown], escape_msg=escape_msg) except Shared.CallSuper: return super().javaScriptPrompt(url, js_msg, defaultValue)
class LWebView(AbstractWebView): toggleDevTools = Signal() def __init__(self, *, private, parent=None): super().__init__(parent) theme_color = self.style().standardPalette().color(QPalette.Base) page = LWebEnginePage(theme_color=theme_color, profile=None, parent=self) self.setPage(page) def contextMenuEvent(self, e): menu = QMenu(self) reloadAction = menu.addAction("Reload") openDevToolsAction = menu.addAction("Open developer tools") action = menu.exec_(self.mapToGlobal(e.pos())) if action == openDevToolsAction: self.toggleDevTools.emit() elif action == reloadAction: self.reload() def shutdown(self): self.page().shutdown() # def createWindow(self, wintype): """Called by Qt when a page wants to create a new window.
class Device: servicesResolved = Signal() connected = Signal() disconnected = Signal() # characteristicChanged = Signal(typing.Any, typing.Any) def __init__(self, mac_address, manager, managed=True, *args, **kwargs): """ Represents a BLE GATT device. This class is intended to be sublcassed with a device-specific implementations that reflect the device's GATT profile. :param mac_address: MAC address of this device :manager: `DeviceManager` that shall manage this device :managed: If False, the created device will not be managed by the device manager Particularly of interest for sub classes of `DeviceManager` who want to decide on certain device properties if they then create a subclass instance of that `Device` or not. """ super().__init__(*args, **kwargs) self.mac_address = mac_address self.manager = manager self.services = [] self._bus = manager._bus self._object_manager = manager._object_manager # TODO: Device needs to be created if it's not yet known to bluetoothd, see "test-device" in bluez-5.43/test/ self._device_path = '/org/bluez/%s/dev_%s' % ( manager.adapter_name, mac_address.replace(':', '_').upper()) device_object = self._bus.get_object('org.bluez', self._device_path) self._object = dbus.Interface(device_object, 'org.bluez.Device1') self._properties = dbus.Interface(self._object, 'org.freedesktop.DBus.Properties') self._properties_signal = None self._connect_retry_attempt = None if managed: manager._manage_device(self) def advertised(self): """ Called when an advertisement package has been received from the device. Requires device discovery to run. """ pass def is_registered(self): # TODO: Implement, see __init__ return False def register(self): # TODO: Implement, see __init__ return def invalidate(self): self._disconnect_signals() def connect(self): """ Connects to the device. Blocks until the connection was successful. """ self._connect_retry_attempt = 0 self._connect_signals() self._connect() def _connect(self): self._connect_retry_attempt += 1 try: self._object.Connect() if not self.services and self.is_services_resolved(): self.services_resolved() except dbus.exceptions.DBusException as e: if (e.get_dbus_name() == 'org.freedesktop.DBus.Error.UnknownObject' ): self.connect_failed( Failed( "Device does not exist, check adapter name and MAC address." )) elif ((e.get_dbus_name() == 'org.bluez.Error.Failed') and (e.get_dbus_message() == "Operation already in progress")): pass elif ( (self._connect_retry_attempt < 5) and (e.get_dbus_name() == 'org.bluez.Error.Failed') and (e.get_dbus_message() == "Software caused connection abort")): self._connect() elif (e.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply'): # TODO: How to handle properly? # Reproducable when we repeatedly shut off Nuimo immediately after its flashing Bluetooth icon appears self.connect_failed(_error_from_dbus_error(e)) else: self.connect_failed(_error_from_dbus_error(e)) def _connect_signals(self): if self._properties_signal is None: self._properties_signal = self._properties.connect_to_signal( 'PropertiesChanged', self.properties_changed) self._connect_service_signals() def _connect_service_signals(self): for service in self.services: service._connect_signals() def connect_succeeded(self): """ Will be called when `connect()` has finished connecting to the device. Will not be called if the device was already connected. """ pass def connect_failed(self, error): """ Called when the connection could not be established. """ self._disconnect_signals() def disconnect(self): """ Disconnects from the device, if connected. """ self._object.Disconnect() def disconnect_succeeded(self): """ Will be called when the device has disconnected. """ self._disconnect_signals() self.services = [] def _disconnect_signals(self): if self._properties_signal is not None: self._properties_signal.remove() self._properties_signal = None self._disconnect_service_signals() def _disconnect_service_signals(self): for service in self.services: service._disconnect_signals() def is_connected(self): """ Returns `True` if the device is connected, otherwise `False`. """ return self._properties.Get('org.bluez.Device1', 'Connected') == 1 def is_services_resolved(self): """ Returns `True` is services are discovered, otherwise `False`. """ return self._properties.Get('org.bluez.Device1', 'ServicesResolved') == 1 def alias(self): """ Returns the device's alias (name). """ try: return self._properties.Get('org.bluez.Device1', 'Alias') except dbus.exceptions.DBusException as e: if e.get_dbus_name() == 'org.freedesktop.DBus.Error.UnknownObject': # BlueZ sometimes doesn't provide an alias, we then simply return `None`. # Might occur when device was deleted as the following issue points out: # https://github.com/blueman-project/blueman/issues/460 return None else: raise _error_from_dbus_error(e) def properties_changed(self, sender, changed_properties, invalidated_properties): """ Called when a device property has changed or got invalidated. """ if 'Connected' in changed_properties: if changed_properties['Connected']: self.connect_succeeded() self.connected.emit() else: self.disconnect_succeeded() self.disconnected.emit() if ('ServicesResolved' in changed_properties and changed_properties['ServicesResolved'] == 1 and not self.services): self.services_resolved() def services_resolved(self): """ Called when all device's services and characteristics got resolved. """ self._disconnect_service_signals() services_regex = re.compile(self._device_path + '/service[0-9abcdef]{4}$') managed_services = [ service for service in self._object_manager.GetManagedObjects().items() if services_regex.match(service[0]) ] self.services = [ Service(device=self, path=service[0], uuid=service[1]['org.bluez.GattService1']['UUID']) for service in managed_services ] self._connect_service_signals() self.servicesResolved.emit() def characteristic_value_updated(self, characteristic, value): """ Called when a characteristic value has changed. """ # self.characteristicChanged.emit(characteristic, value) # To be implemented by subclass pass def characteristic_read_value_failed(self, characteristic, error): """ Called when a characteristic value read command failed. """ # To be implemented by subclass pass def characteristic_write_value_succeeded(self, characteristic): """ Called when a characteristic value write command succeeded. """ # To be implemented by subclass pass def characteristic_write_value_failed(self, characteristic, error): """ Called when a characteristic value write command failed. """ # To be implemented by subclass pass def characteristic_enable_notifications_succeeded(self, characteristic): """ Called when a characteristic notifications enable command succeeded. """ # To be implemented by subclass pass def characteristic_enable_notifications_failed(self, characteristic, error): """ Called when a characteristic notifications enable command failed. """ # To be implemented by subclass pass
class DeviceManager(QObject): # devices = {} aliases = [] deviceDiscovered = Signal(Device) running = False def __init__(self, adapter_name, *args, **kwargs): super().__init__(*args, **kwargs) self.listener = None self.adapter_name = adapter_name self._bus = dbus.SystemBus() try: adapter_object = self._bus.get_object('org.bluez', '/org/bluez/' + adapter_name) except dbus.exceptions.DBusException as e: raise _error_from_dbus_error(e) object_manager_object = self._bus.get_object("org.bluez", "/") self._adapter = dbus.Interface(adapter_object, 'org.bluez.Adapter1') self._adapter_properties = dbus.Interface( self._adapter, 'org.freedesktop.DBus.Properties') self._object_manager = dbus.Interface( object_manager_object, "org.freedesktop.DBus.ObjectManager") self._device_path_regex = re.compile('^/org/bluez/' + adapter_name + '/dev((_[A-Z0-9]{2}){6})$') self._devices = {} self._discovered_devices = {} self._interface_added_signal = None self._properties_changed_signal = None self._main_loop = None self.update_devices() @property def is_adapter_powered(self): return self._adapter_properties.Get('org.bluez.Adapter1', 'Powered') == 1 @is_adapter_powered.setter def is_adapter_powered(self, powered): return self._adapter_properties.Set('org.bluez.Adapter1', 'Powered', dbus.Boolean(powered)) def run(self): """ Starts the main loop that is necessary to receive Bluetooth events from the Bluetooth adapter. This call blocks until you call `stop()` to stop the main loop. """ if self.running: return self._interface_added_signal = self._bus.add_signal_receiver( self._interfaces_added, dbus_interface='org.freedesktop.DBus.ObjectManager', signal_name='InterfacesAdded') # TODO: Also listen to 'interfaces removed' events? self._properties_changed_signal = self._bus.add_signal_receiver( self._properties_changed, dbus_interface=dbus.PROPERTIES_IFACE, signal_name='PropertiesChanged', arg0='org.bluez.Device1', path_keyword='path') def disconnect_signals(): for device in self._devices.values(): device.invalidate() self._properties_changed_signal.remove() self._interface_added_signal.remove() self.running = True # self._main_loop = GObject.MainLoop() # try: # self._main_loop.run() # disconnect_signals() # except Exception: # disconnect_signals() # raise def stop(self): """ Stops the main loop started with `start()` """ if self.running: self.running = False def _manage_device(self, device): existing_device = self._devices.get(device.mac_address) if existing_device is not None: existing_device.invalidate() self._devices[device.mac_address] = device def update_devices(self): managed_objects = self._object_manager.GetManagedObjects().items() possible_mac_addresses = [ self._mac_address(path) for path, _ in managed_objects ] mac_addresses = [m for m in possible_mac_addresses if m is not None] new_mac_addresses = [ m for m in mac_addresses if m not in self._devices ] for mac_address in new_mac_addresses: self.make_device(mac_address) # TODO: Remove devices from `_devices` that are no longer managed, i.e. deleted def devices(self): """ Returns all known Bluetooth devices. """ self.update_devices() return self._devices.values() def start_discovery(self, service_uuids=[]): """Starts a discovery for BLE devices with given service UUIDs. :param service_uuids: Filters the search to only return devices with given UUIDs. """ discovery_filter = {'Transport': 'le'} if service_uuids: # D-Bus doesn't like empty lists, it needs to guess the type discovery_filter['UUIDs'] = service_uuids try: self._adapter.SetDiscoveryFilter(discovery_filter) self._adapter.StartDiscovery() except dbus.exceptions.DBusException as e: if e.get_dbus_name() == 'org.bluez.Error.NotReady': raise NotReady( "Bluetooth adapter not ready. " "Set `is_adapter_powered` to `True` or run 'echo \"power on\" | sudo bluetoothctl'." ) if e.get_dbus_name() == 'org.bluez.Error.InProgress': # Discovery was already started - ignore exception pass else: raise _error_from_dbus_error(e) def stop_discovery(self): """ Stops the discovery started with `start_discovery` """ try: self._adapter.StopDiscovery() except dbus.exceptions.DBusException as e: if (e.get_dbus_name() == 'org.bluez.Error.Failed') and ( e.get_dbus_message() == 'No discovery started'): pass else: raise _error_from_dbus_error(e) def _interfaces_added(self, path, interfaces): self._device_discovered(path, interfaces) def _properties_changed(self, interface, changed, invalidated, path): # TODO: Handle `changed` and `invalidated` properties and update device self._device_discovered(path, [interface]) def _device_discovered(self, path, interfaces): if 'org.bluez.Device1' not in interfaces: return mac_address = self._mac_address(path) if not mac_address: return device = self._devices.get(mac_address) or self.make_device( mac_address) if device is not None: self.device_discovered(device) def device_discovered(self, device): device.advertised() self.deviceDiscovered.emit(device) def _mac_address(self, device_path): match = self._device_path_regex.match(device_path) if not match: return None return match.group(1)[1:].replace('_', ':').lower() def make_device(self, mac_address): """ Makes and returns a `Device` instance with specified MAC address. Override this method to return a specific subclass instance of `Device`. Return `None` if the specified device shall not be supported by this class. """ return Device(mac_address=mac_address, manager=self) def add_device(self, mac_address): """ Adds a device with given MAC address without discovery. """ # TODO: Implement pass def remove_device(self, mac_address): """ Removes a device with the given MAC address """ # TODO: Implement pass def remove_all_devices(self, skip_alias=None): self.update_devices() keys_to_be_deleted = [] for key, device in self._devices.items(): if skip_alias and device.alias() == skip_alias: continue mac_address = device.mac_address.replace(':', '_').upper() path = '/org/bluez/%s/dev_%s' % (self.adapter_name, mac_address) self._adapter.RemoveDevice(path) keys_to_be_deleted.append(key) for key in keys_to_be_deleted: del self._devices[key] self.update_devices()
class AbstractApplication(QApplication): windows: typing.List[AbstractWindow] = [] windowAdded = Signal() windowRemoved = Signal() beforeRun = Signal() def __init__(self, argv, name): qt_argv = utils.cleanArgs(argv) super().__init__(qt_argv) self.aboutToQuit.connect(self._exiting) parser = utils.get_argparser() args = parser.parse_args(argv) log.init_log(args) log.init.debug("Log initialized.") args.name = name if args.version: from luminos.utils import version print(version.version()) sys.exit(0) log.init.debug("Initializing directories...") standarddir.init(args) log.init.debug("Initializing application configuration...") if config.instance is None: self.config = Configuration( path.join(standarddir.config(), "{}.yml".format(name))) else: self.config = config.instance from PyQt5.QtWebEngineWidgets import QWebEnginePage if (hasattr(args, "enable_inspector") and not hasattr( QWebEnginePage, 'setInspectedPage')): # only Qt < 5.11 os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str( utils.random_port()) def _exiting(self): log.config.debug("saving configuration") if self.config is not None: self.config.save() def __init_subclass__(cls, **kwargs): if HAS_PYTHON_3_6: super().__init_subclass__() cls.__pre_init__() @classmethod def __pre_init__(cls): pass def exec_(self): self.beforeRun.emit() return super().exec_() def addWindow(self, window: AbstractWindow) -> None: self.windows.append(window) self.windowAdded.emit(window) def removeWindow(self, window: AbstractWindow) -> None: self.windows.remove(window) self.windowRemoved.emit(window) @classmethod def running(self) -> bool: return QApplication.instance() is not None
class PluginManager(QObject): pluginAdded = Signal() pluginRemoved = Signal() pluginActivated = Signal() pluginDeactivated = Signal() loadStarted = Signal() loadFinished = Signal() beforeLoad = Signal() bridgeInitialize = Signal() def __init__(self, plugins_dirs: list = [], parent=None): super().__init__(parent) log.plugins.debug("Initializing PluginManager") lapp = QApplication.instance() from luminos.Application import Application assert isinstance(lapp, Application) self._plugins = {} self._injector = PluginInjector() self._loadedPlugins = {} self._pluginsResources = {} self._pluginsDirs = plugins_dirs + standarddir.defaultPluginsDir() self.loadStarted.connect(self._loadStarted) self.beforeLoad.connect(self._beforeLoad) self.loadFinished.connect(self._loadFinished) self.bridgeInitialize.connect(self._bridgeInitialize) config.instance.changed.connect(self._pluginsStateChanged) self._loadPlugins() def _bridgeInitialize(self, page): for name, resources in self._pluginsResources.items(): for resource in resources: script_name = name + "_" + os.path.basename(resource) if resource.endswith(".js"): injectionPoint = QWebEngineScript.DocumentReady page.injectScript(resource, script_name, injectionPoint) elif resource.endswith(".css"): injectionPoint = QWebEngineScript.DocumentReady page.injectStylesheet(resource, script_name, injectionPoint) def _beforeLoad(self, channel, page): for name, plugin in self._plugins.items(): if 'beforeLoad' in dir(plugin): plugin.beforeLoad(channel, page) elif 'before_load' in dir(plugin): plugin.before_load(channel, page) def _loadStarted(self, page): """""" for name, plugin in self._plugins.items(): if 'loadStarted' in dir(plugin): plugin.loadStarted(page) elif 'load_started' in dir(plugin): plugin.load_started(page) def _loadFinished(self, page): for name, plugin in self._plugins.items(): if 'loadFinished' in dir(plugin): plugin.loadStarted(page) elif 'load_finished' in dir(plugin): plugin.load_started(page) def addPluginPath(self, path: str): assert os.path.isabs(path) if not path in self._pluginsDirs: self._pluginsDirs.append(path) self._loadPlugins() def _loadPlugin(self, plugin_name): if plugin_name in self._loadedPlugins.keys(): return self._loadedPlugins[plugin_name] identities_paths = [] for directory in self._pluginsDirs: identities_paths += utils.findFiles("*.plugin", directory) module = None for f in identities_paths: info = PluginInfo(f) name = f if info.has_section("plugin") and info.has_option( "plugin", "Name"): name = info.get("plugin", "Name") else: continue if name == plugin_name: if not info.isValid(): log.plugins.debug( f"Plugin identity {name} is not valid, please read documentation " "about how to write plugin.") else: parentdir = os.path.dirname(f) module_path = os.path.join(parentdir, info.get("plugin", "Module")) if (not module_path.endswith(".py")): module_path += ".py" if os.path.exists(module_path): try: module_path = module_path package = "luminos.plugins.{}.{}".format( name, name) spec = importlib.util.spec_from_file_location( package, module_path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) self._loadedPlugins[name] = module except ImportError: log.plugins.error( f"Unable to load plugin module {name}") break else: log.plugins.warning( f"module specified in {name} doesn't exists, it will be ignored." ) return module def _loadPlugins(self): """""" identities_paths = [] for directory in self._pluginsDirs: identities_paths += utils.findFiles("*.plugin", directory) plugins: typing.List[PluginInfo] = [] for f in identities_paths: info = PluginInfo(f) name = f if info.has_section("plugin") and info.has_option( "plugin", "Name"): name = info.get("plugin", "Name") else: continue # if it's already exists it means that user just add a new plugins directory if name in self._loadedPlugins.keys(): continue if not info.isValid(): log.plugins.debug( f"Plugin identity {name} is not valid, please read documentation " "about how to write plugin.") else: parentdir = os.path.dirname(f) module_path = os.path.join(parentdir, info.get("plugin", "Module")) if (not module_path.endswith(".py")): module_path += ".py" if os.path.exists(module_path): info.set("plugin", "Path", module_path) plugins.append(info) else: log.plugins.warning( f"module specified in {f} doesn't exists, it will be ignored." ) log.plugins.info(f"{len(plugins)} plugins found.") for plugin in plugins: try: name = plugin.get("plugin", "Name") module_name = plugin.get("plugin", "Module").replace(".py", "") module_path = plugin.get("plugin", "Path") parentdir = os.path.dirname(module_path) log.plugins.info(f"creating namespace for plugin {name}") # create a fake module for this plugin namespace package = f"{name}" module = types.ModuleType(package) module.__path__ = parentdir self._injector.provide(package, module) # TODO: add support to import module in subfolder # try to load all python file except for the main file and __init__.py for f in utils.findFiles("*.py", parentdir): basename = os.path.splitext(os.path.basename(f))[0] if basename == module_name or basename == '__init__': continue tail = f[len(parentdir + '/'):].replace(os.path.sep, '.').replace( '.py', '') package = f"{name}.{tail}" m_path = f log.plugins.info( f"load external module for plugin {name} with name {module.__name__}" ) spec = importlib.util.spec_from_file_location( package, m_path) module = importlib.util.module_from_spec(spec) module.__path__ = m_path self._injector.provide(package, module) log.plugins.info( f"importing main plugin module for plugin {name}") package = f"{name}.{module_name}" spec = importlib.util.spec_from_file_location( package, module_path) module = importlib.util.module_from_spec(spec) module.__path__ = module_path self._injector.provide(package, module) spec.loader.exec_module(module) self._loadedPlugins[name] = module """ By default plugin will be enabled if there was no plugin configuration. """ cfg = config.instance.get(f"plugins.{name}") shouldActivate = True if cfg is None: shouldActivate = False cfg = dict() cfg['enabled'] = True config.instance.set(f"plugins.{name}.enabled", True) # if this is the first time the plugin is registered code above will trigger _pluginStateChange # and activate it, so we don't need to activate it again here if cfg['enabled'] and shouldActivate: if 'activate' in dir(module): module.activate() self._plugins[name] = module if plugin.has_option("plugin", "Resources"): resources = ast.literal_eval( plugin.get("plugin", "Resources")) base_path = os.path.dirname(module_path) def to_abspath(path: str): if not os.path.isabs(path): return os.path.join(base_path, path) return path resources = list(map(to_abspath, resources)) self._pluginsResources[name] = resources except ImportError as e: name = plugin.get("plugin", "Name") log.plugins.error( f"Unable to load plugin module {name} : ${e.msg}") @config.change_filter("plugins") def _pluginsStateChanged(self, key: str, value): """We only interested with the name and the value""" res = re.findall("plugins\\.(.*)\\.enabled", key) if key.endswith("enabled") and len(res) > 0: name = res[0] if not value: self.disablePlugin(name) elif value: self.enablePlugin(name) def enablePlugin(self, name: str): """""" log.plugins.debug(f"enabling plugin {name}") if not name in self._plugins.keys(): module = self._loadPlugin(name) if module is not None: if "activate" in dir(module): module.activate() self.pluginActivated.emit(name) self._plugins[name] = module self.pluginAdded.emit(name) else: log.plugins.warning(f"Unable activate plugin {name}") def disablePlugin(self, name: str): """""" log.plugins.debug(f"disabling plugin {name}") if name in self._plugins.keys(): module = self._plugins[name] if "deactivate" in dir(module): module.deactivate() self.pluginDeactivated.emit(name) self._plugins.pop(name, None) self.pluginRemoved.emit(name)
class AbstractWindow(QWidget): app = None config = None settings = None onClose = Signal() def __init__(self, application=None): """""" log.init.debug("initializing Window") super().__init__() # self register our window to application from luminos.core.AbstractApplication import AbstractApplication if application is not None and isinstance(application, AbstractApplication): self.app = application self.config = application.config self.app.addWindow(self) self.setWindowFlags(Qt.Window) self.setAttribute(Qt.WA_DeleteOnClose, True) self.setMinimumSize(QSize(640, 480)) def closeEvent(self, e): super().closeEvent(e) self.onClose.emit() # unregister our window from application if self.app is not None: self.app.removeWindow(self) def __init_subclass__(cls, **kwargs): if HAS_PYTHON_3_6: super().__init_subclass__() cls.__pre_init__() @classmethod def __pre_init__(cls): pass def restoreState(self, name: str): width = self.config.get("windows.{}.width".format(name)) height = self.config.get("windows.{}.height".format(name)) if width is not None and height is not None: self.resize(QSize(width, height)) maximized = self.config.get("windows.{}.maximized".format(name)) if maximized is not None: if maximized: self.setWindowState(Qt.WindowMaximized) # move window position to the center of the screen centerPoint = QDesktopWidget().availableGeometry().center() frameGm = self.frameGeometry() frameGm.moveCenter(centerPoint) self.move(frameGm.topLeft()) def saveState(self, name: str): maximized = self.isMaximized() if not maximized: self.config.set("windows.{}.width".format(name), self.width()) self.config.set("windows.{}.height".format(name), self.height()) self.config.set("windows.{}.maximized".format(name), maximized) self.config.save()