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 de450c1..b2aa12f 100644
--- a/Makefile
+++ b/Makefile
@@ -65,8 +65,12 @@ build: _build_init _apply_config
$(DO) build $(PREFIX)
$(DO) prepare-install $(PREFIX)
-build_dev: install
- $(MAYBE_SUDO_DO) install-dev
+build_old: _build_init _apply_config
+ $(DO) build_old $(PREFIX)
+ $(DO) prepare-install $(PREFIX)
+
+build_dev: build
+ $(call colorecho, Built for dev)
clean:
$(DO) clean
@@ -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 cba8596..99b21b4 100644
--- a/README.md
+++ b/README.md
@@ -24,19 +24,24 @@ 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
+- python-xlib
+
+Install PIP dependencies with:
```sh
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
@@ -46,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
@@ -80,8 +87,8 @@ 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"
+[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 16165f0..83c217e 100755
--- a/build/utils.sh
+++ b/build/utils.sh
@@ -18,17 +18,15 @@ 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}" \
- && mv main.py __main__.py \
&& zip -rq ../"${PKGNAME}.zip" . -x '**__pycache__**' 'resources/*' \
&& cd - >/dev/null \
&& mkdir -p "${INSTALL_ROOT}${PREFIX}"/{bin,share} \
@@ -37,25 +35,29 @@ do_build() {
&& chmod +x "${INSTALL_ROOT}${PREFIX}/bin/web-greeter")
}
-do_install() {
- [[ -e "${DESTDIR}" ]] || mkdir -p "${DESTDIR}"
- cp -R "${INSTALL_ROOT}"/* "${DESTDIR}"
-}
+do_build() {
+ cd "${BUILD_DIR}"
-do_install_dev() {
- cp -RH "${REPO_DIR}/whither/whither" /usr/lib/python3.6/site-packages
+ 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/
}
-# 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
+do_install() {
+ [[ -e "${DESTDIR}" ]] || mkdir -p "${DESTDIR}"
+ cp -R "${INSTALL_ROOT}"/* "${DESTDIR}"
+ 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() {
@@ -73,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"
@@ -115,7 +121,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"
}
@@ -137,6 +142,11 @@ case "$1" in
do_build
;;
+ build_old)
+ PREFIX="$2"
+ do_old_build
+ ;;
+
build-init)
init_build_dir
;;
@@ -152,10 +162,6 @@ case "$1" in
clean_build_dir
;;
- install-dev)
- do_install_dev
- ;;
-
prepare-install)
PREFIX="$2"
prepare_install
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
}
diff --git a/requirements.txt b/requirements.txt
index 18b4b84..ad03dbf 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,6 @@
PyGObject
-whither @ https://github.com/JezerM/whither/tarball/master
+PyQt5
+PyQtWebEngine
+ruamel.yaml
+python-xlib
+cx_freeze
diff --git a/web-greeter/__main__.py b/web-greeter/__main__.py
new file mode 100644
index 0000000..a46e6be
--- /dev/null
+++ b/web-greeter/__main__.py
@@ -0,0 +1,102 @@
+# -*- 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, argparse, os
+from typing import List
+
+# 3rd-Party Libs
+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["frame"] = 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/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..619a97d 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.
#
@@ -26,20 +27,22 @@
# along with Web Greeter; If not, see .
# Standard Lib
+from browser.error_prompt import Dialog
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 QFileSystemWatcher, QVariant, QTimer
+
+from config import web_greeter_config
+from utils.battery import Battery
+from utils.screensaver import reset_screensaver
+import globals
# This Application
from . import (
@@ -51,28 +54,61 @@ 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:
+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:
+ if self._config["features"]["backlight"]["enabled"] != True:
return -1
try:
level = subprocess.run(["xbacklight", "-get"], stdout=subprocess.PIPE,
@@ -85,39 +121,51 @@ 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
-
- if self._config.features.battery == True:
- self._battery = battery.Battery()
-
- LightDMGreeter.connect_to_daemon_sync()
+ self._themes_directory = web_greeter_config["app"]["theme_dir"]
+
+ if self._config["features"]["battery"]:
+ self._battery = Battery()
+
+ 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]
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):
@@ -149,93 +197,91 @@ 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)
@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)
+ @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 +296,114 @@ 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
+ return self._shared_data_directory or ''
- @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()
+ setBrightness(quantity)
- @bridge.method(int)
+ @Bridge.method(int)
def brightnessIncrease(self, quantity):
- thread = threading.Thread(target=changeBrightness,
- args=(self, "-inc", quantity))
- thread.start()
+ increaseBrightness(quantity)
- @bridge.method(int)
+ @Bridge.method(int)
def brightnessDecrease(self, quantity):
- thread = threading.Thread(target=changeBrightness,
- args=(self, "-dec", quantity))
- thread.start()
+ decreaseBrightness(quantity)
- @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)
+ started = LightDMGreeter.start_session_sync(session)
+ if started or self.is_authenticated():
+ reset_screensaver()
+ return started
- @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/__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/__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..974912b
--- /dev/null
+++ b/web-greeter/browser/browser.py
@@ -0,0 +1,374 @@
+# -*- 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
+
+import re
+from browser.window import MainWindow
+import os
+from typing import (
+ Dict,
+ Tuple,
+ TypeVar,
+)
+
+# 3rd-Party Libs
+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
+from PyQt5.QtWebChannel import QWebChannel
+
+from browser.error_prompt import WebPage
+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
+from utils.screensaver import reset_screensaver, set_screensaver, init_display
+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.WindowState.WindowNoState,
+ 'MINIMIZED': Qt.WindowState.WindowMinimized,
+ 'MAXIMIZED': Qt.WindowState.WindowMaximized,
+ 'FULLSCREEN': Qt.WindowState.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+
+]
+
+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
+ window: QMainWindow
+ states = WINDOW_STATES
+
+ def __init__(self):
+ 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.WidgetAttribute.WA_DeleteOnClose)
+ self.window.setWindowTitle("Web Greeter")
+
+
+ self.window.setWindowFlags(
+ self.window.windowFlags() | Qt.WindowType.MaximizeUsingFullscreenGeometryHint
+ )
+
+ if web_greeter_config["app"]["frame"]:
+ self._init_menu_bar()
+ else:
+ self.window.setWindowFlags(self.window.windowFlags() | Qt.WindowType.FramelessWindowHint)
+
+ screen_size = self.desktop.availableGeometry().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"]
+
+ try:
+ self.window.windowHandle().setWindowState(state)
+ except Exception:
+ self.window.setWindowState(state)
+
+ self.window.setCursor(Qt.CursorShape.ArrowCursor)
+
+ init_display()
+
+ 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):
+ reset_screensaver()
+
+ def show(self):
+ self.window.show()
+ logger.debug("Window is ready")
+
+ def run(self) -> int:
+ 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 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):
+ url_scheme: QWebEngineUrlScheme
+
+ def __init__(self):
+ super().__init__()
+ self.init()
+ self.load()
+
+ def init(self):
+ logger.debug("Initializing Browser Window")
+
+ 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._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.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_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)
+ titlebar = QWidget(self.qdock)
+ layout = NoneLayout()
+ titlebar.setLayout(layout)
+ self.qdock.setTitleBarWidget(titlebar)
+
+ 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
+ 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()
+ else:
+ self.qdock.show()
+ self.dev_view.setFocus()
+
+ 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.OpenModeFlag.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..c035629
--- /dev/null
+++ b/web-greeter/browser/error_prompt.py
@@ -0,0 +1,136 @@
+# -*- 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 typing import List
+from PyQt5.QtWebEngineWidgets import QWebEnginePage
+from PyQt5.QtWidgets import QAbstractButton, 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 Dialog(QDialog):
+ def __init__(self, parent=None, title:str = "Dialog", message:str = "Message", detail:str = "", buttons: List[str] = []):
+ super().__init__(parent)
+ self.setWindowTitle(title)
+
+ self.buttonBox = QDialogButtonBox()
+ for i in range(0, len(buttons)):
+ button = QPushButton(buttons[i])
+ button.role = i
+ self.buttonBox.addButton(button, QDialogButtonBox.ButtonRole.NoRole)
+
+ self.buttonBox.clicked.connect(self.handle_click)
+
+ self.layout = QVBoxLayout()
+ self.layout.addWidget(QLabel(message))
+ self.layout.addWidget(QLabel(detail))
+ self.layout.addWidget(self.buttonBox)
+
+ 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 = 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 == 2: # Cancel
+ return
+ elif result == 1: # Default theme
+ web_greeter_config["config"]["greeter"]["theme"] = "gruvbox"
+ globals.greeter.load_theme()
+ return
+ elif result == 0: # 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..370998f
--- /dev/null
+++ b/web-greeter/browser/interceptor.py
@@ -0,0 +1,56 @@
+# -*- 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')
+ and not url.startswith('devtools')
+ )
+
+ 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/browser/window.py b/web-greeter/browser/window.py
new file mode 100644
index 0000000..5f8ad95
--- /dev/null
+++ b/web-greeter/browser/window.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+#
+# window.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 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 __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()
diff --git a/web-greeter/config.py b/web-greeter/config.py
new file mode 100644
index 0000000..82acb9f
--- /dev/null
+++ b/web-greeter/config.py
@@ -0,0 +1,89 @@
+# -*- 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/",
+ "version": {
+ "full": "3.0.0",
+ "major": 3,
+ "minor": 3,
+ "micro": 0,
+ },
+ }
+}
+
+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/utils/keyboard.py b/web-greeter/logger.py
similarity index 60%
rename from web-greeter/utils/keyboard.py
rename to web-greeter/logger.py
index 89272ef..a409a3b 100644
--- a/web-greeter/utils/keyboard.py
+++ b/web-greeter/logger.py
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
#
-# keyboard.py
+# logger.py
#
+# Copyright © 2017 Antergos
# Copyright © 2021 JezerM
#
# This file is part of Web Greeter.
@@ -25,22 +26,25 @@
# 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 logging import (
+ getLogger,
+ DEBUG,
+ Formatter,
+ StreamHandler
+)
-from PyQt5.QtCore import QUrl, pyqtSignal, Qt
-from PyQt5.QtGui import QKeyEvent
+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()
-import globals
+global logger
+logger = getLogger("debug")
-
-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
+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/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,
+ }},
+ )
diff --git a/web-greeter/utils/battery.py b/web-greeter/utils/battery.py
index 2abb44e..3ba4c21 100644
--- a/web-greeter/utils/battery.py
+++ b/web-greeter/utils/battery.py
@@ -1,30 +1,11 @@
-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
+from shutil import which
running = False
@@ -95,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
@@ -116,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"
@@ -152,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)
@@ -183,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
@@ -195,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"),
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/screensaver.py b/web-greeter/utils/screensaver.py
new file mode 100644
index 0000000..6d339f8
--- /dev/null
+++ b/web-greeter/utils/screensaver.py
@@ -0,0 +1,39 @@
+from logger import logger
+from Xlib.display import Display
+
+saved_data: dict[str, int]
+saved = False
+available = False
+
+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, available, display
+ if saved or not available:
+ 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, 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"])
+ display.flush()
+ saved = False
+ logger.debug("Screensaver reset")
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
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