From 7c154980ace5dc2d0e396a759550d0758c661bb4 Mon Sep 17 00:00:00 2001 From: JezerM Date: Mon, 6 Sep 2021 19:05:28 -0600 Subject: [PATCH 01/13] Removed whither package, partially. --- build/utils.sh | 1 - web-greeter/__main__.py | 42 +++++ web-greeter/bridge/Config.py | 30 +-- web-greeter/bridge/Greeter.py | 147 ++++++++------- web-greeter/bridge/ThemeUtils.py | 19 +- web-greeter/bridge/devtools.py | 46 +++++ web-greeter/browser/__init__.py | 0 web-greeter/browser/bridge.py | 46 +++++ web-greeter/browser/browser.py | 276 ++++++++++++++++++++++++++++ web-greeter/browser/error_prompt.py | 140 ++++++++++++++ web-greeter/browser/interceptor.py | 55 ++++++ web-greeter/browser/url_scheme.py | 74 ++++++++ web-greeter/config.py | 83 +++++++++ web-greeter/globals.py | 223 +--------------------- web-greeter/logger.py | 50 +++++ web-greeter/main.py | 176 ------------------ web-greeter/requirements.txt | 0 web-greeter/whither.yml | 62 ------- 18 files changed, 910 insertions(+), 560 deletions(-) create mode 100644 web-greeter/__main__.py create mode 100644 web-greeter/bridge/devtools.py create mode 100644 web-greeter/browser/__init__.py create mode 100644 web-greeter/browser/bridge.py create mode 100644 web-greeter/browser/browser.py create mode 100644 web-greeter/browser/error_prompt.py create mode 100644 web-greeter/browser/interceptor.py create mode 100644 web-greeter/browser/url_scheme.py create mode 100644 web-greeter/config.py create mode 100644 web-greeter/logger.py delete mode 100644 web-greeter/main.py create mode 100644 web-greeter/requirements.txt delete mode 100644 web-greeter/whither.yml 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 From 7620d6ca817e8f515d571a5f3dfa994deacae125 Mon Sep 17 00:00:00 2001 From: JezerM Date: Mon, 6 Sep 2021 21:17:44 -0600 Subject: [PATCH 02/13] Removed unnecessary things --- web-greeter/bridge/devtools.py | 35 +++-- web-greeter/browser/browser.py | 3 +- .../{utils/keyboard.py => browser/window.py} | 28 ++-- web-greeter/utils/battery.py | 22 +-- web-greeter/utils/errorPrompt.py | 144 ------------------ web-greeter/utils/interceptor.py | 37 ----- web-greeter/utils/pkg_json.py | 133 ---------------- web-greeter/utils/theme.py | 113 -------------- 8 files changed, 30 insertions(+), 485 deletions(-) rename web-greeter/{utils/keyboard.py => browser/window.py} (62%) delete mode 100644 web-greeter/utils/errorPrompt.py delete mode 100644 web-greeter/utils/interceptor.py delete mode 100644 web-greeter/utils/pkg_json.py delete mode 100644 web-greeter/utils/theme.py diff --git a/web-greeter/bridge/devtools.py b/web-greeter/bridge/devtools.py index 6c00ca5..2b562a1 100644 --- a/web-greeter/bridge/devtools.py +++ b/web-greeter/bridge/devtools.py @@ -1,29 +1,29 @@ # -*- coding: utf-8 -*- # -# devtools.py +# devtools.py # -# Copyright © 2016-2017 Antergos +# Copyright © 2016-2017 Antergos # -# This file is part of whither. +# This file is part of Web Greeter. # -# 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. +# 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. # -# 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. +# 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 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. +# 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 . +# You should have received a copy of the GNU General Public License +# along with Web Greeter; If not, see . # 3rd-Party Libs from PyQt5.QtCore import QUrl @@ -32,7 +32,6 @@ from PyQt5.QtWebEngineWidgets import ( QWebEnginePage, ) - class DevTools: def __init__(self): diff --git a/web-greeter/browser/browser.py b/web-greeter/browser/browser.py index a46013f..37d50c9 100644 --- a/web-greeter/browser/browser.py +++ b/web-greeter/browser/browser.py @@ -28,6 +28,7 @@ # Standard lib +from browser.window import MainWindow from bridge.devtools import DevTools import os from typing import ( @@ -93,7 +94,7 @@ class Application: QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) self.app = QApplication([]) - self.window = QMainWindow() + self.window = MainWindow() self.desktop = self.app.desktop() self.window.setAttribute(Qt.WA_DeleteOnClose) diff --git a/web-greeter/utils/keyboard.py b/web-greeter/browser/window.py similarity index 62% rename from web-greeter/utils/keyboard.py rename to web-greeter/browser/window.py index 89272ef..2fd5a35 100644 --- a/web-greeter/utils/keyboard.py +++ b/web-greeter/browser/window.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# keyboard.py +# window.py # # Copyright © 2021 JezerM # @@ -25,22 +25,14 @@ # You should have received a copy of the GNU General Public License # along with Web Greeter; If not, see . -from whither.toolkits.bootstrap import WebPage, MainWindow - -from PyQt5.QtCore import QUrl, pyqtSignal, Qt +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QMainWindow from PyQt5.QtGui import QKeyEvent -import globals - - -def keyPressEvent(self, keyEvent: QKeyEvent): - super(MainWindow, self).keyPressEvent(keyEvent) - if (keyEvent.key() == Qt.Key.Key_MonBrightnessUp): - globals.greeter.greeter.brightnessIncrease( - globals.greeter.config.features.backlight["value"]) - if (keyEvent.key() == Qt.Key.Key_MonBrightnessDown): - globals.greeter.greeter.brightnessDecrease( - globals.greeter.config.features.backlight["value"]) - - -MainWindow.keyPressEvent = keyPressEvent +class MainWindow(QMainWindow): + def keyPressEvent(self, keyEvent: QKeyEvent) -> None: + super().keyPressEvent(keyEvent) + if (keyEvent.key() == Qt.Key.Key_MonBrightnessUp): + pass + elif (keyEvent.key() == Qt.Key.Key_MonBrightnessDown): + pass diff --git a/web-greeter/utils/battery.py b/web-greeter/utils/battery.py index 2abb44e..43e18be 100644 --- a/web-greeter/utils/battery.py +++ b/web-greeter/utils/battery.py @@ -1,30 +1,10 @@ -import os import subprocess import shlex import re import math from threading import Thread import time - -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("battery") -logger.propagate = False -stream_handler = StreamHandler() -stream_handler.setLevel(DEBUG) -stream_handler.setFormatter(formatter) -logger.setLevel(DEBUG) -logger.addHandler(stream_handler) +from logger import logger running = False diff --git a/web-greeter/utils/errorPrompt.py b/web-greeter/utils/errorPrompt.py deleted file mode 100644 index d030aa0..0000000 --- a/web-greeter/utils/errorPrompt.py +++ /dev/null @@ -1,144 +0,0 @@ -# -*- coding: utf-8 -*- -# -# errorPrompt.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 . - -from whither.toolkits.bootstrap import WebPage - -from PyQt5.QtWidgets import QDialogButtonBox, QDialog, QVBoxLayout, QLabel, QPushButton - -from logging import ( - getLogger, - DEBUG, - ERROR, - Formatter, - StreamHandler, -) - -import globals - -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) - - -def javaScriptConsoleMessage(self, level: WebPage.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 globals.greeter.config.greeter.detect_theme_errors: - return - - dia = ErrorDialog(globals.greeter._main_window.widget.centralWidget(), err) - - dia.exec() - result = dia.result() - - if result == 0: # Cancel - return - elif result == 1: # Default theme - globals.custom_config["app"]["greeter"]["theme"] = "gruvbox" - globals.greeter.get_and_apply_user_config() - globals.greeter.load_theme() - return - elif result == 2: # Reload - globals.greeter.load_theme() - return - - return - - -WebPage.javaScriptConsoleMessage = javaScriptConsoleMessage diff --git a/web-greeter/utils/interceptor.py b/web-greeter/utils/interceptor.py deleted file mode 100644 index ee2c5f9..0000000 --- a/web-greeter/utils/interceptor.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# -# interceptor.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 . - -""" Url Request Interceptor """ - -# 3rd-Party Libs -from whither.bridge import UrlRequestInterceptor as Interceptor - - -class UrlRequestInterceptor(Interceptor): - - def intercept_request(self, info): - self.interceptRequest(info) diff --git a/web-greeter/utils/pkg_json.py b/web-greeter/utils/pkg_json.py deleted file mode 100644 index 7f04c16..0000000 --- a/web-greeter/utils/pkg_json.py +++ /dev/null @@ -1,133 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pkg_json.py -# -# Copyright © 2016-2017 Antergos -# -# This file is part of Web Greeter for LightDM. -# -# 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 . - -""" Utility class used to manage greeter themes' package.json files. """ - -# Standard Lib -import json -import os - - -class MissingKeyError(KeyError): - - def __init__(self, keys: list): - self.keys = keys - msg_part = ' is' if len(keys) == 1 else 's are' - msg = 'Required key{0} missing: {1}'.format(msg_part, keys) - - super().__init__(msg) - - -class PackageJSON: - """ - Holds data from a theme's package.json file. - - Attributes: - _optional_keys (tuple): Top-level keys that aren't required. - _required_keys (tuple): Top-level keys that are required. - _wg_theme_keys (tuple): Keys nested under `wg_theme` key. All are required. - - author (dict): Author's info. Required: `name`. Optional: `email`, `url`. - bugs (str): Issue tracker url. - config (dict): Theme configuration data. - description (str): Short description. - display_name (str): Display name. - entry_point (str): Path to HTML file relative to theme's root directory. - homepage (str): Homepage url. - name (str): Package name. - scripts (list): All JavaScript files required by the theme. Paths should be relative - to the theme's root directory. Vendor scripts provided by the greeter - should be listed by their name instead of file path. - styles (list): All CSS files required by the theme. Paths should be relative - to the theme's root directory. Vendor styles provided by the greeter - should be listed by their name instead of file path. - supports (list): List of greeter versions supported by the theme. The version format - is MAJOR[.MINOR[.PATCH]] where MINOR and PATCH are optional. - Examples: - `3` : `2.9.9` < compatible versions < `4.0.0` - `3.0` : `3` < compatible versions < `3.1` - `3.0.1`: compatible version == `3.0.1` - version (str): Theme version. - """ - _optional_keys = ( - 'config', - 'description', - 'name', - ) - - _required_keys = ( - 'author', - 'bugs', - 'homepage', - 'version', - 'wg_theme', - ) - - _wg_theme_keys = ( - 'display_name', - 'entry_point', - 'scripts', - 'styles', - 'supports', - ) - - def __init__(self, path: str) -> None: - """ - Args: - path (str): Absolute path to `package.json` file. - """ - self.path = path - - self._initialize() - - def _initialize(self): - package_json = os.path.join(self.path, 'package.json') - - if not os.path.exists(package_json): - raise FileNotFoundError - - data = json.loads(package_json) - missing_keys = [k for k in self._required_keys if k not in data] - - if missing_keys: - raise MissingKeyError(missing_keys) - - if not isinstance(data['wg_theme'], dict): - raise TypeError('wg_theme: Expected type(dict)!') - - missing_keys = [k for k in self._wg_theme_keys if k not in data['wg_theme']] - - if missing_keys: - raise MissingKeyError(missing_keys) - - for key, value in data['wg_theme'].items(): - setattr(self, key, value) - - del data['wg_theme'] - - for key, value in data.items(): - setattr(self, key, value) diff --git a/web-greeter/utils/theme.py b/web-greeter/utils/theme.py deleted file mode 100644 index e431441..0000000 --- a/web-greeter/utils/theme.py +++ /dev/null @@ -1,113 +0,0 @@ -# -*- coding: utf-8 -*- -# -# theme.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 . - -""" Utility class used to find and manage greeter themes. """ - -# Standard Lib -import os -from os.path import abspath - -# This Application -from .pkg_json import PackageJSON - -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("theme") -logger.propagate = False -stream_handler = StreamHandler() -stream_handler.setLevel(DEBUG) -stream_handler.setFormatter(formatter) -logger.setLevel(DEBUG) -logger.addHandler(stream_handler) - - -def checkTheme(self): - theme: str = self.config.greeter.theme - dir = self.config.themes_dir - path_to_theme: str = os.path.join(dir, theme, "index.html") - def_theme = "gruvbox" - - if theme.startswith("/"): path_to_theme = theme; - elif "." in theme or "/" in theme: - path_to_theme = os.path.abspath(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): - logger.error("\"{0}\" theme does not exists. Using \"{1}\"".format(theme, def_theme)) - path_to_theme = os.path.join(dir, def_theme, "index.html") - - return path_to_theme - - -def listThemes(self): - themes_dir = self.config.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) - - return dirlist - - -class Theme: - """ - Represents a greeter theme installed on the local system. - - Args: - path (str): The absolute path to the theme's directory. - - Attributes: - data (PackageJSON): The theme's data sourced from its `package.json` file. - """ - - def __init__(self, path: str) -> None: - self.path = path - - self._initialize() - - def _initialize(self) -> None: - package_json = os.path.join(self.path, 'package.json') - - try: - self.data = PackageJSON(package_json) - except Exception: - self.data = None From adb5435af430243504bdacf7f0bfc17e1cc60fe7 Mon Sep 17 00:00:00 2001 From: JezerM Date: Tue, 7 Sep 2021 22:22:48 -0600 Subject: [PATCH 03/13] Added arguments parser --- web-greeter/__main__.py | 70 +++++++++++++++++++-- web-greeter/browser/browser.py | 3 +- web-greeter/{bridge => browser}/devtools.py | 3 + web-greeter/config.py | 8 ++- 4 files changed, 76 insertions(+), 8 deletions(-) rename web-greeter/{bridge => browser}/devtools.py (95%) diff --git a/web-greeter/__main__.py b/web-greeter/__main__.py index e6a0a8b..fa6d7af 100644 --- a/web-greeter/__main__.py +++ b/web-greeter/__main__.py @@ -26,17 +26,77 @@ # along with Web Greeter; If not, see . # Standard lib -import sys -import ruamel.yaml as yaml -import os +import sys, argparse, os +from typing import List # 3rd-Party Libs -from browser.browser import Browser -from logger import logger import globals import config +def list_themes() -> List[str]: + themes_dir = config.web_greeter_config["app"]["theme_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) + + return dirlist + +def print_themes(): + themes_dir = config.web_greeter_config["app"]["theme_dir"] + themes_dir = themes_dir if os.path.exists(themes_dir) else "/usr/share/web-greeter/themes" + themes = list_themes() + print("Themes are located in {themes_dir}\n".format(themes_dir = themes_dir)) + for theme in themes: + print("-", theme) + + +def set_theme(theme: str): + config.web_greeter_config["config"]["greeter"]["theme"] = theme + +def set_debug(value: bool): + conf = config.web_greeter_config["config"] + app = config.web_greeter_config["app"] + conf["greeter"]["debug_mode"] = value + app["decorated"] = value + app["fullscreen"] = not value + +def parse(argv): + version = config.web_greeter_config["app"]["version"]["full"] + parser = argparse.ArgumentParser(prog="web-greeter", add_help=False) + parser.add_argument("-h", "--help", action="help", help="Show this help message and exit") + parser.add_argument("-v", "--version", action="version", version=version, help="Show version number") + + parser.add_argument("--debug", action="store_true", help="Run the greeter in debug mode", dest="debug", default=None) + parser.add_argument("--normal", action="store_false", help="Run in non-debug mode", dest="debug") + parser.add_argument("--list", action="store_true", help="List available themes") + parser.add_argument("--theme", help="Set the theme to use", metavar="[name]") + + args: argparse.Namespace + + try: + args = parser.parse_args(argv) + except argparse.ArgumentError: + sys.exit() + + # print(args) + + if (args.list): + print_themes() + sys.exit() + if (args.theme): + set_theme(args.theme) + if (args.debug != None): + set_debug(args.debug) + if __name__ == '__main__': + parse(sys.argv[1:]) + + from browser.browser import Browser + globals.greeter = Browser() greeter = globals.greeter greeter.run() diff --git a/web-greeter/browser/browser.py b/web-greeter/browser/browser.py index 37d50c9..c0cffe0 100644 --- a/web-greeter/browser/browser.py +++ b/web-greeter/browser/browser.py @@ -29,7 +29,7 @@ # Standard lib from browser.window import MainWindow -from bridge.devtools import DevTools +from browser.devtools import DevTools import os from typing import ( Dict, @@ -138,7 +138,6 @@ class Browser(Application): 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' diff --git a/web-greeter/bridge/devtools.py b/web-greeter/browser/devtools.py similarity index 95% rename from web-greeter/bridge/devtools.py rename to web-greeter/browser/devtools.py index 2b562a1..540f14f 100644 --- a/web-greeter/bridge/devtools.py +++ b/web-greeter/browser/devtools.py @@ -32,6 +32,8 @@ from PyQt5.QtWebEngineWidgets import ( QWebEnginePage, ) +from logger import logger + class DevTools: def __init__(self): @@ -42,4 +44,5 @@ class DevTools: self.view.load(QUrl('http://127.0.0.1:12345')) self.view.show() + logger.debug("DevTools initialized") diff --git a/web-greeter/config.py b/web-greeter/config.py index a40cfb1..82acb9f 100644 --- a/web-greeter/config.py +++ b/web-greeter/config.py @@ -66,7 +66,13 @@ web_greeter_config = { "fullscreen": True, "frame": False, "debug_mode": False, - "theme_dir": "/usr/share/web-greeter/themes/" + "theme_dir": "/usr/share/web-greeter/themes/", + "version": { + "full": "3.0.0", + "major": 3, + "minor": 3, + "micro": 0, + }, } } From cc4daa58ca4a0359a6b6096d0c3da3d151fcffc6 Mon Sep 17 00:00:00 2001 From: JezerM Date: Tue, 7 Sep 2021 22:24:53 -0600 Subject: [PATCH 04/13] Updated build tools and README --- Makefile | 4 ++-- README.md | 13 ++++++++----- build/utils.sh | 9 --------- requirements.txt | 4 +++- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/Makefile b/Makefile index de450c1..9aa5bbe 100644 --- a/Makefile +++ b/Makefile @@ -65,8 +65,8 @@ build: _build_init _apply_config $(DO) build $(PREFIX) $(DO) prepare-install $(PREFIX) -build_dev: install - $(MAYBE_SUDO_DO) install-dev +build_dev: build + $(call colorecho, Built for dev) clean: $(DO) clean diff --git a/README.md b/README.md index cba8596..bd02eef 100644 --- a/README.md +++ b/README.md @@ -24,14 +24,18 @@ Gruvbox and Dracula themes! ## Dependencies | | arch | ubuntu | fedora | openSUSE | |-----------------------|---------------|----------------------|---------------------|-----------------------| -|**[whither][whither]** | \*install it from source\* |**liblightdm-gobject** |lightdm |liblightdm-gobject-dev|lightdm-gobject-devel|liblightdm-gobject-1-0 | |**pygobject** |python-gobject |python3-gi |pygobject3 |python3-gobject | - -> ***NOTE*** Be sure to have [whither][whither] installed from this source +|**pyqt5** |python-pyqt5 |python3-pyqt5 |python3-qt5 |python3-qt5 | +|**qt5-webengine** |qt5-webengine |libqt5webengine5 |qt5-qtwebengine |libqt5-qtwebengine | ### PIP -Above dependencies can be installed with pip as well. +- PyGObject +- PyQt5 +- PyQtWebEngine +- ruamel.yaml + +Install PIP dependencies with: ```sh pip install -r requirements.txt ``` @@ -80,7 +84,6 @@ web-greeter --debug > ***Note:*** Do not use `lightdm --test-mode` as it is not supported. [antergos]: https://github.com/Antergos "Antergos" -[whither]: https://github.com/JezerM/whither "Whither" [nody-greeter]: https://github.com/JezerM/nody-greeter "Nody Greeter" [acpilight]: https://gitlab.com/wavexx/acpilight "acpilight" [WebArchive]: https://web.archive.org/web/20190524032923/https://doclets.io/Antergos/web-greeter/stable "Web Archive" diff --git a/build/utils.sh b/build/utils.sh index c226561..e6d4910 100755 --- a/build/utils.sh +++ b/build/utils.sh @@ -41,10 +41,6 @@ do_install() { cp -R "${INSTALL_ROOT}"/* "${DESTDIR}" } -do_install_dev() { - cp -RH "${REPO_DIR}/whither/whither" /usr/lib/python3.6/site-packages -} - # Not used generate_pot_file() { REPO_ROOT="$(dirname "${REPO_DIR}")" @@ -114,7 +110,6 @@ set_config() { [[ -z "$1" || -z "$2" ]] && return 1 sed -i "s|'@$1@'|$2|g" \ - "${BUILD_DIR}/web-greeter/whither.yml" \ "${BUILD_DIR}/dist/web-greeter.yml" } @@ -151,10 +146,6 @@ case "$1" in clean_build_dir ;; - install-dev) - do_install_dev - ;; - prepare-install) PREFIX="$2" prepare_install diff --git a/requirements.txt b/requirements.txt index 18b4b84..636bc57 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ PyGObject -whither @ https://github.com/JezerM/whither/tarball/master +PyQt5 +PyQtWebEngine +ruamel.yaml From 543b7926d51d39190024745cf052b5c97952cfc4 Mon Sep 17 00:00:00 2001 From: JezerM Date: Sun, 12 Sep 2021 00:10:12 -0600 Subject: [PATCH 05/13] Added devtools as a side view --- web-greeter/browser/browser.py | 56 +++++++++++++++++++++--------- web-greeter/browser/devtools.py | 48 ------------------------- web-greeter/browser/interceptor.py | 1 + web-greeter/browser/window.py | 12 +++++-- 4 files changed, 51 insertions(+), 66 deletions(-) delete mode 100644 web-greeter/browser/devtools.py diff --git a/web-greeter/browser/browser.py b/web-greeter/browser/browser.py index c0cffe0..fa94e74 100644 --- a/web-greeter/browser/browser.py +++ b/web-greeter/browser/browser.py @@ -39,13 +39,13 @@ from typing import ( # 3rd-Party Libs from PyQt5.QtCore import QUrl, Qt, QCoreApplication, QFile -from PyQt5.QtWidgets import QApplication, QDesktopWidget, QMainWindow +from PyQt5.QtWidgets import QApplication, QDesktopWidget, QDockWidget, QMainWindow from PyQt5.QtWebEngineCore import QWebEngineUrlScheme -from PyQt5.QtWebEngineWidgets import QWebEngineScript, QWebEngineProfile, QWebEngineSettings, QWebEngineView +from PyQt5.QtWebEngineWidgets import QWebEngineScript, QWebEngineProfile, QWebEngineSettings, QWebEngineView, QWebEnginePage from PyQt5.QtGui import QColor from PyQt5.QtWebChannel import QWebChannel -from browser.error_prompt import WebPage, errorPrompt +from browser.error_prompt import WebPage from browser.url_scheme import QtUrlSchemeHandler from browser.interceptor import QtUrlRequestInterceptor @@ -64,10 +64,10 @@ 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, + 'NORMAL': Qt.WindowState.WindowNoState, + 'MINIMIZED': Qt.WindowState.WindowMinimized, + 'MAXIMIZED': Qt.WindowState.WindowMaximized, + 'FULLSCREEN': Qt.WindowState.WindowFullScreen, } # type: Dict[str, Qt.WindowState] DISABLED_SETTINGS = [ @@ -90,20 +90,20 @@ class Application: states = WINDOW_STATES def __init__(self): - QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling) - QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) + QCoreApplication.setAttribute(Qt.ApplicationAttribute.AA_EnableHighDpiScaling) + QApplication.setAttribute(Qt.ApplicationAttribute.AA_EnableHighDpiScaling) self.app = QApplication([]) self.window = MainWindow() self.desktop = self.app.desktop() - self.window.setAttribute(Qt.WA_DeleteOnClose) + self.window.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) self.window.setWindowTitle("Web Greeter") - self.window.setWindowFlags(self.window.windowFlags() | Qt.FramelessWindowHint) + self.window.setWindowFlags(self.window.windowFlags() | Qt.WindowType.FramelessWindowHint) self.window.setWindowFlags( - self.window.windowFlags() | Qt.MaximizeUsingFullscreenGeometryHint + self.window.windowFlags() | Qt.WindowType.MaximizeUsingFullscreenGeometryHint ) state = self.states['NORMAL'] @@ -112,7 +112,7 @@ class Application: except Exception: self.window.setWindowState(state) - self.window.setCursor(Qt.ArrowCursor) + self.window.setCursor(Qt.CursorShape.ArrowCursor) self.app.aboutToQuit.connect(self._before_exit) @@ -166,14 +166,16 @@ class Browser(Application): self._initialize_page() if web_greeter_config["config"]["greeter"]["debug_mode"]: - self.devtools = DevTools() + self._initialize_devtools() if web_greeter_config["config"]["greeter"]["secure_mode"]: self.profile.setUrlRequestInterceptor(self.interceptor) self.page.setBackgroundColor(QColor(0, 0, 0)) + self.window.setStyleSheet("""QMainWindow, QWebEngineView { + background: #000000; + }""") - self.view.show() self.window.setCentralWidget(self.view) logger.debug("Browser Window created") @@ -190,6 +192,28 @@ class Browser(Application): self.load_script(':/_greeter/js/bundle.js', 'Web Greeter Bundle') self.load_theme() + def _initialize_devtools(self): + self.dev_view = QWebEngineView(parent=self.window) + self.dev_page = QWebEnginePage() + self.dev_view.setPage(self.dev_page) + self.page.setDevToolsPage(self.dev_page) + + self.qdock = QDockWidget() + self.qdock.setWidget(self.dev_view) + self.qdock.setMinimumWidth(int(self.window.width() / 2)) + + self.window.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.qdock) + self.qdock.hide() + logger.debug("DevTools initialized") + + def toggle_devtools(self): + if not web_greeter_config["config"]["greeter"]["debug_mode"]: + return + if self.qdock.isVisible(): + self.qdock.hide() + else: + self.qdock.show() + def _initialize_page(self): page_settings = self.page.settings().globalSettings() @@ -241,7 +265,7 @@ class Browser(Application): # print(script_file, path) - if script_file.open(QFile.ReadOnly): + if script_file.open(QFile.OpenModeFlag.ReadOnly): script_string = str(script_file.readAll(), 'utf-8') script.setInjectionPoint(QWebEngineScript.DocumentCreation) diff --git a/web-greeter/browser/devtools.py b/web-greeter/browser/devtools.py deleted file mode 100644 index 540f14f..0000000 --- a/web-greeter/browser/devtools.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -# -# devtools.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.QtCore import QUrl -from PyQt5.QtWebEngineWidgets import ( - QWebEngineView, - QWebEnginePage, -) - -from logger import logger - -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() - logger.debug("DevTools initialized") - diff --git a/web-greeter/browser/interceptor.py b/web-greeter/browser/interceptor.py index f5d7307..370998f 100644 --- a/web-greeter/browser/interceptor.py +++ b/web-greeter/browser/interceptor.py @@ -44,6 +44,7 @@ class QtUrlRequestInterceptor(QWebEngineUrlRequestInterceptor): not_devtools = ( not url.startswith('http://127.0.0.1') and not url.startswith('ws://127.0.0.1') + and not url.startswith('devtools') ) block_request = not_devtools and not_data_uri and not_webg_uri and not_local_file diff --git a/web-greeter/browser/window.py b/web-greeter/browser/window.py index 2fd5a35..0ef46dd 100644 --- a/web-greeter/browser/window.py +++ b/web-greeter/browser/window.py @@ -29,10 +29,18 @@ from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QMainWindow from PyQt5.QtGui import QKeyEvent +import globals + class MainWindow(QMainWindow): def keyPressEvent(self, keyEvent: QKeyEvent) -> None: super().keyPressEvent(keyEvent) - if (keyEvent.key() == Qt.Key.Key_MonBrightnessUp): + key = keyEvent.key() + mod = keyEvent.modifiers() # type: Qt.KeyboardModifiers + if (key == Qt.Key.Key_MonBrightnessUp): pass - elif (keyEvent.key() == Qt.Key.Key_MonBrightnessDown): + elif (key == Qt.Key.Key_MonBrightnessDown): pass + elif (key == Qt.Key.Key_I + and mod & Qt.KeyboardModifier.ControlModifier + and mod & Qt.KeyboardModifier.ShiftModifier): + globals.greeter.toggle_devtools() From 2b37112e07d3a948927b654f28913f044ec098d0 Mon Sep 17 00:00:00 2001 From: JezerM Date: Sun, 12 Sep 2021 13:02:12 -0600 Subject: [PATCH 06/13] Fixed some battery problems --- web-greeter/utils/battery.py | 43 +++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/web-greeter/utils/battery.py b/web-greeter/utils/battery.py index 43e18be..3ba4c21 100644 --- a/web-greeter/utils/battery.py +++ b/web-greeter/utils/battery.py @@ -5,6 +5,7 @@ import math from threading import Thread import time from logger import logger +from shutil import which running = False @@ -75,17 +76,17 @@ class Battery: present = read_first_line(bstr + "/present") if tonumber(present) == 1: - rate_current = tonumber(read_first_line(bstr + "/current_now")) - rate_voltage = tonumber(read_first_line(bstr + "/voltage_now")) - rate_power = tonumber(read_first_line((bstr + "/power_now"))) - charge_full = tonumber(read_first_line(bstr + "/charge_full")) - charge_design = tonumber(read_first_line(bstr + "/charge_full_design")) + rate_current = tonumber(read_first_line(bstr + "/current_now")) or 0 + rate_voltage = tonumber(read_first_line(bstr + "/voltage_now")) or 0 + rate_power = tonumber(read_first_line((bstr + "/power_now"))) or 0 + charge_full = tonumber(read_first_line(bstr + "/charge_full")) or 0 + charge_design = tonumber(read_first_line(bstr + "/charge_full_design")) or 0 energy_now = tonumber(read_first_line(bstr + "/energy_now") - or read_first_line(bstr + "/charge_now")) - energy_full = tonumber(read_first_line(bstr + "/energy_full") or charge_full) + or read_first_line(bstr + "/charge_now")) or 0 + energy_full = tonumber(read_first_line(bstr + "/energy_full") or charge_full) or 0 energy_percentage = tonumber(read_first_line(bstr + "/capacity") - or math.floor(energy_now / energy_full * 100)) + or math.floor(energy_now / energy_full * 100)) or 0 self._batteries[i]["status"] = read_first_line(bstr + "/status") or "N/A" self._batteries[i]["perc"] = energy_percentage or self._batteries[i].perc @@ -96,14 +97,14 @@ class Battery: self._batteries[i]["capacity"] = math.floor( charge_full / charge_design * 100) - sum_rate_current = sum_rate_current + (rate_current or 0) - sum_rate_voltage = sum_rate_voltage + (rate_voltage or 0) - sum_rate_power = sum_rate_power + (rate_power or 0) - sum_rate_energy = sum_rate_energy + (rate_power or (((rate_voltage or 0) * (rate_current or 0)) / 1e6)) - sum_energy_now = sum_energy_now + (energy_now or 0) - sum_energy_full = sum_energy_full + (energy_full or 0) - sum_charge_full = sum_charge_full + (charge_full or 0) - sum_charge_design = sum_charge_design + (charge_design or 0) + sum_rate_current = sum_rate_current + rate_current + sum_rate_voltage = sum_rate_voltage + rate_voltage + sum_rate_power = sum_rate_power + rate_power + sum_rate_energy = sum_rate_energy + (rate_power or ((rate_voltage * rate_current) / 1e6)) + sum_energy_now = sum_energy_now + energy_now + sum_energy_full = sum_energy_full + energy_full + sum_charge_full = sum_charge_full + charge_full + sum_charge_design = sum_charge_design + charge_design self.capacity = math.floor(min(100, sum_charge_full / sum_charge_design * 100)) self.status = len(self._batteries) > 0 and self._batteries[0]["status"] or "N/A" @@ -132,8 +133,8 @@ class Battery: rate_time = sum_energy_now / div if 0 < rate_time and rate_time < 0.01: - rate_time_magnitude = tonumber(abs(math.floor(math.log10(rate_time)))) - rate_time = rate_time * 10 ^ (rate_time_magnitude - 2) + rate_time_magnitude = tonumber(abs(math.floor(math.log10(rate_time)))) or 0 + rate_time = int(rate_time * 10) ^ (rate_time_magnitude - 2) hours = math.floor(rate_time) minutes = math.floor((rate_time - hours) * 60) @@ -163,6 +164,9 @@ class Battery: def get_state(self): return self.status + def get_ac_status(self): + return self.ac_status + def get_capacity(self): return self.capacity @@ -175,6 +179,9 @@ class Battery: acpi_tries = 0 def acpi_listen(callback, onerror): + if not which("acpi_listen"): + return + global acpi_tries try: main = subprocess.Popen(shlex.split("acpi_listen"), From 1361b89ad6076a98a5e971368c6305263b548f98 Mon Sep 17 00:00:00 2001 From: JezerM Date: Sun, 12 Sep 2021 13:05:27 -0600 Subject: [PATCH 07/13] Improved Devtools, Dialog, and Brightness keys --- web-greeter/__main__.py | 2 +- web-greeter/bridge/Greeter.py | 78 +++++++++++++++++++++-------- web-greeter/bridge/__init__.py | 1 + web-greeter/browser/browser.py | 34 +++++++++++-- web-greeter/browser/error_prompt.py | 48 ++++++++---------- web-greeter/browser/window.py | 55 ++++++++++++++------ 6 files changed, 154 insertions(+), 64 deletions(-) diff --git a/web-greeter/__main__.py b/web-greeter/__main__.py index fa6d7af..a46e6be 100644 --- a/web-greeter/__main__.py +++ b/web-greeter/__main__.py @@ -61,7 +61,7 @@ def set_debug(value: bool): conf = config.web_greeter_config["config"] app = config.web_greeter_config["app"] conf["greeter"]["debug_mode"] = value - app["decorated"] = value + app["frame"] = value app["fullscreen"] = not value def parse(argv): diff --git a/web-greeter/bridge/Greeter.py b/web-greeter/bridge/Greeter.py index 19deb2c..ff84c10 100644 --- a/web-greeter/bridge/Greeter.py +++ b/web-greeter/bridge/Greeter.py @@ -27,6 +27,7 @@ # along with Web Greeter; If not, see . # Standard Lib +from browser.error_prompt import Dialog import subprocess import threading @@ -36,9 +37,11 @@ gi.require_version('LightDM', '1') from gi.repository import LightDM from browser.bridge import Bridge, BridgeObject -from PyQt5.QtCore import QVariant, QTimer +from PyQt5.QtCore import QFileSystemWatcher, QVariant, QTimer from config import web_greeter_config +from utils.battery import Battery +import globals # This Application from . import ( @@ -56,19 +59,52 @@ LightDMGreeter = LightDM.Greeter() LightDMUsers = LightDM.UserList() -def changeBrightness(self, method: str, quantity: int): - if self._config["features"]["backlight"]["enabled"] != True: +def changeBrightness(method: str, quantity: int = None): + backlight = web_greeter_config["config"]["features"]["backlight"] + if not backlight["enabled"]: return + if not quantity: + quantity = backlight["value"] try: - steps = self._config["features"]["backlight"]["steps"] + steps = backlight["steps"] child = subprocess.run(["xbacklight", method, str(quantity), "-steps", str(steps)]) if child.returncode == 1: raise ChildProcessError("xbacklight returned 1") except Exception as err: logger.error("Brightness: {}".format(err)) else: - self.brightness_update.emit() + if globals.greeter: + globals.greeter.greeter.brightness_update.emit() +def increaseBrightness(quantity: int = None): + backlight = web_greeter_config["config"]["features"]["backlight"] + if not backlight["enabled"]: + return + if not quantity: + quantity = backlight["value"] + thread = threading.Thread(target=changeBrightness, + args=("-inc", quantity)) + thread.start() + +def decreaseBrightness(quantity: int = None): + backlight = web_greeter_config["config"]["features"]["backlight"] + if not backlight["enabled"]: + return + if not quantity: + quantity = backlight["value"] + thread = threading.Thread(target=changeBrightness, + args=("-dec", quantity)) + thread.start() + +def setBrightness(quantity: int = None): + backlight = web_greeter_config["config"]["features"]["backlight"] + if not backlight["enabled"]: + return + if not quantity: + quantity = backlight["value"] + thread = threading.Thread(target=changeBrightness, + args=("-set", quantity)) + thread.start() def getBrightness(self): if self._config["features"]["backlight"]["enabled"] != True: @@ -106,13 +142,23 @@ class Greeter(BridgeObject): self._shared_data_directory = '' self._themes_directory = web_greeter_config["app"]["theme_dir"] - # if self._config.features.battery == True: - # self._battery = battery.Battery() + if self._config["features"]["battery"]: + self._battery = Battery() - LightDMGreeter.connect_to_daemon_sync() + try: + LightDMGreeter.connect_to_daemon_sync() + except Exception as err: + logger.error(err) + dia = Dialog(title="An error ocurred", + message="Detected a problem that could interfere with the system login process", + detail="LightDM: {0}\nYou can continue without major problems, but you won't be able to log in".format(err), + buttons=["Okay"]) + dia.exec() + pass self._connect_signals() self._determine_shared_data_directory_path() + logger.debug("LightDM API connected") def _determine_shared_data_directory_path(self): user = LightDMUsers.get_users()[0] @@ -174,9 +220,7 @@ class Greeter(BridgeObject): @brightness.setter def brightness(self, quantity): - thread = threading.Thread(target=changeBrightness, - args=(self, "-set", quantity)) - thread.start() + setBrightness(quantity) @Bridge.prop(bool, notify=noop_signal) def can_hibernate(self): @@ -305,21 +349,15 @@ class Greeter(BridgeObject): @Bridge.method(int) def brightnessSet(self, quantity): - thread = threading.Thread(target=changeBrightness, - args=(self, "-set", quantity)) - thread.start() + setBrightness(quantity) @Bridge.method(int) def brightnessIncrease(self, quantity): - thread = threading.Thread(target=changeBrightness, - args=(self, "-inc", quantity)) - thread.start() + increaseBrightness(quantity) @Bridge.method(int) def brightnessDecrease(self, quantity): - thread = threading.Thread(target=changeBrightness, - args=(self, "-dec", quantity)) - thread.start() + decreaseBrightness(quantity) @Bridge.method() def cancel_authentication(self): diff --git a/web-greeter/bridge/__init__.py b/web-greeter/bridge/__init__.py index 6e164be..fb8d8ab 100644 --- a/web-greeter/bridge/__init__.py +++ b/web-greeter/bridge/__init__.py @@ -119,6 +119,7 @@ def battery_to_dict(battery): name = battery.get_name(), level = battery.get_level(), state = battery.get_state(), + ac_status = battery.get_ac_status(), capacity = battery.get_capacity(), time = battery.get_time(), watt = battery.get_watt() diff --git a/web-greeter/browser/browser.py b/web-greeter/browser/browser.py index fa94e74..86271bf 100644 --- a/web-greeter/browser/browser.py +++ b/web-greeter/browser/browser.py @@ -29,7 +29,6 @@ # Standard lib from browser.window import MainWindow -from browser.devtools import DevTools import os from typing import ( Dict, @@ -39,10 +38,10 @@ from typing import ( # 3rd-Party Libs from PyQt5.QtCore import QUrl, Qt, QCoreApplication, QFile -from PyQt5.QtWidgets import QApplication, QDesktopWidget, QDockWidget, QMainWindow +from PyQt5.QtWidgets import QAction, QApplication, QDesktopWidget, QDockWidget, QMainWindow, qApp from PyQt5.QtWebEngineCore import QWebEngineUrlScheme from PyQt5.QtWebEngineWidgets import QWebEngineScript, QWebEngineProfile, QWebEngineSettings, QWebEngineView, QWebEnginePage -from PyQt5.QtGui import QColor +from PyQt5.QtGui import QColor, QIcon from PyQt5.QtWebChannel import QWebChannel from browser.error_prompt import WebPage @@ -106,7 +105,13 @@ class Application: self.window.windowFlags() | Qt.WindowType.MaximizeUsingFullscreenGeometryHint ) + if web_greeter_config["app"]["frame"]: + self._init_menu_bar() + state = self.states['NORMAL'] + if web_greeter_config["app"]["fullscreen"]: + state = self.states["FULLSCREEN"] + try: self.window.windowHandle().setWindowState(state) except Exception: @@ -127,6 +132,27 @@ class Application: logger.debug("Web Greeter started") return self.app.exec_() + def _init_menu_bar(self): + exit_action = QAction(QIcon('exit.png'), '&Exit', self.window) + exit_action.setShortcut('Ctrl+Q') + exit_action.setStatusTip('Exit application') + exit_action.triggered.connect(qApp.quit) + + menu_bar = self.window.menuBar() + + file_menu = menu_bar.addMenu('&File') + file_menu.addAction(exit_action) + + edit_menu = menu_bar.addMenu('&Edit') + edit_menu.addAction(exit_action) + + view_menu = menu_bar.addMenu('&View') + view_menu.addAction(exit_action) + + about_menu = menu_bar.addMenu('&About') + about_menu.addAction(exit_action) + + class Browser(Application): url_scheme: QWebEngineUrlScheme @@ -211,8 +237,10 @@ class Browser(Application): return if self.qdock.isVisible(): self.qdock.hide() + self.view.setFocus() else: self.qdock.show() + self.dev_view.setFocus() def _initialize_page(self): page_settings = self.page.settings().globalSettings() diff --git a/web-greeter/browser/error_prompt.py b/web-greeter/browser/error_prompt.py index 1a38cf4..c035629 100644 --- a/web-greeter/browser/error_prompt.py +++ b/web-greeter/browser/error_prompt.py @@ -28,8 +28,9 @@ # Standard lib # 3rd-Party Libs +from typing import List from PyQt5.QtWebEngineWidgets import QWebEnginePage -from PyQt5.QtWidgets import QDialogButtonBox, QDialog, QVBoxLayout, QLabel, QPushButton +from PyQt5.QtWidgets import QAbstractButton, QDialogButtonBox, QDialog, QVBoxLayout, QLabel, QPushButton from config import web_greeter_config import globals @@ -86,54 +87,49 @@ class WebPage(QWebEnginePage): source=sourceID, line=lineNumber, msg=message) errorPrompt(errorMessage) -class ErrorDialog(QDialog): - def __init__(self, parent=None, err=""): +class Dialog(QDialog): + def __init__(self, parent=None, title:str = "Dialog", message:str = "Message", detail:str = "", buttons: List[str] = []): super().__init__(parent) - - self.setWindowTitle("Error") + self.setWindowTitle(title) 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) + for i in range(0, len(buttons)): + button = QPushButton(buttons[i]) + button.role = i + self.buttonBox.addButton(button, QDialogButtonBox.ButtonRole.NoRole) - self.buttonBox.accepted.connect(self.accept) - self.buttonBox.rejected.connect(self.reject) + self.buttonBox.clicked.connect(self.handle_click) 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(QLabel(message)) + self.layout.addWidget(QLabel(detail)) self.layout.addWidget(self.buttonBox) - self.setLayout(self.layout) - def handle_reload(self, value: bool): - self.done(2) + self.setLayout(self.layout) + def handle_click(self, button: QAbstractButton): + self.done(button.role) def errorPrompt(err): if not web_greeter_config["config"]["greeter"]["detect_theme_errors"]: return - dia = ErrorDialog(globals.greeter.window, err) + dia = Dialog(parent=globals.greeter.window, title="Error", + message="An error ocurred. Do you want to change to default theme?", + detail=err, + buttons=["Reload theme", "Set default theme", "Cancel"], + ) dia.exec() result = dia.result() - if result == 0: # Cancel + if result == 2: # Cancel return elif result == 1: # Default theme web_greeter_config["config"]["greeter"]["theme"] = "gruvbox" globals.greeter.load_theme() return - elif result == 2: # Reload + elif result == 0: # Reload globals.greeter.load_theme() return diff --git a/web-greeter/browser/window.py b/web-greeter/browser/window.py index 0ef46dd..5f8ad95 100644 --- a/web-greeter/browser/window.py +++ b/web-greeter/browser/window.py @@ -25,22 +25,49 @@ # You should have received a copy of the GNU General Public License # along with Web Greeter; If not, see . -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QMainWindow +from bridge.Greeter import changeBrightness, decreaseBrightness, increaseBrightness +from PyQt5.QtCore import QFileSystemWatcher, Qt +from PyQt5.QtWidgets import QAction, QMainWindow from PyQt5.QtGui import QKeyEvent +from config import web_greeter_config import globals class MainWindow(QMainWindow): - def keyPressEvent(self, keyEvent: QKeyEvent) -> None: - super().keyPressEvent(keyEvent) - key = keyEvent.key() - mod = keyEvent.modifiers() # type: Qt.KeyboardModifiers - if (key == Qt.Key.Key_MonBrightnessUp): - pass - elif (key == Qt.Key.Key_MonBrightnessDown): - pass - elif (key == Qt.Key.Key_I - and mod & Qt.KeyboardModifier.ControlModifier - and mod & Qt.KeyboardModifier.ShiftModifier): - globals.greeter.toggle_devtools() + def __init__(self): + super().__init__() + self.init_actions() + # self.watchBrightness() + + def init_actions(self): + devAct = QAction(text="&Toggle Devtools", parent=self) + devAct.setShortcut("Shift+Ctrl+I") + devAct.triggered.connect(self.toggle_devtools) + + monBUp = QAction(text="&Increase brightness", parent=self) + monBDo = QAction(text="&Decrease brightness", parent=self) + monBUp.setShortcut(Qt.Key.Key_MonBrightnessUp) + monBDo.setShortcut(Qt.Key.Key_MonBrightnessDown) + monBUp.triggered.connect(self.inc_brightness) + monBDo.triggered.connect(self.dec_brightness) + + self.addAction(devAct) + self.addAction(monBUp) + self.addAction(monBDo) + + def toggle_devtools(self): + globals.greeter.toggle_devtools() + + def inc_brightness(self): + increaseBrightness() + def dec_brightness(self): + decreaseBrightness() + + def watchBrightness(self): + self.watcher = QFileSystemWatcher(parent=self) + self.watcher.addPath("/sys/class/backlight/intel_backlight/brightness") + self.watcher.fileChanged.connect(self.updateBrightness) + + def updateBrightness(self): + if globals.greeter: + globals.greeter.greeter.brightness_update.emit() From 9d661a14bacb88fa9bc6239bb83114198ef1e087 Mon Sep 17 00:00:00 2001 From: JezerM Date: Sun, 12 Sep 2021 19:47:50 -0600 Subject: [PATCH 08/13] Bash and Zsh completion updated --- dist/web-greeter-bash | 3 ++- dist/web-greeter-zsh | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/dist/web-greeter-bash b/dist/web-greeter-bash index ab51e11..dcb7083 100644 --- a/dist/web-greeter-bash +++ b/dist/web-greeter-bash @@ -9,12 +9,13 @@ _web-greeter() { case "${last}" in --theme) + _filedir options=$(ls -1d /usr/share/web-greeter/themes/*/ | cut -c 1- | rev | cut -c 2- | rev | sort | sed 's/\/usr\/share\/web-greeter\/themes\///') ;; esac - COMPREPLY=( $(compgen -W "${options}" -- "${cur}") ) + COMPREPLY+=( $(compgen -W "${options}" -- "${cur}") ) } complete -F _web-greeter web-greeter diff --git a/dist/web-greeter-zsh b/dist/web-greeter-zsh index 3de4b0b..e9d81d9 100644 --- a/dist/web-greeter-zsh +++ b/dist/web-greeter-zsh @@ -9,13 +9,19 @@ _webgreeter() { '--debug[Runs the greeter in debug mode]' '--normal[Runs in non-debug mode]' '--list[Lists available themes]' - "--theme[Sets the theme to use]:theme:(${themes})" + "--theme[Sets the theme to use]:theme:->themes" '--help[Show help]' '-h[Show help]' '--version[Print program version]' '-v[Print program version]' ) _arguments $args[@] && ret=0 + case "$state" in + themes) + _files + _values 'themes' "${(uonzf)${themes}}" + ;; + esac return ret } From 6102c66f1fec5dfcb32b7b475616cb72e2364a7d Mon Sep 17 00:00:00 2001 From: JezerM Date: Sun, 12 Sep 2021 19:53:49 -0600 Subject: [PATCH 09/13] Added screensaver support --- README.md | 1 + requirements.txt | 1 + web-greeter/bridge/Greeter.py | 6 +++++- web-greeter/browser/browser.py | 6 +++++- web-greeter/utils/screensaver.py | 30 ++++++++++++++++++++++++++++++ 5 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 web-greeter/utils/screensaver.py diff --git a/README.md b/README.md index bd02eef..30a28b5 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Gruvbox and Dracula themes! - PyQt5 - PyQtWebEngine - ruamel.yaml +- python-xlib Install PIP dependencies with: ```sh diff --git a/requirements.txt b/requirements.txt index 636bc57..7b6762f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ PyGObject PyQt5 PyQtWebEngine ruamel.yaml +python-xlib diff --git a/web-greeter/bridge/Greeter.py b/web-greeter/bridge/Greeter.py index ff84c10..6e89e90 100644 --- a/web-greeter/bridge/Greeter.py +++ b/web-greeter/bridge/Greeter.py @@ -41,6 +41,7 @@ from PyQt5.QtCore import QFileSystemWatcher, QVariant, QTimer from config import web_greeter_config from utils.battery import Battery +from utils.screensaver import reset_screensaver import globals # This Application @@ -396,7 +397,10 @@ class Greeter(BridgeObject): def start_session(self, session): if not session.strip(): return - return LightDMGreeter.start_session_sync(session) + started = LightDMGreeter.start_session_sync(session) + if started or self.is_authenticated(): + reset_screensaver() + return started @Bridge.method(result=bool) def suspend(self): diff --git a/web-greeter/browser/browser.py b/web-greeter/browser/browser.py index 86271bf..4bc16b1 100644 --- a/web-greeter/browser/browser.py +++ b/web-greeter/browser/browser.py @@ -51,6 +51,7 @@ from browser.interceptor import QtUrlRequestInterceptor from logger import logger from config import web_greeter_config from bridge import Greeter, Config, ThemeUtils +from utils.screensaver import reset_screensaver, set_screensaver import resources # Typing Helpers @@ -119,10 +120,13 @@ class Application: self.window.setCursor(Qt.CursorShape.ArrowCursor) + timeout = web_greeter_config["config"]["greeter"]["screensaver_timeout"] + set_screensaver(timeout or 300) + self.app.aboutToQuit.connect(self._before_exit) def _before_exit(self): - pass + reset_screensaver() def show(self): self.window.show() diff --git a/web-greeter/utils/screensaver.py b/web-greeter/utils/screensaver.py new file mode 100644 index 0000000..34d8630 --- /dev/null +++ b/web-greeter/utils/screensaver.py @@ -0,0 +1,30 @@ +from logger import logger +from Xlib.display import Display + +saved_data: dict[str, int] +saved = False + +display = Display() + +def set_screensaver(timeout: int): + global saved_data, saved + if saved: + return + display.sync() + data: dict[str, int] = display.get_screen_saver()._data or {} + saved_data = data + saved = True + + display.set_screen_saver(timeout, data["interval"], data["prefer_blanking"], data["allow_exposures"]) + display.flush() + logger.debug("Screensaver timeout set") + +def reset_screensaver(): + global saved_data, saved + if not saved: + return + display.sync() + display.set_screen_saver(saved_data["timeout"], saved_data["interval"], saved_data["prefer_blanking"], saved_data["allow_exposures"]) + display.flush() + saved = False + logger.debug("Screensaver reset") From df0b3ccd36916a2e16700ba49477d21fb7c850bc Mon Sep 17 00:00:00 2001 From: JezerM Date: Mon, 13 Sep 2021 09:00:58 -0600 Subject: [PATCH 10/13] Added cursor theme support --- web-greeter/browser/browser.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/web-greeter/browser/browser.py b/web-greeter/browser/browser.py index 4bc16b1..f7da972 100644 --- a/web-greeter/browser/browser.py +++ b/web-greeter/browser/browser.py @@ -28,6 +28,7 @@ # Standard lib +import re from browser.window import MainWindow import os from typing import ( @@ -83,6 +84,17 @@ ENABLED_SETTINGS = [ 'FocusOnNavigationEnabled', # Qt 5.11+ ] +def getDefaultCursor(): + cursor_theme = "" + file = open("/usr/share/icons/default/index.theme") + matched = re.search(r"Inherits=.*", file.read()) + file.close() + if not matched: + logger.error("Default cursor couldn't be get") + return "" + cursor_theme = matched.group().replace("Inherits=", "") + return cursor_theme + class Application: app: QApplication desktop: QDesktopWidget @@ -123,6 +135,9 @@ class Application: timeout = web_greeter_config["config"]["greeter"]["screensaver_timeout"] set_screensaver(timeout or 300) + cursor_theme = web_greeter_config["config"]["greeter"]["icon_theme"] + os.environ["XCURSOR_THEME"] = cursor_theme if cursor_theme != None else getDefaultCursor() + self.app.aboutToQuit.connect(self._before_exit) def _before_exit(self): From 57df0113cab4146ec8d3d42b271bd0df63eadf1b Mon Sep 17 00:00:00 2001 From: JezerM Date: Mon, 13 Sep 2021 10:55:18 -0600 Subject: [PATCH 11/13] Xlib display error now catched, and fixed window size when no window manager --- web-greeter/browser/browser.py | 9 ++++++++- web-greeter/utils/screensaver.py | 19 ++++++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/web-greeter/browser/browser.py b/web-greeter/browser/browser.py index f7da972..5a52ca9 100644 --- a/web-greeter/browser/browser.py +++ b/web-greeter/browser/browser.py @@ -52,7 +52,7 @@ from browser.interceptor import QtUrlRequestInterceptor from logger import logger from config import web_greeter_config from bridge import Greeter, Config, ThemeUtils -from utils.screensaver import reset_screensaver, set_screensaver +from utils.screensaver import reset_screensaver, set_screensaver, init_display import resources # Typing Helpers @@ -121,6 +121,11 @@ class Application: if web_greeter_config["app"]["frame"]: self._init_menu_bar() + screen_size = self.desktop.screen().size() + + self.window.setBaseSize(screen_size) + self.window.resize(screen_size) + state = self.states['NORMAL'] if web_greeter_config["app"]["fullscreen"]: state = self.states["FULLSCREEN"] @@ -132,6 +137,8 @@ class Application: self.window.setCursor(Qt.CursorShape.ArrowCursor) + init_display() + timeout = web_greeter_config["config"]["greeter"]["screensaver_timeout"] set_screensaver(timeout or 300) diff --git a/web-greeter/utils/screensaver.py b/web-greeter/utils/screensaver.py index 34d8630..6d339f8 100644 --- a/web-greeter/utils/screensaver.py +++ b/web-greeter/utils/screensaver.py @@ -3,12 +3,21 @@ from Xlib.display import Display saved_data: dict[str, int] saved = False +available = False -display = Display() +display = None + +def init_display(): + global display, available + try: + display = Display() + available = True + except Exception as err: + logger.error(f"Xlib error: {err}") def set_screensaver(timeout: int): - global saved_data, saved - if saved: + global saved_data, saved, available, display + if saved or not available: return display.sync() data: dict[str, int] = display.get_screen_saver()._data or {} @@ -20,8 +29,8 @@ def set_screensaver(timeout: int): logger.debug("Screensaver timeout set") def reset_screensaver(): - global saved_data, saved - if not saved: + global saved_data, saved, available, display + if not saved or not available: return display.sync() display.set_screen_saver(saved_data["timeout"], saved_data["interval"], saved_data["prefer_blanking"], saved_data["allow_exposures"]) From c161417a412214b7e6e642774f6294e39435bb31 Mon Sep 17 00:00:00 2001 From: JezerM Date: Mon, 13 Sep 2021 16:04:24 -0600 Subject: [PATCH 12/13] Bugfix --- web-greeter/bridge/Greeter.py | 4 +++- web-greeter/browser/browser.py | 30 +++++++++++++++++++++++++----- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/web-greeter/bridge/Greeter.py b/web-greeter/bridge/Greeter.py index 6e89e90..619a97d 100644 --- a/web-greeter/bridge/Greeter.py +++ b/web-greeter/bridge/Greeter.py @@ -164,6 +164,8 @@ class Greeter(BridgeObject): def _determine_shared_data_directory_path(self): user = LightDMUsers.get_users()[0] user_data_dir = LightDMGreeter.ensure_shared_data_dir_sync(user.get_name()) + if user_data_dir == None: + return self._shared_data_directory = user_data_dir.rpartition('/')[0] def _connect_signals(self): @@ -320,7 +322,7 @@ class Greeter(BridgeObject): @Bridge.prop(str, notify=noop_signal) def shared_data_directory(self): - return self._shared_data_directory + return self._shared_data_directory or '' @Bridge.prop(bool, notify=noop_signal) def show_manual_login_hint(self): diff --git a/web-greeter/browser/browser.py b/web-greeter/browser/browser.py index 5a52ca9..974912b 100644 --- a/web-greeter/browser/browser.py +++ b/web-greeter/browser/browser.py @@ -38,8 +38,8 @@ from typing import ( ) # 3rd-Party Libs -from PyQt5.QtCore import QUrl, Qt, QCoreApplication, QFile -from PyQt5.QtWidgets import QAction, QApplication, QDesktopWidget, QDockWidget, QMainWindow, qApp +from PyQt5.QtCore import QRect, QUrl, Qt, QCoreApplication, QFile, QSize +from PyQt5.QtWidgets import QAction, QApplication, QDesktopWidget, QDockWidget, QMainWindow, QLayout, qApp, QWidget from PyQt5.QtWebEngineCore import QWebEngineUrlScheme from PyQt5.QtWebEngineWidgets import QWebEngineScript, QWebEngineProfile, QWebEngineSettings, QWebEngineView, QWebEnginePage from PyQt5.QtGui import QColor, QIcon @@ -112,7 +112,6 @@ class Application: self.window.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) self.window.setWindowTitle("Web Greeter") - self.window.setWindowFlags(self.window.windowFlags() | Qt.WindowType.FramelessWindowHint) self.window.setWindowFlags( self.window.windowFlags() | Qt.WindowType.MaximizeUsingFullscreenGeometryHint @@ -120,8 +119,10 @@ class Application: if web_greeter_config["app"]["frame"]: self._init_menu_bar() + else: + self.window.setWindowFlags(self.window.windowFlags() | Qt.WindowType.FramelessWindowHint) - screen_size = self.desktop.screen().size() + screen_size = self.desktop.availableGeometry().size() self.window.setBaseSize(screen_size) self.window.resize(screen_size) @@ -178,6 +179,17 @@ class Application: about_menu = menu_bar.addMenu('&About') about_menu.addAction(exit_action) +class NoneLayout(QLayout): + def __init__(self): + super().__init__() + + def sizeHint(self) -> QSize: + size = QSize(0, 0) + return size + + def minimumSizeHint(self) -> QSize: + size = QSize(0, 0) + return size class Browser(Application): @@ -252,7 +264,10 @@ class Browser(Application): self.qdock = QDockWidget() self.qdock.setWidget(self.dev_view) - self.qdock.setMinimumWidth(int(self.window.width() / 2)) + titlebar = QWidget(self.qdock) + layout = NoneLayout() + titlebar.setLayout(layout) + self.qdock.setTitleBarWidget(titlebar) self.window.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.qdock) self.qdock.hide() @@ -261,6 +276,11 @@ class Browser(Application): def toggle_devtools(self): if not web_greeter_config["config"]["greeter"]["debug_mode"]: return + win_size = self.window.size() + # dev_size = self.qdock.size() + + self.qdock.resize(int(win_size.width() / 2), int(win_size.height())) + if self.qdock.isVisible(): self.qdock.hide() self.view.setFocus() From 70b19cba4209eb830f8f35847c527fa299ade6ce Mon Sep 17 00:00:00 2001 From: JezerM Date: Mon, 13 Sep 2021 20:33:04 -0600 Subject: [PATCH 13/13] Updated build system --- .gitignore | 1 + Makefile | 8 ++++++++ README.md | 5 ++++- build/utils.sh | 40 ++++++++++++++++++++++++++++------------ requirements.txt | 1 + web-greeter/setup.py | 28 ++++++++++++++++++++++++++++ 6 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 web-greeter/setup.py diff --git a/.gitignore b/.gitignore index 906c067..20f4999 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ lib64/ parts/ sdist/ var/ +web-greeter/dist *.egg-info/ .installed.cfg *.egg diff --git a/Makefile b/Makefile index 9aa5bbe..b2aa12f 100644 --- a/Makefile +++ b/Makefile @@ -65,6 +65,10 @@ build: _build_init _apply_config $(DO) build $(PREFIX) $(DO) prepare-install $(PREFIX) +build_old: _build_init _apply_config + $(DO) build_old $(PREFIX) + $(DO) prepare-install $(PREFIX) + build_dev: build $(call colorecho, Built for dev) @@ -75,5 +79,9 @@ install: build $(MAYBE_SUDO_DO) install $(DESTDIR) $(PREFIX) $(call colorecho, SUCCESS!) +install_old: build_old + $(MAYBE_SUDO_DO) install $(DESTDIR) $(PREFIX) + $(call colorecho, SUCCESS!) + .PHONY: all _apply_config _build_init build build_dev clean install diff --git a/README.md b/README.md index 30a28b5..99b21b4 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Install PIP dependencies with: pip install -r requirements.txt ``` -> ***NOTE*** Be sure to install pip libraries as root too +> ***NOTE*** Be sure to install pip libraries as root too, or use a venv to install these dependencies ## Download & Install ```sh @@ -51,6 +51,8 @@ sudo pip install -r requirements.txt sudo make install ``` +This will build and install **web-greeter** with [cx_freeze][cx_freeze]. Either `sudo make install_old`, which will use the old zippy way to install **web-greeter**; it's strongly recommended to not use the last one, as it depends on the actual python interpreter and its libraries. Update python or delete a library, and **web-greeter** won't work. + See [latest release][releases]. ## Theme JavaScript API @@ -86,6 +88,7 @@ web-greeter --debug [antergos]: https://github.com/Antergos "Antergos" [nody-greeter]: https://github.com/JezerM/nody-greeter "Nody Greeter" +[cx_freeze]: https://github.com/marcelotduarte/cx_Freeze "cx_Freeze" [acpilight]: https://gitlab.com/wavexx/acpilight "acpilight" [WebArchive]: https://web.archive.org/web/20190524032923/https://doclets.io/Antergos/web-greeter/stable "Web Archive" [gh-pages]: https://jezerm.github.io/web-greeter/ "API Documentation" diff --git a/build/utils.sh b/build/utils.sh index e6d4910..83c217e 100755 --- a/build/utils.sh +++ b/build/utils.sh @@ -18,13 +18,12 @@ combine_javascript_sources() { bootstrap.js > bundle.js } -do_build() { +do_old_build() { cd "${BUILD_DIR}" # Compile Resources (combine_javascript_sources \ - && pyrcc5 -o "${BUILD_DIR}/${PKGNAME}/resources.py" ../resources.qrc \ - && cp "${BUILD_DIR}/${PKGNAME}/resources.py" "${REPO_DIR}/web-greeter") + && pyrcc5 -o "${BUILD_DIR}/${PKGNAME}/resources.py" ../resources.qrc) # Create "Zip Application" (cd "${PKGNAME}" \ @@ -36,21 +35,29 @@ do_build() { && chmod +x "${INSTALL_ROOT}${PREFIX}/bin/web-greeter") } +do_build() { + cd "${BUILD_DIR}" + + echo "Building web-greeter with cx_freeze..." + python3 "${BUILD_DIR}/${PKGNAME}/setup.py" build >& setup_log + echo "setup.py log inside ${BUILD_DIR}/setup_log" + + mkdir -p "${INSTALL_ROOT}"/opt/web-greeter + mv "${BUILD_DIR}/${PKGNAME}"/dist/* "${INSTALL_ROOT}"/opt/web-greeter/ +} + do_install() { [[ -e "${DESTDIR}" ]] || mkdir -p "${DESTDIR}" cp -R "${INSTALL_ROOT}"/* "${DESTDIR}" -} - -# Not used -generate_pot_file() { - REPO_ROOT="$(dirname "${REPO_DIR}")" - xgettext --from-code UTF-8 -o "${REPO_ROOT}/po/web-greeter.pot" -d web-greeter "${REPO_ROOT}"/src/*.c + ln -sf "${DESTDIR}"/opt/web-greeter/web-greeter "${DESTDIR}"/usr/bin/web-greeter } init_build_dir() { [[ -e "${BUILD_DIR}/web-greeter" ]] && rm -rf "${BUILD_DIR}/web-greeter" [[ -e "${BUILD_DIR}/dist" ]] && rm -rf "${BUILD_DIR}/dist" - cp -R -t "${BUILD_DIR}" "${REPO_DIR}/web-greeter" "${REPO_DIR}/dist" + rsync -a "${REPO_DIR}/web-greeter" "${BUILD_DIR}" --exclude "dist" --exclude "__pycache__" + rsync -a "${REPO_DIR}/dist" "${BUILD_DIR}" + cp "${REPO_DIR}/README.md" "${BUILD_DIR}/web-greeter" } prepare_install() { @@ -68,8 +75,12 @@ prepare_install() { cp "${BUILD_DIR}/dist/${PKGNAME}.1" "${INSTALL_ROOT}${PREFIX}/share/man/man1" # Command line completions - cp "${BUILD_DIR}/dist/${PKGNAME}-bash" "${INSTALL_ROOT}${PREFIX}/share/bash-completion/completions/${PKGNAME}" - cp "${BUILD_DIR}/dist/${PKGNAME}-zsh" "${INSTALL_ROOT}${PREFIX}/share/zsh/vendor-completions/_${PKGNAME}" + if [[ -f /usr/bin/bash ]]; then + cp "${BUILD_DIR}/dist/${PKGNAME}-bash" "${INSTALL_ROOT}${PREFIX}/share/bash-completion/completions/${PKGNAME}" + fi + if [[ -f /usr/bin/zsh ]]; then + cp "${BUILD_DIR}/dist/${PKGNAME}-zsh" "${INSTALL_ROOT}${PREFIX}/share/zsh/vendor-completions/_${PKGNAME}" + fi # Greeter Config cp "${BUILD_DIR}/dist/${PKGNAME}.yml" "${INSTALL_ROOT}/etc/lightdm" @@ -131,6 +142,11 @@ case "$1" in do_build ;; + build_old) + PREFIX="$2" + do_old_build + ;; + build-init) init_build_dir ;; diff --git a/requirements.txt b/requirements.txt index 7b6762f..ad03dbf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ PyQt5 PyQtWebEngine ruamel.yaml python-xlib +cx_freeze diff --git a/web-greeter/setup.py b/web-greeter/setup.py new file mode 100644 index 0000000..e0afeba --- /dev/null +++ b/web-greeter/setup.py @@ -0,0 +1,28 @@ +from cx_Freeze import setup, Executable +import os + +setup_dir = os.path.abspath(os.path.dirname(__file__)) +os.chdir(setup_dir) + +long_description = "" + +if os.path.exists(os.path.join(setup_dir, "README.md")): + with open("README.md", "r", encoding="utf-8") as fh: + long_description = fh.read() + +setup( + name = "web-greeter", + version = "3.0.0", + license = 'GPL-3.0', + author = "Antergos Linux Project, Jezer Mejía", + author_email = "amyuki4@gmail.com", + description = "A modern, visually appealing greeter for LightDM", + long_description = long_description, + long_description_content_type="text/markdown", + executables = [Executable("__main__.py", target_name="web-greeter")], + options = {"build_exe": { + "build_exe": "dist", + "packages": ["gi", "Xlib"], + "silent_level": 0, + }}, + )