Browse Source

Added error handling, and some changes

sisyphus
JezerM 4 years ago
parent
commit
527e299c76
  1. 4
      Makefile
  2. 17
      README.md
  3. 17
      dist/web-greeter.yml
  4. 8
      web-greeter/bridge/Config.py
  5. 87
      web-greeter/bridge/Greeter.py
  6. 14
      web-greeter/bridge/__init__.py
  7. 119
      web-greeter/globals.py
  8. 83
      web-greeter/greeter.py
  9. 46
      web-greeter/resources/js/docs/Greeter.js
  10. 120
      web-greeter/utils/config.py
  11. 2
      web-greeter/whither.yml

4
Makefile

@ -30,6 +30,8 @@ themes_dir ?= $(abspath $(PREFIX)/share/web-greeter/themes)
logo_image ?= $(themes_dir)/default/img/antergos-logo-user.png
stays_on_top := True
user_image ?= $(themes_dir)/default/img/antergos.png
battery_enabled := False
backlight_enabled := False
ifeq ($(MAKECMDGOALS),build_dev)
@ -51,6 +53,8 @@ _apply_config:
@$(SET_CONFIG) logo_image $(logo_image)
@$(SET_CONFIG) stays_on_top $(stays_on_top)
@$(SET_CONFIG) user_image $(user_image)
@$(SET_CONFIG) battery_enabled $(battery_enabled)
@$(SET_CONFIG) backlight_enabled $(backlight_enabled)
_build_init: clean
$(DO) build-init

17
README.md

