diff --git a/Makefile b/Makefile
index 70e0b63..83e0d3b 100644
--- a/Makefile
+++ b/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
diff --git a/README.md b/README.md
index de84d30..4c202b3 100644
--- a/README.md
+++ b/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"
diff --git a/dist/web-greeter.yml b/dist/web-greeter.yml
index 258eda8..c500766 100644
--- a/dist/web-greeter.yml
+++ b/dist/web-greeter.yml
@@ -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
diff --git a/web-greeter/bridge/Config.py b/web-greeter/bridge/Config.py
index c3fe31f..1b2a489 100644
--- a/web-greeter/bridge/Config.py
+++ b/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
diff --git a/web-greeter/bridge/Greeter.py b/web-greeter/bridge/Greeter.py
index 75053b5..3460d35 100644
--- a/web-greeter/bridge/Greeter.py
+++ b/web-greeter/bridge/Greeter.py
@@ -26,6 +26,8 @@
# along with Web Greeter; If not, see .
# 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)
diff --git a/web-greeter/bridge/__init__.py b/web-greeter/bridge/__init__.py
index 551b16d..e401038 100644
--- a/web-greeter/bridge/__init__.py
+++ b/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 .
+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
diff --git a/web-greeter/globals.py b/web-greeter/globals.py
new file mode 100644
index 0000000..3814336
--- /dev/null
+++ b/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 .
+
+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 = {}
+
diff --git a/web-greeter/greeter.py b/web-greeter/greeter.py
index f5680fe..0c668e7 100644
--- a/web-greeter/greeter.py
+++ b/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()
diff --git a/web-greeter/resources/js/docs/Greeter.js b/web-greeter/resources/js/docs/Greeter.js
index 6f96344..16b1c13 100644
--- a/web-greeter/resources/js/docs/Greeter.js
+++ b/web-greeter/resources/js/docs/Greeter.js
@@ -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() {}
+
}
diff --git a/web-greeter/utils/config.py b/web-greeter/utils/config.py
new file mode 100644
index 0000000..a2cda15
--- /dev/null
+++ b/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 .
+
+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!!!
+
diff --git a/web-greeter/whither.yml b/web-greeter/whither.yml
index 8cf5161..c5dcec1 100644
--- a/web-greeter/whither.yml
+++ b/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@'