diff --git a/build/utils.sh b/build/utils.sh index 16165f0..c226561 100755 --- a/build/utils.sh +++ b/build/utils.sh @@ -28,7 +28,6 @@ do_build() { # Create "Zip Application" (cd "${PKGNAME}" \ - && mv main.py __main__.py \ && zip -rq ../"${PKGNAME}.zip" . -x '**__pycache__**' 'resources/*' \ && cd - >/dev/null \ && mkdir -p "${INSTALL_ROOT}${PREFIX}"/{bin,share} \ diff --git a/web-greeter/__main__.py b/web-greeter/__main__.py new file mode 100644 index 0000000..e6a0a8b --- /dev/null +++ b/web-greeter/__main__.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# +# __main__.py +# +# Copyright © 2021 JezerM +# +# This file is part of Web Greeter. +# +# Web Greeter is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Web Greeter is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# The following additional terms are in effect as per Section 7 of the license: +# +# The preservation of all legal notices and author attributions in +# the material or in the Appropriate Legal Notices displayed +# by works containing it is required. +# +# You should have received a copy of the GNU General Public License +# along with Web Greeter; If not, see . + +# Standard lib +import sys +import ruamel.yaml as yaml +import os + +# 3rd-Party Libs +from browser.browser import Browser +from logger import logger +import globals +import config + +if __name__ == '__main__': + globals.greeter = Browser() + greeter = globals.greeter + greeter.run() diff --git a/web-greeter/bridge/Config.py b/web-greeter/bridge/Config.py index a790cef..c9329bc 100644 --- a/web-greeter/bridge/Config.py +++ b/web-greeter/bridge/Config.py @@ -3,6 +3,7 @@ # Config.py # # Copyright © 2017 Antergos +# Copyright © 2021 JezerM # # This file is part of Web Greeter. # @@ -26,17 +27,15 @@ # along with Web Greeter; If not, see . # 3rd-Party Libs -from whither.bridge import ( - BridgeObject, - bridge, - Variant, -) +from browser.bridge import Bridge, BridgeObject +from PyQt5.QtCore import QVariant import gi gi.require_version('LightDM', '1') from gi.repository import LightDM from typing import List +from config import web_greeter_config from . import ( layout_to_dict @@ -56,28 +55,29 @@ def get_layouts(config_layouts: List[str]): class Config(BridgeObject): - noop_signal = bridge.signal() + noop_signal = Bridge.signal() - def __init__(self, config, *args, **kwargs): + def __init__(self, *args, **kwargs): super().__init__(name='Config', *args, **kwargs) - self._branding = config.branding.as_dict() - self._greeter = config.greeter.as_dict() - self._features = config.features.as_dict() - self._layouts = get_layouts(config.layouts) + config = web_greeter_config["config"] + self._branding = config["branding"] + self._greeter = config["greeter"] + self._features = config["features"] + self._layouts = get_layouts(config["layouts"]) - @bridge.prop(Variant, notify=noop_signal) + @Bridge.prop(QVariant, notify=noop_signal) def branding(self): return self._branding - @bridge.prop(Variant, notify=noop_signal) + @Bridge.prop(QVariant, notify=noop_signal) def greeter(self): return self._greeter - @bridge.prop(Variant, notify=noop_signal) + @Bridge.prop(QVariant, notify=noop_signal) def features(self): return self._features - @bridge.prop(Variant, notify=noop_signal) + @Bridge.prop(QVariant, notify=noop_signal) def layouts(self): return self._layouts diff --git a/web-greeter/bridge/Greeter.py b/web-greeter/bridge/Greeter.py index b9c5307..19deb2c 100644 --- a/web-greeter/bridge/Greeter.py +++ b/web-greeter/bridge/Greeter.py @@ -3,6 +3,7 @@ # Greeter.py # # Copyright © 2017 Antergos +# Copyright © 2021 JezerM # # This file is part of Web Greeter. # @@ -27,19 +28,17 @@ # Standard Lib import subprocess -import re import threading # 3rd-Party Libs import gi gi.require_version('LightDM', '1') from gi.repository import LightDM -from whither.bridge import ( - BridgeObject, - bridge, - Variant, -) -from PyQt5.QtCore import QTimer + +from browser.bridge import Bridge, BridgeObject +from PyQt5.QtCore import QVariant, QTimer + +from config import web_greeter_config # This Application from . import ( @@ -51,17 +50,17 @@ from . import ( logger ) -import utils.battery as battery +# import utils.battery as battery LightDMGreeter = LightDM.Greeter() LightDMUsers = LightDM.UserList() def changeBrightness(self, method: str, quantity: int): - if self._config.features.backlight["enabled"] != True: + if self._config["features"]["backlight"]["enabled"] != True: return try: - steps = self._config.features.backlight["steps"] + steps = self._config["features"]["backlight"]["steps"] child = subprocess.run(["xbacklight", method, str(quantity), "-steps", str(steps)]) if child.returncode == 1: raise ChildProcessError("xbacklight returned 1") @@ -72,7 +71,7 @@ def changeBrightness(self, method: str, quantity: int): def getBrightness(self): - if self._config.features.backlight["enabled"] != True: + if self._config["features"]["backlight"]["enabled"] != True: return -1 try: level = subprocess.run(["xbacklight", "-get"], stdout=subprocess.PIPE, @@ -85,30 +84,30 @@ def getBrightness(self): class Greeter(BridgeObject): # LightDM.Greeter Signals - authentication_complete = bridge.signal() - autologin_timer_expired = bridge.signal() - idle = bridge.signal() - reset = bridge.signal() - show_message = bridge.signal(str, LightDM.MessageType, arguments=('text', 'type')) - show_prompt = bridge.signal(str, LightDM.PromptType, arguments=('text', 'type')) + authentication_complete = Bridge.signal() + autologin_timer_expired = Bridge.signal() + idle = Bridge.signal() + reset = Bridge.signal() + show_message = Bridge.signal(str, LightDM.MessageType, arguments=('text', 'type')) + show_prompt = Bridge.signal(str, LightDM.PromptType, arguments=('text', 'type')) - brightness_update = bridge.signal() - battery_update = bridge.signal() + brightness_update = Bridge.signal() + battery_update = Bridge.signal() - noop_signal = bridge.signal() - property_changed = bridge.signal() + noop_signal = Bridge.signal() + property_changed = Bridge.signal() _battery = None - def __init__(self, config, *args, **kwargs): + def __init__(self, *args, **kwargs): super().__init__(name='LightDMGreeter', *args, **kwargs) - self._config = config + self._config = web_greeter_config["config"] self._shared_data_directory = '' - self._themes_directory = config.themes_dir + self._themes_directory = web_greeter_config["app"]["theme_dir"] - if self._config.features.battery == True: - self._battery = battery.Battery() + # if self._config.features.battery == True: + # self._battery = battery.Battery() LightDMGreeter.connect_to_daemon_sync() @@ -149,27 +148,27 @@ class Greeter(BridgeObject): self.property_changed.emit() QTimer().singleShot(300, lambda: _signal.emit(*args)) - @bridge.prop(str, notify=property_changed) + @Bridge.prop(str, notify=property_changed) def authentication_user(self): return LightDMGreeter.get_authentication_user() or '' - @bridge.prop(bool, notify=noop_signal) + @Bridge.prop(bool, notify=noop_signal) def autologin_guest(self): return LightDMGreeter.get_autologin_guest_hint() - @bridge.prop(int, notify=noop_signal) + @Bridge.prop(int, notify=noop_signal) def autologin_timeout(self): return LightDMGreeter.get_autologin_timeout_hint() - @bridge.prop(str, notify=noop_signal) + @Bridge.prop(str, notify=noop_signal) def autologin_user(self): return LightDMGreeter.get_autologin_user_hint() - @bridge.prop(Variant, notify=battery_update) + @Bridge.prop(QVariant, notify=battery_update) def batteryData(self): return battery_to_dict(self._battery) - @bridge.prop(int, notify=brightness_update) + @Bridge.prop(int, notify=brightness_update) def brightness(self): return getBrightness(self) @@ -179,63 +178,63 @@ class Greeter(BridgeObject): args=(self, "-set", quantity)) thread.start() - @bridge.prop(bool, notify=noop_signal) + @Bridge.prop(bool, notify=noop_signal) def can_hibernate(self): return LightDM.get_can_hibernate() - @bridge.prop(bool, notify=noop_signal) + @Bridge.prop(bool, notify=noop_signal) def can_restart(self): return LightDM.get_can_restart() - @bridge.prop(bool, notify=noop_signal) + @Bridge.prop(bool, notify=noop_signal) def can_shutdown(self): return LightDM.get_can_shutdown() - @bridge.prop(bool, notify=noop_signal) + @Bridge.prop(bool, notify=noop_signal) def can_suspend(self): return LightDM.get_can_suspend() - @bridge.prop(bool, notify=noop_signal) + @Bridge.prop(bool, notify=noop_signal) def can_access_brightness(self): - return self._config.features.backlight["enabled"] + return self._config["features"]["backlight"]["enabled"] - @bridge.prop(bool, notify=noop_signal) + @Bridge.prop(bool, notify=noop_signal) def can_access_battery(self): - return self._config.features.battery + return self._config["features"]["battery"] - @bridge.prop(str, notify=noop_signal) + @Bridge.prop(str, notify=noop_signal) def default_session(self): return LightDMGreeter.get_default_session_hint() - @bridge.prop(bool, notify=noop_signal) + @Bridge.prop(bool, notify=noop_signal) def has_guest_account(self): return LightDMGreeter.get_has_guest_account_hint() - @bridge.prop(bool, notify=noop_signal) + @Bridge.prop(bool, notify=noop_signal) def hide_users_hint(self): return LightDMGreeter.get_hide_users_hint() - @bridge.prop(str, notify=noop_signal) + @Bridge.prop(str, notify=noop_signal) def hostname(self): return LightDM.get_hostname() - @bridge.prop(bool, notify=property_changed) + @Bridge.prop(bool, notify=property_changed) def in_authentication(self): return LightDMGreeter.get_in_authentication() - @bridge.prop(bool, notify=property_changed) + @Bridge.prop(bool, notify=property_changed) def is_authenticated(self): return LightDMGreeter.get_is_authenticated() - @bridge.prop(Variant, notify=property_changed) + @Bridge.prop(QVariant, notify=property_changed) def language(self): return language_to_dict(LightDM.get_language()) - @bridge.prop(Variant, notify=noop_signal) + @Bridge.prop(QVariant, notify=noop_signal) def languages(self): return [language_to_dict(lang) for lang in LightDM.get_languages()] - @bridge.prop(Variant, notify=noop_signal) + @Bridge.prop(QVariant, notify=noop_signal) def layout(self): return layout_to_dict(LightDM.get_layout()) @@ -250,117 +249,117 @@ class Greeter(BridgeObject): ) return LightDM.set_layout(LightDM.Layout(**lay)) - @bridge.prop(Variant, notify=noop_signal) + @Bridge.prop(QVariant, notify=noop_signal) def layouts(self): return [layout_to_dict(layout) for layout in LightDM.get_layouts()] - @bridge.prop(bool, notify=noop_signal) + @Bridge.prop(bool, notify=noop_signal) def lock_hint(self): return LightDMGreeter.get_lock_hint() - @bridge.prop(Variant, notify=property_changed) + @Bridge.prop(QVariant, notify=property_changed) def remote_sessions(self): return [session_to_dict(session) for session in LightDM.get_remote_sessions()] - @bridge.prop(bool, notify=noop_signal) + @Bridge.prop(bool, notify=noop_signal) def select_guest_hint(self): return LightDMGreeter.get_select_guest_hint() - @bridge.prop(str, notify=noop_signal) + @Bridge.prop(str, notify=noop_signal) def select_user_hint(self): return LightDMGreeter.get_select_user_hint() or '' - @bridge.prop(Variant, notify=noop_signal) + @Bridge.prop(QVariant, notify=noop_signal) def sessions(self): return [session_to_dict(session) for session in LightDM.get_sessions()] - @bridge.prop(str, notify=noop_signal) + @Bridge.prop(str, notify=noop_signal) def shared_data_directory(self): return self._shared_data_directory - @bridge.prop(bool, notify=noop_signal) + @Bridge.prop(bool, notify=noop_signal) def show_manual_login_hint(self): return LightDMGreeter.get_show_manual_login_hint() - @bridge.prop(bool, notify=noop_signal) + @Bridge.prop(bool, notify=noop_signal) def show_remote_login_hint(self): return LightDMGreeter.get_show_remote_login_hint() - @bridge.prop(str, notify=noop_signal) + @Bridge.prop(str, notify=noop_signal) def themes_directory(self): return self._themes_directory - @bridge.prop(Variant, notify=noop_signal) + @Bridge.prop(QVariant, notify=noop_signal) def users(self): return [user_to_dict(user) for user in LightDMUsers.get_users()] - @bridge.method(str) + @Bridge.method(str) def authenticate(self, username): LightDMGreeter.authenticate(username) self.property_changed.emit() - @bridge.method() + @Bridge.method() def authenticate_as_guest(self): LightDMGreeter.authenticate_as_guest() self.property_changed.emit() - @bridge.method(int) + @Bridge.method(int) def brightnessSet(self, quantity): thread = threading.Thread(target=changeBrightness, args=(self, "-set", quantity)) thread.start() - @bridge.method(int) + @Bridge.method(int) def brightnessIncrease(self, quantity): thread = threading.Thread(target=changeBrightness, args=(self, "-inc", quantity)) thread.start() - @bridge.method(int) + @Bridge.method(int) def brightnessDecrease(self, quantity): thread = threading.Thread(target=changeBrightness, args=(self, "-dec", quantity)) thread.start() - @bridge.method() + @Bridge.method() def cancel_authentication(self): LightDMGreeter.cancel_authentication() self.property_changed.emit() - @bridge.method() + @Bridge.method() def cancel_autologin(self): LightDMGreeter.cancel_autologin() self.property_changed.emit() - @bridge.method(result=bool) + @Bridge.method(result=bool) def hibernate(self): return LightDM.hibernate() - @bridge.method(str) + @Bridge.method(str) def respond(self, response): LightDMGreeter.respond(response) self.property_changed.emit() - @bridge.method(result=bool) + @Bridge.method(result=bool) def restart(self): return LightDM.restart() - @bridge.method(str) + @Bridge.method(str) def set_language(self, lang): if self.is_authenticated: LightDMGreeter.set_language(lang) self.property_changed.emit() - @bridge.method(result=bool) + @Bridge.method(result=bool) def shutdown(self): return LightDM.shutdown() - @bridge.method(str, result=bool) + @Bridge.method(str, result=bool) def start_session(self, session): if not session.strip(): return return LightDMGreeter.start_session_sync(session) - @bridge.method(result=bool) + @Bridge.method(result=bool) def suspend(self): return LightDM.suspend() diff --git a/web-greeter/bridge/ThemeUtils.py b/web-greeter/bridge/ThemeUtils.py index 5f4fdf4..fb614ba 100644 --- a/web-greeter/bridge/ThemeUtils.py +++ b/web-greeter/bridge/ThemeUtils.py @@ -3,6 +3,7 @@ # ThemeUtils.py # # Copyright © 2017 Antergos +# Copyright © 2021 JezerM # # This file is part of Web Greeter. # @@ -31,29 +32,27 @@ from glob import glob import tempfile # 3rd-Party Libs -from whither.bridge import ( - BridgeObject, - bridge, - Variant, -) +from browser.bridge import Bridge, BridgeObject +from PyQt5.QtCore import QVariant +from config import web_greeter_config class ThemeUtils(BridgeObject): - def __init__(self, greeter, config, *args, **kwargs): + def __init__(self, greeter, *args, **kwargs): super().__init__(name='ThemeUtils', *args, **kwargs) - self._config = config + self._config = web_greeter_config self._greeter = greeter self._allowed_dirs = ( - self._config.themes_dir, - self._config.branding.background_images_dir, + self._config["app"]["theme_dir"], + self._config["config"]["branding"]["background_images_dir"], self._greeter.shared_data_directory, tempfile.gettempdir(), ) - @bridge.method(str, bool, result=Variant) + @Bridge.method(str, bool, result=QVariant) def dirlist(self, dir_path, only_images=True): if not dir_path or not isinstance(dir_path, str) or '/' == dir_path: return [] diff --git a/web-greeter/bridge/devtools.py b/web-greeter/bridge/devtools.py new file mode 100644 index 0000000..6c00ca5 --- /dev/null +++ b/web-greeter/bridge/devtools.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# +# devtools.py +# +# Copyright © 2016-2017 Antergos +# +# This file is part of whither. +# +# whither is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# whither is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# The following additional terms are in effect as per Section 7 of the license: +# +# The preservation of all legal notices and author attributions in +# the material or in the Appropriate Legal Notices displayed +# by works containing it is required. +# +# You should have received a copy of the GNU General Public License +# along with whither; If not, see . + +# 3rd-Party Libs +from PyQt5.QtCore import QUrl +from PyQt5.QtWebEngineWidgets import ( + QWebEngineView, + QWebEnginePage, +) + + +class DevTools: + + def __init__(self): + super().__init__() + + self.view = QWebEngineView() + self.page = self.view.page() # type: QWebEnginePage + + self.view.load(QUrl('http://127.0.0.1:12345')) + self.view.show() + diff --git a/web-greeter/browser/__init__.py b/web-greeter/browser/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web-greeter/browser/bridge.py b/web-greeter/browser/bridge.py new file mode 100644 index 0000000..f5c87ad --- /dev/null +++ b/web-greeter/browser/bridge.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# +# bridge.py +# +# Copyright © 2016-2017 Antergos +# +# This file is part of Web Greeter. +# +# Web Greeter is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Web Greeter is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# The following additional terms are in effect as per Section 7 of the license: +# +# The preservation of all legal notices and author attributions in +# the material or in the Appropriate Legal Notices displayed +# by works containing it is required. +# +# You should have received a copy of the GNU General Public License +# along with Web Greeter; If not, see . + +from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty + +class Bridge: + @staticmethod + def method(*args, **kwargs): + return pyqtSlot(*args, **kwargs) + + @staticmethod + def prop(*args, **kwargs): + return pyqtProperty(*args, **kwargs) + + @staticmethod + def signal(*args, **kwargs): + return pyqtSignal(*args, **kwargs) + +class BridgeObject(QObject): + def __init__(self, name: str): + super().__init__(parent=None) + self._name = name diff --git a/web-greeter/browser/browser.py b/web-greeter/browser/browser.py new file mode 100644 index 0000000..a46013f --- /dev/null +++ b/web-greeter/browser/browser.py @@ -0,0 +1,276 @@ +# -*- coding: utf-8 -*- +# +# browser.py +# +# Copyright © 2017 Antergos +# Copyright © 2021 JezerM +# +# This file is part of Web Greeter. +# +# Web Greeter is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Web Greeter is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# The following additional terms are in effect as per Section 7 of the license: +# +# The preservation of all legal notices and author attributions in +# the material or in the Appropriate Legal Notices displayed +# by works containing it is required. +# +# You should have received a copy of the GNU General Public License +# along with Web Greeter; If not, see . + +# Standard lib + +from bridge.devtools import DevTools +import os +from typing import ( + Dict, + Tuple, + TypeVar, +) + +# 3rd-Party Libs +from PyQt5.QtCore import QUrl, Qt, QCoreApplication, QFile +from PyQt5.QtWidgets import QApplication, QDesktopWidget, QMainWindow +from PyQt5.QtWebEngineCore import QWebEngineUrlScheme +from PyQt5.QtWebEngineWidgets import QWebEngineScript, QWebEngineProfile, QWebEngineSettings, QWebEngineView +from PyQt5.QtGui import QColor +from PyQt5.QtWebChannel import QWebChannel + +from browser.error_prompt import WebPage, errorPrompt +from browser.url_scheme import QtUrlSchemeHandler +from browser.interceptor import QtUrlRequestInterceptor + +from logger import logger +from config import web_greeter_config +from bridge import Greeter, Config, ThemeUtils +import resources + +# Typing Helpers +BridgeObjects = Tuple['BridgeObject'] +Url = TypeVar('Url', str, QUrl) + +os.environ["QT_DEVICE_PIXEL_RATIO"] = "0" +os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" +os.environ["QT_SCREEN_SCALE_FACTORS"] = "1" +os.environ["QT_SCALE_FACTOR"] = "1" + +WINDOW_STATES = { + 'NORMAL': Qt.WindowNoState, + 'MINIMIZED': Qt.WindowMinimized, + 'MAXIMIZED': Qt.WindowMaximized, + 'FULLSCREEN': Qt.WindowFullScreen, +} # type: Dict[str, Qt.WindowState] + +DISABLED_SETTINGS = [ + 'PluginsEnabled', # Qt 5.6+ +] + +ENABLED_SETTINGS = [ + 'FocusOnNavigationEnabled', # Qt 5.8+ + 'FullScreenSupportEnabled', # Qt 5.6+ + 'LocalContentCanAccessFileUrls', + 'ScreenCaptureEnabled', # Qt 5.7+ + 'ScrollAnimatorEnabled', + 'FocusOnNavigationEnabled', # Qt 5.11+ +] + +class Application: + app: QApplication + desktop: QDesktopWidget + window: QMainWindow + states = WINDOW_STATES + + def __init__(self): + QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling) + QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) + + self.app = QApplication([]) + self.window = QMainWindow() + self.desktop = self.app.desktop() + + self.window.setAttribute(Qt.WA_DeleteOnClose) + self.window.setWindowTitle("Web Greeter") + + self.window.setWindowFlags(self.window.windowFlags() | Qt.FramelessWindowHint) + + self.window.setWindowFlags( + self.window.windowFlags() | Qt.MaximizeUsingFullscreenGeometryHint + ) + + state = self.states['NORMAL'] + try: + self.window.windowHandle().setWindowState(state) + except Exception: + self.window.setWindowState(state) + + self.window.setCursor(Qt.ArrowCursor) + + self.app.aboutToQuit.connect(self._before_exit) + + def _before_exit(self): + pass + + def show(self): + self.window.show() + logger.debug("Window is ready") + + def run(self) -> int: + logger.debug("Web Greeter started") + return self.app.exec_() + + +class Browser(Application): + url_scheme: QWebEngineUrlScheme + + def __init__(self): + super().__init__() + self.init() + self.load() + + def init(self): + logger.debug("Initializing Browser Window") + web_greeter_config["config"]["greeter"]["debug_mode"] = True + + if web_greeter_config["config"]["greeter"]["debug_mode"]: + os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = '12345' + + url_scheme = "web-greeter" + self.url_scheme = QWebEngineUrlScheme(url_scheme.encode()) + self.url_scheme.setDefaultPort(QWebEngineUrlScheme.SpecialPort.PortUnspecified) + self.url_scheme.setFlags(QWebEngineUrlScheme.Flag.SecureScheme or + QWebEngineUrlScheme.Flag.LocalScheme or + QWebEngineUrlScheme.Flag.LocalAccessAllowed) + QWebEngineUrlScheme.registerScheme(self.url_scheme) + + self.profile = QWebEngineProfile.defaultProfile() + self.interceptor = QtUrlRequestInterceptor(url_scheme) + self.url_scheme_handler = QtUrlSchemeHandler() + + self.view = QWebEngineView(parent=self.window) + self.page = WebPage() + self.view.setPage(self.page) + + self.channel = QWebChannel(self.page) + self.bridge_initialized = False + + self.profile.installUrlSchemeHandler(url_scheme.encode(), self.url_scheme_handler) + + self._initialize_page() + + if web_greeter_config["config"]["greeter"]["debug_mode"]: + self.devtools = DevTools() + + if web_greeter_config["config"]["greeter"]["secure_mode"]: + self.profile.setUrlRequestInterceptor(self.interceptor) + + self.page.setBackgroundColor(QColor(0, 0, 0)) + + self.view.show() + self.window.setCentralWidget(self.view) + + logger.debug("Browser Window created") + + self.show() + + def load(self): + self.greeter = Greeter() + self.greeter_config = Config() + self.theme_utils = ThemeUtils(self.greeter) + + self.bridge_objects = (self.greeter, self.greeter_config, self.theme_utils) + self.initialize_bridge_objects() + self.load_script(':/_greeter/js/bundle.js', 'Web Greeter Bundle') + self.load_theme() + + def _initialize_page(self): + page_settings = self.page.settings().globalSettings() + + if not web_greeter_config["config"]["greeter"]["secure_mode"]: + ENABLED_SETTINGS.append('LocalContentCanAccessRemoteUrls') + else: + DISABLED_SETTINGS.append('LocalContentCanAccessRemoteUrls') + + for setting in DISABLED_SETTINGS: + try: + page_settings.setAttribute(getattr(QWebEngineSettings, setting), False) + except AttributeError: + pass + + for setting in ENABLED_SETTINGS: + try: + page_settings.setAttribute(getattr(QWebEngineSettings, setting), True) + except AttributeError: + pass + + self.page.setView(self.view) + + def load_theme(self): + theme = web_greeter_config["config"]["greeter"]["theme"] + dir = "/usr/share/web-greeter/themes/" + path_to_theme = os.path.join(dir, theme, "index.html") + def_theme = "gruvbox" + + if (theme.startswith("/")): path_to_theme = theme + elif (theme.__contains__(".") or theme.__contains__("/")): + path_to_theme = os.path.join(os.getcwd(), theme) + + if (not path_to_theme.endswith(".html")): + path_to_theme = os.path.join(path_to_theme, "index.html") + + if (not os.path.exists(path_to_theme)): + print("Path does not exists") + path_to_theme = os.path.join(dir, def_theme, "index.html") + + url = QUrl("web-greeter://app/{0}".format(path_to_theme)) + self.page.load(url) + + logger.debug("Theme loaded") + + @staticmethod + def _create_webengine_script(path: Url, name: str) -> QWebEngineScript: + script = QWebEngineScript() + script_file = QFile(path) + + # print(script_file, path) + + if script_file.open(QFile.ReadOnly): + script_string = str(script_file.readAll(), 'utf-8') + + script.setInjectionPoint(QWebEngineScript.DocumentCreation) + script.setName(name) + script.setWorldId(QWebEngineScript.MainWorld) + script.setSourceCode(script_string) + # print(script_string) + + return script + + def _get_channel_api_script(self) -> QWebEngineScript: + return self._create_webengine_script(':/qtwebchannel/qwebchannel.js', 'QWebChannel API') + + def _init_bridge_channel(self) -> None: + self.page.setWebChannel(self.channel) + self.page.scripts().insert(self._get_channel_api_script()) + self.bridge_initialized = True + + def initialize_bridge_objects(self) -> None: + if not self.bridge_initialized: + self._init_bridge_channel() + registered_objects = self.channel.registeredObjects() + + for obj in self.bridge_objects: + if obj not in registered_objects: + self.channel.registerObject(obj._name, obj) + # print("Registered", obj._name) + + def load_script(self, path: Url, name: str): + script = self._create_webengine_script(path, name) + self.page.scripts().insert(script) + diff --git a/web-greeter/browser/error_prompt.py b/web-greeter/browser/error_prompt.py new file mode 100644 index 0000000..1a38cf4 --- /dev/null +++ b/web-greeter/browser/error_prompt.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- +# +# error_prompt.py +# +# Copyright © 2021 JezerM +# +# This file is part of Web Greeter. +# +# Web Greeter is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Web Greeter is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# The following additional terms are in effect as per Section 7 of the license: +# +# The preservation of all legal notices and author attributions in +# the material or in the Appropriate Legal Notices displayed +# by works containing it is required. +# +# You should have received a copy of the GNU General Public License +# along with Web Greeter; If not, see . + +# Standard lib + +# 3rd-Party Libs +from PyQt5.QtWebEngineWidgets import QWebEnginePage +from PyQt5.QtWidgets import QDialogButtonBox, QDialog, QVBoxLayout, QLabel, QPushButton +from config import web_greeter_config + +import globals +from logging import ( + getLogger, + DEBUG, + Formatter, + StreamHandler, +) + +log_format = ''.join([ + '%(asctime)s [ %(levelname)s ] %(filename)s %(', + 'lineno)d: %(message)s' +]) +formatter = Formatter(fmt=log_format, datefmt="%Y-%m-%d %H:%M:%S") +logger = getLogger("javascript") +logger.propagate = False +stream_handler = StreamHandler() +stream_handler.setLevel(DEBUG) +stream_handler.setFormatter(formatter) +logger.setLevel(DEBUG) +logger.addHandler(stream_handler) + +class WebPage(QWebEnginePage): + + def javaScriptConsoleMessage(self, level: QWebEnginePage.JavaScriptConsoleMessageLevel, message: str, lineNumber: int, sourceID: str): + if sourceID == "": + sourceID = "console" + + logLevel = 0 + if level == WebPage.JavaScriptConsoleMessageLevel.ErrorMessageLevel: + logLevel = 40 + elif level == WebPage.JavaScriptConsoleMessageLevel.WarningMessageLevel: + logLevel = 30 + elif level == WebPage.JavaScriptConsoleMessageLevel.InfoMessageLevel: + return + else: + return + + record = logger.makeRecord( + name="javascript", + level=logLevel, + fn="", + lno=lineNumber, + msg=message, + args=(), + exc_info=None + ) + record.filename = sourceID + logger.handle(record) + + if logLevel == 40: + errorMessage = "{source} {line}: {msg}".format( + source=sourceID, line=lineNumber, msg=message) + errorPrompt(errorMessage) + +class ErrorDialog(QDialog): + def __init__(self, parent=None, err=""): + super().__init__(parent) + + self.setWindowTitle("Error") + + self.buttonBox = QDialogButtonBox() + cancelBtn = QPushButton("Cancel") + defaultBtn = QPushButton("Set default theme") + reloadBtn = QPushButton("Reload theme") + + reloadBtn.clicked.connect(self.handle_reload) + + self.buttonBox.addButton(defaultBtn, QDialogButtonBox.ButtonRole.AcceptRole) + self.buttonBox.addButton(reloadBtn, QDialogButtonBox.ButtonRole.ResetRole) + self.buttonBox.addButton(cancelBtn, QDialogButtonBox.ButtonRole.RejectRole) + + self.buttonBox.accepted.connect(self.accept) + self.buttonBox.rejected.connect(self.reject) + + self.layout = QVBoxLayout() + message = QLabel("An error ocurred. Do you want to change to default theme?") + err = QLabel(err) + self.layout.addWidget(message) + self.layout.addWidget(err) + self.layout.addWidget(self.buttonBox) + self.setLayout(self.layout) + + def handle_reload(self, value: bool): + self.done(2) + + +def errorPrompt(err): + if not web_greeter_config["config"]["greeter"]["detect_theme_errors"]: + return + + dia = ErrorDialog(globals.greeter.window, err) + + dia.exec() + result = dia.result() + + if result == 0: # Cancel + return + elif result == 1: # Default theme + web_greeter_config["config"]["greeter"]["theme"] = "gruvbox" + globals.greeter.load_theme() + return + elif result == 2: # Reload + globals.greeter.load_theme() + return + + return diff --git a/web-greeter/browser/interceptor.py b/web-greeter/browser/interceptor.py new file mode 100644 index 0000000..f5d7307 --- /dev/null +++ b/web-greeter/browser/interceptor.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# +# interceptor.py +# +# Copyright © 2016-2017 Antergos +# +# This file is part of Web Greeter. +# +# Web Greeter is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Web Greeter is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# The following additional terms are in effect as per Section 7 of the license: +# +# The preservation of all legal notices and author attributions in +# the material or in the Appropriate Legal Notices displayed +# by works containing it is required. +# +# You should have received a copy of the GNU General Public License +# along with Web Greeter; If not, see . + +# 3rd-Party Libs +from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor, QWebEngineUrlRequestInfo + +class QtUrlRequestInterceptor(QWebEngineUrlRequestInterceptor): + + def __init__(self, url_scheme: str): + super().__init__() + self._url_scheme = url_scheme + + def intercept_request(self, info: QWebEngineUrlRequestInfo) -> None: + url = info.requestUrl().toString() + not_webg_uri = self._url_scheme != info.requestUrl().scheme() + not_data_uri = 'data' != info.requestUrl().scheme() + not_local_file = not info.requestUrl().isLocalFile() + + # print(url) + + not_devtools = ( + not url.startswith('http://127.0.0.1') and not url.startswith('ws://127.0.0.1') + ) + + block_request = not_devtools and not_data_uri and not_webg_uri and not_local_file + + info.block(block_request) # Block everything that is not allowed + + def interceptRequest(self, info: QWebEngineUrlRequestInfo) -> None: + self.intercept_request(info) + diff --git a/web-greeter/browser/url_scheme.py b/web-greeter/browser/url_scheme.py new file mode 100644 index 0000000..b3216f6 --- /dev/null +++ b/web-greeter/browser/url_scheme.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# +# url_scheme.py +# +# Copyright © 2016-2018 Antergos +# Copyright © 2021 JezerM +# +# This file is part of Web Greeter. +# +# Web Greeter is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Web Greeter is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# The following additional terms are in effect as per Section 7 of the license: +# +# The preservation of all legal notices and author attributions in +# the material or in the Appropriate Legal Notices displayed +# by works containing it is required. +# +# You should have received a copy of the GNU General Public License +# along with Web Greeter; If not, see . + +""" Custom Url Scheme Handler """ + +# Standard Lib +import os +import mimetypes + +# 3rd-Party Libs +from PyQt5.QtCore import QBuffer, QIODevice +from PyQt5.QtWebEngineCore import QWebEngineUrlSchemeHandler, QWebEngineUrlRequestJob + + +class QtUrlSchemeHandler(QWebEngineUrlSchemeHandler): + + def requestStarted(self, job: QWebEngineUrlRequestJob) -> None: + path = job.requestUrl().path() + path = os.path.realpath(path) + + # print("PATH", job.requestUrl().path()) + + if not path: + # print("JOB FAIL", path) + job.fail(QWebEngineUrlRequestJob.UrlInvalid) + return + + if not os.path.exists(path): + # print("NOT FOUND", path) + job.fail(QWebEngineUrlRequestJob.UrlNotFound) + return + + try: + with open(path, 'rb') as file: + content_type = mimetypes.guess_type(path) + if not content_type[0]: + content_type = ["text/plain", None] + buffer = QBuffer(parent=self) + + buffer.open(QIODevice.WriteOnly) + buffer.write(file.read()) + buffer.seek(0) + buffer.close() + + job.reply(content_type[0].encode(), buffer) + + except Exception as err: + raise err + diff --git a/web-greeter/config.py b/web-greeter/config.py new file mode 100644 index 0000000..a40cfb1 --- /dev/null +++ b/web-greeter/config.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# +# config.py +# +# Copyright © 2021 JezerM +# +# This file is part of Web Greeter. +# +# Web Greeter is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Web Greeter is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# The following additional terms are in effect as per Section 7 of the license: +# +# The preservation of all legal notices and author attributions in +# the material or in the Appropriate Legal Notices displayed +# by works containing it is required. +# +# You should have received a copy of the GNU General Public License +# along with Web Greeter; If not, see . +# Standard lib + +import sys +import os +import ruamel.yaml as yaml + +import globals +from logger import logger + +path_to_config = "/etc/lightdm/web-greeter.yml" + +global web_greeter_config +web_greeter_config = { + "config": { + "branding": { + "background_images_dir": "/usr/share/backgrounds", + "logo_image": "", + "user_image": "", + }, + "greeter": { + "debug_mode": False, + "detect_theme_errors": True, + "screensaver_timeout": 300, + "secure_mode": True, + "theme": "gruvbox", + "icon_theme": None, + "time_language": None, + }, + "layouts": ["us", "latam"], + "features": { + "battery": False, + "backlight": { + "enabled": False, + "value": 10, + "steps": 0, + } + } + }, + "app": { + "fullscreen": True, + "frame": False, + "debug_mode": False, + "theme_dir": "/usr/share/web-greeter/themes/" + } +} + +def load_config(): + try: + if (not os.path.exists(path_to_config)): + raise Exception("Config file not found") + file = open(path_to_config, "r") + web_greeter_config["config"] = yaml.safe_load(file) + except Exception as err: + logger.error("Config was not loaded:\n\t{0}".format(err)) + pass + +load_config() diff --git a/web-greeter/globals.py b/web-greeter/globals.py index 3ed597c..4ce29d8 100644 --- a/web-greeter/globals.py +++ b/web-greeter/globals.py @@ -1,222 +1 @@ -# -*- coding: utf-8 -*- -# -# globals.py -# -# Copyright © 2017 Antergos -# -# This file is part of Web Greeter. -# -# Web Greeter is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# Web Greeter is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# The following additional terms are in effect as per Section 7 of the license: -# -# The preservation of all legal notices and author attributions in -# the material or in the Appropriate Legal Notices displayed -# by works containing it is required. -# -# You should have received a copy of the GNU General Public License -# along with Web Greeter; If not, see . - -import sys -import pkg_resources -import os -from typing import ( - ClassVar, - Type, - List, - Tuple, -) - -# 3rd-Party Libs -from whither.app import App -from whither.base.config_loader import ConfigLoader -from whither.bridge import BridgeObject - -# This Application -import resources -from bridge import ( - Config, - Greeter, - ThemeUtils, -) -from logging import ( - getLogger, - DEBUG, - ERROR, - Formatter, - StreamHandler, -) - -from PyQt5.QtWidgets import QMainWindow -from PyQt5.QtGui import QColor -from PyQt5.QtCore import QResource -import subprocess - -from utils import theme - -# Typing Helpers -BridgeObj = Type[BridgeObject] - - -log_format = ''.join([ - '%(asctime)s [ %(levelname)s ] %(module)s - %(filename)s:%(', - 'lineno)d : %(funcName)s | %(message)s' -]) -formatter = Formatter(fmt=log_format, datefmt="%Y-%m-%d %H:%M:%S") -stream_handler = StreamHandler() -logger = getLogger("debug") - -stream_handler.setLevel(DEBUG) -stream_handler.setFormatter(formatter) -logger.propagate = False -logger.setLevel(DEBUG) -logger.addHandler(stream_handler) - -initial_timeout = 0 - - -def setScreenSaver(timeout: int): - global initial_timeout - timeout = timeout if timeout >= 0 else 300 - try: - child = subprocess.Popen(["xset", "q"], stdout=subprocess.PIPE, - stderr=subprocess.PIPE, text=True) - awk = subprocess.run( - ["awk", "/^ timeout: / {print $2}"], stdout=subprocess.PIPE, stdin=child.stdout, text=True) - - initial_timeout = int(awk.stdout.replace("\n", "")) - - subprocess.run(["xset", "s", str(timeout)], check=True) - - except Exception as err: - logger.error("Screensaver timeout couldn't be set") - else: - logger.debug("Screensaver timeout set") - - -def resetScreenSaver(): - try: - subprocess.run(["xset", "s", str(initial_timeout)]) - except Exception as err: - logger.error("Screensaver reset failed") - else: - logger.debug("Screensaver reset") - - -def getDefaultCursor(): - cursor_theme = "" - try: - child = subprocess.Popen(["cat", "/usr/share/icons/default/index.theme"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) - awk = subprocess.run( - ["awk", "-F=", "/Inherits/ {print $2}"], stdout=subprocess.PIPE, stdin=child.stdout, text=True) - cursor_theme = awk.stdout.replace("\n", "") - except Exception as err: - logger.error("Default cursor couldn't be get") - return cursor_theme - - -def loadStyle(path): - file = QResource(path) - file = file.uncompressedData() - file = str(file.data(), encoding='utf-8') - return file - - -BASE_DIR = os.path.dirname(os.path.realpath(__file__)) -CONFIG_FILE = os.path.join(BASE_DIR, 'whither.yml') - - -class WebGreeter(App): - greeter = None # type: ClassVar[BridgeObj] | None - greeter_config = None # type: ClassVar[BridgeObj] | None - theme_utils = None # type: ClassVar[BridgeObj] | None - - def __init__(self, *args, **kwargs) -> None: - super().__init__('WebGreeter', *args, **kwargs) - self.logger.debug('Web Greeter started.') - self.greeter = Greeter(self.config) - self.greeter_config = Config(self.config) - self.theme_utils = ThemeUtils(self.greeter, self.config) - self._web_container.bridge_objects = (self.greeter, self.greeter_config, self.theme_utils) - - style = loadStyle(":/_greeter/css/style.css") - self._main_window.widget.setStyleSheet(style) - page = self._main_window.widget.centralWidget().page() - page.setBackgroundColor(QColor(0, 0, 0)) - - setScreenSaver(self.config.greeter["screensaver_timeout"]) - - self._web_container.initialize_bridge_objects() - self._web_container.load_script(':/_greeter/js/bundle.js', 'Web Greeter Bundle') - self.load_theme() - - @classmethod - def __pre_init__(cls): - ConfigLoader.add_filter(cls.validate_greeter_config_data) - - def _before_main_window_init(self): - self.get_and_apply_user_config() - - def _before_exit(self): - resetScreenSaver() - - @classmethod - def validate_greeter_config_data(cls, key: str, data: str) -> str: - if "'@" not in data: - return data - - if 'WebGreeter' == key: - path = '../build/web-greeter/whither.yml' - else: - path = '../build/dist/web-greeter.yml' - - return open(path, 'r').read() - - def get_and_apply_user_config(self): - self.logger.debug("Aplying config") - config_file = os.path.join(self.config.config_dir, 'web-greeter.yml') - branding_config = ConfigLoader('branding', config_file).config - greeter_config = ConfigLoader('greeter', config_file).config - features_config = ConfigLoader('features', config_file).config - layouts_config = ConfigLoader('layouts', config_file).config - - greeter_config.update(custom_config["app"]["greeter"]) - - self.config.branding.update(branding_config) - self.config.greeter.update(greeter_config) - self.config.features.update(features_config) - self.config.layouts = layouts_config - - cursor_theme = greeter_config["icon_theme"] - os.environ["XCURSOR_THEME"] = cursor_theme if cursor_theme != None else getDefaultCursor() - - self._config.debug_mode = greeter_config['debug_mode'] - self._config.allow_remote_urls = not greeter_config['secure_mode'] - self._config.context_menu.enabled = greeter_config['debug_mode'] - self._config.window.update(custom_config["whither"]["window"]) - - def load_theme(self): - self.logger.debug('Loading theme...') - theme_url = theme.checkTheme(self) - self._web_container.load(theme_url) - - -global custom_config -global greeter -custom_config = { - "whither": { - "window": {} - }, - "app": { - "greeter": {} - } -} +global greeter # type: Browser diff --git a/web-greeter/logger.py b/web-greeter/logger.py new file mode 100644 index 0000000..a409a3b --- /dev/null +++ b/web-greeter/logger.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# +# logger.py +# +# Copyright © 2017 Antergos +# Copyright © 2021 JezerM +# +# This file is part of Web Greeter. +# +# Web Greeter is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Web Greeter is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# The following additional terms are in effect as per Section 7 of the license: +# +# The preservation of all legal notices and author attributions in +# the material or in the Appropriate Legal Notices displayed +# by works containing it is required. +# +# You should have received a copy of the GNU General Public License +# along with Web Greeter; If not, see . + +from logging import ( + getLogger, + DEBUG, + Formatter, + StreamHandler +) + +log_format = ''.join([ + '%(asctime)s [ %(levelname)s ] %(module)s - %(filename)s:%(', + 'lineno)d : %(funcName)s | %(message)s' +]) +formatter = Formatter(fmt=log_format, datefmt="%Y-%m-%d %H:%M:%S") +stream_handler = StreamHandler() + +global logger +logger = getLogger("debug") + +stream_handler.setLevel(DEBUG) +stream_handler.setFormatter(formatter) +logger.propagate = False +logger.setLevel(DEBUG) +logger.addHandler(stream_handler) diff --git a/web-greeter/main.py b/web-greeter/main.py deleted file mode 100644 index 064dd33..0000000 --- a/web-greeter/main.py +++ /dev/null @@ -1,176 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: utf-8 -*- -# -# main.py -# -# Copyright © 2021 JezerM -# -# This file is part of Web Greeter. -# -# Web Greeter is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# Web Greeter is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# The following additional terms are in effect as per Section 7 of the license: -# -# The preservation of all legal notices and author attributions in -# the material or in the Appropriate Legal Notices displayed -# by works containing it is required. -# -# You should have received a copy of the GNU General Public License -# along with Web Greeter; If not, see . - -# Standard Lib -import sys -import ruamel.yaml as yaml -import pkg_resources -import os -from typing import ( List ) - -# 3rd-Party Libs - -# This Application -from utils import errorPrompt, keyboard - -import globals -from globals import WebGreeter, logger - - -def loadWhitherConf(): - global whither_yaml - global webGreeter_conf - global file_test - try: - file_test = pkg_resources.resource_string("__main__", 'whither.yml').decode('utf-8') - except Exception: - file_test = pkg_resources.resource_string(__file__, 'whither.yml').decode('utf-8') - - whither_yaml = yaml.safe_load(file_test) - webGreeter_conf = whither_yaml["WebGreeter"] - - -def show_help(): - version = webGreeter_conf["app"]["version"]["full"] - help_text = """Usage: - web-greeter [OPTION...] - LightDM Web Greeter - - --debug Runs the greeter in debug mode - --normal Runs in non-debug mode - - --list Lists available themes - --theme Sets the theme to use - - -h, --help Show this help list - -v, --version Print program version""".format( - version = version -) - print(help_text) - - -def show_version(): - version = webGreeter_conf["app"]["version"]["full"] - print("{version}".format(version = version)) - -def changeConfig(option: str, value): - custom_config[option] = value - return - - -def debugMode(value: bool): - window = dict(custom_config["whither"]["window"]) - greeter = dict(custom_config["app"]["greeter"]) - if value: - greeter["debug_mode"] = True - window["decorated"] = True - window["stays_on_top"] = False - window["initial_state"] = "normal" - else: - greeter["debug_mode"] = False - window["decorated"] = False - window["stays_on_top"] = True - custom_config["whither"]["window"] = window - custom_config["app"]["greeter"] = greeter - - -def changeTheme(theme: str): - custom_config["app"]["greeter"]["theme"] = theme - - -def listThemes(quiet = False): - themes_dir = webGreeter_conf["app"]["themes_dir"] - themes_dir = themes_dir if os.path.exists(themes_dir) else "/usr/share/web-greeter/themes" - filenames = os.listdir(themes_dir) - - dirlist = [] - for file in filenames: - if os.path.isdir(os.path.join(themes_dir, file)): - dirlist.append(file) - - if not quiet: - print("Themes are located in {themes_dir}\n".format(themes_dir = themes_dir)) - for theme in dirlist: - print("-", theme) - - return dirlist - - -args_lenght = sys.argv.__len__() - - -def yargs(args: List[str]): - loadWhitherConf() - used = 0 - - if args[0] == "--help" or args[0] == "-h": - show_help() - used += 1 - exit() - elif args[0] == "--version" or args[0] == "-v": - show_version() - used += 1 - exit() - elif args[0] == "--debug": - debugMode(True) - used += 1 - elif args[0] == "--normal": - debugMode(False) - used += 1 - elif args[0] == "--theme": - if args.__len__() > 1: - changeTheme(args[1]) - used += 2 - else: - print("No theme provided") - used += 1 - exit(1) - elif args[0] == "--list": - listThemes() - used += 1 - exit() - else: - show_help() - used += 1 - exit(1) - for x in range(used): - args.pop(0) - if args.__len__() != 0: - yargs(args) - - -if __name__ == '__main__': - custom_config = globals.custom_config - - if args_lenght > 1: - args = sys.argv - args.pop(0) - yargs(args) - - globals.greeter = WebGreeter() - - globals.greeter.run() diff --git a/web-greeter/requirements.txt b/web-greeter/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/web-greeter/whither.yml b/web-greeter/whither.yml deleted file mode 100644 index 7cd2937..0000000 --- a/web-greeter/whither.yml +++ /dev/null @@ -1,62 +0,0 @@ -# Whither (Universal Linux Apps) Configuration - -# App Name -WebGreeter: - # Whither's Config - whither: - allow_remote_urls: False - at_spi_service: - enabled: '@at_spi_service@' - command: /usr/lib/at-spi2-core/at-spi-bus-launcher - arg: --launch-immediately - app_id: web-greeter - url_scheme: web-greeter - context_menu: - enabled: False - debug_mode: '@debug_mode@' - entry_point: - autoload: False - toolbar: - enabled: False - toolkit: auto - window: - decorated: '@decorated@' - initial_state: maximized - stays_on_top: '@stays_on_top@' - title: Web Greeter for LightDM - - # App's Config - app: - branding: - background_images_dir: '@background_images_dir@' - logo_image: '@logo_image@' - user_image: '@user_image@' - config_dir: '@config_dir@' - greeter: - debug_mode: '@debug_mode@' - detect_theme_errors: True - screensaver_timeout: 300 - secure_mode: True - theme: gruvbox - icon_theme: - time_language: - layouts: - - us - - latam - features: - battery: False - backlight: - enabled: False - value: 10 - steps: 0 - greeters_dir: '@greeters_dir@' - locale_dir: '@locale_dir@' - themes_dir: '@themes_dir@' - version: - full: '3.0.0' - major: 3 - minor: 0 - micro: 0 - alpha: False - beta: False - rc: False