diff --git a/.pylintrc b/.pylintrc
new file mode 100644
index 0000000..c4a617a
--- /dev/null
+++ b/.pylintrc
@@ -0,0 +1,569 @@
+[MASTER]
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code.
+extension-pkg-allow-list=
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
+# for backward compatibility.)
+extension-pkg-whitelist=PyQt5
+
+# Return non-zero exit code if any of these messages/categories are detected,
+# even if score is above --fail-under value. Syntax same as enable. Messages
+# specified are enabled, while categories only check already-enabled messages.
+fail-on=
+
+# Specify a score threshold to be exceeded before program exits with error.
+fail-under=9
+
+# Files or directories to be skipped. They should be base names, not paths.
+ignore=CVS,resources
+
+# Add files or directories matching the regex patterns to the ignore-list. The
+# regex matches against paths and can be in Posix or Windows format.
+ignore-paths=
+
+# Files or directories matching the regex patterns are skipped. The regex
+# matches against base names, not paths.
+ignore-patterns=setup.py,resources.py
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+init-hook=import sys; sys.path.append('./src');
+
+# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
+# number of processors available to use.
+jobs=1
+
+# Control the amount of potential inferred values when inferring a single
+# object. This can help the performance when dealing with large functions or
+# complex, nested conditions.
+limit-inference-results=100
+
+# List of plugins (as comma separated values of python module names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# Minimum Python version to use for version dependent checks. Will default to
+# the version used to run pylint.
+py-version=3.8
+
+# When enabled, pylint would attempt to guess common misconfiguration and emit
+# user-friendly hints instead of false-positive error messages.
+suggestion-mode=yes
+
+# Allow loading of arbitrary C extensions. Extensions are imported into the
+# active Python interpreter and may run arbitrary code.
+unsafe-load-any-extension=no
+
+
+[MESSAGES CONTROL]
+
+# Only show warnings with the listed confidence levels. Leave empty to show
+# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
+confidence=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once). You can also use "--disable=all" to
+# disable everything first and then reenable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use "--disable=all --enable=classes
+# --disable=W".
+disable=raw-checker-failed,
+ bad-inline-option,
+ locally-disabled,
+ file-ignored,
+ suppressed-message,
+ useless-suppression,
+ deprecated-pragma,
+ use-symbolic-message-instead,
+ missing-module-docstring
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once). See also the "--disable" option for examples.
+enable=c-extension-no-member
+
+
+[REPORTS]
+
+# Python expression which should return a score less than or equal to 10. You
+# have access to the variables 'error', 'warning', 'refactor', and 'convention'
+# which contain the number of messages in each category, as well as 'statement'
+# which is the total number of statements analyzed. This score is used by the
+# global evaluation report (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details.
+#msg-template=
+
+# Set the output format. Available formats are text, parseable, colorized, json
+# and msvs (visual studio). You can also give a reporter class, e.g.
+# mypackage.mymodule.MyReporterClass.
+output-format=text
+
+# Tells whether to display a full report or only the messages.
+reports=no
+
+# Activate the evaluation score.
+score=yes
+
+
+[REFACTORING]
+
+# Maximum number of nested blocks for function / method body
+max-nested-blocks=5
+
+# Complete name of functions that never returns. When checking for
+# inconsistent-return-statements if a never returning function is called then
+# it will be considered as an explicit return statement and no message will be
+# printed.
+never-returning-functions=sys.exit,argparse.parse_error
+
+
+[LOGGING]
+
+# The type of string formatting that logging methods do. `old` means using %
+# formatting, `new` is for `{}` formatting.
+logging-format-style=old
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format.
+logging-modules=logging
+
+
+[STRING]
+
+# This flag controls whether inconsistent-quotes generates a warning when the
+# character used as a quote delimiter is used inconsistently within a module.
+check-quote-consistency=no
+
+# This flag controls whether the implicit-str-concat should generate a warning
+# on implicit string concatenation in sequences defined over several lines.
+check-str-concat-over-line-jumps=no
+
+
+[SPELLING]
+
+# Limits count of emitted suggestions for spelling mistakes.
+max-spelling-suggestions=4
+
+# Spelling dictionary name. Available dictionaries: none. To make it work,
+# install the 'python-enchant' package.
+spelling-dict=
+
+# List of comma separated words that should be considered directives if they
+# appear and the beginning of a comment and should not be checked.
+spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
+
+# List of comma separated words that should not be checked.
+spelling-ignore-words=
+
+# A path to a file that contains the private dictionary; one word per line.
+spelling-private-dict-file=
+
+# Tells whether to store unknown words to the private dictionary (see the
+# --spelling-private-dict-file option) instead of raising a message.
+spelling-store-unknown-words=no
+
+
+[FORMAT]
+
+# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
+expected-line-ending-format=
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )??$
+
+# Number of spaces of indent required inside a hanging or continued line.
+indent-after-paren=4
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string=' '
+
+# Maximum number of characters on a single line.
+max-line-length=100
+
+# Maximum number of lines in a module.
+max-module-lines=1000
+
+# Allow the body of a class to be on the same line as the declaration if body
+# contains single statement.
+single-line-class-stmt=no
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=no
+
+
+[BASIC]
+
+# Naming style matching correct argument names.
+argument-naming-style=snake_case
+
+# Regular expression matching correct argument names. Overrides argument-
+# naming-style.
+#argument-rgx=
+
+# Naming style matching correct attribute names.
+attr-naming-style=snake_case
+
+# Regular expression matching correct attribute names. Overrides attr-naming-
+# style.
+#attr-rgx=
+
+# Bad variable names which should always be refused, separated by a comma.
+bad-names=foo,
+ bar,
+ baz,
+ toto,
+ tutu,
+ tata
+
+# Bad variable names regexes, separated by a comma. If names match any regex,
+# they will always be refused
+bad-names-rgxs=
+
+# Naming style matching correct class attribute names.
+class-attribute-naming-style=any
+
+# Regular expression matching correct class attribute names. Overrides class-
+# attribute-naming-style.
+#class-attribute-rgx=
+
+# Naming style matching correct class constant names.
+class-const-naming-style=UPPER_CASE
+
+# Regular expression matching correct class constant names. Overrides class-
+# const-naming-style.
+#class-const-rgx=
+
+# Naming style matching correct class names.
+class-naming-style=PascalCase
+
+# Regular expression matching correct class names. Overrides class-naming-
+# style.
+#class-rgx=
+
+# Naming style matching correct constant names.
+const-naming-style=UPPER_CASE
+
+# Regular expression matching correct constant names. Overrides const-naming-
+# style.
+#const-rgx=
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=-1
+
+# Naming style matching correct function names.
+function-naming-style=snake_case
+
+# Regular expression matching correct function names. Overrides function-
+# naming-style.
+#function-rgx=
+
+# Good variable names which should always be accepted, separated by a comma.
+good-names=i,
+ j,
+ k,
+ ex,
+ Run,
+ _
+
+# Good variable names regexes, separated by a comma. If names match any regex,
+# they will always be accepted
+good-names-rgxs=
+
+# Include a hint for the correct naming format with invalid-name.
+include-naming-hint=no
+
+# Naming style matching correct inline iteration names.
+inlinevar-naming-style=any
+
+# Regular expression matching correct inline iteration names. Overrides
+# inlinevar-naming-style.
+#inlinevar-rgx=
+
+# Naming style matching correct method names.
+method-naming-style=snake_case
+
+# Regular expression matching correct method names. Overrides method-naming-
+# style.
+#method-rgx=
+
+# Naming style matching correct module names.
+module-naming-style=snake_case
+
+# Regular expression matching correct module names. Overrides module-naming-
+# style.
+#module-rgx=
+
+# Colon-delimited sets of names that determine each other's naming style when
+# the name regexes allow several styles.
+name-group=
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=^_
+
+# List of decorators that produce properties, such as abc.abstractproperty. Add
+# to this list to register other decorators that produce valid properties.
+# These decorators are taken in consideration only for invalid-name.
+property-classes=abc.abstractproperty
+
+# Naming style matching correct variable names.
+variable-naming-style=snake_case
+
+# Regular expression matching correct variable names. Overrides variable-
+# naming-style.
+#variable-rgx=
+
+
+[VARIABLES]
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid defining new builtins when possible.
+additional-builtins=
+
+# Tells whether unused global variables should be treated as a violation.
+allow-global-unused-variables=yes
+
+# List of names allowed to shadow builtins
+allowed-redefined-builtins=
+
+# List of strings which can identify a callback function by name. A callback
+# name must start or end with one of those strings.
+callbacks=cb_,
+ _cb
+
+# A regular expression matching the name of dummy variables (i.e. expected to
+# not be used).
+dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore.
+ignored-argument-names=_.*|^ignored_|^unused_
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# List of qualified module names which can have objects that can redefine
+# builtins.
+redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
+
+
+[SIMILARITIES]
+
+# Comments are removed from the similarity computation
+ignore-comments=yes
+
+# Docstrings are removed from the similarity computation
+ignore-docstrings=yes
+
+# Imports are removed from the similarity computation
+ignore-imports=no
+
+# Signatures are removed from the similarity computation
+ignore-signatures=no
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,
+ XXX,
+ TODO
+
+# Regular expression of note tags to take in consideration.
+#notes-rgx=
+
+
+[TYPECHECK]
+
+# List of decorators that produce context managers, such as
+# contextlib.contextmanager. Add to this list to register other decorators that
+# produce valid context managers.
+contextmanager-decorators=contextlib.contextmanager
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E1101 when accessed. Python regular
+# expressions are accepted.
+generated-members=
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# class is considered mixin if its name matches the mixin-class-rgx option.
+ignore-mixin-members=yes
+
+# Tells whether to warn about missing members when the owner of the attribute
+# is inferred to be None.
+ignore-none=yes
+
+# This flag controls whether pylint should warn about no-member and similar
+# checks whenever an opaque object is returned when inferring. The inference
+# can return multiple potential results while evaluating a Python object, but
+# some branches might not be evaluated, which results in partial inference. In
+# that case, it might be useful to still emit no-member and other checks for
+# the rest of the inferred objects.
+ignore-on-opaque-inference=yes
+
+# List of class names for which member attributes should not be checked (useful
+# for classes with dynamically set attributes). This supports the use of
+# qualified names.
+ignored-classes=optparse.Values,thread._local,_thread._local
+
+# List of module names for which member attributes should not be checked
+# (useful for modules/projects where namespaces are manipulated during runtime
+# and thus existing member attributes cannot be deduced by static analysis). It
+# supports qualified module names, as well as Unix pattern matching.
+ignored-modules=
+
+# Show a hint with possible names when a member name was not found. The aspect
+# of finding the hint is based on edit distance.
+missing-member-hint=yes
+
+# The minimum edit distance a name should have in order to be considered a
+# similar match for a missing member name.
+missing-member-hint-distance=1
+
+# The total number of similar names that should be taken in consideration when
+# showing a hint for a missing member.
+missing-member-max-choices=1
+
+# Regex pattern to define which classes are considered mixins ignore-mixin-
+# members is set to 'yes'
+mixin-class-rgx=.*[Mm]ixin
+
+# List of decorators that change the signature of a decorated function.
+signature-mutators=
+
+
+[CLASSES]
+
+# Warn about protected attribute access inside special methods
+check-protected-access-in-special-methods=no
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,
+ __new__,
+ setUp,
+ __post_init__
+
+# List of member names, which should be excluded from the protected access
+# warning.
+exclude-protected=_asdict,
+ _fields,
+ _replace,
+ _source,
+ _make
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=cls
+
+
+[IMPORTS]
+
+# List of modules that can be imported at any level, not just the top level
+# one.
+allow-any-import-level=
+
+# Allow wildcard imports from modules that define __all__.
+allow-wildcard-with-all=no
+
+# Analyse import fallback blocks. This can be used to support both Python 2 and
+# 3 compatible code, which means that the block might have code that exists
+# only in one or another interpreter, leading to false positives when analysed.
+analyse-fallback-blocks=no
+
+# Deprecated modules which should not be used, separated by a comma.
+deprecated-modules=
+
+# Output a graph (.gv or any supported image format) of external dependencies
+# to the given file (report RP0402 must not be disabled).
+ext-import-graph=
+
+# Output a graph (.gv or any supported image format) of all (i.e. internal and
+# external) dependencies to the given file (report RP0402 must not be
+# disabled).
+import-graph=
+
+# Output a graph (.gv or any supported image format) of internal dependencies
+# to the given file (report RP0402 must not be disabled).
+int-import-graph=
+
+# Force import order to recognize a module as part of the standard
+# compatibility libraries.
+known-standard-library=
+
+# Force import order to recognize a module as part of a third party library.
+known-third-party=enchant
+
+# Couples of modules and preferred modules, separated by a comma.
+preferred-modules=
+
+
+[DESIGN]
+
+# List of regular expressions of class ancestor names to ignore when counting
+# public methods (see R0903)
+exclude-too-few-public-methods=
+
+# List of qualified class names to ignore when counting class parents (see
+# R0901)
+ignored-parents=
+
+# Maximum number of arguments for function / method.
+max-args=5
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Maximum number of boolean expressions in an if statement (see R0916).
+max-bool-expr=5
+
+# Maximum number of branch for function / method body.
+max-branches=12
+
+# Maximum number of locals for function / method body.
+max-locals=15
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+# Maximum number of return / yield for function / method body.
+max-returns=6
+
+# Maximum number of statements in function / method body.
+max-statements=50
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "BaseException, Exception".
+overgeneral-exceptions=BaseException,
+ Exception
diff --git a/src/__main__.py b/src/__main__.py
index c8bb07d..ece3017 100644
--- a/src/__main__.py
+++ b/src/__main__.py
@@ -26,14 +26,16 @@
# along with Web Greeter; If not, see .
# Standard lib
-import sys, argparse, os
+import sys
+import argparse
+import os
from typing import List
# 3rd-Party Libs
-import globals
import config
def list_themes() -> List[str]:
+ """List available 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"
filenames = os.listdir(themes_dir)
@@ -46,18 +48,21 @@ def list_themes() -> List[str]:
return dirlist
def print_themes():
+ """Print available 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))
+ print(f"Themes are located in {themes_dir}\n")
for theme in themes:
print("-", theme)
def set_theme(theme: str):
+ """Sets the theme"""
config.web_greeter_config["config"]["greeter"]["theme"] = theme
def set_debug(value: bool):
+ """Sets debug mode"""
conf = config.web_greeter_config["config"]
app = config.web_greeter_config["app"]
conf["greeter"]["debug_mode"] = value
@@ -65,16 +70,23 @@ def set_debug(value: bool):
app["fullscreen"] = not value
def parse(argv):
+ """Parse command arguments"""
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]")
- parser.add_argument("--no-sandbox", action="store_true", help=argparse.SUPPRESS)
+ 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]")
+ parser.add_argument("--no-sandbox", action = "store_true", help = argparse.SUPPRESS)
args: argparse.Namespace
@@ -85,20 +97,21 @@ def parse(argv):
# print(args)
- if (args.list):
+ if args.list:
print_themes()
sys.exit()
- if (args.theme):
+ if args.theme:
set_theme(args.theme)
- if (args.debug != None):
+ if args.debug is not None:
set_debug(args.debug)
if __name__ == '__main__':
parse(sys.argv[1:])
+ import globales
from browser.browser import Browser
- globals.greeter = Browser()
- greeter = globals.greeter
+ globales.greeter = Browser()
+ greeter = globales.greeter
greeter.show()
greeter.run()
diff --git a/src/bridge/Config.py b/src/bridge/Config.py
index c9329bc..2547d91 100644
--- a/src/bridge/Config.py
+++ b/src/bridge/Config.py
@@ -26,27 +26,32 @@
# You should have received a copy of the GNU General Public License
# along with Web Greeter; If not, see .
-# 3rd-Party Libs
-from browser.bridge import Bridge, BridgeObject
-from PyQt5.QtCore import QVariant
+# pylint: disable=wrong-import-position
+# Standard Lib
+from typing import List
+
+# 3rd-Party Libs
import gi
gi.require_version('LightDM', '1')
from gi.repository import LightDM
-from typing import List
+from PyQt5.QtCore import QVariant
+
+# This application
+from browser.bridge import Bridge, BridgeObject
from config import web_greeter_config
-from . import (
- layout_to_dict
-)
+from . import layout_to_dict
def get_layouts(config_layouts: List[str]):
+ """Get layouts from web-greeter's config"""
layouts = LightDM.get_layouts()
final_layouts: list[LightDM.Layout] = []
for ldm_lay in layouts:
for conf_lay in config_layouts:
- if type(conf_lay) != str: return
+ if not isinstance(conf_lay, str):
+ return []
conf_lay = conf_lay.replace(" ", "\t")
if ldm_lay.get_name() == conf_lay:
final_layouts.append(layout_to_dict(ldm_lay))
@@ -54,17 +59,19 @@ def get_layouts(config_layouts: List[str]):
class Config(BridgeObject):
+ # pylint: disable=no-self-use,missing-function-docstring,too-many-public-methods,invalid-name
+ """Config bridge class, known as `greeter_config` in javascript"""
noop_signal = Bridge.signal()
def __init__(self, *args, **kwargs):
super().__init__(name='Config', *args, **kwargs)
- config = web_greeter_config["config"]
- self._branding = config["branding"]
- self._greeter = config["greeter"]
- self._features = config["features"]
- 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(QVariant, notify=noop_signal)
def branding(self):
@@ -81,3 +88,5 @@ class Config(BridgeObject):
@Bridge.prop(QVariant, notify=noop_signal)
def layouts(self):
return self._layouts
+
+config = Config()
diff --git a/src/bridge/Greeter.py b/src/bridge/Greeter.py
index 6eedf8a..c5364b7 100644
--- a/src/bridge/Greeter.py
+++ b/src/bridge/Greeter.py
@@ -26,33 +26,32 @@
# You should have received a copy of the GNU General Public License
# along with Web Greeter; If not, see .
-# Standard Lib
-from browser.error_prompt import Dialog
-import subprocess
-import threading
+# pylint: disable=wrong-import-position
# 3rd-Party Libs
import gi
gi.require_version('LightDM', '1')
from gi.repository import LightDM
+from gi.repository.GLib import GError
+
+from PyQt5.QtCore import QVariant
+# This Application
+from logger import logger
+from browser.error_prompt import Dialog
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
+from utils.screensaver import screensaver
from utils.brightness import BrightnessController
-import globals
-# This Application
from . import (
language_to_dict,
layout_to_dict,
session_to_dict,
user_to_dict,
- battery_to_dict,
- logger
+ battery_to_dict
)
# import utils.battery as battery
@@ -60,18 +59,9 @@ from . import (
LightDMGreeter = LightDM.Greeter()
LightDMUsers = LightDM.UserList()
-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:
- logger.error("Brightness: {}".format(err))
- return -1
-
class Greeter(BridgeObject):
+ # pylint: disable=no-self-use,missing-function-docstring,too-many-public-methods,invalid-name
+ """Greeter bridge class, known as `lightdm` in javascript"""
# LightDM.Greeter Signals
authentication_complete = Bridge.signal()
@@ -103,14 +93,18 @@ class Greeter(BridgeObject):
try:
LightDMGreeter.connect_to_daemon_sync()
- except Exception as err:
+ except GError 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 = Dialog(
+ title = "An error ocurred",
+ message = "Detected a problem that could interfere" \
+ " with the system login process",
+ detail = f"LightDM: {err}\n" \
+ "You can continue without major problems, " \
+ "but you won't be able to log in",
+ buttons = ["Okay"]
+ )
dia.exec()
- pass
self._connect_signals()
self._determine_shared_data_directory_path()
@@ -119,35 +113,32 @@ class Greeter(BridgeObject):
def _determine_shared_data_directory_path(self):
user = LightDMUsers.get_users()[0]
user_data_dir = LightDMGreeter.ensure_shared_data_dir_sync(user.get_name())
- if user_data_dir == None:
+ if user_data_dir is None:
return
self._shared_data_directory = user_data_dir.rpartition('/')[0]
def _connect_signals(self):
LightDMGreeter.connect(
'authentication-complete',
- lambda greeter: self._emit_signal(self.authentication_complete)
+ lambda _: self._emit_signal(self.authentication_complete)
)
LightDMGreeter.connect(
'autologin-timer-expired',
- lambda greeter: self._emit_signal(self.autologin_timer_expired)
+ lambda _: self._emit_signal(self.autologin_timer_expired)
)
- LightDMGreeter.connect('idle', lambda greeter: self._emit_signal(self.idle))
- LightDMGreeter.connect('reset', lambda greeter: self._emit_signal(self.reset))
+ LightDMGreeter.connect('idle', lambda _: self._emit_signal(self.idle))
+ LightDMGreeter.connect('reset', lambda _: self._emit_signal(self.reset))
LightDMGreeter.connect(
'show-message',
- lambda greeter, msg, mtype: self._emit_signal(self.show_message, msg, mtype.real)
+ lambda _, msg, mtype: self._emit_signal(self.show_message, msg, mtype.real)
)
LightDMGreeter.connect(
'show-prompt',
- lambda greeter, msg, mtype: self._emit_signal(self.show_prompt, msg, mtype.real)
+ lambda _, msg, mtype: self._emit_signal(self.show_prompt, msg, mtype.real)
)
- if self._battery:
- self._battery.connect(lambda: self.battery_update.emit())
-
def _emit_signal(self, _signal, *args):
self.property_changed.emit()
_signal.emit(*args)
@@ -173,6 +164,10 @@ class Greeter(BridgeObject):
def batteryData(self):
return battery_to_dict(self._battery)
+ @Bridge.prop(QVariant, notify=battery_update)
+ def battery_data(self):
+ return battery_to_dict(self._battery)
+
@Bridge.prop(int, notify=brightness_update)
def brightness(self):
return self._brightness_controller.brightness
@@ -243,7 +238,7 @@ class Greeter(BridgeObject):
@layout.setter
def layout(self, layout):
- if type(layout) != dict:
+ if not isinstance(layout, dict):
return False
lay = dict(
name = layout.get("name") or "",
@@ -310,14 +305,26 @@ class Greeter(BridgeObject):
def brightnessSet(self, quantity):
self._brightness_controller.set_brightness(quantity)
+ @Bridge.method(int)
+ def brightness_set(self, quantity):
+ self._brightness_controller.inc_brightness(quantity)
+
@Bridge.method(int)
def brightnessIncrease(self, quantity):
self._brightness_controller.inc_brightness(quantity)
+ @Bridge.method(int)
+ def brightness_increase(self, quantity):
+ self._brightness_controller.inc_brightness(quantity)
+
@Bridge.method(int)
def brightnessDecrease(self, quantity):
self._brightness_controller.dec_brightness(quantity)
+ @Bridge.method(int)
+ def brightness_decrease(self, quantity):
+ self._brightness_controller.inc_brightness(quantity)
+
@Bridge.method()
def cancel_authentication(self):
LightDMGreeter.cancel_authentication()
@@ -343,7 +350,7 @@ class Greeter(BridgeObject):
@Bridge.method(str)
def set_language(self, lang):
- if self.is_authenticated:
+ if self.is_authenticated is True:
LightDMGreeter.set_language(lang)
self.property_changed.emit()
@@ -354,13 +361,15 @@ class Greeter(BridgeObject):
@Bridge.method(str, result=bool)
def start_session(self, session):
if not session.strip():
- return
- started = LightDMGreeter.start_session_sync(session)
+ return False
+ started: bool = LightDMGreeter.start_session_sync(session)
if started or self.is_authenticated:
- logger.debug("Session \"" + session + "\" started")
- reset_screensaver()
+ logger.debug("Session \"%s\" started", session)
+ screensaver.reset_screensaver()
return started
@Bridge.method(result=bool)
def suspend(self):
return LightDM.suspend()
+
+greeter = Greeter()
diff --git a/src/bridge/ThemeUtils.py b/src/bridge/ThemeUtils.py
index 7dc6bf2..b957a4b 100644
--- a/src/bridge/ThemeUtils.py
+++ b/src/bridge/ThemeUtils.py
@@ -26,25 +26,32 @@
# You should have received a copy of the GNU General Public License
# along with Web Greeter; If not, see .
+# pylint: disable=wrong-import-position
+
# Standard Lib
import os
import re
import tempfile
# 3rd-Party Libs
-from browser.bridge import Bridge, BridgeObject
from PyQt5.QtCore import QVariant
+# This application
+from browser.bridge import Bridge, BridgeObject
+
from config import web_greeter_config
from logger import logger
+from bridge.Greeter import greeter
class ThemeUtils(BridgeObject):
+ # pylint: disable=no-self-use,missing-function-docstring,too-many-public-methods,invalid-name
+ """ThemeUtils bridge class, known as `theem_utils` in javascript"""
- def __init__(self, greeter, *args, **kwargs):
+ def __init__(self, greeter_object, *args, **kwargs):
super().__init__(name='ThemeUtils', *args, **kwargs)
self._config = web_greeter_config
- self._greeter = greeter
+ self._greeter = greeter_object
self._allowed_dirs = (
os.path.dirname(
@@ -61,8 +68,11 @@ class ThemeUtils(BridgeObject):
if not dir_path or not isinstance(dir_path, str) or '/' == dir_path:
return []
- if (dir_path.startswith("./")):
- dir_path = os.path.join(os.path.dirname(self._config["config"]["greeter"]["theme"]), dir_path)
+ if dir_path.startswith("./"):
+ dir_path = os.path.join(
+ os.path.dirname(self._config["config"]["greeter"]["theme"]),
+ dir_path
+ )
dir_path = os.path.realpath(os.path.normpath(dir_path))
@@ -77,7 +87,7 @@ class ThemeUtils(BridgeObject):
break
if not allowed:
- logger.error("Path \"" + dir_path + "\" is not allowed");
+ logger.error("Path \"%s\" is not allowed", dir_path)
return []
result = []
@@ -91,3 +101,5 @@ class ThemeUtils(BridgeObject):
result.sort()
return result
+
+theme_utils = ThemeUtils(greeter)
diff --git a/src/bridge/__init__.py b/src/bridge/__init__.py
index 68220d5..14ce51a 100644
--- a/src/bridge/__init__.py
+++ b/src/bridge/__init__.py
@@ -26,90 +26,70 @@
# You should have received a copy of the GNU General Public License
# along with Web Greeter; If not, see .
-import re
-import threading, time
-
-from logging import (
- getLogger,
- DEBUG,
- ERROR,
- Formatter,
- StreamHandler,
-)
-
-log_format = ''.join([
- '%(asctime)s [ %(levelname)s ] %(filename)s %(',
- 'lineno)d : %(funcName)s | %(message)s'
-])
-formatter = Formatter(fmt=log_format, datefmt="%Y-%m-%d %H:%M:%S")
-logger = getLogger("greeter")
-logger.propagate = False
-stream_handler = StreamHandler()
-stream_handler.setLevel(DEBUG)
-stream_handler.setFormatter(formatter)
-logger.setLevel(DEBUG)
-logger.addHandler(stream_handler)
-
def language_to_dict(lang):
- if (not lang):
- return dict()
- return dict(code=lang.get_code(), name=lang.get_name(), territory=lang.get_territory())
+ """Returns a dict from LightDMLanguage object"""
+ if not lang:
+ return {}
+ return {
+ "code": lang.get_code(),
+ "name": lang.get_name(),
+ "territory": lang.get_territory()
+ }
def layout_to_dict(layout):
- if (not layout):
- return dict()
- return dict(
- description=layout.get_description(),
- name=layout.get_name(),
- short_description=layout.get_short_description()
- )
+ """Returns a dict from LightDMLayout object"""
+ if not layout:
+ return {}
+ return {
+ "description": layout.get_description(),
+ "name": layout.get_name(),
+ "short_description": layout.get_short_description()
+ }
def session_to_dict(session):
- if (not session):
- return dict()
- return dict(
- comment=session.get_comment(),
- key=session.get_key(),
- name=session.get_name(),
- type=session.get_session_type(),
- )
+ """Returns a dict from LightDMSession object"""
+ if not session:
+ return {}
+ return {
+ "comment": session.get_comment(),
+ "key": session.get_key(),
+ "name": session.get_name(),
+ "type": session.get_session_type(),
+ }
def user_to_dict(user):
- if (not user):
- return dict()
- return dict(
- background=user.get_background(),
- display_name=user.get_display_name(),
- home_directory=user.get_home_directory(),
- image=user.get_image(),
- language=user.get_language(),
- layout=user.get_layout(),
- layouts=user.get_layouts(),
- logged_in=user.get_logged_in(),
- session=user.get_session(),
- username=user.get_name(),
- )
+ """Returns a dict from LightDMUser object"""
+ if not user:
+ return {}
+ return {
+ "background": user.get_background(),
+ "display_name": user.get_display_name(),
+ "home_directory": user.get_home_directory(),
+ "image": user.get_image(),
+ "language": user.get_language(),
+ "layout": user.get_layout(),
+ "layouts": user.get_layouts(),
+ "logged_in": user.get_logged_in(),
+ "session": user.get_session(),
+ "username": user.get_name(),
+ }
def battery_to_dict(battery):
- if (not battery):
- return dict()
- if (len(battery._batteries) == 0):
- return dict()
- return dict(
- name = battery.get_name(),
- level = battery.get_level(),
- status = battery.get_status(),
- ac_status = battery.get_ac_status(),
- capacity = battery.get_capacity(),
- time = battery.get_time(),
- watt = battery.get_watt()
- )
-
-
-from .Greeter import Greeter
-from .Config import Config
-from .ThemeUtils import ThemeUtils
+ """Returns a dict from Battery object"""
+ if not battery:
+ return {}
+ if len(battery.batteries) == 0:
+ return {}
+ return {
+ "name": battery.get_name(),
+ "level": battery.get_level(),
+ "status": battery.get_status(),
+ "ac_status": battery.get_ac_status(),
+ "capacity": battery.get_capacity(),
+ "time": battery.get_time(),
+ "watt": battery.get_watt()
+ }
diff --git a/src/browser/bridge.py b/src/browser/bridge.py
index f5c87ad..600d551 100644
--- a/src/browser/bridge.py
+++ b/src/browser/bridge.py
@@ -28,19 +28,24 @@
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty
class Bridge:
+ """Bridge class"""
@staticmethod
def method(*args, **kwargs):
+ """Declare a method"""
return pyqtSlot(*args, **kwargs)
@staticmethod
def prop(*args, **kwargs):
+ """Declare a property"""
return pyqtProperty(*args, **kwargs)
@staticmethod
def signal(*args, **kwargs):
+ """Declare a signal"""
return pyqtSignal(*args, **kwargs)
class BridgeObject(QObject):
+ """BridgeObject class"""
def __init__(self, name: str):
super().__init__(parent=None)
self._name = name
diff --git a/src/browser/browser.py b/src/browser/browser.py
index 413edfe..b627211 100644
--- a/src/browser/browser.py
+++ b/src/browser/browser.py
@@ -30,7 +30,6 @@
import re
import sys
-from browser.window import MainWindow
import os
from typing import (
Dict,
@@ -39,26 +38,38 @@ from typing import (
)
# 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.QtCore import QUrl, Qt, QCoreApplication, QFile, QSize
from PyQt5.QtWebEngineCore import QWebEngineUrlScheme
-from PyQt5.QtWebEngineWidgets import QWebEngineScript, QWebEngineProfile, QWebEngineSettings, QWebEngineView, QWebEnginePage
+from PyQt5.QtWidgets import (
+ QAction, QApplication, QDesktopWidget,
+ QDockWidget, QMainWindow, QLayout, qApp, QWidget
+)
+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 browser.window import MainWindow
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
+from bridge.Greeter import greeter
+from bridge.Config import config
+from bridge.ThemeUtils import theme_utils
+from utils.screensaver import screensaver
+
+# pylint: disable-next=unused-import
+# Do not ever remove this import
import resources
# Typing Helpers
-BridgeObjects = Tuple['BridgeObject']
-Url = TypeVar('Url', str, QUrl)
+BridgeObjects = Tuple["BridgeObject"]
+Url = TypeVar("Url", str, QUrl)
os.environ["QT_DEVICE_PIXEL_RATIO"] = "0"
os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"
@@ -66,10 +77,10 @@ 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,
+ 'NORMAL': Qt.WindowNoState,
+ 'MINIMIZED': Qt.WindowMinimized,
+ 'MAXIMIZED': Qt.WindowMaximized,
+ 'FULLSCREEN': Qt.WindowFullScreen,
} # type: Dict[str, Qt.WindowState]
DISABLED_SETTINGS = [
@@ -85,14 +96,15 @@ ENABLED_SETTINGS = [
'FocusOnNavigationEnabled', # Qt 5.11+
]
-def getDefaultCursor():
+def get_default_cursor():
+ """Gets the default cursor theme"""
+ default_theme = "/usr/share/icons/default/index.theme"
cursor_theme = ""
matched = None
try:
- file = open("/usr/share/icons/default/index.theme")
- matched = re.search(r"Inherits=.*", file.read())
- file.close()
- except Exception:
+ with open(default_theme, "r", encoding = "utf-8") as file:
+ matched = re.search(r"Inherits=.*", file.read())
+ except IOError:
return ""
if not matched:
logger.error("Default cursor couldn't be get")
@@ -101,31 +113,34 @@ def getDefaultCursor():
return cursor_theme
class Application:
+ """Main 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)
+ QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
+ QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
self.app = QApplication(sys.argv)
self.window = MainWindow()
self.desktop = self.app.desktop()
- self.window.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
+ self.window.setAttribute(Qt.WA_DeleteOnClose)
self.window.setWindowTitle("Web Greeter")
self.window.setWindowFlags(
- self.window.windowFlags() | Qt.WindowType.MaximizeUsingFullscreenGeometryHint
+ self.window.windowFlags() | Qt.MaximizeUsingFullscreenGeometryHint
)
if web_greeter_config["app"]["frame"]:
self._init_menu_bar()
else:
- self.window.setWindowFlags(self.window.windowFlags() | Qt.WindowType.FramelessWindowHint)
+ self.window.setWindowFlags(
+ self.window.windowFlags() | Qt.FramelessWindowHint
+ )
screen_size = self.desktop.availableGeometry().size()
@@ -138,29 +153,34 @@ class Application:
try:
self.window.windowHandle().setWindowState(state)
- except Exception:
+ except (AttributeError, TypeError):
self.window.setWindowState(state)
- self.window.setCursor(Qt.CursorShape.ArrowCursor)
-
- init_display()
+ self.window.setCursor(Qt.ArrowCursor)
timeout = web_greeter_config["config"]["greeter"]["screensaver_timeout"]
- set_screensaver(timeout or 300)
+ screensaver.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()
+ if cursor_theme is not None:
+ os.environ["XCURSOR_THEME"] = cursor_theme
+ else:
+ os.environ["XCURSOR_THEME"] = get_default_cursor()
self.app.aboutToQuit.connect(self._before_exit)
- def _before_exit(self):
- reset_screensaver()
+ @classmethod
+ def _before_exit(cls):
+ """Runs before exit"""
+ screensaver.reset_screensaver()
def show(self):
+ """Show window"""
self.window.show()
logger.debug("Window is ready")
def run(self) -> int:
+ """Runs the application"""
logger.debug("Web Greeter started")
return self.app.exec_()
@@ -185,23 +205,33 @@ class Application:
about_menu.addAction(exit_action)
class NoneLayout(QLayout):
- def __init__(self):
- super().__init__()
-
- def count(self) -> int:
+ """Layout that shows nothing"""
+ @classmethod
+ def count(cls) -> int:
+ # pylint: disable=missing-function-docstring
return 0
- def sizeHint(self) -> QSize:
+ @classmethod
+ def sizeHint(cls) -> QSize:
+ # pylint: disable=invalid-name,missing-function-docstring
size = QSize(0, 0)
return size
- def minimumSizeHint(self) -> QSize:
+ @classmethod
+ def minimumSizeHint(cls) -> QSize:
+ # pylint: disable=invalid-name,missing-function-docstring
size = QSize(0, 0)
return size
class Browser(Application):
+ # pylint: disable=too-many-instance-attributes
+ """The main browser"""
url_scheme: QWebEngineUrlScheme
+ bridge_initialized: bool
+ dev_view: QWebEngineView
+ dev_page: WebPage
+ qdock: QDockWidget
def __init__(self):
super().__init__()
@@ -209,6 +239,7 @@ class Browser(Application):
self.load()
def init(self):
+ """Initialize browser"""
logger.debug("Initializing Browser Window")
if web_greeter_config["config"]["greeter"]["debug_mode"]:
@@ -216,10 +247,10 @@ class Browser(Application):
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)
+ self.url_scheme.setDefaultPort(QWebEngineUrlScheme.PortUnspecified)
+ self.url_scheme.setFlags(QWebEngineUrlScheme.SecureScheme or
+ QWebEngineUrlScheme.LocalScheme or
+ QWebEngineUrlScheme.LocalAccessAllowed)
QWebEngineUrlScheme.registerScheme(self.url_scheme)
self.profile = QWebEngineProfile.defaultProfile()
@@ -243,7 +274,7 @@ class Browser(Application):
self.view.setContextMenuPolicy(Qt.PreventContextMenu)
if web_greeter_config["config"]["greeter"]["secure_mode"]:
- if (hasattr(QWebEngineProfile, "setUrlRequestInterceptor")):
+ if hasattr(QWebEngineProfile, "setUrlRequestInterceptor"):
self.profile.setUrlRequestInterceptor(self.interceptor)
else: # Older Qt5 versions
self.profile.setRequestInterceptor(self.interceptor)
@@ -258,10 +289,11 @@ class Browser(Application):
logger.debug("Browser Window created")
def load(self):
+ """Load theme and initialize bridge"""
self.load_theme()
- self.greeter = Greeter()
- self.greeter_config = Config()
- self.theme_utils = ThemeUtils(self.greeter)
+ self.greeter = greeter
+ self.greeter_config = config
+ self.theme_utils = theme_utils
self.bridge_objects = (self.greeter, self.greeter_config, self.theme_utils)
self.initialize_bridge_objects()
@@ -280,11 +312,12 @@ class Browser(Application):
titlebar.setLayout(layout)
self.qdock.setTitleBarWidget(titlebar)
- self.window.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.qdock)
+ self.window.addDockWidget(Qt.RightDockWidgetArea, self.qdock)
self.qdock.hide()
logger.debug("DevTools initialized")
def toggle_devtools(self):
+ """Toggle devtools"""
if not web_greeter_config["config"]["greeter"]["debug_mode"]:
return
win_size = self.window.size()
@@ -322,26 +355,28 @@ class Browser(Application):
self.page.setView(self.view)
def load_theme(self):
+ """Load theme"""
theme = web_greeter_config["config"]["greeter"]["theme"]
- dir = "/usr/share/web-greeter/themes/"
- path_to_theme = os.path.join(dir, theme, "index.html")
+ dir_t = "/usr/share/web-greeter/themes/"
+ path_to_theme = os.path.join(dir_t, theme, "index.html")
def_theme = "gruvbox"
- if (theme.startswith("/")): path_to_theme = theme
- elif (theme.__contains__(".") or theme.__contains__("/")):
+ if theme.startswith("/"):
+ path_to_theme = theme
+ elif theme.__contains__(".") or theme.__contains__("/"):
path_to_theme = os.path.join(os.getcwd(), theme)
path_to_theme = os.path.realpath(path_to_theme)
- if (not path_to_theme.endswith(".html")):
+ 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)):
+ if not os.path.exists(path_to_theme):
print("Path does not exists", path_to_theme)
- path_to_theme = os.path.join(dir, def_theme, "index.html")
+ path_to_theme = os.path.join(dir_t, def_theme, "index.html")
web_greeter_config["config"]["greeter"]["theme"] = path_to_theme
- url = QUrl("web-greeter://app/{0}".format(path_to_theme))
+ url = QUrl(f"web-greeter://app/{path_to_theme}")
self.page.load(url)
logger.debug("Theme loaded")
@@ -353,7 +388,7 @@ class Browser(Application):
# print(script_file, path)
- if script_file.open(QFile.OpenModeFlag.ReadOnly):
+ if script_file.open(QFile.ReadOnly):
script_string = str(script_file.readAll(), 'utf-8')
script.setInjectionPoint(QWebEngineScript.DocumentCreation)
@@ -372,19 +407,21 @@ class Browser(Application):
self.bridge_initialized = True
def initialize_bridge_objects(self) -> None:
+ """Initialize bridge objects :D"""
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:
+ # pylint: disable=protected-access
self.channel.registerObject(obj._name, obj)
# print("Registered", obj._name)
def load_script(self, path: Url, name: str):
+ """Loads a script in page"""
qt_api = self._get_channel_api_script()
qt_api_source = qt_api.sourceCode()
script = self._create_webengine_script(path, name)
script.setSourceCode(qt_api_source + "\n" + script.sourceCode())
self.page.scripts().insert(script)
-
diff --git a/src/browser/error_prompt.py b/src/browser/error_prompt.py
index c035629..aa6389a 100644
--- a/src/browser/error_prompt.py
+++ b/src/browser/error_prompt.py
@@ -29,23 +29,30 @@
# 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,
)
+from PyQt5.QtWebEngineWidgets import QWebEnginePage
+from PyQt5.QtWidgets import (
+ QAbstractButton,
+ QDialogButtonBox,
+ QDialog,
+ QVBoxLayout,
+ QLabel,
+ QPushButton
+)
+from config import web_greeter_config
+
+import globales
-log_format = ''.join([
+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")
+formatter = Formatter(fmt=LOG_FORMAT, datefmt="%Y-%m-%d %H:%M:%S")
logger = getLogger("javascript")
logger.propagate = False
stream_handler = StreamHandler()
@@ -55,66 +62,79 @@ 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:
+ """web-greeter's webpage class"""
+
+ def javaScriptConsoleMessage(
+ self, level: QWebEnginePage.JavaScriptConsoleMessageLevel,
+ message: str, line_number: int, source_id: str
+ ):
+ # pylint: disable = no-self-use,missing-function-docstring,invalid-name
+ if source_id == "":
+ source_id = "console"
+
+ log_level = 0
+ if level == WebPage.ErrorMessageLevel:
+ log_level = 40
+ elif level == WebPage.WarningMessageLevel:
+ log_level = 30
+ elif level == WebPage.InfoMessageLevel:
return
else:
return
record = logger.makeRecord(
name="javascript",
- level=logLevel,
+ level=log_level,
fn="",
- lno=lineNumber,
+ lno=line_number,
msg=message,
args=(),
exc_info=None
)
- record.filename = sourceID
+ record.filename = source_id
logger.handle(record)
- if logLevel == 40:
- errorMessage = "{source} {line}: {msg}".format(
- source=sourceID, line=lineNumber, msg=message)
- errorPrompt(errorMessage)
+ if log_level == 40:
+ errorMessage = f"{source_id} {line_number}: {message}"
+ error_prompt(errorMessage)
class Dialog(QDialog):
- def __init__(self, parent=None, title:str = "Dialog", message:str = "Message", detail:str = "", buttons: List[str] = []):
+ """Popup dialog class"""
+
+ def __init__(
+ self, parent = None, title: str = "Dialog",
+ message: str = "Message", detail: str = "",
+ buttons: List[str] = None
+ ):
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.button_box = QDialogButtonBox()
+ if buttons is not None:
+ for i, btn in enumerate(buttons, 0):
+ button = QPushButton(btn)
+ button.role = i
+ self.button_box.addButton(button, QDialogButtonBox.NoRole)
- self.buttonBox.clicked.connect(self.handle_click)
+ self.button_box.clicked.connect(self.handle_click)
self.layout = QVBoxLayout()
self.layout.addWidget(QLabel(message))
self.layout.addWidget(QLabel(detail))
- self.layout.addWidget(self.buttonBox)
+ self.layout.addWidget(self.button_box)
self.setLayout(self.layout)
def handle_click(self, button: QAbstractButton):
+ # pylint: disable=missing-function-docstring
self.done(button.role)
-def errorPrompt(err):
+def error_prompt(err):
+ """Prompts a popup dialog on error"""
if not web_greeter_config["config"]["greeter"]["detect_theme_errors"]:
return
- dia = Dialog(parent=globals.greeter.window, title="Error",
+ dia = Dialog(parent=globales.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"],
@@ -124,13 +144,9 @@ def errorPrompt(err):
result = dia.result()
if result == 2: # Cancel
- return
+ pass
elif result == 1: # Default theme
web_greeter_config["config"]["greeter"]["theme"] = "gruvbox"
- globals.greeter.load_theme()
- return
+ globales.greeter.load_theme()
elif result == 0: # Reload
- globals.greeter.load_theme()
- return
-
- return
+ globales.greeter.load_theme()
diff --git a/src/browser/interceptor.py b/src/browser/interceptor.py
index 370998f..98b79fe 100644
--- a/src/browser/interceptor.py
+++ b/src/browser/interceptor.py
@@ -29,12 +29,14 @@
from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor, QWebEngineUrlRequestInfo
class QtUrlRequestInterceptor(QWebEngineUrlRequestInterceptor):
+ """Url request interceptor for web-greeter's protocol"""
def __init__(self, url_scheme: str):
super().__init__()
self._url_scheme = url_scheme
def intercept_request(self, info: QWebEngineUrlRequestInfo) -> None:
+ """Intercept request"""
url = info.requestUrl().toString()
not_webg_uri = self._url_scheme != info.requestUrl().scheme()
not_data_uri = 'data' != info.requestUrl().scheme()
@@ -43,14 +45,18 @@ class QtUrlRequestInterceptor(QWebEngineUrlRequestInterceptor):
# print(url)
not_devtools = (
- not url.startswith('http://127.0.0.1') and not url.startswith('ws://127.0.0.1')
+ 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
+ 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:
+ # pylint: disable=invalid-name,missing-function-docstring
self.intercept_request(info)
-
diff --git a/src/browser/url_scheme.py b/src/browser/url_scheme.py
index b3216f6..8f64fd5 100644
--- a/src/browser/url_scheme.py
+++ b/src/browser/url_scheme.py
@@ -38,8 +38,10 @@ from PyQt5.QtWebEngineCore import QWebEngineUrlSchemeHandler, QWebEngineUrlReque
class QtUrlSchemeHandler(QWebEngineUrlSchemeHandler):
+ """URL Scheme Handler for web-greeter's protocol"""
def requestStarted(self, job: QWebEngineUrlRequestJob) -> None:
+ # pylint: disable=invalid-name,missing-function-docstring
path = job.requestUrl().path()
path = os.path.realpath(path)
@@ -58,8 +60,8 @@ class QtUrlSchemeHandler(QWebEngineUrlSchemeHandler):
try:
with open(path, 'rb') as file:
content_type = mimetypes.guess_type(path)
- if not content_type[0]:
- content_type = ["text/plain", None]
+ if content_type[0] is None:
+ content_type = ("text/plain", None)
buffer = QBuffer(parent=self)
buffer.open(QIODevice.WriteOnly)
@@ -67,8 +69,10 @@ class QtUrlSchemeHandler(QWebEngineUrlSchemeHandler):
buffer.seek(0)
buffer.close()
- job.reply(content_type[0].encode(), buffer)
+ if content_type[0] is None:
+ job.reply("text/plain", "")
+ else:
+ job.reply(content_type[0].encode(), buffer)
except Exception as err:
raise err
-
diff --git a/src/browser/window.py b/src/browser/window.py
index 0925a67..40904e5 100644
--- a/src/browser/window.py
+++ b/src/browser/window.py
@@ -25,46 +25,56 @@
# You should have received a copy of the GNU General Public License
# along with Web Greeter; If not, see .
-from PyQt5.QtCore import QFileSystemWatcher, Qt
+from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QAction, QMainWindow
-from PyQt5.QtGui import QKeyEvent
from config import web_greeter_config
-import globals
+import globales
class MainWindow(QMainWindow):
+ """Main window for web-greeter"""
+
def __init__(self):
super().__init__()
self.init_actions()
def init_actions(self):
- devAct = QAction(text="&Toggle Devtools", parent=self)
- devAct.setShortcut("Shift+Ctrl+I")
- devAct.triggered.connect(self.toggle_devtools)
+ """Initialize window actions and their shortcuts"""
+ dev_act = QAction(text="&Toggle Devtools", parent=self)
+ dev_act.setShortcut("Shift+Ctrl+I")
+ dev_act.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)
+ mon_bright_up = QAction(text="&Increase brightness", parent=self)
+ mon_bright_down = QAction(text="&Decrease brightness", parent=self)
+ mon_bright_up.setShortcut(Qt.Key_MonBrightnessUp)
+ mon_bright_down.setShortcut(Qt.Key_MonBrightnessDown)
+ mon_bright_up.triggered.connect(self.inc_brightness)
+ mon_bright_down.triggered.connect(self.dec_brightness)
- self.addAction(devAct)
- self.addAction(monBUp)
- self.addAction(monBDo)
+ self.addAction(dev_act)
+ self.addAction(mon_bright_up)
+ self.addAction(mon_bright_down)
- def toggle_devtools(self):
- globals.greeter.toggle_devtools()
+ @classmethod
+ def toggle_devtools(cls):
+ """Toggle devtools"""
+ globales.greeter.toggle_devtools()
- def inc_brightness(self):
- if globals.greeter:
+ @classmethod
+ def inc_brightness(cls):
+ """Increase brightness"""
+ if globales.greeter:
value = web_greeter_config["config"]["features"]["backlight"]["value"]
- globals.greeter.greeter.inc_brightness(value)
- def dec_brightness(self):
- if globals.greeter:
+ globales.greeter.greeter.inc_brightness(value)
+ @classmethod
+ def dec_brightness(cls):
+ """Decrease brightness"""
+ if globales.greeter:
value = web_greeter_config["config"]["features"]["backlight"]["value"]
- globals.greeter.greeter.dec_brightness(value)
+ globales.greeter.greeter.dec_brightness(value)
- def updateBrightness(self):
- if globals.greeter:
- globals.greeter.greeter.brightness_update.emit()
+ @classmethod
+ def update_brightness(cls):
+ """Updates brightness"""
+ if globales.greeter:
+ globales.greeter.greeter.brightness_update.emit()
diff --git a/src/config.py b/src/config.py
index 1ec4106..43be369 100644
--- a/src/config.py
+++ b/src/config.py
@@ -26,16 +26,13 @@
# along with Web Greeter; If not, see .
# Standard lib
-import sys
import os
-import ruamel.yaml as yaml
+from ruamel import yaml
-import globals
from logger import logger
-path_to_config = "/etc/lightdm/web-greeter.yml"
+PATH_TO_CONFIG = "/etc/lightdm/web-greeter.yml"
-global web_greeter_config
web_greeter_config = {
"config": {
"branding": {
@@ -77,13 +74,13 @@ web_greeter_config = {
}
def load_config():
+ """Load web-greeter's config"""
try:
- if (not os.path.exists(path_to_config)):
+ 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
+ with open(PATH_TO_CONFIG, "r", encoding="utf-8") as file:
+ web_greeter_config["config"] = yaml.safe_load(file)
+ except IOError as err:
+ logger.error("Config was not loaded:\n\t%s", err)
load_config()
diff --git a/src/globales.py b/src/globales.py
new file mode 100644
index 0000000..3bca367
--- /dev/null
+++ b/src/globales.py
@@ -0,0 +1,5 @@
+"""Global variables"""
+
+from browser.browser import Browser
+
+greeter: Browser # pylint: disable=invalid-name
diff --git a/src/globals.py b/src/globals.py
deleted file mode 100644
index 4ce29d8..0000000
--- a/src/globals.py
+++ /dev/null
@@ -1 +0,0 @@
-global greeter # type: Browser
diff --git a/src/logger.py b/src/logger.py
index a409a3b..9e28170 100644
--- a/src/logger.py
+++ b/src/logger.py
@@ -33,14 +33,13 @@ from logging import (
StreamHandler
)
-log_format = ''.join([
+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")
+formatter = Formatter(fmt=LOG_FORMAT, datefmt="%Y-%m-%d %H:%M:%S")
stream_handler = StreamHandler()
-global logger
logger = getLogger("debug")
stream_handler.setLevel(DEBUG)
diff --git a/src/utils/acpi.py b/src/utils/acpi.py
new file mode 100644
index 0000000..73e1c36
--- /dev/null
+++ b/src/utils/acpi.py
@@ -0,0 +1,63 @@
+
+import subprocess
+from threading import Thread
+from typing import List, Callable, Any
+from shutil import which
+from logger import logger
+
+Callback = Callable[[str], Any]
+
+class ACPIController:
+ """ACPI controller"""
+
+ tries = 0
+ callbacks: List[Callback] = []
+
+ def __init__(self):
+ if self.check_acpi():
+ self.listen()
+ else:
+ logger.error("ACPI: acpi_listen does not exists")
+
+ @staticmethod
+ def check_acpi() -> bool:
+ """Checks if acpi_listen does exists"""
+ if which("acpi_listen"):
+ return True
+ return False
+
+ def connect(self, callback: Callback):
+ """Connect callback to ACPI controller"""
+ self.callbacks.append(callback)
+
+ def disconnect(self, callback: Callback):
+ """Disconnect callback from ACPI controller"""
+ self.callbacks.remove(callback)
+
+ def _listen(self):
+ try:
+ with subprocess.Popen("acpi_listen",
+ stdout = subprocess.PIPE,
+ text = True) as process:
+ if not process.stdout:
+ raise IOError("No stdout")
+ while True:
+ line = process.stdout.readline().strip()
+ if not line:
+ continue
+ for _, callback in enumerate(self.callbacks):
+ callback(line)
+ except IOError as err:
+ logger.error("ACPI: %s", err)
+ if self.tries < 5:
+ self.tries += 1
+ logger.debug("Restarting acpi_listen")
+ self._listen()
+
+ def listen(self):
+ """Listens to acpi_listen"""
+ self.thread = Thread(target = self._listen)
+ self.thread.daemon = True
+ self.thread.start()
+
+ACPI = ACPIController()
diff --git a/src/utils/battery.py b/src/utils/battery.py
index 02b7ab3..a3924bc 100644
--- a/src/utils/battery.py
+++ b/src/utils/battery.py
@@ -1,48 +1,40 @@
-import subprocess
-import shlex
+import os
import re
import math
-from threading import Thread
import time
-from logger import logger
-from shutil import which
-
-running = False
+from typing import Union
+import globales
+from utils.acpi import ACPI
class Battery:
+ # pylint: disable=too-many-instance-attributes
+ """Battery controller"""
- _batteries = []
- ac = "AC0"
+ batteries = []
+ ac_path = "AC0"
pspath = "/sys/class/power_supply/"
perc = -1
status = "N/A"
capacity = 0
time = ""
watt = 0
-
- callbacks = []
+ running_update = False
def __init__(self):
- if len(self._batteries) == 0:
+ if len(self.batteries) == 0:
scandir_line(self.pspath, self._update_batteries)
- start_timer(self.full_update, self.onerror)
+ ACPI.connect(self.acpi_listen)
self.full_update()
- def connect(self, callback):
- self.callbacks.append(callback)
-
- def disconnect(self, callback):
- self.callbacks.remove(callback)
-
- def onerror(self):
- self._batteries = []
- for cb in self.callbacks:
- cb()
+ def acpi_listen(self, data: str):
+ """Listens"""
+ if re.match(r"battery|ac_adapter", data):
+ self.full_update()
def _update_batteries(self, line):
bstr = re.match(r"BAT\w+", line)
if bstr:
- self._batteries.append(dict(
+ self.batteries.append(dict(
name = bstr.group(),
status = "N/A",
perc = 0,
@@ -50,17 +42,18 @@ class Battery:
))
else:
match = re.match(r"A\w+", line)
- self.ac = match.group() if match else self.ac
+ self.ac_path = match.group() if match else self.ac_path
# Based on "bat" widget from "lain" awesome-wm library
# * (c) 2013, Luca CPZ
# * (c) 2010-2012, Peter Hofmann
# @see https://github.com/lcpz/lain/blob/master/widget/bat.lua
def full_update(self):
- global running
- if running:
+ # pylint: disable=too-many-locals,too-many-statements,too-many-branches
+ """Do a full update"""
+ if self.running_update:
return
- running = True
+ self.running_update = True
sum_rate_current = 0
sum_rate_voltage = 0
@@ -71,8 +64,7 @@ class Battery:
sum_charge_full = 0
sum_charge_design = 0
- for i in range(len(self._batteries)):
- battery = self._batteries[i]
+ for i, battery in enumerate(self.batteries):
bstr = self.pspath + battery["name"]
present = read_first_line(bstr + "/present")
@@ -89,33 +81,34 @@ class Battery:
energy_percentage = tonumber(read_first_line(bstr + "/capacity")
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
+ self.batteries[i]["status"] = read_first_line(bstr + "/status") or "N/A"
+ self.batteries[i]["perc"] = energy_percentage or self.batteries[i].perc
if not charge_design or charge_design == 0:
- self._batteries[i]["capacity"] = 0
+ self.batteries[i]["capacity"] = 0
else:
- self._batteries[i]["capacity"] = math.floor(
+ self.batteries[i]["capacity"] = math.floor(
charge_full / charge_design * 100)
- 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_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 or 1) * 100))
- self.status = len(self._batteries) > 0 and self._batteries[0]["status"] or "N/A"
+ self.status = self.batteries[0]["status"] if len(self.batteries) > 0 else "N/A"
- for i in range(len(self._batteries)):
- battery = self._batteries[i]
+ for i, battery in enumerate(self.batteries):
if battery["status"] == "Discharging" or battery["status"] == "Charging":
self.status = battery["status"]
- self.ac_status = tonumber(read_first_line(self.pspath + self.ac + "/online")) or "N/A"
+ self.ac_status = tonumber(read_first_line(self.pspath + self.ac_path + "/online")) or 0
if self.status != "N/A":
if self.status != "Full" and sum_rate_power == 0 and self.ac_status == 1:
@@ -126,117 +119,86 @@ class Battery:
elif self.status != "Full":
rate_time = 0
if (sum_rate_power > 0 or sum_rate_current > 0):
- div = (sum_rate_power > 0 and sum_rate_power) or sum_rate_current
+ div = sum_rate_power > 0 or sum_rate_current
if self.status == "Charging":
rate_time = (sum_energy_full - sum_energy_now) / div
else:
rate_time = sum_energy_now / div
- if 0 < rate_time and rate_time < 0.01:
+ if rate_time and rate_time < 0.01:
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)
- self.perc = math.floor(min(100, (sum_energy_now / sum_energy_full) * 100) + 0.5)
- self.time = "{:02d}:{:02d}".format(hours, minutes)
- self.watt = "{:.2f}".format(sum_rate_energy / 1e6)
+ self.perc = math.floor(
+ min(100, (sum_energy_now / sum_energy_full) * 100) + 0.5
+ )
+ self.time = f"{hours:02d}:{minutes:02d}"
+ self.watt = f"{sum_rate_energy/1e6:.2f}"
elif self.status == "Full":
self.perc = 100
self.time = "00:00"
self.watt = 0
- self.perc = self.perc == None and 0 or self.perc
+ self.perc = self.perc if self.perc is not None else 0
- for cb in self.callbacks:
- cb()
+ if hasattr(globales, "greeter"):
+ globales.greeter.greeter.battery_update.emit()
time.sleep(0.1)
- running = False
+ self.running_update = False
def get_name(self):
- return self._batteries[0]["name"]
+ """Get name"""
+ return self.batteries[0]["name"]
def get_level(self):
+ """Get level"""
return self.perc
def get_status(self):
+ """Get status"""
return self.status
def get_ac_status(self):
+ """Get AC status"""
return self.ac_status
def get_capacity(self):
+ """Get capacity"""
return self.capacity
def get_time(self):
+ """Get time"""
return self.time
def get_watt(self):
+ """Get watt"""
return self.watt
-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"),
- stdout=subprocess.PIPE, text=True)
- awky = subprocess.Popen(shlex.split("grep --line-buffered -E 'battery|ac_adapter'"),
- stdout=subprocess.PIPE, stdin=main.stdout, text=True)
- while True:
- if (awky.stdout == None): continue
- output = awky.stdout.readline()
- if output == "" and awky.poll() != None:
- break
- if output:
- callback()
- logger.warning("acpi_listen terminated")
- if acpi_tries < 5:
- acpi_tries += 1
- logger.debug("Restarting acpi_listen")
- return acpi_listen(callback, onerror)
- else:
- raise Exception("acpi_listen exceeded 5 restarts")
- except Exception as err:
- logger.error("Battery error: " + err.__str__())
- onerror()
-
def scandir_line(path, callback):
- main = subprocess.Popen(shlex.split("ls -1 {}".format(path)),
- stdout=subprocess.PIPE, text=True)
- while True:
- if (main.stdout == None): continue
- line = main.stdout.readline()
- if line == "" and main.poll() != None:
- break
- if line:
- callback(line)
-
-def read_first_line(path):
+ """List directory"""
+ lines = os.listdir(path)
+ for _, line in enumerate(lines):
+ callback(line)
+
+def read_first_line(path) -> Union[str, None]:
+ """Just read the first line of file"""
try:
- file = open(path, "r")
first = None
- if file:
+ with open(path, "r", encoding = "utf-8") as file:
first = file.readline()
first = first.replace("\n", "")
- file.close()
return first
- except Exception:
+ except IOError:
return None
-def tonumber(asa):
+def tonumber(string) -> Union[int, None]:
+ """Converts string to int or None"""
try:
- return int(asa)
- except Exception:
+ return int(string)
+ except (ValueError, TypeError):
return None
-
-def start_timer(callback, onerror):
- thread = Thread(target = acpi_listen, args=(callback, onerror,))
- thread.daemon = True
- thread.start()
diff --git a/src/utils/brightness.py b/src/utils/brightness.py
index b2091cb..ab75e20 100644
--- a/src/utils/brightness.py
+++ b/src/utils/brightness.py
@@ -28,16 +28,17 @@
import os
import stat
import time
-import pyinotify
from typing import List
from threading import Thread
-import globals
+import pyinotify
from logger import logger
from config import web_greeter_config
+import globales
sys_path = ["/sys/class/backlight/"]
def get_controllers() -> List[str]:
+ """Get brightness controllers path"""
ctrls: List[str] = []
for dev in sys_path:
if os.path.exists(dev) and stat.S_ISDIR(os.stat(dev).st_mode):
@@ -47,15 +48,20 @@ def get_controllers() -> List[str]:
return ctrls
class EventHandler(pyinotify.ProcessEvent):
- def process_IN_MODIFY(self, event):
- if globals.greeter:
- globals.greeter.greeter.brightness_update.emit()
+ """PyInotify handler"""
+ @classmethod
+ def process_IN_MODIFY(cls, _):
+ # pylint: disable=invalid-name,missing-function-docstring
+ if hasattr(globales, "greeter"):
+ globales.greeter.greeter.brightness_update.emit()
# Behavior based on "acpilight"
# Copyright(c) 2016-2019 by wave++ "Yuri D'Elia"
# See https://gitlab.com/wavexx/acpilight
class BrightnessController:
+ # pylint: disable=too-many-instance-attributes
+ """Brightness controller for web-greeter"""
_controllers: List[str] = []
_available: bool = False
@@ -69,8 +75,8 @@ class BrightnessController:
def __init__(self):
self._controllers = get_controllers()
if (len(self._controllers) == 0 or
- self._controllers[0] == None or
- web_greeter_config["config"]["features"]["backlight"]["enabled"] == False):
+ self._controllers[0] is None or
+ not web_greeter_config["config"]["features"]["backlight"]["enabled"]):
self._available = False
return
b_path = self._controllers[0]
@@ -78,8 +84,8 @@ class BrightnessController:
self._brightness_path = os.path.join(b_path, "brightness")
self._max_brightness_path = os.path.join(b_path, "max_brightness")
- with open(self._max_brightness_path, "r") as f:
- self._max_brightness = int(f.read())
+ with open(self._max_brightness_path, "r", encoding = "utf-8") as file:
+ self._max_brightness = int(file.read())
steps = web_greeter_config["config"]["features"]["backlight"]["steps"]
self.steps = 1 if steps <= 1 else steps
@@ -87,15 +93,17 @@ class BrightnessController:
self.watch_brightness()
def _watch(self):
- wm = pyinotify.WatchManager()
+ watch_manager = pyinotify.WatchManager()
handler = EventHandler()
- wm.add_watch(self._brightness_path, pyinotify.IN_MODIFY)
+ # pylint: disable-next=no-member
+ watch_manager.add_watch(self._brightness_path, pyinotify.IN_MODIFY)
- notifier = pyinotify.Notifier(wm, handler)
+ notifier = pyinotify.Notifier(watch_manager, handler)
notifier.loop()
def watch_brightness(self):
+ """Starts a thread to watch brightness"""
if not self._available:
return
thread = Thread(target = self._watch)
@@ -104,43 +112,53 @@ class BrightnessController:
@property
def max_brightness(self) -> int:
+ """Max brightness"""
return self._max_brightness
@property
def real_brightness(self) -> int:
- if not self._available: return -1
+ """Real brightness"""
+ if not self._available:
+ return -1
try:
- with open(self._brightness_path, "r") as f:
- return int(f.read())
+ with open(self._brightness_path, "r", encoding = "utf-8") as file:
+ return int(file.read())
except OSError:
- logger.error("Couldn't read from \"" + self._brightness_path + "\"")
+ logger.error("Couldn't read from \"%s\"", self._brightness_path)
return -1
@real_brightness.setter
- def real_brightness(self, v: int):
- if not self._available: return
- if v > self.max_brightness: v = self.max_brightness
- elif v <= 0: v = 0
+ def real_brightness(self, value: int):
+ if not self._available:
+ return
+ if value > self.max_brightness:
+ value = self.max_brightness
+ elif value <= 0:
+ value = 0
- if not os.path.exists(self._brightness_path): return
+ if not os.path.exists(self._brightness_path):
+ return
try:
- with open(self._brightness_path, "w") as f:
- f.write(str(round(v)))
+ with open(self._brightness_path, "w", encoding = "utf-8") as file:
+ file.write(str(round(value)))
except OSError:
- logger.error("Couldn't write to \"" + self._brightness_path + "\"")
+ logger.error("Couldn't write to \"%s\"", self._brightness_path)
@property
def brightness(self) -> int:
- if not self._available: return -1
+ """Brightness"""
+ if not self._available:
+ return -1
return round(self.real_brightness * 100 / self.max_brightness)
@brightness.setter
- def brightness(self, v: int):
- self.real_brightness = round(v * self.max_brightness / 100)
+ def brightness(self, value: int):
+ self.real_brightness = round(value * self.max_brightness / 100)
def _set_brightness(self, value: int):
- if not self._available: return
+ if not self._available:
+ return
steps = self.steps or 1
sleep = self.delay / steps
current = self.brightness
@@ -155,11 +173,14 @@ class BrightnessController:
self.brightness = round(brigh)
def set_brightness(self, value: int):
+ """Set brightness"""
thread = Thread(target = self._set_brightness, args = (value,))
thread.start()
def inc_brightness(self, value: int):
+ """Increase brightness"""
self.set_brightness(self.brightness + value)
def dec_brightness(self, value: int):
+ """Decrease brightness"""
self.set_brightness(self.brightness - value)
diff --git a/src/utils/screensaver.py b/src/utils/screensaver.py
index 6cafc59..ac713e9 100644
--- a/src/utils/screensaver.py
+++ b/src/utils/screensaver.py
@@ -1,40 +1,55 @@
-from logger import logger
from Xlib.display import Display
+from Xlib.error import DisplayError
+from logger import logger
+
+class Screensaver:
+ """Screensaver class"""
+
+ display: Display
+ available: bool = False
+ saved_data: dict[str, int]
+ saved: bool = False
+
+ def __init__(self):
+ self.init_display()
+
+ def init_display(self):
+ """Init display"""
+ try:
+ self.display = Display()
+ self.available = True
+ except DisplayError as err:
+ logger.error("Xlib error: %s", err)
+
+ def set_screensaver(self, timeout: int):
+ """Set screensaver timeout"""
+ if self.saved or not self.available:
+ return
+ self.display.sync()
+ self.display.sync()
+ # pylint: disable-next=protected-access
+ data: dict[str, int] = self.display.get_screen_saver()._data or {}
+ self.saved_data = data
+ self.saved = True
+
+ self.display.set_screen_saver(timeout,
+ data["interval"],
+ data["prefer_blanking"],
+ data["allow_exposures"])
+ self.display.flush()
+ logger.debug("Screensaver timeout set")
+
+ def reset_screensaver(self):
+ """Reset screensaver"""
+ if not self.saved or not self.available:
+ return
+ self.display.sync()
+ self.display.set_screen_saver(self.saved_data["timeout"],
+ self.saved_data["interval"],
+ self.saved_data["prefer_blanking"],
+ self.saved_data["allow_exposures"])
+ self.display.flush()
+ self.saved = False
+ logger.debug("Screensaver reset")
-saved_data: dict = {}
-saved = False
-available = False
-
-display: Display
-
-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()
- 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")
+screensaver = Screensaver()