@ -26,7 +26,20 @@ sudo make install
```
## Theme Javascript API
[Antergos](https://github.com/Antergos) documentation is no longer available. Although, you can see the man-pages `man web-greeter` for some documentation and explanation. Also, you can explore the provided [themes](./themes) for real use cases.
[Antergos][Antergos] documentation is no longer available. Although, you can see the man-pages `man web-greeter` for some documentation and explanation. Also, you can explore the provided [themes](./themes) for real use cases.
## Enable features
### Brightness control
To control the brightness inside the greeter, I recommend to use [acpilight][acpilight] replacement for `xbacklight`.
udev rules are needed to be applied before using it. Then, lightdm will need to be allowed to change backlight values, to do so add lightdm user to **video** group: `sudo usermod -a -G video lightdm`
If you don't want to or don't have a compatible device, disable it inside `/etc/lightdm/web-greeter.yml`
### Battery status
`acpi` is the only tool you need (and a battery).
You can disable it inside `/etc/lightdm/web-greeter.yml`
## Debugging
You can run the greeter from within your desktop session if you add the following line to the desktop file for your session located in `/usr/share/xsessions/`: `X-LightDM-Allow-Greeter=true`.
@ -41,4 +54,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"
[acpilight]: https://gitlab.com/wavexx/acpilight "acpilight"

17
dist/web-greeter.yml vendored

@ -31,3 +31,20 @@ greeter:
theme: default
time_format: LT
time_language: auto
#
# features:
# battery: Enable greeter and themes to get battery status.
# backlight:
# enabled: Enable greeter and themes to control display backlight.
# value: The amount to increase/decrease brightness by greeter.
# steps: How many steps are needed to do the change. 0 for instant change.
#
# NOTE: Backlight feature uses 'acpilight' or 'xbacklight' as brightness controller
#
features:
battery: '@battery_enabled@'
backlight:
enabled: '@backlight_enabled@'
value: 10
steps: 0

8
web-greeter/bridge/Config.py

@ -40,7 +40,9 @@ class Config(BridgeObject):
def __init__(self, config, *args, **kwargs):
super().__init__(name='Config', *args, **kwargs)
self._branding, self._greeter = config.branding.as_dict(), config.greeter.as_dict()
self._branding = config.branding.as_dict()
self._greeter = config.greeter.as_dict()
self._features = config.features.as_dict()
@bridge.prop(Variant, notify=noop_signal)
def branding(self):
@ -49,3 +51,7 @@ class Config(BridgeObject):
@bridge.prop(Variant, notify=noop_signal)
def greeter(self):
return self._greeter
@bridge.prop(Variant, notify=noop_signal)
def features(self):
return self._features

87
web-greeter/bridge/Greeter.py

@ -26,6 +26,8 @@
# along with Web Greeter; If not, see <http://www.gnu.org/licenses/>.
# Standard Lib
import subprocess
import re
# 3rd-Party Libs
import gi
@ -44,13 +46,54 @@ from . import (
layout_to_dict,
session_to_dict,
user_to_dict,
battery_to_dict,
)
LightDMGreeter = LightDM.Greeter()
LightDMUsers = LightDM.UserList()
def changeBrightness(self, method: str, quantity: int):
if self._config.features.backlight["enabled"] != True:
return
try:
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")
except Exception as err:
print("[ERROR] Brightness:", err)
finally:
self.property_changed.emit()
pass
def getBrightness(self):
if self._config.features.backlight["enabled"] != True:
return -1
try:
level = subprocess.run(["xbacklight", "-get"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True)
return int(level.stdout)
except Exception as err:
print("[ERROR] Battery:", err)
return -1
def updateBattery(self):
if self._config.features.battery != True:
return
try:
acpi = subprocess.run(["acpi", "-b"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True)
battery = acpi.stdout.split(": ")
data = re.sub("%|,", "", battery[1])
level = data.split(" ")[1]
self._battery = int(level)
self._acpi = acpi.stdout
except Exception as err:
print("[ERROR] Battery: ", err)
else:
self.property_changed.emit()
class Greeter(BridgeObject):
# LightDM.Greeter Signals
@ -64,11 +107,15 @@ class Greeter(BridgeObject):
noop_signal = bridge.signal()
property_changed = bridge.signal()
def __init__(self, themes_dir, *args, **kwargs):
_battery = -1
_acpi = ""
def __init__(self, config, *args, **kwargs):
super().__init__(name='LightDMGreeter', *args, **kwargs)
self._config = config
self._shared_data_directory = ''
self._themes_directory = themes_dir
self._themes_directory = config.themes_dir
LightDMGreeter.connect_to_daemon_sync()
@ -122,6 +169,14 @@ class Greeter(BridgeObject):
def autologin_user(self):
return LightDMGreeter.get_autologin_user_hint()
@bridge.prop(Variant, notify=property_changed)
def batteryData(self):
return battery_to_dict(self._acpi)
@bridge.prop(int, notify=property_changed)
def brightness(self):
return getBrightness(self)
@bridge.prop(bool, notify=noop_signal)
def can_hibernate(self):
return LightDM.get_can_hibernate()
@ -138,6 +193,14 @@ class Greeter(BridgeObject):
def can_suspend(self):
return LightDM.get_can_suspend()
@bridge.prop(bool, notify=noop_signal)
def can_access_brightness(self):
return self._config.features.backlight["enabled"]
@bridge.prop(bool, notify=noop_signal)
def can_access_battery(self):
return self._config.features.battery
@bridge.prop(str, notify=noop_signal)
def default_session(self):
return LightDMGreeter.get_default_session_hint()
@ -228,6 +291,18 @@ class Greeter(BridgeObject):
LightDMGreeter.authenticate_as_guest()
self.property_changed.emit()
@bridge.method(int)
def brightnessSet(self, quantity):
return changeBrightness(self, "-set", quantity)
@bridge.method(int)
def brightnessIncrease(self, quantity):
return changeBrightness(self, "-inc", quantity)
@bridge.method(int)
def brightnessDecrease(self, quantity):
return changeBrightness(self, "-dec", quantity)
@bridge.method()
def cancel_authentication(self):
LightDMGreeter.cancel_authentication()
@ -269,7 +344,7 @@ class Greeter(BridgeObject):
def suspend(self):
return LightDM.suspend()
@bridge.method()
def batteryUpdate(self):
return updateBattery(self)

14
web-greeter/bridge/__init__.py

@ -26,6 +26,8 @@
# You should have received a copy of the GNU General Public License
# along with Web Greeter; If not, see <http://www.gnu.org/licenses/>.
import re
def language_to_dict(lang):
return dict(code=lang.get_code(), name=lang.get_name(), territory=lang.get_territory())
@ -64,6 +66,18 @@ def user_to_dict(user):
# ---->>> END DEPRECATED! <<<----
)
def battery_to_dict(batt):
if batt == "":
return dict()
formatted = re.sub("%|,|\n", "", batt)
colon = formatted.split(": ")
splitted = colon[1].split(" ")
return dict(
name = colon[0],
level = int(splitted[1]),
state = splitted[0]
)
from .Greeter import Greeter
from .Config import Config

119
web-greeter/globals.py

@ -0,0 +1,119 @@
# -*- 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 <http://www.gnu.org/licenses/>.
import sys
import yaml
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,
)
# Typing Helpers
BridgeObj = Type[BridgeObject]
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)
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_web_container_init(self):
self.get_and_apply_user_config()
@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
greeter_config.update(custom_config)
self.config.branding.update(branding_config)
self.config.greeter.update(greeter_config)
self.config.features.update(features_config)
self._config.debug_mode = greeter_config['debug_mode']
self._config.allow_remote_urls = not greeter_config['secure_mode']
def load_theme(self):
self.logger.debug('Loading theme...')
theme_url = '/{0}/{1}/index.html'.format(self.config.themes_dir, self.config.greeter.theme)
self._web_container.load(theme_url)
global custom_config
global greeter
custom_config = {}

83
web-greeter/greeter.py

@ -35,6 +35,7 @@ from typing import (
ClassVar,
Type,
List,
Tuple,
)
from logging import (
getLogger,
@ -44,81 +45,12 @@ from logging import (
)
# 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,
)
# Typing Helpers
BridgeObj = Type[BridgeObject]
BASE_DIR = os.path.dirname(os.path.realpath(__file__))
CONFIG_FILE = os.path.join(BASE_DIR, 'whither.yml')
custom_config = {}
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.themes_dir)
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)
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_web_container_init(self):
self.get_and_apply_user_config()
from utils import config
@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
greeter_config.update(custom_config)
self.config.branding.update(branding_config)
self.config.greeter.update(greeter_config)
self._config.debug_mode = greeter_config['debug_mode']
self._config.allow_remote_urls = not greeter_config['secure_mode']
def load_theme(self):
self.logger.debug('Loading theme...')
theme_url = '/{0}/{1}/index.html'.format(self.config.themes_dir, self.config.greeter.theme)
self._web_container.load(theme_url)
import globals
from globals import WebGreeter
def loadWhitherConf():
global whither_yaml
@ -257,11 +189,14 @@ def yargs(args: List[str]):
pass
if __name__ == '__main__':
custom_config = globals.custom_config
if args_lenght > 1:
args = sys.argv
args.pop(0)
yargs(args)
greeter = WebGreeter()
greeter.run()
globals.greeter = WebGreeter()
globals.greeter.run()

46
web-greeter/resources/js/docs/Greeter.js vendored

@ -308,6 +308,52 @@ class Greeter {
*/
suspend() {}
/**
* Gets the brightness
* @type {Number}
* @readonly
*/
get brightness() {}
/**
* Set the brightness
* @arg {Number} quantity The quantity to set
*/
brightnessSet( quantity ) {}
/**
* Increase the brightness
* @arg {Number} quantity The quantity to increase
*/
brightnessIncrease( quantity ) {}
/**
* Decrease the brightness
* @arg {Number} quantity The quantity to decrease
*/
brightnessDecrease( quantity ) {}
/**
* Gets the battery data
* @type {Object}
* @readonly
*/
get batteryData() {}
/**
* Whether or not the greeter can access to battery data
* @type {boolean}
* @readonly
*/
get can_access_battery() {}
/**
* Whether or not the greeter can control display brightness
* @type {boolean}
* @readonly
*/
get can_access_brightness() {}
}

120
web-greeter/utils/config.py

@ -0,0 +1,120 @@
# -*- 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 <http://www.gnu.org/licenses/>.
from whither.toolkits.bootstrap import WebPage, MainWindow
from PyQt5.QtCore import QUrl, pyqtSignal, Qt
from PyQt5.QtWidgets import QDialogButtonBox, QDialog, QVBoxLayout, QLabel, QPushButton, QAbstractButton
from PyQt5.QtGui import QKeyEvent
import globals
def javaScriptConsoleMessage(self, level: WebPage.JavaScriptConsoleMessageLevel, message: str, lineNumber: int, sourceID: str):
if sourceID == "":
sourceID = "console"
error = False
typeLog = ""
if level == WebPage.JavaScriptConsoleMessageLevel.ErrorMessageLevel:
typeLog = "[ERROR]"
error = True
elif level == WebPage.JavaScriptConsoleMessageLevel.WarningMessageLevel:
typeLog = "[WARNING]"
elif level == WebPage.JavaScriptConsoleMessageLevel.InfoMessageLevel:
typeLog = "[LOG]"
else:
return
logMessage = "{typ} {source} {line}: {msg}".format(typ = typeLog, msg = message, source = sourceID, line = lineNumber)
print(logMessage)
if error:
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):
print("ERROR PROMPT")
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["theme"] = "default"
globals.greeter.get_and_apply_user_config()
globals.greeter.load_theme()
return
elif result == 2: # Reload
globals.greeter.load_theme()
return
return
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
WebPage.javaScriptConsoleMessage = javaScriptConsoleMessage # Yep, you can override functions like this!!!

2
web-greeter/whither.yml

@ -39,6 +39,8 @@ WebGreeter:
theme: default
time_format: LT
time_language: auto
features:
battery: False
greeters_dir: '@greeters_dir@'
locale_dir: '@locale_dir@'
themes_dir: '@themes_dir@'

Loading…
Cancel
Save