├── .coafile ├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── actions │ └── build │ │ └── action.yml └── workflows │ ├── build.yml │ └── publish.yml ├── .gitignore ├── .gitmodules ├── .pylintrc ├── COPYING ├── Makefile ├── NEWS.md ├── README.md ├── build ├── DEBIAN │ ├── conffiles │ ├── control │ └── postinst ├── Xgreeter.patch ├── ci │ └── Dockerfile └── utils.sh ├── circle.yml ├── dist ├── 90-greeter-wrapper.conf ├── Xgreeter ├── com.github.jezerm.web-greeter.svg ├── web-greeter-bash ├── web-greeter-zsh ├── web-greeter.1 ├── web-greeter.appdata.xml ├── web-greeter.desktop ├── web-greeter.yml └── web-xgreeter.desktop ├── docs ├── Greeter.js ├── GreeterConfig.js ├── LightDMObjects.js ├── ThemeUtils.js └── bootstrap.js ├── requirements.txt ├── src ├── __init__.py ├── __main__.py ├── bindings │ ├── __init__.py │ ├── screensaver.c │ └── screensaver.py ├── bridge │ ├── Config.py │ ├── Greeter.py │ ├── GreeterComm.py │ ├── ThemeUtils.py │ └── __init__.py ├── browser │ ├── __init__.py │ ├── bridge.py │ ├── browser.py │ ├── browser_interfaces.py │ ├── error_prompt.py │ ├── interceptor.py │ ├── url_scheme.py │ └── window.py ├── config.py ├── globales.py ├── logger.py ├── requirements.txt ├── resources │ ├── css │ │ └── style.css │ ├── js │ │ ├── GreeterComm.js │ │ ├── ThemeUtils.js │ │ ├── bootstrap.js │ │ └── docs │ │ │ ├── Greeter.js │ │ │ ├── GreeterConfig.js │ │ │ └── LightDMObjects.js │ └── resources.qrc └── utils │ ├── __init__.py │ ├── acpi.py │ ├── battery.py │ └── brightness.py └── web-greeter.doap /.coafile: -------------------------------------------------------------------------------- 1 | [Default] 2 | bears = IndentBear,LineLengthBear,SpaceConsistencyBear 3 | max_line_length = 99 4 | use_spaces = False 5 | indent_cli_options = -bad -bap -bbo -nbc -br -brs -brf -c33 -cd33 -cdb -ce -ci4 -cli4 -cbi4 -cdw \ 6 | -cp33 -cs -d0 -di1 -fc1 -fca -hnl -i4 -ip0 -l99 -lp -npcs -ut -nprs -npsl \ 7 | -saf -sai -saw -sc -nsob -nss -ts4 8 | 9 | [C] 10 | bears = CPPLintBear,ClangASTPrintBear,ClangBear,ClangCloneDetectionBear,ClangFunctionDifferenceBear 11 | files = **/*.(c|h|cpp) 12 | cpplint_ignore = whitespace/tab 13 | 14 | [JavaScript] 15 | bears = JSHintBear 16 | files = **/*.js 17 | ignore = node_modules/ 18 | 19 | [Python] 20 | bears=PEP8Bear,PyCommentedCodeBear,PyImportSortBear,PyLintBear,PyUnusedCodeBear 21 | files = **/*.py 22 | ignore = build/**/*.py 23 | 24 | [Docker] 25 | bears= DockerfileLintBear 26 | files= **/Dockerfile 27 | 28 | [Markdown] 29 | bears = AlexBear,MarkdownBear 30 | files = **/*.md 31 | 32 | [JSON] 33 | bears = JSONFormatBear 34 | files = **/*.json 35 | ignore = node_modules/**/*.json 36 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | continuation_indent_size = 4 7 | curly_bracket_next_line = false 8 | end_of_line = lf 9 | indent_brace_style = 1TBS 10 | indent_size = 4 11 | indent_style = space 12 | insert_final_newline = true 13 | max_line_length = 100 14 | tab_width = 4 15 | trim_trailing_whitespace = true 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | **/_vendor/** linguist-vendored 2 | **/docs/** linguist-vendored 3 | **/i18n/** linguist-vendored 4 | **/.tx/** linguist-vendored 5 | **/translations.js linguist-vendored 6 | **/themes/** linguist-vendored 7 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: jezerm 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Environment 11 | - OS: [e.g. Ubuntu 21.04] 12 | - web-greeter version: output of `web-greeter --version` 13 | - LightDM version: output of `lightdm --version` 14 | 15 | ## Bug description 16 | A clear and concise description of what the bug is. 17 | 18 | 25 | 26 | ## Steps to reproduce 27 | Steps to reproduce the behavior. 28 | 29 | ## Expected behavior 30 | A clear and concise description of what you expected to happen. 31 | 32 | ## Screenshots 33 | If applicable, add screenshots to help explain your problem. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Environment 11 | - OS: [e.g. Ubuntu 21.04] 12 | - web-greeter version: output of `web-greeter --version` 13 | 14 | ## Description of feature 15 | A clear and concise description of what you want to happen. 16 | 17 | ## Possible solutions 18 | Add alternatives solutions to this feature or problem. 19 | -------------------------------------------------------------------------------- /.github/actions/build/action.yml: -------------------------------------------------------------------------------- 1 | name: "Build web-greeter" 2 | description: "Builds web-greeter" 3 | inputs: 4 | target-distro: 5 | required: true 6 | description: "Target distro" 7 | runs: 8 | using: "composite" 9 | steps: 10 | - 11 | name: Checkout 12 | uses: actions/checkout@v2 13 | with: 14 | submodules: recursive 15 | - 16 | name: Install dependencies 17 | shell: bash 18 | run: | 19 | sudo apt update 20 | sudo apt install \ 21 | liblightdm-gobject-1-dev \ 22 | libgirepository1.0-dev \ 23 | libqt5webengine5 \ 24 | pyqt5-dev-tools \ 25 | python3-gi \ 26 | python3-pyqt5 \ 27 | python3-ruamel.yaml \ 28 | python3-pyinotify \ 29 | python3-xlib \ 30 | python3-pip \ 31 | dpkg 32 | - 33 | name: Build web-greeter 34 | shell: bash 35 | run: make build 36 | - 37 | name: Prepare deb build (common) 38 | shell: bash 39 | run: | 40 | cp -r build/DEBIAN/ build/install_root/ 41 | - 42 | name: Prepare deb build (for Debian) 43 | shell: bash 44 | if: ${{ inputs.target-distro == 'debian' }} 45 | run: | 46 | sed -i "s/liblightdm-gobject-1-dev/liblightdm-gobject-dev/g" build/install_root/DEBIAN/control 47 | - 48 | name: Build deb 49 | shell: bash 50 | run: | 51 | cd build/ 52 | dpkg-deb --root-owner-group --build install_root "web-greeter.deb" 53 | dpkg --info "./web-greeter.deb" 54 | - 55 | name: 'Upload Artifact' 56 | uses: actions/upload-artifact@v2 57 | with: 58 | name: build-${{ inputs.target-distro }} 59 | path: ./build/web-greeter.deb 60 | retention-days: 7 61 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Test 2 | 3 | on: 4 | push: 5 | paths: 6 | - "src/**" 7 | - ".github/workflows/build.yml" 8 | - ".github/actions/build/action.yml" 9 | pull_request: 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build-ubuntu: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - 17 | name: Checkout local actions 18 | uses: actions/checkout@v2 19 | with: 20 | submodules: recursive 21 | - 22 | name: Build and install web-greeter 23 | uses: "./.github/actions/build" 24 | with: 25 | target-distro: ubuntu 26 | build-debian: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - 30 | name: Checkout local actions 31 | uses: actions/checkout@v2 32 | with: 33 | submodules: recursive 34 | - 35 | name: Build web-greeter 36 | uses: "./.github/actions/build" 37 | with: 38 | target-distro: debian 39 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | publish-ubuntu: 11 | name: Publish ubuntu binaries 12 | runs-on: ubuntu-latest 13 | steps: 14 | - 15 | name: Checkout local actions 16 | uses: actions/checkout@v2 17 | with: 18 | submodules: recursive 19 | - 20 | name: Build and install web-greeter 21 | uses: "./.github/actions/build" 22 | with: 23 | target-distro: ubuntu 24 | - name: Upload binaries to release 25 | uses: svenstaro/upload-release-action@v2 26 | with: 27 | repo_token: ${{ secrets.GITHUB_TOKEN }} 28 | file: build/web-greeter.deb 29 | asset_name: web-greeter-$tag-ubuntu.deb 30 | tag: ${{ github.ref }} 31 | overwrite: true 32 | publish-debian: 33 | name: Publish debian binaries 34 | runs-on: ubuntu-latest 35 | steps: 36 | - 37 | name: Checkout local actions 38 | uses: actions/checkout@v2 39 | with: 40 | submodules: recursive 41 | - 42 | name: Build web-greeter 43 | uses: "./.github/actions/build" 44 | with: 45 | target-distro: debian 46 | - name: Upload binaries to release 47 | uses: svenstaro/upload-release-action@v2 48 | with: 49 | repo_token: ${{ secrets.GITHUB_TOKEN }} 50 | file: build/web-greeter.deb 51 | asset_name: web-greeter-$tag-debian.deb 52 | tag: ${{ github.ref }} 53 | overwrite: true 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bundle.js 2 | resources.py 3 | build/** 4 | !build/.gitignore 5 | !build/ci/ 6 | !build/utils.sh 7 | !build/DEBIAN/ 8 | whither/ 9 | 10 | ### JetBrains template 11 | .idea/ 12 | 13 | 14 | ### Python template 15 | # Byte-compiled / optimized / DLL files 16 | __pycache__/ 17 | 18 | # C extensions 19 | *.so 20 | 21 | # Distribution / packaging 22 | .Python 23 | env/ 24 | venv/ 25 | develop-eggs/ 26 | downloads/ 27 | eggs/ 28 | .eggs/ 29 | lib/ 30 | lib64/ 31 | parts/ 32 | sdist/ 33 | var/ 34 | web-greeter/dist 35 | *.egg-info/ 36 | .installed.cfg 37 | *.egg 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Translations 44 | *.mo 45 | 46 | ### Autotools template 47 | Makefile.in 48 | /autom4te.cache 49 | /aclocal.m4 50 | /compile 51 | /configure 52 | /depcomp 53 | /install-sh 54 | /missing 55 | /stamp-h1 56 | 57 | # gh-pages 58 | _book/ 59 | node_modules/ 60 | package-lock.json 61 | 62 | # vscode 63 | .vscode/ 64 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "themes"] 2 | path = themes 3 | url = https://github.com/JezerM/web-greeter-themes.git 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DESTDIR ?= / 2 | PREFIX ?= /usr 3 | ENABLE_BASH_COMPLETION ?= true 4 | ENABLE_ZSH_COMPLETION ?= true 5 | 6 | 7 | BUILD_DIR := $(abspath build) 8 | REPO_DIR := $(abspath ./) 9 | INSTALL_ROOT := $(abspath build/install_root) 10 | INSTALL_PREFIX :=$(abspath $(shell echo ${INSTALL_ROOT}/${PREFIX})) 11 | DESTDIR_PREFIX :=$(abspath $(shell echo ${DESTDIR}/${PREFIX})) 12 | 13 | DISTRO := $(shell [ -e "/etc/os-release" ] && cat /etc/os-release|sed -n -e 's/^ID="\?\(\w*\)"\?/\1/p' ) 14 | 15 | ifeq (${ENABLE_BASH_COMPLETION}, true) 16 | ifeq ($(shell pkg-config --exists bash-completion && echo 0), 0) 17 | bashcompletiondir := $(shell pkg-config --variable=completionsdir bash-completion) 18 | endif 19 | endif 20 | 21 | ifeq (${ENABLE_ZSH_COMPLETION}, true) 22 | ifeq ($(shell which zsh >/dev/null 2>&1 && echo 0), 0) 23 | zshcompletiondir := /usr/share/zsh/site-functions/ 24 | endif 25 | endif 26 | 27 | ifdef bashcompletiondir 28 | bashcompletiondir_local := ${INSTALL_ROOT}/${bashcompletiondir} 29 | $(info Bash completion to install at: ${bashcompletiondir}) 30 | else 31 | $(info No Bash completion) 32 | endif 33 | ifdef zshcompletiondir 34 | zshcompletiondir_local := ${INSTALL_ROOT}/${zshcompletiondir} 35 | $(info ZSH completion to install at: ${zshcompletiondir}) 36 | else 37 | $(info No ZSH completion) 38 | endif 39 | 40 | all: build 41 | 42 | # Dist and web-greeter directories 43 | build/dist := ${BUILD_DIR}/dist 44 | build/web-greeter := ${BUILD_DIR}/web-greeter 45 | 46 | $(build/dist): dist/* 47 | @rm -rf "${BUILD_DIR}/dist" 48 | @rsync -a "${REPO_DIR}/dist" "${BUILD_DIR}" 49 | @cp "${REPO_DIR}/NEWS.md" "${BUILD_DIR}/dist/NEWS.md" 50 | @echo "✔ Dist directory created at ${build/dist}" 51 | 52 | $(build/web-greeter): src/* 53 | @rsync -a "${REPO_DIR}/src/" "${BUILD_DIR}/web-greeter" --exclude "dist" --exclude "__pycache__" 54 | @cp "${REPO_DIR}/README.md" "${BUILD_DIR}/web-greeter/" 55 | @echo "✔ Build directory created at ${build/web-greeter}" 56 | 57 | # Resources 58 | bundle.js := ${BUILD_DIR}/web-greeter/resources/js/bundle.js 59 | bundle/ThemeUtils := ${REPO_DIR}/src/resources/js/ThemeUtils.js 60 | bundle/GreeterComm := ${REPO_DIR}/src/resources/js/GreeterComm.js 61 | bundle/bootstrap := ${REPO_DIR}/src/resources/js/bootstrap.js 62 | 63 | $(bundle.js): $(build/web-greeter) $(bundle/ThemeUtils) $(bundle/bootstrap) $(bundle/GreeterComm) 64 | @cd build/web-greeter/resources/js; \ 65 | cat ${bundle/ThemeUtils} ${bundle/bootstrap} ${bundle/GreeterComm} > bundle.js 66 | 67 | resources.py := ${BUILD_DIR}/web-greeter/resources.py 68 | 69 | $(resources.py): $(bundle.js) 70 | @pyrcc5 -o ${BUILD_DIR}/web-greeter/resources.py\ 71 | ${BUILD_DIR}/web-greeter/resources/resources.qrc 72 | @cp ${resources.py} src/ 73 | @echo "✔ Resources compiled with pyrcc5" 74 | 75 | # Install root, where everything will be copied to 76 | $(INSTALL_ROOT): 77 | @for d in man/man1 metainfo doc/web-greeter web-greeter \ 78 | xgreeters applications icons/hicolor/scalable/apps; do \ 79 | mkdir -p "${INSTALL_PREFIX}/share/$$d"; \ 80 | done 81 | @for d in lightdm xdg/lightdm/lightdm.conf.d; do \ 82 | mkdir -p "${INSTALL_ROOT}/etc/$$d"; \ 83 | done 84 | @for d in lib/web-greeter bin; do \ 85 | mkdir -p "${INSTALL_PREFIX}/$$d"; \ 86 | done 87 | @echo "✔ Install root created at ${INSTALL_ROOT}" 88 | 89 | # ZSH completion install 90 | $(zshcompletiondir_local): $(INSTALL_ROOT) 91 | @if [ -n "${zshcompletiondir}" ]; then \ 92 | mkdir -p "${zshcompletiondir_local}"; \ 93 | cp "${BUILD_DIR}/dist/web-greeter-zsh" "${zshcompletiondir_local}/_web-greeter"; \ 94 | echo " ZSH completion copied"; \ 95 | fi 96 | 97 | # Bash completion install 98 | $(bashcompletiondir_local): $(INSTALL_ROOT) 99 | @if [ -n "${bashcompletiondir}" ]; then \ 100 | mkdir -p "${bashcompletiondir_local}"; \ 101 | cp "${BUILD_DIR}/dist/web-greeter-bash" "${bashcompletiondir_local}/web-greeter"; \ 102 | echo " Bash completion copied"; \ 103 | fi 104 | 105 | build_completions: $(zshcompletiondir_local) $(bashcompletiondir_local) 106 | 107 | # Theme installation 108 | THEMES_DIR := $(abspath ${DESTDIR_PREFIX}/share/web-greeter/themes) 109 | THEMES_DIR_LOCAL := $(abspath ${INSTALL_PREFIX}/share/web-greeter/themes) 110 | themes/gruvbox := $(abspath ${THEMES_DIR_LOCAL}/gruvbox) 111 | themes/gruvbox/js := $(abspath ${REPO_DIR}/themes/themes/gruvbox/js) 112 | themes/dracula := $(abspath ${THEMES_DIR_LOCAL}/dracula) 113 | themes/dracula/js := $(abspath ${REPO_DIR}/themes/themes/dracula/js) 114 | themes/simple := $(abspath ${THEMES_DIR_LOCAL}/simple) 115 | themes/_vendor := $(abspath ${INSTALL_PREFIX}/share/web-greeter/_vendor) 116 | 117 | $(THEMES_DIR_LOCAL): $(INSTALL_ROOT) 118 | @mkdir -p "${THEMES_DIR_LOCAL}" 119 | 120 | $(themes/gruvbox/js): themes/themes/gruvbox/ts 121 | @tsc --build themes/themes/gruvbox 122 | @echo " Gruvbox theme compiled" 123 | 124 | $(themes/dracula/js): themes/themes/dracula/ts 125 | @tsc --build themes/themes/dracula 126 | @echo " Dracula theme compiled" 127 | 128 | $(themes/gruvbox): $(THEMES_DIR_LOCAL) $(themes/gruvbox/js) themes/themes/gruvbox 129 | @if [ -d "${themes/gruvbox}" ]; then \ 130 | rm -rf "${themes/gruvbox}"; \ 131 | fi 132 | @cp -r "${REPO_DIR}/themes/themes/gruvbox" "${themes/gruvbox}" 133 | @echo " Gruvbox theme copied" 134 | $(themes/dracula): $(THEMES_DIR_LOCAL) $(themes/dracula/js) themes/themes/dracula 135 | @if [ -d "${themes/dracula}" ]; then \ 136 | rm -rf "${themes/dracula}"; \ 137 | fi 138 | @cp -r "${REPO_DIR}/themes/themes/dracula" "${themes/dracula}" 139 | @echo " Dracula theme copied" 140 | $(themes/simple): $(THEMES_DIR_LOCAL) themes/themes/simple/* 141 | @cp -r "${REPO_DIR}/themes/themes/simple" "${themes/simple}" 142 | @echo " Simple theme copied" 143 | 144 | $(themes/_vendor): $(INSTALL_ROOT) themes/themes/_vendor/* 145 | @cp -r "${REPO_DIR}/themes/themes/_vendor" "${themes/_vendor}" 146 | @echo " Theme vendors copied" 147 | 148 | build_themes: $(themes/gruvbox) $(themes/dracula) $(themes/simple) $(themes/_vendor) 149 | 150 | # Dist files 151 | dist/web-greeter.1 := $(abspath ${DESTDIR_PREFIX}/share/man/man1/web-greeter.1.gz) 152 | dist/news := $(abspath ${DESTDIR_PREFIX}/share/doc/web-greeter/NEWS.gz) 153 | dist/metainfo := $(abspath ${DESTDIR_PREFIX}/share/metainfo/web-greeter.appdata.xml) 154 | dist/xg-desktop := $(abspath ${DESTDIR_PREFIX}/share/xgreeters/web-greeter.desktop) 155 | dist/app-desktop := $(abspath ${DESTDIR_PREFIX}/share/applications/web-greeter.desktop) 156 | dist/app-icon-scalable := $(abspath ${DESTDIR_PREFIX}/share/icons/hicolor/scalable/apps/com.github.jezerm.web-greeter.svg) 157 | 158 | dist_local/web-greeter.1 := $(abspath ${INSTALL_PREFIX}/share/man/man1/web-greeter.1.gz) 159 | dist_local/news := $(abspath ${INSTALL_PREFIX}/share/doc/web-greeter/NEWS.gz) 160 | dist_local/metainfo := $(abspath ${INSTALL_PREFIX}/share/metainfo/web-greeter.appdata.xml) 161 | dist_local/xg-desktop := $(abspath ${INSTALL_PREFIX}/share/xgreeters/web-greeter.desktop) 162 | dist_local/app-desktop := $(abspath ${INSTALL_PREFIX}/share/applications/web-greeter.desktop) 163 | dist_local/app-icon-scalable := $(abspath ${INSTALL_PREFIX}/share/icons/hicolor/scalable/apps/com.github.jezerm.web-greeter.svg) 164 | 165 | $(dist_local/web-greeter.1): $(build/dist) $(INSTALL_ROOT) ${BUILD_DIR}/dist/web-greeter.1 166 | @gzip -c9 "${BUILD_DIR}/dist/web-greeter.1" > \ 167 | "${dist_local/web-greeter.1}" 168 | 169 | $(dist_local/news): $(build/dist) $(INSTALL_ROOT) ${BUILD_DIR}/dist/NEWS.md 170 | @gzip -c9 "${BUILD_DIR}/dist/NEWS.md" > \ 171 | "${dist_local/news}" 172 | 173 | $(dist_local/metainfo): $(build/dist) $(INSTALL_ROOT) ${BUILD_DIR}/dist/web-greeter.appdata.xml 174 | @cp "${BUILD_DIR}/dist/web-greeter.appdata.xml" \ 175 | "${dist_local/metainfo}" 176 | 177 | $(dist_local/xg-desktop): $(build/dist) $(INSTALL_ROOT) ${BUILD_DIR}/dist/web-xgreeter.desktop 178 | @cp "${BUILD_DIR}/dist/web-xgreeter.desktop" \ 179 | "${dist_local/xg-desktop}" 180 | 181 | $(dist_local/app-desktop): $(build/dist) $(INSTALL_ROOT) ${BUILD_DIR}/dist/web-greeter.desktop 182 | @cp "${BUILD_DIR}/dist/web-greeter.desktop" \ 183 | "${dist_local/app-desktop}" 184 | 185 | $(dist_local/app-icon-scalable): $(build/dist) $(INSTALL_ROOT) ${BUILD_DIR}/dist/com.github.jezerm.web-greeter.svg 186 | @cp "${BUILD_DIR}/dist/com.github.jezerm.web-greeter.svg" \ 187 | "${dist_local/app-icon-scalable}" 188 | 189 | build_dist_files: $(dist_local/web-greeter.1) $(dist_local/news) $(dist_local/metainfo) $(dist_local/xg-desktop) $(dist_local/app-desktop) $(dist_local/app-icon-scalable) 190 | @echo "✔ Dist files copied" 191 | 192 | # Config files 193 | config/web-greeter := $(abspath ${DESTDIR}/etc/lightdm/web-greeter.yml) 194 | config/lightdm-wrapper := $(abspath ${DESTDIR}/etc/xdg/lightdm/lightdm.conf.d/90-greeter-wrapper.conf) 195 | config/Xgreeter := $(abspath ${DESTDIR}/etc/lightdm/Xgreeter) 196 | 197 | config_local/web-greeter := $(abspath ${INSTALL_ROOT}/etc/lightdm/web-greeter.yml) 198 | config_local/lightdm-wrapper := $(abspath ${INSTALL_ROOT}/etc/xdg/lightdm/lightdm.conf.d/90-greeter-wrapper.conf) 199 | config_local/Xgreeter := $(abspath ${INSTALL_ROOT}/etc/lightdm/Xgreeter) 200 | 201 | $(config_local/web-greeter): $(INSTALL_ROOT) ${BUILD_DIR}/dist/web-greeter.yml 202 | @cp "${BUILD_DIR}/dist/web-greeter.yml" "${config_local/web-greeter}" 203 | $(config_local/lightdm-wrapper): $(INSTALL_ROOT) ${BUILD_DIR}/dist/90-greeter-wrapper.conf 204 | @cp "${BUILD_DIR}/dist/90-greeter-wrapper.conf" "${config_local/lightdm-wrapper}" 205 | $(config_local/Xgreeter): $(INSTALL_ROOT) ${BUILD_DIR}/dist/Xgreeter ${BUILD_DIR}/Xgreeter.patch /etc/os-release 206 | @install -Dm755 "${BUILD_DIR}/dist/Xgreeter" "${config_local/Xgreeter}" 207 | @case "${DISTRO}" in \ 208 | fedora) \ 209 | patch -bN "${config_local/Xgreeter}" "${BUILD_DIR}/Xgreeter.patch"; \ 210 | ;; \ 211 | esac 212 | 213 | build_config: $(config_local/web-greeter) $(config_local/lightdm-wrapper) $(config_local/Xgreeter) 214 | @echo "✔ Config copied" 215 | 216 | build_install_root: $(INSTALL_ROOT) build_dist_files build_config build_themes build_completions 217 | 218 | # Binaries 219 | bin/web-greeter := $(abspath ${DESTDIR}/bin/web-greeter) 220 | bin_local/web-greeter := $(abspath ${INSTALL_PREFIX}/bin/web-greeter) 221 | 222 | bin/screensaver.so := ${BUILD_DIR}/web-greeter/bindings/_screensaver.so 223 | bin/screensaver.c := ${BUILD_DIR}/web-greeter/bindings/screensaver.c 224 | 225 | $(bin/screensaver.so): $(build/web-greeter) 226 | @gcc ${bin/screensaver.c} -o ${bin/screensaver.so} -shared -lX11 -lxcb 227 | @cp ${bin/screensaver.so} src/bindings/ 228 | @echo "✔ Screensaver.so compiled" 229 | 230 | $(bin_local/web-greeter): build_install_root $(resources.py) $(bin/screensaver.so) 231 | @rm -rf "${INSTALL_PREFIX}/lib/web-greeter/*" 232 | @cp -R "${BUILD_DIR}/web-greeter"/* "${INSTALL_PREFIX}/lib/web-greeter" 233 | @printf "#!/usr/bin/env bash\npython3 ${DESTDIR_PREFIX}/lib/web-greeter \$$@" > \ 234 | "${bin_local/web-greeter}" 235 | @chmod +x "${bin_local/web-greeter}" 236 | @echo "✔ web-greeter binary copied" 237 | 238 | # Useful rules 239 | .PHONY: build 240 | build: $(bin_local/web-greeter) 241 | @echo "✔ Build succeeded" 242 | 243 | .PHONY: install 244 | install: build 245 | [ -e "${DESTDIR}" ] || mkdir -p "${DESTDIR}" 246 | cp -R "${INSTALL_ROOT}"/* "${DESTDIR}" 247 | @echo "✔ Install succeeded" 248 | 249 | # Uninstall everything except themes and web-greeter.yml 250 | uninstall_preserve: 251 | @rm -rf "${DESTDIR_PREFIX}/lib/web-greeter/" 252 | @rm -f "${dist/web-greeter.1}" 253 | @rm -f "${dist/app-desktop}" 254 | @rm -f "${dist/xg-desktop}" 255 | @rm -f "${dist/metainfo}" 256 | @rm -f "${dist/news}" 257 | @rm -f "${dist/app-icon-scalable}" 258 | @rm -f "${config/lightdm-wrapper}" 259 | @rm -f "${config/Xgreeter}" 260 | @rm -f "${bin/web-greeter}" 261 | @if [ -n "${bashcompletiondir}" ]; then \ 262 | rm -f "${bashcompletiondir}/web-greeter"; \ 263 | fi 264 | @if [ -n "${zshcompletiondir}" ]; then \ 265 | rm -f "${zshcompletiondir}/_web-greeter"; \ 266 | fi 267 | 268 | # Uninstall everything 269 | uninstall_all: uninstall_preserve 270 | @rm -rf "${DESTDIR_PREFIX}/share/web-greeter/" 271 | @rm -f "${config/web-greeter}" 272 | 273 | .PHONY: uninstall 274 | uninstall: uninstall_preserve 275 | @echo -e " Themes are not uninstalled. Remove them manually or use \`make uninstall_all\`:\ 276 | \n${DESTDIR_PREFIX}/share/web-greeter" 277 | @echo -e " web-greeter config was not uninstalled. Remove it manually or use \`make uninstall_all\`:\ 278 | \n${config/web-greeter}" 279 | 280 | run: $(resources.py) 281 | python3 src 282 | 283 | run_debug: $(resources.py) 284 | python3 src --debug 285 | 286 | clean: 287 | rm -rf ${INSTALL_ROOT} ${BUILD_DIR}/dist ${BUILD_DIR}/web-greeter 288 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | Web Greeter Icon 8 | 9 |

Web Greeter

10 |

11 | A LightDM greeter made with PyQt5 12 |

13 |

14 | 15 | License Information 16 | 17 |

18 |
19 | 20 | A modern, visually appealing greeter for LightDM, that allows to create web based themes with HTML, 21 | CSS and JavaScript. 22 | 23 | This is a fork of the [Antergos web-greeter](https://github.com/Antergos/web-greeter) that tries to 24 | fix and improve this project for a modern and current use. Due to this, some API changes are 25 | needed, which implies that current themes would need to do changes to work correctly. 26 | 27 | Also, check out [nody-greeter][nody-greeter], a greeter made in **Node.js** with **Electron**! 28 | (Actually, faster than Web Greeter) 29 | 30 | ## [See Live Demo][live_demo] 31 | 32 | Gruvbox and Dracula themes! 33 | 34 | ## Features 35 | 36 | - Create themes with HTML, CSS and JavaScript! 37 | - Should work everywhere. 38 | - JavaScript error handling, allowing to load the default theme. 39 | - Themes could be simple, or very complex. 40 | - Battery and brightness control. 41 | - Tab completion for zsh and bash. 42 | 43 | ## Available distro packages 44 | 45 | ### Arch 46 | 47 | - AUR: https://aur.archlinux.org/packages/web-greeter/ 48 | 49 | ```sh 50 | yay -S web-greeter 51 | ``` 52 | 53 | ### Ubuntu/Debian 54 | 55 | Download from the [latest release](https://github.com/JezerM/web-greeter/releases/latest) and 56 | install with apt. 57 | 58 | ```sh 59 | apt install ./web-greeter-VER-DISTRO.deb 60 | ``` 61 | 62 | ## Dependencies 63 | | | arch | ubuntu | fedora | openSUSE | debian | 64 | |--------------------------|----------------------|-------------------------|---------------------|-----------------------|-------------------------| 65 | |**liblightdm-gobject** |lightdm |liblightdm-gobject-1-dev |lightdm-gobject-devel|typelib-1_0-LightDM-1 |liblightdm-gobject-dev | 66 | |**pygobject** |python-gobject |python3-gi |pygobject3 |python3-gobject |python3-gi | 67 | |**pyqt5** |python-pyqt5 |python3-pyqt5 |python3-qt5 |python3-qt5-devel |python3-pyqt5 | 68 | |**pyqt5-webengine** |python-pyqt5-webengine|python3-pyqt5.qtwebengine|python3-qt5-webengine|python3-qtwebengine-qt5|python3-pyqt5.qtwebengine| 69 | |**python-yaml** |python-ruamel-yaml |python3-ruamel.yaml |python3-ruamel-yaml |python3-ruamel.yaml |python3-ruamel.yaml | 70 | |**python-inotify** |python-pyinotify |python3-pyinotify |python3-inotify |python3-pyinotify |python3-pyinotify | 71 | |**qt5-webengine** |qt5-webengine |libqt5webengine5 |qt5-qtwebengine |libqt5-qtwebengine |libqt5webengine5 | 72 | |**gobject-introspection** |gobject-introspection |gobject-introspection |gobject-introspection|gobject-introspection |gobject-introspection | 73 | |**libxcb** |libxcb |libxcb1-dev |libxcb-devel |libxcb |libxcb1-dev | 74 | |**libx11** |libx11 |libx11-dev |libX11-devel |libX11 |libx11-dev | 75 | 76 | > Note: web-greeter does not work in Fedora. See #19 77 | 78 | ### Build dependencies 79 | 80 | - rsync 81 | - make 82 | - tsc (`npm i -g typescript`) 83 | - pyrcc5 (Should be installed with above dependencies) 84 | - base-devel (build-essential) 85 | 86 | ### PIP 87 | - PyGObject 88 | - PyQt5 89 | - PyQtWebEngine 90 | - ruamel.yaml 91 | - pyinotify 92 | 93 | PIP dependencies are no longer required as long as common dependencies are satisfied. However, you 94 | can install PIP dependencies with: 95 | 96 | ```sh 97 | pip install -r requirements.txt 98 | ``` 99 | 100 | > ***NOTE*** If using PIP, be sure to install these dependencies as root. Yet, not recommended. 101 | 102 | ## Download & Install 103 | ```sh 104 | git clone --recursive https://github.com/JezerM/web-greeter.git 105 | cd web-greeter 106 | sudo make install 107 | ``` 108 | 109 | This will build **web-greeter** and package all the files to be installed. 110 | 111 | See [latest release][releases]. 112 | 113 | ### Uninstall 114 | 115 | Use `sudo make uninstall` to uninstall web-greeter, but preserving web-greeter.yml and themes. 116 | Either, use `sudo make uninstall_all` to remove everything related to web-greeter. 117 | 118 | ## Theme JavaScript API 119 | [Antergos][Antergos] documentation is no longer available, although it is accesible through 120 | [Web Archive][WebArchive]. Current and updated documentation is available at [web-greeter-page/docs][web-greeter-docs]. 121 | 122 | You can access the man-pages `man web-greeter` for some documentation and explanation. Also, you can 123 | explore the provided [themes](https://github.com/JezerM/web-greeter-themes/tree/master/themes) for real use cases. 124 | 125 | Additionally, you can install the TypeScript types definitions inside your theme with npm: 126 | 127 | ```sh 128 | npm install nody-greeter-types 129 | ``` 130 | 131 | ## Additional features 132 | 133 | ### Brightness control 134 | `acpi` is the only tool needed to control the brightness, besides a compatible device. 135 | This functionality is based on [acpilight][acpilight] replacement for `xbacklight`. 136 | 137 | udev rules are needed to be applied before using it, check [acpilight rules][acpilight_rules]. 138 | Then, lightdm will need to be allowed to change backlight values, to do so add lightdm user 139 | to the **video** group: `sudo usermod -a -G video lightdm` 140 | 141 | Enable it inside `/etc/lightdm/web-greeter.yml` 142 | 143 | ### Battery status 144 | `acpi` and `acpi_listen` are the only tools you need (and a battery). 145 | This functionality is based on ["bat" widget][bat_widget] from ["lain" awesome-wm library][lain]. 146 | 147 | You can enable it inside `/etc/lightdm/web-greeter.yml` 148 | 149 | ## Debugging 150 | You can run the greeter from within your desktop session if you add the following line to the desktop 151 | file for your session located in `/usr/share/xsessions/`: `X-LightDM-Allow-Greeter=true`. 152 | 153 | You have to log out and log back in after adding that line. Then you can run the greeter 154 | from command line. 155 | 156 | Themes can be opened with a debug console if you set `debug_mode` as `true` 157 | inside `/etc/lightdm/web-greeter.yml`. Or, you could run the `web-greeter` with 158 | the parameter `--debug`. I recommend to use the last one, as it is easier and handy. 159 | 160 | ```sh 161 | web-greeter --debug 162 | ``` 163 | 164 | Check `web-greeter --help` for more commands. 165 | 166 | > ***Note:*** Do not use `lightdm --test-mode` as it is not supported. 167 | 168 | ## Troubleshooting 169 | 170 | Before setting **web-greeter** as your LightDM Greeter, you should make sure it does work also with LightDM: 171 | 172 | - Run **web-greeter** as root with `--no-sandbox` flag ("Unable to determine socket to daemon" and "XLib" related errors are expected) 173 | - Run `lightdm --test-mode`. Although it's not supported, if it does work then it could help to debug lightdm. 174 | 175 | ### LightDM crashes and tries to recover over and over again 176 | 177 | LightDM does this when the greeter crashes, so it could mean **web-greeter** was not installed 178 | correctly, or some dependencies were updated/removed after a distro update. 179 | 180 | ### Import errors 181 | 182 | If you see something like this: `ImportError: libQt5WebEngineCore.so.5: undefined symbol: _ZNSt12out_of_rangeC1EPKc, version Qt_5`, check out this [StackOverflow response](https://stackoverflow.com/a/68811630). 183 | 184 | With some PyQt5 import errors like `ModuleNotFoundError: No module named 'PyQt5.QtWebEngineWidgets'`, check out this [GitHub response](https://github.com/spyder-ide/spyder/issues/8952#issuecomment-499418456). 185 | 186 | web-greeter related import errors: 187 | 188 | - `AttributeError: module 'globals' has no attribute 'greeter'` means some exception happened inside the Browser constructor, maybe related to LightDM or PyQt5. 189 | - `ModuleNotFoundError: No module named 'resources'` could mean `path/to/web-greeter-clone/web-greeter/resources.py` was not compiled with pyrcc5 in the build/install methods. 190 | 191 | [antergos]: https://github.com/Antergos "Antergos" 192 | [nody-greeter]: https://github.com/JezerM/nody-greeter "Nody Greeter" 193 | [cx_freeze]: https://github.com/marcelotduarte/cx_Freeze "cx_Freeze" 194 | [acpilight]: https://gitlab.com/wavexx/acpilight/ "acpilight" 195 | [acpilight_rules]: https://gitlab.com/wavexx/acpilight/-/blob/master/90-backlight.rules "udev rules" 196 | [bat_widget]: https://github.com/lcpz/lain/blob/master/widget/bat.lua "Battery widget" 197 | [lain]: https://github.com/lcpz/lain "Lain awesome library" 198 | [WebArchive]: https://web.archive.org/web/20190524032923/https://doclets.io/Antergos/web-greeter/stable "Web Archive" 199 | [web-greeter-docs]: https://web-greeter-page.vercel.app "Documentation" 200 | [live_demo]: https://jezerm.github.io/web-greeter-themes/ "Live Demo" 201 | [releases]: https://github.com/JezerM/web-greeter/releases "Releases" 202 | -------------------------------------------------------------------------------- /build/DEBIAN/conffiles: -------------------------------------------------------------------------------- 1 | /etc/lightdm/web-greeter.yml 2 | /etc/lightdm/Xgreeter 3 | /etc/xdg/lightdm/lightdm.conf.d/90-greeter-wrapper.conf 4 | -------------------------------------------------------------------------------- /build/DEBIAN/control: -------------------------------------------------------------------------------- 1 | Package: web-greeter 2 | Version: 3.5.3 3 | Provides: lightdm-greeter 4 | Replaces: lightdm-webkit-greeter 5 | Section: x11 6 | Priority: optional 7 | Homepage: https://github.com/JezerM/web-greeter 8 | Installed-Size: 18068 9 | Architecture: amd64 10 | Maintainer: JezerM 11 | Depends: liblightdm-gobject-1-0, liblightdm-gobject-1-dev, python3, python3-gi, python3-pyqt5, pyqt5-dev, python3-pyinotify, libqt5webengine5, python3-pyqt5.qtwebengine, python3-ruamel.yaml, libxcb1, libx11-6 12 | Description: A modern, visually appealing greeter for LightDM. 13 | Web Greeter utilizes themes built with HTML/CSS/JavaScript for it's login screen. Web Greeter 14 | themes provide modern, visually appealing, and feature-rich login screens. Two themes are 15 | included by default. There is also a growing number of 3rd-Party themes available online. 16 | -------------------------------------------------------------------------------- /build/DEBIAN/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Link to the binary 4 | if [ -e '/opt/web-greeter' ]; then 5 | ln -sf '/opt/web-greeter/web-greeter' '/usr/bin/web-greeter' 6 | fi 7 | 8 | update-desktop-database /usr/share/applications || true 9 | -------------------------------------------------------------------------------- /build/Xgreeter.patch: -------------------------------------------------------------------------------- 1 | --- Xgreeter 2022-12-21 15:40:30.120000813 -0600 2 | +++ Xgreeter-new 2022-12-22 16:32:31.520001039 -0600 3 | @@ -4,4 +4,4 @@ 4 | 5 | xsetroot -cursor_name left_ptr 6 | 7 | -exec $@ 8 | +exec $@ --no-sandbox 9 | -------------------------------------------------------------------------------- /build/ci/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | LABEL maintainer Jezer Mejía 3 | 4 | RUN apt-get update 5 | 6 | RUN DEBIAN_FRONTEND=noninteractive apt-get install -y tzdata 7 | 8 | RUN DEBIAN_FRONTEND=noninteractive apt-get upgrade -y 9 | 10 | RUN DEBIAN_FRONTEND=noninteractive \ 11 | apt-get install -y \ 12 | build-essential 13 | 14 | RUN DEBIAN_FRONTEND=noninteractive \ 15 | apt-get install -y \ 16 | liblightdm-gobject-1-dev \ 17 | gobject-introspection \ 18 | libgirepository1.0-dev \ 19 | libqt5webengine5 \ 20 | pyqt5-dev-tools \ 21 | libxcb1 \ 22 | libx11-6 \ 23 | libcairo2 24 | 25 | RUN DEBIAN_FRONTEND=noninteractive \ 26 | apt-get install -y \ 27 | python3-gi \ 28 | python3-pyqt5 \ 29 | python3-pyqt5.qtwebengine \ 30 | python3-ruamel.yaml \ 31 | python3-pyinotify \ 32 | python3-pip 33 | 34 | RUN DEBIAN_FRONTEND=noninteractive \ 35 | apt-get install -y \ 36 | rsync \ 37 | sudo 38 | 39 | VOLUME /build 40 | WORKDIR /build 41 | -------------------------------------------------------------------------------- /build/utils.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | BUILD_DIR="$(realpath $(dirname "${BASH_SOURCE[0]}"))" 4 | REPO_DIR="$(dirname "${BUILD_DIR}")" 5 | INSTALL_ROOT="${BUILD_DIR}/install_root" 6 | PKGNAME='web-greeter' 7 | DESTDIR='' 8 | PREFIX='' 9 | 10 | clean_build_dir() { 11 | find "${BUILD_DIR}" -type f ! -path '**/ci/**' ! -name '*.yml' ! -path "**/DEBIAN/**" ! -name \ 12 | utils.sh ! -name setup_log -delete 13 | find "${BUILD_DIR}" -type d ! -name build ! -path '**/ci' -delete 2>/dev/null || true 14 | } 15 | 16 | combine_javascript_sources() { 17 | cd "${BUILD_DIR}/${PKGNAME}/resources/js" 18 | cat ThemeUtils.js \ 19 | bootstrap.js > bundle.js 20 | } 21 | 22 | do_build() { 23 | cd "${BUILD_DIR}" 24 | 25 | # Compile Resources 26 | (combine_javascript_sources \ 27 | && pyrcc5 -o "${BUILD_DIR}/${PKGNAME}/resources.py" ../resources.qrc \ 28 | && cp "${BUILD_DIR}/${PKGNAME}/resources.py" "${REPO_DIR}/src") 29 | 30 | # Create "Zip Application" 31 | (cd "${PKGNAME}" \ 32 | && zip -rq ../"${PKGNAME}.zip" . -x '**__pycache__**' 'resources/*' \ 33 | && cd - >/dev/null \ 34 | && mkdir -p "${INSTALL_ROOT}${PREFIX}"/{bin,share} \ 35 | && echo '#!/usr/bin/env python3' >> "${INSTALL_ROOT}${PREFIX}/bin/web-greeter" \ 36 | && cat web-greeter.zip >> "${INSTALL_ROOT}${PREFIX}/bin/web-greeter" \ 37 | && chmod +x "${INSTALL_ROOT}${PREFIX}/bin/web-greeter") 38 | } 39 | 40 | do_build_freeze() { 41 | cd "${BUILD_DIR}" 42 | 43 | echo "Building web-greeter with cx_freeze..." 44 | python3 "${BUILD_DIR}/${PKGNAME}/setup.py" build >& setup_log 45 | echo "setup.py log inside ${BUILD_DIR}/setup_log" 46 | 47 | mkdir -p "${INSTALL_ROOT}"/opt/web-greeter 48 | mv "${BUILD_DIR}/${PKGNAME}"/dist/* "${INSTALL_ROOT}"/opt/web-greeter/ 49 | } 50 | 51 | do_install() { 52 | [[ -e "${DESTDIR}" ]] || mkdir -p "${DESTDIR}" 53 | cp -R "${INSTALL_ROOT}"/* "${DESTDIR}" 54 | if [[ -e "${DESTDIR}"/opt/web-greeter ]]; then 55 | opt_web=$(echo "${DESTDIR}"/opt/web-greeter/web-greeter | sed -E 's/\/\//\//g') 56 | dest_bin=$(echo "${DESTDIR}"/"${PREFIX}"/bin/web-greeter | sed -E 's/\/\//\//g') 57 | ln -sf "${opt_web}" "${dest_bin}" 58 | fi 59 | } 60 | 61 | init_build_dir() { 62 | [[ -e "${BUILD_DIR}/web-greeter" ]] && rm -rf "${BUILD_DIR}/web-greeter" 63 | [[ -e "${BUILD_DIR}/dist" ]] && rm -rf "${BUILD_DIR}/dist" 64 | rsync -a "${REPO_DIR}/src/" "${BUILD_DIR}/web-greeter" --exclude "dist" --exclude "__pycache__" 65 | rsync -a "${REPO_DIR}/dist" "${BUILD_DIR}" 66 | cp "${REPO_DIR}/NEWS.md" "${BUILD_DIR}/dist/NEWS.md" 67 | cp "${REPO_DIR}/README.md" "${BUILD_DIR}/web-greeter/" 68 | } 69 | 70 | prepare_install() { 71 | cd "${BUILD_DIR}" 72 | INSTALL_PREFIX=$(echo ${INSTALL_ROOT}/${PREFIX} | sed -E 's/\/\//\//g') 73 | mkdir -p \ 74 | "${INSTALL_PREFIX}"/share/{man/man1,metainfo,doc/web-greeter,web-greeter,xgreeters,applications,zsh/vendor-completions,bash-completion/completions} \ 75 | "${INSTALL_ROOT}"/etc/{lightdm,xdg/lightdm/lightdm.conf.d} \ 76 | "${INSTALL_PREFIX}"/bin 77 | 78 | # Themes 79 | (cp -R "${REPO_DIR}/themes" "${INSTALL_PREFIX}/share/web-greeter" \ 80 | && cd "${INSTALL_PREFIX}/share/web-greeter" \ 81 | && mv themes/_vendor .) 82 | 83 | # Man Page 84 | gzip -c9 "${BUILD_DIR}/dist/${PKGNAME}.1" > "${INSTALL_PREFIX}/share/man/man1/${PKGNAME}.1.gz" 85 | 86 | # News 87 | gzip -c9 "${BUILD_DIR}/dist/NEWS.md" > "${INSTALL_PREFIX}/share/doc/web-greeter/NEWS.gz" 88 | 89 | # Command line completions 90 | if [[ -f /usr/bin/bash ]]; then 91 | cp "${BUILD_DIR}/dist/${PKGNAME}-bash" "${INSTALL_PREFIX}/share/bash-completion/completions/${PKGNAME}" 92 | fi 93 | if [[ -f /usr/bin/zsh ]]; then 94 | cp "${BUILD_DIR}/dist/${PKGNAME}-zsh" "${INSTALL_PREFIX}/share/zsh/vendor-completions/_${PKGNAME}" 95 | fi 96 | 97 | # Greeter Config 98 | cp "${BUILD_DIR}/dist/${PKGNAME}.yml" "${INSTALL_ROOT}/etc/lightdm" 99 | 100 | # AppData File 101 | cp "${BUILD_DIR}/dist/${PKGNAME}.appdata.xml" "${INSTALL_PREFIX}/share/metainfo" 102 | 103 | # Greeter desktop File 104 | cp "${BUILD_DIR}/dist/web-xgreeter.desktop" "${INSTALL_PREFIX}/share/xgreeters/web-greeter.desktop" 105 | 106 | # Application desktop File 107 | cp "${BUILD_DIR}/dist/web-greeter.desktop" "${INSTALL_PREFIX}/share/applications/web-greeter.desktop" 108 | 109 | # Xgreeter wrapper 110 | cp "${BUILD_DIR}/dist/90-greeter-wrapper.conf" \ 111 | "${INSTALL_ROOT}/etc/xdg/lightdm/lightdm.conf.d/90-greeter-wrapper.conf" 112 | 113 | install -Dm755 "${BUILD_DIR}/dist/Xgreeter" "${INSTALL_ROOT}/etc/lightdm/Xgreeter" 114 | 115 | # Don't install hidden files 116 | find "${INSTALL_ROOT}" -type f -name '.git*' -delete 117 | rm -rf "${INSTALL_PREFIX}/share/web-greeter/themes/default/.tx" 118 | 119 | if [[ "${DESTDIR}" != '/' ]]; then 120 | # Save a list of installed files for uninstall command 121 | find "${INSTALL_ROOT}" -fprint /tmp/.installed_files 122 | 123 | while read _file 124 | do 125 | [[ -d "${_file}" && *'/web-greeter/'* != "${_file}" ]] && continue 126 | 127 | echo "${_file##*/install_root}" >> "${INSTALL_PREFIX}/share/web-greeter/.installed_files" 128 | 129 | done < /tmp/.installed_files 130 | 131 | rm /tmp/.installed_files 132 | fi 133 | } 134 | 135 | do_uninstall() { 136 | # Man Page 137 | DESTDIR_PREFIX=$(echo ${DESTDIR}/${PREFIX} | sed -E 's/\/\//\//g') 138 | rm -f ${DESTDIR_PREFIX}/share/man/man1/web-greeter.1 139 | 140 | # Command line completions 141 | if [[ -f /usr/bin/bash ]]; then 142 | rm -f ${DESTDIR_PREFIX}/share/bash-completion/completions/${PKGNAME} 143 | fi 144 | if [[ -f /usr/bin/zsh ]]; then 145 | rm -f ${DESTDIR_PREFIX}/share/zsh/vendor-completions/_${PKGNAME} 146 | fi 147 | 148 | # Greeter Config 149 | #rm ${DESTDIR}/etc/lightdm/${PKGNAME}.yml 150 | 151 | # Themes 152 | #rm -rf ${DESTDIR_PREFIX}/share/web-greeter 153 | 154 | # AppData File 155 | rm -f ${DESTDIR_PREFIX}/share/metainfo/${PKGNAME}.appdata.xml 156 | 157 | # Greeter desktop file 158 | rm -f ${DESTDIR_PREFIX}/share/xgreeters/web-greeter.desktop 159 | 160 | # Application desktop file 161 | rm -f ${DESTDIR_PREFIX}/share/applications/web-greeter.desktop 162 | 163 | # XGreeter wrapper 164 | rm -f ${DESTDIR}/etc/xdg/lightdm/lightdm.conf.d/90-greeter-wrapper.conf 165 | rm -f ${DESTDIR}/etc/lightdm/Xgreeter 166 | 167 | # Binary 168 | rm -f ${DESTDIR_PREFIX}/bin/web-greeter 169 | [[ -e "${DESTDIR}"/opt/web-greeter ]] && rm -rf ${DESTDIR}/opt/web-greeter 170 | 171 | echo "Themes are not uninstalled. Remove them manually: 172 | ${DESTDIR_PREFIX}/share/web-greeter/" 173 | echo "web-greeter config was not uninstalled. Remove it manually: 174 | ${DESTDIR}/etc/lightdm/${PKGNAME}.yml" 175 | } 176 | 177 | set_config() { 178 | [[ -z "$1" || -z "$2" ]] && return 1 179 | 180 | sed -i "s|'@$1@'|$2|g" \ 181 | "${BUILD_DIR}/dist/web-greeter.yml" 182 | } 183 | 184 | 185 | 186 | cd "${REPO_DIR}/build" >/dev/null 187 | 188 | case "$1" in 189 | combine-js) 190 | combine_javascript_sources 191 | ;; 192 | 193 | clean) 194 | clean_build_dir 195 | ;; 196 | 197 | build) 198 | PREFIX="$2" 199 | do_build 200 | ;; 201 | 202 | build_freeze) 203 | PREFIX="$2" 204 | do_build_freeze 205 | ;; 206 | 207 | build-init) 208 | init_build_dir 209 | ;; 210 | 211 | gen-pot) 212 | generate_pot_file 213 | ;; 214 | 215 | install) 216 | DESTDIR="$2" 217 | PREFIX="$3" 218 | do_install 219 | clean_build_dir 220 | ;; 221 | 222 | prepare-install) 223 | PREFIX="$2" 224 | prepare_install 225 | ;; 226 | 227 | uninstall) 228 | DESTDIR="$2" 229 | PREFIX="$3" 230 | do_uninstall 231 | ;; 232 | 233 | set-config) 234 | set_config "$2" "$3" 235 | ;; 236 | esac 237 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | timezone: America/Chicago 3 | services: 4 | - docker 5 | 6 | dependencies: 7 | cache_directories: 8 | - ~/docker 9 | override: 10 | - '[[ -e ~/docker/image2.tar ]] && docker load -i ~/docker/image2.tar || true' 11 | - '[[ -e ~/docker/image2.tar ]] || docker build --rm=false -t antergos/ci-ubuntu build/ci' 12 | - '[[ -e ~/docker/image2.tar ]] || mkdir -p ~/docker; docker save antergos/ci-ubuntu > ~/docker/image2.tar' 13 | 14 | test: 15 | override: 16 | - docker run -v ${PWD}:/build:rw antergos/ci-ubuntu /bin/bash -c "make -j1 install" 17 | -------------------------------------------------------------------------------- /dist/90-greeter-wrapper.conf: -------------------------------------------------------------------------------- 1 | [Seat:*] 2 | greeter-wrapper=/etc/lightdm/Xgreeter 3 | -------------------------------------------------------------------------------- /dist/Xgreeter: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # LightDM wrapper to run around greeter X sessions. 4 | 5 | xsetroot -cursor_name left_ptr 6 | 7 | exec $@ 8 | -------------------------------------------------------------------------------- /dist/web-greeter-bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # bash completion for web-greeter 3 | 4 | _web-greeter() { 5 | local cur="${COMP_WORDS[COMP_CWORD]}" 6 | local last="${COMP_WORDS[COMP_CWORD - 1]}" 7 | local xpat='!*.jpg' 8 | local options='--debug -d --normal -n --mode --list --theme --help -h --version -v --api-version' 9 | 10 | for word in "${COMP_WORDS[@]}"; do 11 | case "${word}" in 12 | --debug | --normal | -d | -n | --mode) 13 | options=$(echo "${options}" | sed 's/--normal\|-n//g') 14 | options=$(echo "${options}" | sed 's/--debug\|-d//g') 15 | options=$(echo "${options}" | sed 's/--mode//g') 16 | ;; 17 | esac 18 | done 19 | 20 | case "${last}" in 21 | --theme) 22 | _filedir 23 | options=$(ls -1d /usr/share/web-greeter/themes/*/ | cut -c 1- | 24 | rev | cut -c 2- | rev | sort | sed 's/\/usr\/share\/web-greeter\/themes\///') 25 | ;; 26 | --mode) 27 | options="debug normal" 28 | ;; 29 | esac 30 | 31 | COMPREPLY+=( $(compgen -W "${options}" -- "${cur}") ) 32 | } 33 | 34 | complete -F _web-greeter web-greeter 35 | -------------------------------------------------------------------------------- /dist/web-greeter-zsh: -------------------------------------------------------------------------------- 1 | #compdef web-greeter 2 | 3 | _webgreeter() { 4 | integer ret=1 5 | local -a args 6 | local themes=$(ls -1d /usr/share/web-greeter/themes/*/ | cut -c 1- | 7 | rev | cut -c 2- | rev | sort | sed 's/\/usr\/share\/web-greeter\/themes\///') 8 | args+=( 9 | "(-d -n --debug --normal)--mode[Set browser mode]:mode:->modes" 10 | "(--debug -d --normal -n --mode)"{--debug,-d}"[Runs the greeter in debug mode]" 11 | "(--normal -n --debug -d --mode)"{--normal,-n}"[Runs in non-debug mode]" 12 | '--list[Lists available themes]' 13 | "--theme[Sets the theme to use]:theme:->themes" 14 | "(--help -h)"{--help,-h}"[Show help]" 15 | "(--version -v)"{--version,-v}"[Print program version]" 16 | "--api-version[Print JavaScript API version number]" 17 | ) 18 | _arguments $args[@] && ret=0 19 | case "$state" in 20 | themes) 21 | _files 22 | _values 'themes' "${(uonzf)${themes}}" 23 | ;; 24 | modes) 25 | _values 'modes' "debug" "normal" 26 | ;; 27 | esac 28 | return ret 29 | } 30 | 31 | _webgreeter 32 | -------------------------------------------------------------------------------- /dist/web-greeter.1: -------------------------------------------------------------------------------- 1 | .TH "web-greeter" "1" "2022.2.10" 2 | .nh 3 | .ad l 4 | .SH "NAME" 5 | web-greeter 6 | .SH "SYNOPSIS" 7 | .PP 8 | LightDM greeter that uses chromium for theming via HTML/JavaScript\&. 9 | .PP 10 | .SH "DESCRIPTION" 11 | .PP 12 | web-greeter is a LightDM greeter that uses chromium for theming\&. Themes can be written 13 | using a combination of HTML and Javascript\&. 14 | .PP 15 | .SH "OPTIONS" 16 | .TP 17 | \fB\-h, \-\-help\fR 18 | Shows the help 19 | .TP 20 | \fB\-v, \-\-version\fR 21 | Print program version 22 | .TP 23 | \fB\-\-debug\fR 24 | Forces the greeter to run in debug mode 25 | .TP 26 | \fB\-\-normal\fR 27 | Forces the greeter to run in normal mode 28 | .TP 29 | \fB\-\-list\fR 30 | Shows the available themes 31 | .TP 32 | \fB\-\-theme\ \fITHEME\fR 33 | Sets the theme to use 34 | .PP 35 | .SH "THEME JAVASCRIPT API" 36 | Please note that all properties and functions which are marked as "deprecated" are 37 | only available for backwards compatibility and will be removed in a future version of 38 | web-greeter\&. Theme authors should not use any deprecated properties or 39 | functions in new works and should update any existing works which make use of 40 | deprecated properties and/or functions to ensure continued proper functionality\&. 41 | .PP 42 | See full documentation on https://jezerm\&.github\&.io/web-greeter/ 43 | .PP 44 | The following signals are available to connect javascript functions when a LightDM 45 | or web-greeter signal occurs: 46 | .PP 47 | \fBlightdm.authentication_complete\fR 48 | .RS 4 49 | Gets emitted when the greeter has completed authentication\&. 50 | .RE 51 | .PP 52 | \fBlightdm.autologin_timer_expired\fR 53 | .RS 4 54 | Gets emitted when the automatic login timer has expired\&. 55 | .RE 56 | .PP 57 | \fBlightdm.show_message\fR 58 | .RS 4 59 | Gets emitted when the greeter should show a message to the user\&. 60 | This signal emits a \fBmessage: string\fR and a \fBtype: number\fR\&. 61 | .RE 62 | .PP 63 | \fBlightdm.show_prompt\fR 64 | .RS 4 65 | Gets emitted when the greeter should show a prompt to the user\&. 66 | This signal emits a \fBmessage: string\fR and a \fBtype: number\fR\&. 67 | .RE 68 | .PP 69 | The following functions are available for the greeter to call to execute 70 | actions within LightDM: 71 | .PP 72 | \fBlightdm\&.authenticate(username)\fR 73 | .RS 4 74 | Specifies the username of the user we'd like to start authenticating as\&. Note that 75 | if you call lightdm.authenticate with no argument, LightDM (via PAM) will issue 76 | a show_prompt() call to ask for the username\&. 77 | .RE 78 | .PP 79 | \fBlightdm\&.authenticate_as_guest()\fR 80 | .RS 4 81 | Authenticates as the guest user\&. 82 | .RE 83 | .PP 84 | \fBlightdm\&.cancel_authentication()\fR 85 | .RS 4 86 | Cancels the authentication of any user currently in the process of 87 | authenticating\&. 88 | .RE 89 | .PP 90 | \fBlightdm\&.cancel_autologin()\fR 91 | .RS 4 92 | Cancels the authentication of the autologin user\&. 93 | .RE 94 | .PP 95 | \fBlightdm\&.start_session(session)\fR 96 | .RS 4 97 | Once LightDM has successfully authenticated the user, start the user's session 98 | by calling this function\&. "session" is the authenticated user's session\&. 99 | If no session is passed, start the authenticated user with the system default 100 | session. 101 | .RE 102 | .PP 103 | \fBlightdm\&.respond(text)\fR 104 | .RS 4 105 | When LightDM has prompted for input, provide the response to LightDM\&. 106 | .RE 107 | .PP 108 | \fBlightdm\&.set_language(lang)\fR 109 | .RS 4 110 | Will set the language for the current LightDM session\&. 111 | .RE 112 | .PP 113 | \fBlightdm\&.shutdown()\fR 114 | .RS 4 115 | Shuts down the system, if the greeter has the authority to do so\&. 116 | Check if greeter can shutdown with \fBlightdm\&.can_shutdown\fR 117 | .RE 118 | .PP 119 | \fBlightdm\&.restart()\fR 120 | .RS 4 121 | Restarts the system, if the greeter has the authority to do so\&. 122 | Check if greeter can restart with \fBlightdm\&.can_restart\fR 123 | .RE 124 | .PP 125 | \fBlightdm\&.suspend()\fR 126 | .RS 4 127 | Suspends the system, if the greeter has the authority to do so\&. 128 | Check if greeter can suspend with \fBlightdm\&.can_suspend\fR 129 | .RE 130 | .PP 131 | \fBlightdm\&.hibernate()\fR 132 | .RS 4 133 | Hibernates the system, if the greeter has the authority to do so\&. 134 | Check if greeter can hibernate with \fBlightdm\&.can_hibernate\fR 135 | .RE 136 | .PP 137 | Variables available within the greeter are: 138 | .PP 139 | \fBlightdm\&.authentication_user\fR: string 140 | .RS 4 141 | The username of the authentication user being authenticated or null if no 142 | authentication is in progress\&. 143 | .RE 144 | .PP 145 | \fBlightdm\&.autologin_guest\fR: boolean 146 | .RS 4 147 | Indicates the guest user should be used for autologin\&. 148 | .RE 149 | .PP 150 | \fBlightdm\&.autologin_timeout\fR: number 151 | .RS 4 152 | The number of seconds to wait before automatically logging in\&. 153 | .RE 154 | .PP 155 | \fBlightdm\&.autologin_user\fR: string 156 | .RS 4 157 | The name of the user account that should be logged into 158 | automatically after timed login delay has passed\&. 159 | .RE 160 | .PP 161 | \fBlightdm\&.can_hibernate\fR: boolean 162 | .RS 4 163 | Whether or not the system can be made to hibernate by the greeter\&. 164 | .RE 165 | .PP 166 | \fBlightdm\&.can_restart\fR: boolean 167 | .RS 4 168 | Whether or not the system can be restarted by the greeter\&. 169 | .RE 170 | .PP 171 | \fBlightdm\&.can_shutdown\fR: boolean 172 | .RS 4 173 | Whether or not the system can be shutdown by the greeter\&. 174 | .RE 175 | .PP 176 | \fBlightdm\&.can_suspend\fR: boolean 177 | .RS 4 178 | Whether or not the system can be suspended by the greeter\&. 179 | .RE 180 | .PP 181 | \fBlightdm\&.default_session\fR: string 182 | .RS 4 183 | The name of the default session (as configured in lightdm.conf)\&. 184 | .RE 185 | .PP 186 | \fBlightdm\&.has_guest_account\fR: boolean 187 | .RS 4 188 | A guest account is available for login\&. 189 | .RE 190 | .PP 191 | \fBlightdm\&.hide_users_hint\fR: boolean 192 | .RS 4 193 | The whole list of users should not be displayed\&. 194 | .RE 195 | .PP 196 | \fBlightdm\&.hostname\fR: string 197 | .RS 4 198 | The hostname of the system\&. 199 | .RE 200 | .PP 201 | \fBlightdm\&.is_authenticated\fR: boolean 202 | .RS 4 203 | Indicates if the user has successfully authenticated\&. 204 | .RE 205 | .PP 206 | \fBlightdm\&.in_authentication\fR: boolean 207 | .RS 4 208 | Indicates if lightdm is currently in the authentication phase\&. 209 | .RE 210 | .PP 211 | \fBlightdm\&.language\fR: LightDM.Language | null 212 | .RS 4 213 | The currently selected language\&. 214 | .RE 215 | .PP 216 | \fBlightdm\&.languages\fR: LightDM.Languages[] 217 | .RS 4 218 | The languages that are available on the system\&. 219 | .RE 220 | .PP 221 | \fBlightdm\&.layout\fR: LightDM.Layout 222 | .RS 4 223 | The currently active layout for the selected user. 224 | .RE 225 | .PP 226 | \fBlightdm\&.layouts\fR: LightDM.Layout[] 227 | .RS 4 228 | The keyboard layouts that are available on the system\&. 229 | .RE 230 | .PP 231 | \fBlightdm\&.select_guest_hint\fR: boolean 232 | .RS 4 233 | The guest user should be selected by default for login\&. 234 | .RE 235 | .PP 236 | \fBlightdm\&.select_user_hint\fR: string 237 | .RS 4 238 | The username that should be selected by default for login\&. 239 | .RE 240 | .PP 241 | \fBlightdm\&.sessions\fR: LightDM.Session[] 242 | .RS 4 243 | The sessions that are available on the system\&. 244 | .RE 245 | .PP 246 | \fBlightdm\&.users\fR: LightDM.User[] 247 | .RS 4 248 | The users that are able to log in\&. Returns an Array of LightDMUser 249 | objects\&. 250 | .RE 251 | .PP 252 | The \fBtheme_utils\fR object has some utility functions associated with it which 253 | are intended to make a theme author's work easier\&. 254 | .PP 255 | \fBtheme_utils\&.dirlist(path)\fR 256 | .RS 4 257 | Returns an array of strings of filenames present at "path", or Null if the 258 | path does not exist\&. 259 | .RE 260 | .PP 261 | \fBtheme_utils\&.bind_this(context)\fR 262 | .RS 4 263 | Binds this to class, context, for all of the class's methods\&. 264 | .RE 265 | .PP 266 | \fBtheme_utils\&.get_current_localized_time()\fR 267 | .RS 4 268 | Get the current time in a localized format\&. Language is auto-detected by default, 269 | but can be set manually in the greeter config file\&. 270 | .RE 271 | \fBtheme_utils\&.get_current_localized_date()\fR 272 | .RS 4 273 | Get the current date in a localized format\&. Language is auto-detected by default, 274 | but can be set manually in the greeter config file\&. 275 | .RE 276 | .PP 277 | Please see the LightDM API documentation for the complete list of calls 278 | available\&. The web-greeter implements all of the LightDM API\&. 279 | .PP 280 | .SH "CONFIGURATION" 281 | .PP 282 | \fB/etc/lightdm/web-greeter\&.yml\fR 283 | .RS 4 284 | Configuration file\&. 285 | .RE 286 | .SH "FILES" 287 | .PP 288 | \fB/usr/share/web-greeter/themes\fR 289 | .RS 4 290 | Directory where themes should be stored\&. 291 | .RE 292 | .SH "EXAMPLES" 293 | .PP 294 | Please see the "dracula", "gruvbox" and "simple" themes that are shipped with web-greeter\&. 295 | .TP 296 | \fBCommand Line\fR 297 | .RS 4 298 | web-greeter --theme simple --debug 299 | .TP 300 | web-greeter --normal 301 | .SH "SEE ALSO" 302 | .PP 303 | http://people\&.ubuntu\&.com/~robert-ancell/lightdm/reference/ 304 | .PP 305 | https://lazka\&.github\&.io/pgi-docs/#LightDM-1 306 | .PP 307 | https://jezerm\&.github\&.io/web-greeter/ 308 | .PP 309 | https://github.com/JezerM/web-greeter 310 | .SH "AUTHOR" 311 | .PP 312 | The legacy lightdm-webkit-greeter was written by Robert Ancell \&. 313 | It was ported to webkit2 by the Antergos Developers \&. Also includes code improvements 314 | contributed by Scott Balneaves \&. Forked and mantained by JezerM \&. 315 | -------------------------------------------------------------------------------- /dist/web-greeter.appdata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | web-greeter 5 | CC0-1.0 6 | GPL-3.0+ 7 | Web Greeter 8 | A modern, visually appealing greeter for LightDM 9 | 10 |

11 | Web Greeter for LightDM utilizes themes built with HTML/CSS/JavaScript for it's login screen. 12 |

13 |

14 | Web Greeter themes provide modern, visually appealing, and feature-rich 15 | login screens. Three themes are included by default. There is also a growing number of 16 | 3rd-Party themes available online. 17 |

18 |
19 | 20 | 21 | 22 | https://github.com/JezerM/web-greeter/raw/master/themes/gruvbox/assets/screenshots/theme-show-1.png 23 | Login screen (gruvbox theme) 24 | 25 | 26 | https://github.com/JezerM/web-greeter/raw/master/themes/gruvbox/assets/screenshots/theme-show-2.png 27 | Login screen shutting down (gruvbox theme) 28 | 29 | 30 | https://github.com/JezerM/web-greeter/raw/master/themes/gruvbox/assets/screenshots/theme-show-3.png 31 | Background selector (gruvbox theme) 32 | 33 | 34 | https://github.com/JezerM/web-greeter/raw/master/themes/dracula/assets/screenshots/theme-show-1.png 35 | Login screen (dracula theme) 36 | 37 | 38 | https://github.com/JezerM/web-greeter/raw/master/themes/dracula/assets/screenshots/theme-show-2.png 39 | Login screen background selector (dracula theme) 40 | 41 | 42 | https://github.com/JezerM/web-greeter/raw/master/themes/dracula/assets/screenshots/theme-show-3.png 43 | Login screen success message (dracula theme) 44 | 45 | 46 | 47 | 50 | 51 | 52 | 53 | 54 |

Changes:

55 |
    56 |
  • Fixed ruamel.yaml usage
  • 57 |
58 |
59 |
60 | 61 | 62 |

Changes:

63 |
    64 |
  • Update themes submodule.
  • 65 |
  • Update openSUSE dependencies.
  • 66 |
  • Fix GreeterReady being dispatched earlier or never.
  • 67 |
  • Possibly issue #19 has been solved, so it could be executed in Fedora.
  • 68 |
  • Fix crash when a Dialog is created before the Application itself.
  • 69 |
70 |
71 |
72 | 73 | 74 |

Added:

75 |
    76 |
  • Added --api-version command line argument, starting at 1.0.0
  • 77 |
78 |

Changes:

79 |
    80 |
  • Removed themes in favor of web-greeter-themes submodule.
  • 81 |
  • Reduced signal execution to 60ms, which should make web-greeter to feel faster.
  • 82 |
83 |
84 |
85 | 86 | 87 |

Added:

88 |
    89 |
  • Added multi-monitor support from nody-greeter
  • 90 |
  • Added "greeter_comm" object for cross-window communication
  • 91 |
  • Added "GreeterBroadcastEvent" for listening to greeter_comm messages
  • 92 |
93 |

Changes:

94 |
    95 |
  • Default themes migrated to Typescript
  • 96 |
  • Added background selector to gruvbox theme
  • 97 |
  • tsc (typescript compiler) is now a required dependency
  • 98 |
  • "lightdm.batteryData" deprecated in favor of "lightdm.battery_data"
  • 99 |
  • Command line arguments updated
  • 100 |
  • Updated bash and zsh completions
  • 101 |
  • Added Web Greeter scalable icon
  • 102 |
  • Lots of bugfixes
  • 103 |
104 |
105 |
106 | 107 | 108 |

Changes:

109 |
    110 |
  • Fix build process
  • 111 |
  • Fix typos in README
  • 112 |
  • Handle lightdm.start_session errors
  • 113 |
  • Fix positioning on multiple screens
  • 114 |
115 |
116 |
117 | 118 | 119 |

Added:

120 |
    121 |
  • Added top Menu-bar menus and items
  • 122 |
  • Improve devtools qdock behavior
  • 123 |
124 |

Changes:

125 |
    126 |
  • Bugfixes related to LightDM signals
  • 127 |
  • Fix web-greeter initialization issues
  • 128 |
  • Migrate build process from utils.sh script to full Makefile
  • 129 |
  • Man-pages updated
  • 130 |
131 |

Removed:

132 |
    133 |
  • Remove python-xlib dependency in favor of python C binding
  • 134 |
135 |
136 |
137 | 138 | 139 |

Changes:

140 |
    141 |
  • Bugfixes related to LightDM signals
  • 142 |
  • Allow --no-sandbox argument
  • 143 |
  • Allow relative path with theme_utils.dirlist
  • 144 |
  • Added brightness controller to use instead of external programs
  • 145 |
146 |
147 |
148 | 149 | 150 |

Changes:

151 |
    152 |
  • Bugfixes related to older Qt versions
  • 153 |
154 |
155 |
156 | 157 | 158 |

Changes:

159 |
    160 |
  • Default build system reverted to zippy method
  • 161 |
  • Battery bugfixes
  • 162 |
  • QWebChannel and Web Greeter bundle merged as one file
  • 163 |
  • Lots of bugfixes
  • 164 |
165 |
166 |
167 | 168 | 169 |

Bugfixes

170 |

Changes:

171 |
    172 |
  • Bugfixes, just bugfixes
  • 173 |
174 |
175 |
176 | 177 | 178 |

Web Greeter 3.1.0 is here!

179 |

The build system changed to cx_freeze, though the previous build/install method, can be used.

180 |

Added:

181 |
    182 |
  • Devtools implemented as a side view
  • 183 |
  • Build system now uses cx_freeze
  • 184 |
  • Added keyboard layout selector, and eye password reveal in both themes
  • 185 |
186 |

Changes:

187 |
    188 |
  • Brightness and battery are now controlled by signals instead of timers
  • 189 |
  • Old build system (zip build) is still usable with `build_old` and `install_old`
  • 190 |
191 |

Removed:

192 |
    193 |
  • whither dependency removed
  • 194 |
195 |
196 |
197 | 198 | 199 |

Finally, Web Greeter 3.0.0 is ready!

200 |

Added:

201 |
    202 |
  • New themes: gruvbox and dracula
  • 203 |
  • Added newer documentation
  • 204 |
  • Support for brightness control
  • 205 |
  • Support for battery status
  • 206 |
  • Support for ES2020, as using Chrome 83
  • 207 |
  • Improved mock.js system
  • 208 |
  • Better debug logging
  • 209 |
  • Custom cursor theme option as "icon_theme"
  • 210 |
  • Some vendors added
  • 211 |
  • Tab completion for "web-greeter" command
  • 212 |
213 |

Changed:

214 |
    215 |
  • "lightdm-webkit2-greeter" name changed to "web-greeter"
  • 216 |
  • "webkit2Gtk" replaced with "PyQtWebEngine"
  • 217 |
  • Man-pages updated
  • 218 |
  • Updated API usage for LightDM 1.26.0
  • 219 |
  • "greeterutil" renamed to "theme_utils"
  • 220 |
  • "config" renamed to "greeter_config"
  • 221 |
  • "lightdm-webkit2-greeter.conf" renamed to "web-greeter.yml"
  • 222 |
  • Themes are now installed inside "/usr/share/web-greeter/themes"
  • 223 |
  • Vendors updated
  • 224 |
  • Previous deprecated methods and properties were removed
  • 225 |
226 |

Removed:

227 |
    228 |
  • Antergos theme removed
  • 229 |
  • Some vendors removed
  • 230 |
  • "time_format" config option removed
  • 231 |
  • Transifex removed, sadly
  • 232 |
233 |
234 |
235 | 236 | 237 |

This is a hotfix release in the 2.2 series, with the following improvements:

238 |
    239 |
  • Implement workaround to prevent the web process from crashing in webkit2gtk 2.14.3. (GH #107)
  • 240 |
241 |
242 |
243 | 244 | 245 |

This is a maintenance release in the 2.2 series, with the following improvements:

246 |
    247 |
  • Increased the timeout for the "theme loaded" check to ensure themes are given enough time to load (when running on less powerful systems). (GH #98)
  • 248 |
  • Fixed issue where users' custom .face image failed to load. (GH #98)
  • 249 |
250 |
251 |
252 | 253 | 254 |

This is a milestone release with the following improvements:

255 |
    256 |
  • The JavaScript API for themes is now fully documented
  • 257 |
  • New Theme Error Recovery System that will alert the user when errors are detected during JavaScript execution and give them the option to to load a fallback theme.
  • 258 |
  • New config option: secure_mode (enabled by default). When enabled, only local http requests are allowed in themes. All non-local requests will be blocked.
  • 259 |
  • It is now possible to override the language and format used by the greeter when displaying the current time. See the greeter config file for details.
  • 260 |
  • A new utility method for getting the current localized time is available to themes.
  • 261 |
  • Simple theme now has a fade out exit animation.
  • 262 |
263 |
    264 |
  • Switched build systems from Autotools to Meson.
  • 265 |
  • Updated API usage for LightDM 1.19.2+.
  • 266 |
  • Updated bundled JS & CSS vendor libs to their latest versions.
  • 267 |
  • Updated translations with latest changes contributed by the Antergos Community on Transifex.
  • 268 |
  • Buttons and user list-box received some minor style enhancements. (Default theme)
  • 269 |
  • Theme is now compatible with the latest jQuery. (Default theme)
  • 270 |
  • Removed deprecated HTML4 tags. (Simple theme)
  • 271 |
  • Improved styles for the input field. (Simple theme)
  • 272 |
273 |
    274 |
  • The ugly default X cursor will no longer be shown after the greeter exits.
  • 275 |
  • The error messages container will now appear correctly (size and position). (Default theme)
  • 276 |
  • It is now once again possible to skip straight to password entry by pressing either the spacebar or the enter key. (Default theme)
  • 277 |
278 |
279 |
280 |
281 | https://github.com/JezerM/web-greeter/issues 282 | https://jezerm.github.io/web-greeter/ 283 | https://github.com/JezerM/web-greeter 284 | 285 | apps.light-locker 286 | 287 | 288 | web-greeter 289 | 290 | amyuki4@gmail.com 291 | web-greeter 292 | JezerM 293 |
294 | 295 | -------------------------------------------------------------------------------- /dist/web-greeter.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=web-greeter 3 | Exec=/opt/web-greeter/web-greeter %U 4 | Terminal=false 5 | Type=Application 6 | Icon=com.github.jezerm.web-greeter 7 | StartupWMClass=web-greeter 8 | Comment=Web based greeter for lightdm 9 | Categories=System; 10 | -------------------------------------------------------------------------------- /dist/web-greeter.yml: -------------------------------------------------------------------------------- 1 | # 2 | # branding: 3 | # background_images_dir: Path to directory that contains background images for use by themes. 4 | # logo_image: Path to logo image for use by greeter themes. 5 | # user_image: Default user image/avatar. This is used by themes when user has no .face image. 6 | # 7 | # NOTE: Paths must be accessible to the lightdm system user account (so they cannot be anywhere in /home) 8 | # 9 | branding: 10 | background_images_dir: /usr/share/backgrounds 11 | logo_image: /usr/share/web-greeter/themes/default/img/antergos-logo-user.png 12 | user_image: /usr/share/web-greeter/themes/default/img/antergos.png 13 | 14 | # 15 | # greeter: 16 | # debug_mode: Enable debug mode for the greeter as well as greeter themes. 17 | # detect_theme_errors: Provide an option to load a fallback theme when theme errors are detected. 18 | # screensaver_timeout: Blank the screen after this many seconds of inactivity. 19 | # secure_mode: Don't allow themes to make remote http requests. 20 | # theme: Greeter theme to use. 21 | # icon_theme: Icon/cursor theme to use, located in /usr/share/icons/, i.e. "Adwaita". Set to None to use default icon theme. 22 | # time_language: Language to use when displaying the date or time, i.e. "en-us", "es-419", "ko", "ja". Set to None to use system's language. 23 | # 24 | # NOTE: See IANA subtags registry for time_language options: https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry 25 | # 26 | greeter: 27 | debug_mode: False 28 | detect_theme_errors: True 29 | screensaver_timeout: 300 30 | secure_mode: True 31 | theme: gruvbox 32 | icon_theme: 33 | time_language: 34 | 35 | # 36 | # layouts A list of preferred layouts to use 37 | # - us "en_us" xkb layout 38 | # - latam "es_latam" xkb layout 39 | # - gb dvorak "en_gb_dvorak" xkb layout 40 | # 41 | # NOTE: See "man xkeyboard-config" for posible layout values. Also, see posible layouts here: https://web.archive.org/web/20161203032703/http://pastebin.com/v2vCPHjs 42 | # A layout value is composed in the main layout, like "us" or "latam", and its variant, like "dvorak", "colemak", "mac" or "mac_intl" 43 | # 44 | layouts: 45 | - us 46 | - latam 47 | 48 | # 49 | # features: 50 | # battery: Enable greeter and themes to get battery status. 51 | # backlight: 52 | # enabled: Enable greeter and themes to control display backlight. 53 | # value: The amount to increase/decrease brightness by greeter. 54 | # steps: How many steps are needed to do the change. 0 for instant change. 55 | # 56 | features: 57 | battery: False 58 | backlight: 59 | enabled: False 60 | value: 10 61 | steps: 0 62 | -------------------------------------------------------------------------------- /dist/web-xgreeter.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Web Greeter 3 | Comment=Greeter for LightDM 4 | Exec=web-greeter 5 | Type=Application 6 | X-Ubuntu-Gettext-Domain=web-greeter 7 | -------------------------------------------------------------------------------- /docs/Greeter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * LightDMGreeter.js 3 | * 4 | * Copyright © 2017 Antergos Developers 5 | * 6 | * This file is part of Web Greeter. 7 | * 8 | * Web Greeter is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * Web Greeter is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * The following additional terms are in effect as per Section 7 of the license: 19 | * 20 | * The preservation of all legal notices and author attributions in 21 | * the material or in the Appropriate Legal Notices displayed 22 | * by works containing it is required. 23 | * 24 | * You should have received a copy of the GNU General Public License 25 | * along with web-greeter; If not, see . 26 | */ 27 | 28 | 29 | /** 30 | * Base class for the greeter's Theme JavaScript API. Greeter themes will interact 31 | * directly with an object derived from this class to facilitate the user log-in process. 32 | * The greeter will automatically create an instance when it starts. 33 | * The instance can be accessed using the global variable: `lightdm`. 34 | * 35 | * @memberOf LightDM 36 | */ 37 | class Greeter { 38 | 39 | constructor() { 40 | if ( 'lightdm' in window ) { 41 | return window.lightdm; 42 | } 43 | 44 | window.lightdm = ThemeUtils.bind_this( this ); 45 | 46 | return window.lightdm; 47 | } 48 | 49 | /** 50 | * The username of the user being authenticated or {@link null} 51 | * if no authentication is in progress 52 | * @type {String|Null} 53 | * @readonly 54 | */ 55 | get authentication_user() {} 56 | 57 | /** 58 | * Whether or not the guest account should be automatically logged 59 | * into when the timer expires. 60 | * @type {Boolean} 61 | * @readonly 62 | */ 63 | get autologin_guest() {} 64 | 65 | /** 66 | * The number of seconds to wait before automatically logging in. 67 | * @type {Number} 68 | * @readonly 69 | */ 70 | get autologin_timeout() {} 71 | 72 | /** 73 | * The username with which to automattically log in when the timer expires. 74 | * @type {String} 75 | * @readonly 76 | */ 77 | get autologin_user() {} 78 | 79 | /** 80 | * The battery data 81 | * @type {Battery} 82 | * @readonly 83 | */ 84 | get batteryData() {} 85 | 86 | /** 87 | * The display brightness 88 | * @type {Number} 89 | */ 90 | get brightness() {} 91 | set brightness( quantity ) {} 92 | 93 | /** 94 | * Whether or not the greeter can access to battery data. 95 | * @type {boolean} 96 | * @readonly 97 | */ 98 | get can_access_battery() {} 99 | 100 | /** 101 | * Whether or not the greeter can control display brightness. 102 | * @type {boolean} 103 | * @readonly 104 | */ 105 | get can_access_brightness() {} 106 | 107 | /** 108 | * Whether or not the greeter can make the system hibernate. 109 | * @type {Boolean} 110 | * @readonly 111 | */ 112 | get can_hibernate() {} 113 | 114 | /** 115 | * Whether or not the greeter can make the system restart. 116 | * @type {Boolean} 117 | * @readonly 118 | */ 119 | get can_restart() {} 120 | 121 | /** 122 | * Whether or not the greeter can make the system shutdown. 123 | * @type {Boolean} 124 | * @readonly 125 | */ 126 | get can_shutdown() {} 127 | 128 | /** 129 | * Whether or not the greeter can make the system suspend/sleep. 130 | * @type {Boolean} 131 | * @readonly 132 | */ 133 | get can_suspend() {} 134 | 135 | /** 136 | * The name of the default session. 137 | * @type {String} 138 | * @readonly 139 | */ 140 | get default_session() {} 141 | 142 | /** 143 | * Whether or not guest sessions are supported. 144 | * @type {Boolean} 145 | * @readonly 146 | */ 147 | get has_guest_account() {} 148 | 149 | /** 150 | * Whether or not user accounts should be hidden. 151 | * @type {boolean} 152 | * @readonly 153 | */ 154 | get hide_users_hint() {} 155 | 156 | /** 157 | * The system's hostname. 158 | * @type {String} 159 | * @readonly 160 | */ 161 | get hostname() {} 162 | 163 | /** 164 | * Whether or not the greeter is in the process of authenticating. 165 | * @type {Boolean} 166 | * @readonly 167 | */ 168 | get in_authentication() {} 169 | 170 | /** 171 | * Whether or not the greeter has successfully authenticated. 172 | * @type {Boolean} 173 | * @readonly 174 | */ 175 | get is_authenticated() {} 176 | 177 | /** 178 | * The current language or {@link null} if no language. 179 | * @type {Language|Null} 180 | * @readonly 181 | */ 182 | get language() {} 183 | 184 | /** 185 | * A list of languages to present to the user. 186 | * @type {Language[]} 187 | * @readonly 188 | */ 189 | get languages() {} 190 | 191 | /** 192 | * The currently active layout for the selected user. 193 | * @type {Layout} 194 | */ 195 | get layout() {} 196 | set layout(layout) {} 197 | 198 | /** 199 | * A list of keyboard layouts to present to the user. 200 | * @type {Layout[]} 201 | * @readonly 202 | */ 203 | get layouts() {} 204 | 205 | /** 206 | * Whether or not the greeter was started as a lock screen. 207 | * @type {Boolean} 208 | * @readonly 209 | */ 210 | get lock_hint() {} 211 | 212 | /** 213 | * Whether or not the guest account should be selected by default. 214 | * @type {Boolean} 215 | * @readonly 216 | */ 217 | get select_guest_hint() {} 218 | 219 | /** 220 | * The username to select by default. 221 | * @type {String} 222 | * @readonly 223 | */ 224 | get select_user_hint() {} 225 | 226 | /** 227 | * List of available sessions. 228 | * @type {Session[]} 229 | * @readonly 230 | */ 231 | get sessions() {} 232 | 233 | /** 234 | * Check if a manual login option should be shown. If {@link true}, the theme should 235 | * provide a way for a username to be entered manually. Otherwise, themes that show 236 | * a user list may limit logins to only those users. 237 | * @type {Boolean} 238 | * @readonly 239 | */ 240 | get show_manual_login_hint() {} 241 | 242 | /** 243 | * Check if a remote login option should be shown. If {@link true}, the theme should provide 244 | * a way for a user to log into a remote desktop server. 245 | * @type {Boolean} 246 | * @readonly 247 | * @internal 248 | */ 249 | get show_remote_login_hint() {} 250 | 251 | /** 252 | * List of available users. 253 | * @type {User[]} 254 | * @readonly 255 | */ 256 | get users() {} 257 | 258 | 259 | /** 260 | * Starts the authentication procedure for a user. 261 | * 262 | * @param {String|Null} username A username or {@link null} to prompt for a username. 263 | */ 264 | authenticate( username ) {} 265 | 266 | /** 267 | * Starts the authentication procedure for the guest user. 268 | */ 269 | authenticate_as_guest() {} 270 | 271 | /** 272 | * Updates the battery data 273 | */ 274 | batteryUpdate() {} 275 | 276 | /** 277 | * Set the brightness to quantity 278 | * @param {Number} quantity The quantity to set 279 | */ 280 | brightnessSet( quantity ) {} 281 | 282 | /** 283 | * Increase the brightness by quantity 284 | * @param {Number} quantity The quantity to increase 285 | */ 286 | brightnessIncrease( quantity ) {} 287 | 288 | /** 289 | * Decrease the brightness by quantity 290 | * @param {Number} quantity The quantity to decrease 291 | */ 292 | brightnessDecrease( quantity ) {} 293 | 294 | /** 295 | * Cancel user authentication that is currently in progress. 296 | */ 297 | cancel_authentication() {} 298 | 299 | /** 300 | * Cancel the automatic login. 301 | */ 302 | cancel_autologin() {} 303 | 304 | /** 305 | * Triggers the system to hibernate. 306 | * @returns {Boolean} {@link true} if hibernation initiated, otherwise {@link false} 307 | */ 308 | hibernate() {} 309 | 310 | /** 311 | * Provide a response to a prompt. 312 | * @param {*} response 313 | */ 314 | respond( response ) {} 315 | 316 | /** 317 | * Triggers the system to restart. 318 | * @returns {Boolean} {@link true} if restart initiated, otherwise {@link false} 319 | */ 320 | restart() {} 321 | 322 | /** 323 | * Set the language for the currently authenticated user. 324 | * @param {String} language The language in the form of a locale specification (e.g. 325 | * 'de_DE.UTF-8') 326 | * @returns {Boolean} {@link true} if successful, otherwise {@link false} 327 | */ 328 | set_language( language ) {} 329 | 330 | /** 331 | * Triggers the system to shutdown. 332 | * @returns {Boolean} {@link true} if shutdown initiated, otherwise {@link false} 333 | */ 334 | shutdown() {} 335 | 336 | /** 337 | * Start a session for the authenticated user. 338 | * @param {String|null} session The session to log into or {@link null} to use the default. 339 | * @returns {Boolean} {@link true} if successful, otherwise {@link false} 340 | */ 341 | start_session( session ) {} 342 | 343 | /** 344 | * Triggers the system to suspend/sleep. 345 | * @returns {Boolean} {@link true} if suspend/sleep initiated, otherwise {@link false} 346 | */ 347 | suspend() {} 348 | 349 | /** 350 | * Gets emitted when the greeter has completed authentication. 351 | * @type {Signal} 352 | */ 353 | authentication_complete; 354 | 355 | /** 356 | * Gets emitted when the automatic login timer has expired. 357 | * @type {Signal} 358 | */ 359 | autologin_timer_expired; 360 | 361 | /** 362 | * Gets emitted when brightness is updated 363 | * @type {Signal} 364 | */ 365 | brightness_update; 366 | 367 | /** 368 | * Gets emitted when the user has logged in and the greeter is no longer needed. 369 | * @type {Signal} 370 | */ 371 | idle; 372 | 373 | /** 374 | * Gets emitted when the user is returning to a greeter that 375 | * was previously marked idle. 376 | * @type {Signal} 377 | */ 378 | reset; 379 | 380 | /** 381 | * Gets emitted when the greeter should show a message to the user. 382 | * @type {Signal} 383 | */ 384 | show_message; 385 | 386 | /** 387 | * Gets emitted when the greeter should show a prompt to the user. 388 | * @type {Signal} 389 | */ 390 | show_prompt; 391 | 392 | } 393 | 394 | /** 395 | * JS-Cookie instance - Themes must manually load the included vendor script in order to use this object. 396 | * @name Cookies 397 | * @type {object} 398 | * @version 2.1.3 399 | * @memberOf window 400 | * @see [JS Cookie Documentation](https://github.com/js-cookie/js-cookie/tree/latest#readme) 401 | */ 402 | 403 | 404 | 405 | -------------------------------------------------------------------------------- /docs/GreeterConfig.js: -------------------------------------------------------------------------------- 1 | /* 2 | * GreeterConfig.js 3 | * 4 | * Copyright © 2017 Antergos Developers 5 | * 6 | * This file is part of Web Greeter. 7 | * 8 | * Web Greeter is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * Web Greeter is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * The following additional terms are in effect as per Section 7 of the license: 19 | * 20 | * The preservation of all legal notices and author attributions in 21 | * the material or in the Appropriate Legal Notices displayed 22 | * by works containing it is required. 23 | * 24 | * You should have received a copy of the GNU General Public License 25 | * along with web-greeter; If not, see . 26 | */ 27 | 28 | 29 | /** 30 | * Provides greeter themes with a way to access values from the greeter's config 31 | * file located at `/etc/lightdm/web-greeter.yml`. The greeter will 32 | * create an instance of this class when it starts. The instance can be accessed 33 | * with the global variable: `greeter_config`. 34 | * 35 | * @memberOf LightDM 36 | */ 37 | class GreeterConfig { 38 | /** 39 | * Holds keys/values from the `branding` section of the config file. 40 | * 41 | * @type {Object} 42 | * @property {String} background_images_dir Path to directory that contains background images 43 | * for use in greeter themes. 44 | * @property {String} logo Path to distro logo image for use in greeter themes. 45 | * @property {String} user_image Default user image/avatar. This is used by greeter themes 46 | * for users that have not configured a `.face` image. 47 | * @readonly 48 | */ 49 | get branding() {} 50 | 51 | /** 52 | * Holds keys/values from the `greeter` section of the config file. 53 | * 54 | * @type {Object} 55 | * @property {Boolean} debug_mode Greeter theme debug mode. 56 | * @property {Boolean} detect_theme_errors Provide an option to load a fallback theme when theme 57 | * errors are detected. 58 | * @property {Number} screensaver_timeout Blank the screen after this many seconds of inactivity. 59 | * @property {Boolean} secure_mode Don't allow themes to make remote http requests. 60 | * @property {String} theme The name of the theme to be used by the greeter. 61 | * @property {String|Null} icon_theme Icon/cursor theme to use, located in /usr/share/icons, i.e "Adwaita". Set to Null to use default icon theme. 62 | * @property {String|Null} time_language Language to use when displaying the date or time, i.e "en-us", "es-419", "ko", "ja". Set to Null to use system's language. 63 | * @readonly 64 | */ 65 | get greeter() {} 66 | 67 | 68 | /** 69 | * Holds keys/values from the `features` section of the config file. 70 | * 71 | * @type {Object} 72 | * @property {Boolean} battery Enable greeter and themes to ger battery status. 73 | * @property {Object} backlight 74 | * @property {Boolean} backlight.enabled Enable greeter and themes to control display backlight. 75 | * @property {Number} backlight.value The amount to increase/decrease brightness by greeter. 76 | * @property {Number} backlight.steps How many steps are needed to do the change. 77 | */ 78 | get features() {} 79 | } 80 | 81 | -------------------------------------------------------------------------------- /docs/LightDMObjects.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2015-2017 Antergos 3 | * 4 | * LightDMObjects.js 5 | * 6 | * This file is part of Web Greeter 7 | * 8 | * Web Greeter is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, 11 | * or any later version. 12 | * 13 | * Web Greeter is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * The following additional terms are in effect as per Section 7 of the license: 19 | * 20 | * The preservation of all legal notices and author attributions in 21 | * the material or in the Appropriate Legal Notices displayed 22 | * by works containing it is required. 23 | * 24 | * You should have received a copy of the GNU General Public License 25 | * along with this program. If not, see . 26 | */ 27 | 28 | /** 29 | * The global window object. 30 | * 31 | * @name window 32 | * @type {object} 33 | * @global 34 | */ 35 | 36 | /** 37 | * The greeter's Theme JavaScript API. 38 | * Accesible through `lightdm` global variable. 39 | * 40 | * @namespace LightDM 41 | */ 42 | 43 | 44 | /** 45 | * Interface for object that holds info about a session. Session objects are not 46 | * created by the theme's code, but rather by the [`LightDM.Greeter`](Greeter.md) class. 47 | * 48 | * @memberOf LightDM 49 | */ 50 | class Session { 51 | constructor( { comment, key, name } ) { 52 | this._comment = comment; 53 | this._key = key; 54 | this._name = name; 55 | } 56 | 57 | /** 58 | * The name for the session. 59 | * @type {String} 60 | * @readonly 61 | */ 62 | get name() { 63 | return this._name; 64 | } 65 | 66 | /** 67 | * The key for the session. 68 | * @type {String} 69 | * @readonly 70 | */ 71 | get key() { 72 | return this._key; 73 | } 74 | 75 | /** 76 | * The comment for the session. 77 | * @type {String} 78 | * @readonly 79 | */ 80 | get comment() { 81 | return this._comment; 82 | } 83 | } 84 | 85 | 86 | /** 87 | * Interface for object that holds info about a language on the system. Language objects are not 88 | * created by the theme's code, but rather by the [`LightDM.Greeter`](Greeter.md) class. 89 | * 90 | * @memberOf LightDM 91 | */ 92 | class Language { 93 | constructor( { code, name, territory } ) { 94 | this._code = code; 95 | this._name = name; 96 | this._territory = territory; 97 | } 98 | 99 | /** 100 | * The code for the language. 101 | * @type {String} 102 | * @readonly 103 | */ 104 | get code() { 105 | return this._code; 106 | } 107 | 108 | /** 109 | * The name for the layout. 110 | * @type {String} 111 | * @readonly 112 | */ 113 | get name() { 114 | return this._name; 115 | } 116 | 117 | /** 118 | * The territory for the language. 119 | * @type {String} 120 | * @readonly 121 | */ 122 | get territory() { 123 | return this._territory; 124 | } 125 | } 126 | 127 | 128 | /** 129 | * Interface for object that holds info about a keyboard layout on the system. Language 130 | * objects are not created by the theme's code, but rather by the [`LightDM.Greeter`](Greeter.md) class. 131 | * 132 | * @memberOf LightDM 133 | */ 134 | class Layout { 135 | constructor( { description, name, short_description } ) { 136 | this._description = description; 137 | this._name = name; 138 | this._short_description = short_description; 139 | } 140 | 141 | /** 142 | * The description for the layout. 143 | * @type {String} 144 | * @readonly 145 | */ 146 | get description() { 147 | return this._description; 148 | } 149 | 150 | /** 151 | * The name for the layout. 152 | * @type {String} 153 | * @readonly 154 | */ 155 | get name() { 156 | return this._name; 157 | } 158 | 159 | /** 160 | * The territory for the layout. 161 | * @type {String} 162 | * @readonly 163 | */ 164 | get short_description() { 165 | return this._short_description; 166 | } 167 | } 168 | 169 | 170 | /** 171 | * Interface for object that holds info about a user account on the system. User 172 | * objects are not created by the theme's code, but rather by the [`LightDM.Greeter`](Greeter.md) class. 173 | * 174 | * @memberOf LightDM 175 | */ 176 | class User { 177 | constructor( user_info ) { 178 | Object.keys(user_info).forEach( key => { 179 | this[`_${key}`] = user_info[key]; 180 | } ); 181 | } 182 | 183 | /** 184 | * The display name for the user. 185 | * @type {String} 186 | * @readonly 187 | */ 188 | get display_name() { 189 | return this._display_name; 190 | } 191 | 192 | /** 193 | * The language for the user. 194 | * @type {String} 195 | * @readonly 196 | */ 197 | get language() { 198 | return this._language; 199 | } 200 | 201 | /** 202 | * The keyboard layout for the user. 203 | * @type {String} 204 | * @readonly 205 | */ 206 | get layout() { 207 | return this._layout; 208 | } 209 | 210 | /** 211 | * The image for the user. 212 | * @type {String} 213 | * @readonly 214 | */ 215 | get image() { 216 | return this._image; 217 | } 218 | 219 | /** 220 | * The home_directory for the user. 221 | * @type {String} 222 | * @readonly 223 | */ 224 | get home_directory() { 225 | return this._home_directory; 226 | } 227 | 228 | /** 229 | * The username for the user. 230 | * @type {String} 231 | * @readonly 232 | */ 233 | get username() { 234 | return this._username; 235 | } 236 | 237 | /** 238 | * Whether or not the user is currently logged in. 239 | * @type {Boolean} 240 | * @readonly 241 | */ 242 | get logged_in() { 243 | return this._logged_in; 244 | } 245 | 246 | /** 247 | * The last session that the user logged into. 248 | * @type {String|Null} 249 | * @readonly 250 | */ 251 | get session() { 252 | return this._session; 253 | } 254 | } 255 | 256 | /** 257 | * Interface for object that holds info about the battery on the system. This object is not created by the theme's code, but rather by the [`LightDM.Greeter`]{@link Greeter.md} class. 258 | * 259 | * @memberOf LightDM 260 | */ 261 | class Battery { 262 | constructor( {level, name, state} ) { 263 | this._level = level; 264 | this._name = name; 265 | this._state = state; 266 | } 267 | 268 | /** 269 | * The battery level. 270 | * @type {String|Null} 271 | * @readonly 272 | */ 273 | get level() { 274 | return this._level; 275 | } 276 | 277 | /** 278 | * The battery's name. 279 | * @type {String|Null} 280 | * @readonly 281 | */ 282 | get name() { 283 | return this._name; 284 | } 285 | 286 | /** 287 | * The state for the battery 288 | * @type {String|Null} 289 | * @readonly 290 | */ 291 | get state() { 292 | return this._state; 293 | } 294 | } 295 | 296 | 297 | /** 298 | * Interface for signals connected to LightDM itself. This is not created by the theme's code, but rather by Web Greeter. 299 | * When Web Greeter triggers the signal, all calbacks are executed. 300 | * 301 | * @memberOf LightDM 302 | */ 303 | class Signal { 304 | 305 | /** 306 | * Connects a callback to the signal. 307 | * @param {Function} callback The callback to attach. 308 | */ 309 | connect( callback ) {} 310 | 311 | /** 312 | * Disconnects a callback to the signal. 313 | * @param {Function} callback The callback to disattach. 314 | */ 315 | disconnect( callback ) {} 316 | } 317 | -------------------------------------------------------------------------------- /docs/ThemeUtils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ThemeUtils.js 3 | * 4 | * Copyright © 2017 Antergos Developers 5 | * 6 | * This file is part of Web Greeter. 7 | * 8 | * Web Greeter is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * Web Greeter is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * The following additional terms are in effect as per Section 7 of the license: 19 | * 20 | * The preservation of all legal notices and author attributions in 21 | * the material or in the Appropriate Legal Notices displayed 22 | * by works containing it is required. 23 | * 24 | * You should have received a copy of the GNU General Public License 25 | * along with web-greeter; If not, see . 26 | */ 27 | 28 | 29 | let time_language = null, 30 | _ThemeUtils = null; 31 | 32 | 33 | /** 34 | * Provides various utility methods for use in greeter themes. The greeter will automatically 35 | * create an instance of this class when it starts. The instance can be accessed 36 | * with the global variable: [`theme_utils`](#dl-window-theme_utils). 37 | * 38 | * @memberOf LightDM 39 | */ 40 | class ThemeUtils { 41 | 42 | constructor( instance ) { 43 | if ( null !== _ThemeUtils ) { 44 | return _ThemeUtils; 45 | } 46 | 47 | _ThemeUtils = instance; 48 | } 49 | 50 | /** 51 | * Binds `this` to class, `context`, for all of the class's methods. 52 | * 53 | * @arg {object} context An ES6 class instance with at least one method. 54 | * 55 | * @return {object} `context` with `this` bound to it for all of its methods. 56 | */ 57 | bind_this( context ) { 58 | let excluded_methods = ['constructor']; 59 | 60 | function not_excluded( _method, _context ) { 61 | let is_excluded = excluded_methods.findIndex( excluded_method => _method === excluded_method ) > -1, 62 | is_method = 'function' === typeof _context[_method]; 63 | 64 | return is_method && !is_excluded; 65 | } 66 | 67 | for ( let obj = context; obj; obj = Object.getPrototypeOf( obj ) ) { 68 | // Stop once we have traveled all the way up the inheritance chain 69 | if ( 'Object' === obj.constructor.name ) { 70 | break; 71 | } 72 | 73 | for ( let method of Object.getOwnPropertyNames( obj ) ) { 74 | if ( not_excluded( method, context ) ) { 75 | context[method] = context[method].bind( context ); 76 | } 77 | } 78 | } 79 | 80 | return context; 81 | } 82 | 83 | 84 | /** 85 | * Returns the contents of directory found at `path` provided that the (normalized) `path` 86 | * meets at least one of the following conditions: 87 | * * Is located within the greeter themes' root directory. 88 | * * Has been explicitly allowed in the greeter's config file. 89 | * * Is located within the greeter's shared data directory (`/var/lib/lightdm-data`). 90 | * * Is located in `/tmp`. 91 | * 92 | * @param {string} path The abs path to desired directory. 93 | * @param {boolean} only_images Include only images in the results. Default `true`. 94 | * @param {function(string[])} callback Callback function to be called with the result. 95 | */ 96 | dirlist( path, only_images = true, callback ) { 97 | if ( '' === path || 'string' !== typeof path ) { 98 | console.error(`theme_utils.dirlist(): path must be a non-empty string!`); 99 | return callback([]); 100 | 101 | } else if ( null !== path.match(/^[^/].+/) ) { 102 | console.error(`theme_utils.dirlist(): path must be absolute!`); 103 | return callback([]); 104 | } 105 | 106 | if ( null !== path.match(/\/\.+(?=\/)/) ) { 107 | // No special directory names allowed (eg ../../) 108 | path = path.replace(/\/\.+(?=\/)/g, '' ); 109 | } 110 | 111 | try { 112 | return _ThemeUtils.dirlist( path, only_images, callback ); 113 | } catch( err ) { 114 | console.error(`theme_utils.dirlist(): ${err}`); 115 | return callback([]); 116 | } 117 | } 118 | 119 | /** 120 | * Get the current date in a localized format. Local language is autodetected by default, but can be set manually in the greeter config file. 121 | * * `language` defaults to the system's language, but can be set manually in the config file. 122 | * 123 | * @returns {Object} The current date. 124 | */ 125 | get_current_localized_date() { 126 | let config = greeter_config.greeter 127 | 128 | var locale = [] 129 | 130 | if (time_language === null) { 131 | time_language = config.time_language || "" 132 | } 133 | 134 | if (time_language != "") { 135 | locale.push(time_language) 136 | } 137 | 138 | let optionsDate = { day: "2-digit", month: "2-digit", year: "2-digit" } 139 | 140 | let fmtDate = Intl.DateTimeFormat(locale, optionsDate) 141 | 142 | let now = new Date() 143 | var date = fmtDate.format(now) 144 | 145 | return date 146 | } 147 | 148 | /** 149 | * Get the current time in a localized format. Local language is autodetected by default, but can be set manually in the greeter config file. 150 | * * `language` defaults to the system's language, but can be set manually in the config file. 151 | * 152 | * @returns {Object} The current time. 153 | */ 154 | get_current_localized_time() { 155 | let config = greeter_config.greeter 156 | 157 | var locale = [] 158 | 159 | if (time_language === null) { 160 | time_language = config.time_language || "" 161 | } 162 | 163 | if (time_language != "") { 164 | locale.push(time_language) 165 | } 166 | 167 | let optionsTime = { hour: "2-digit", minute: "2-digit" } 168 | 169 | let fmtTime = Intl.DateTimeFormat(locale, optionsTime) 170 | 171 | let now = new Date() 172 | var time = fmtTime.format(now) 173 | 174 | return time 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /docs/bootstrap.js: -------------------------------------------------------------------------------- 1 | /* 2 | * bootstrap.js 3 | * 4 | * Copyright © 2017 Antergos Developers 5 | * 6 | * This file is part of Web Greeter. 7 | * 8 | * Web Greeter is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * Web Greeter is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * The following additional terms are in effect as per Section 7 of the license: 19 | * 20 | * The preservation of all legal notices and author attributions in 21 | * the material or in the Appropriate Legal Notices displayed 22 | * by works containing it is required. 23 | * 24 | * You should have received a copy of the GNU General Public License 25 | * along with web-greeter; If not, see . 26 | */ 27 | 28 | 29 | (() => { 30 | let _channel; 31 | 32 | /** 33 | * Greeter Ready Event. Themes should not initialize until this event has fired. 34 | * @event window#GreeterReady 35 | * @name GreeterReady 36 | * @type Event 37 | * @memberOf window 38 | */ 39 | window._ready_event = new Event( 'GreeterReady' ); 40 | 41 | function channel_ready_cb( channel ) { 42 | _channel = channel; 43 | 44 | /** 45 | * Greeter Instance 46 | * @name lightdm 47 | * @type {LightDM.Greeter} 48 | * @memberOf window 49 | */ 50 | window.lightdm = _channel.objects.LightDMGreeter; 51 | 52 | /** 53 | * Greeter Config - Access values from the greeter's config file. 54 | * @name greeter_config 55 | * @type {LightDM.GreeterConfig} 56 | * @memberOf window 57 | */ 58 | window.greeter_config = _channel.objects.Config; 59 | 60 | /** 61 | * Theme Utils - various utility methods for use in greeter themes. 62 | * @name theme_utils 63 | * @type {LightDM.ThemeUtils} 64 | * @memberOf window 65 | */ 66 | window.theme_utils = new ThemeUtils( _channel.objects.ThemeUtils ); 67 | 68 | setTimeout( function () { 69 | window.dispatchEvent( _ready_event ); 70 | }, 2 ); 71 | } 72 | 73 | document.addEventListener( 'DOMContentLoaded', ( event ) => { 74 | new QWebChannel( qt.webChannelTransport, channel_ready_cb ); 75 | }); 76 | 77 | })(); 78 | 79 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyGObject 2 | PyQt5 3 | PyQtWebEngine 4 | ruamel.yaml 5 | pyinotify 6 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JezerM/web-greeter/0bfa7f0036b2336c4d9aa9ad35e0777ab0b41857/src/__init__.py -------------------------------------------------------------------------------- /src/__main__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # __main__.py 4 | # 5 | # Copyright © 2021 JezerM 6 | # 7 | # This file is part of Web Greeter. 8 | # 9 | # Web Greeter is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 3 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # Web Greeter is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # The following additional terms are in effect as per Section 7 of the license: 20 | # 21 | # The preservation of all legal notices and author attributions in 22 | # the material or in the Appropriate Legal Notices displayed 23 | # by works containing it is required. 24 | # 25 | # You should have received a copy of the GNU General Public License 26 | # along with Web Greeter; If not, see . 27 | 28 | # Standard lib 29 | import sys 30 | import argparse 31 | import os 32 | from typing import List 33 | 34 | # 3rd-Party Libs 35 | import config 36 | 37 | def list_themes() -> List[str]: 38 | """List available themes""" 39 | themes_dir = config.web_greeter_config["app"]["theme_dir"] 40 | themes_dir = themes_dir if os.path.exists(themes_dir) else "/usr/share/web-greeter/themes" 41 | filenames = os.listdir(themes_dir) 42 | 43 | dirlist = [] 44 | for file in filenames: 45 | if os.path.isdir(os.path.join(themes_dir, file)): 46 | dirlist.append(file) 47 | 48 | return dirlist 49 | 50 | def print_themes(): 51 | """Print available themes""" 52 | themes_dir = config.web_greeter_config["app"]["theme_dir"] 53 | themes_dir = themes_dir if os.path.exists(themes_dir) else "/usr/share/web-greeter/themes" 54 | themes = list_themes() 55 | print(f"Themes are located in {themes_dir}\n") 56 | for theme in themes: 57 | print("-", theme) 58 | 59 | 60 | def set_theme(theme: str): 61 | """Sets the theme""" 62 | config.web_greeter_config["config"]["greeter"]["theme"] = theme 63 | 64 | def set_debug(value: bool): 65 | """Sets debug mode""" 66 | conf = config.web_greeter_config["config"] 67 | app = config.web_greeter_config["app"] 68 | conf["greeter"]["debug_mode"] = value 69 | app["frame"] = value 70 | app["fullscreen"] = not value 71 | 72 | def parse(argv): 73 | """Parse command arguments""" 74 | version = config.web_greeter_config["app"]["version"]["full"] 75 | api_version = config.web_greeter_config["app"]["api_version"]["full"] 76 | 77 | parser = argparse.ArgumentParser(prog="web-greeter", add_help = False) 78 | parser.add_argument("-h", "--help", action = "help", 79 | help = "Show this help message and exit") 80 | parser.add_argument("-v", "--version", action = "version", 81 | version = version, help = "Show version number") 82 | parser.add_argument("--api-version", action = "version", 83 | version = api_version, help = "Show JavaScript API version number") 84 | 85 | parser.add_argument("--mode", help = "Set browser mode", 86 | choices = ["debug", "normal"]) 87 | parser.add_argument("-d", "--debug", action = "store_true", 88 | help = "Run the greeter in debug mode", 89 | dest = "debug", default = None) 90 | parser.add_argument("-n", "--normal", action = "store_false", 91 | help = "Run in non-debug mode", dest = "debug") 92 | parser.add_argument("--list", action = "store_true", 93 | help = "List available themes") 94 | parser.add_argument("--theme", help = "Set the theme to use", metavar = "[name]") 95 | parser.add_argument("--no-sandbox", action = "store_true", help = argparse.SUPPRESS) 96 | 97 | args: argparse.Namespace 98 | 99 | try: 100 | args = parser.parse_args(argv) 101 | except argparse.ArgumentError: 102 | sys.exit() 103 | 104 | # print(args) 105 | 106 | if args.list: 107 | print_themes() 108 | sys.exit() 109 | if args.theme: 110 | set_theme(args.theme) 111 | if args.mode == "debug": 112 | args.debug = True 113 | elif args.mode == "normal": 114 | args.debug = False 115 | if args.debug is not None: 116 | set_debug(args.debug) 117 | 118 | if __name__ == '__main__': 119 | parse(sys.argv[1:]) 120 | 121 | import globales 122 | from browser.browser import Browser 123 | from bridge import Greeter, Config, ThemeUtils 124 | from PyQt5.QtWidgets import QApplication 125 | from PyQt5.QtCore import Qt, QCoreApplication 126 | 127 | QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling) 128 | QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) 129 | app = QApplication(sys.argv) 130 | 131 | globales.LDMGreeter = Greeter() 132 | globales.LDMGreeterConfig = Config() 133 | globales.LDMThemeUtils = ThemeUtils(globales.LDMGreeter) 134 | 135 | config.load_theme_config() 136 | config.ensure_theme() 137 | 138 | globales.greeter = Browser(app) 139 | browser = globales.greeter 140 | 141 | # browser.load() 142 | browser.show() 143 | browser.run() 144 | -------------------------------------------------------------------------------- /src/bindings/__init__.py: -------------------------------------------------------------------------------- 1 | from .screensaver import screensaver 2 | -------------------------------------------------------------------------------- /src/bindings/screensaver.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | Display* OpenDisplay() { 7 | char *disp = NULL; 8 | Display *display = XOpenDisplay(disp); 9 | return display; 10 | } 11 | 12 | typedef struct { 13 | int timeout; 14 | int interval; 15 | int prefer_blank; 16 | int allow_exp; 17 | } ScreenSaver; 18 | 19 | ScreenSaver *get_screensaver() { 20 | int timeout, interval, prefer_blank, allow_exp; 21 | 22 | Display *display = OpenDisplay(); 23 | if (display == NULL) return NULL; 24 | 25 | XGetScreenSaver(display, &timeout, &interval, &prefer_blank, &allow_exp); 26 | XCloseDisplay(display); 27 | 28 | ScreenSaver *data = malloc(sizeof *data); 29 | data->timeout = timeout; 30 | data->interval = interval; 31 | data->prefer_blank = prefer_blank; 32 | data->allow_exp = allow_exp; 33 | 34 | return data; 35 | } 36 | 37 | void set_screensaver(int timeout, int interval, int prefer_blank, int allow_exp) { 38 | int timeout_i, interval_i, prefer_blank_i, allow_exp_i; 39 | 40 | Display *display = OpenDisplay(); 41 | if (display == NULL) return; 42 | XGetScreenSaver(display, &timeout_i, &interval_i, &prefer_blank_i, &allow_exp_i); 43 | 44 | if (timeout != -1) timeout_i = timeout; 45 | if (interval != -1) interval_i = interval; 46 | if (prefer_blank != -1) prefer_blank_i = prefer_blank; 47 | if (allow_exp != -1) allow_exp_i = allow_exp; 48 | 49 | XSetScreenSaver(display, timeout_i, interval_i, prefer_blank_i, allow_exp_i); 50 | 51 | XCloseDisplay(display); 52 | } 53 | 54 | void force_screensaver(bool value) { 55 | Display *display = OpenDisplay(); 56 | if (display == NULL) return; 57 | 58 | if (value == true) 59 | XForceScreenSaver(display, ScreenSaverActive); 60 | else 61 | XForceScreenSaver(display, ScreenSaverReset); 62 | 63 | XCloseDisplay(display); 64 | } 65 | -------------------------------------------------------------------------------- /src/bindings/screensaver.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import os 3 | from typing import Union 4 | from logger import logger 5 | 6 | 7 | class ScreenSaverData(ctypes.Structure): 8 | """Screensaver data""" 9 | _fields_ = [ 10 | ("timeout", ctypes.c_int), 11 | ("interval", ctypes.c_int), 12 | ("prefer_blank", ctypes.c_int), 13 | ("allow_exp", ctypes.c_int), 14 | ] 15 | 16 | def __str__(self): 17 | timeout = getattr(self, "timeout") 18 | interval = getattr(self, "interval") 19 | prefer_blank = getattr(self, "prefer_blank") 20 | allow_exp = getattr(self, "allow_exp") 21 | return f"{timeout} {interval} {prefer_blank} {allow_exp}" 22 | 23 | ScreenSaverDataPointer = ctypes.POINTER(ScreenSaverData) 24 | 25 | class ScreenSaver: 26 | """Screensaver wrapper""" 27 | 28 | saved_data: Union[ScreenSaverData, None] 29 | saved: bool = False 30 | 31 | def __init__(self): 32 | directory = os.path.dirname(os.path.realpath(__file__)) 33 | libname = os.path.join(directory, "_screensaver.so") 34 | self.clib = ctypes.CDLL(libname) 35 | self.clib.get_screensaver.restype = ScreenSaverDataPointer 36 | 37 | def get_screensaver(self): 38 | """Gets screensaver data""" 39 | data: ScreenSaverDataPointer = self.clib.get_screensaver() 40 | if data is None: 41 | return None 42 | contents: ScreenSaverData = data.contents 43 | return contents 44 | 45 | def set_screensaver(self, timeout = -1, interval = -1, prefer_blank = -1, allow_exp = -1): 46 | """Sets screensaver properties""" 47 | if self.saved: 48 | return 49 | self.saved_data = self.get_screensaver() 50 | self.saved = True 51 | self.clib.set_screensaver( 52 | ctypes.c_int(timeout), 53 | ctypes.c_int(interval), 54 | ctypes.c_int(prefer_blank), 55 | ctypes.c_int(allow_exp) 56 | ) 57 | logger.debug("Screensaver timeout set") 58 | 59 | def reset_screensaver(self): 60 | """Reset screensaver""" 61 | if not self.saved or not self.saved_data: 62 | return 63 | self.clib.set_screensaver( 64 | ctypes.c_int(getattr(self.saved_data, "timeout")), 65 | ctypes.c_int(getattr(self.saved_data, "interval")), 66 | ctypes.c_int(getattr(self.saved_data, "prefer_blank")), 67 | ctypes.c_int(getattr(self.saved_data, "allow_exp")) 68 | ) 69 | self.saved = False 70 | logger.debug("Screensaver reset") 71 | 72 | def force_screensaver(self, value: bool): 73 | """Force screensaver""" 74 | self.clib.force_screensaver(ctypes.c_bool(value)) 75 | 76 | screensaver = ScreenSaver() 77 | -------------------------------------------------------------------------------- /src/bridge/Config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Config.py 4 | # 5 | # Copyright © 2017 Antergos 6 | # Copyright © 2021 JezerM 7 | # 8 | # This file is part of Web Greeter. 9 | # 10 | # Web Greeter is free software; you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation; either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # Web Greeter is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # The following additional terms are in effect as per Section 7 of the license: 21 | # 22 | # The preservation of all legal notices and author attributions in 23 | # the material or in the Appropriate Legal Notices displayed 24 | # by works containing it is required. 25 | # 26 | # You should have received a copy of the GNU General Public License 27 | # along with Web Greeter; If not, see . 28 | 29 | # pylint: disable=wrong-import-position 30 | 31 | # Standard Lib 32 | from typing import List 33 | 34 | # 3rd-Party Libs 35 | import gi 36 | gi.require_version('LightDM', '1') 37 | from gi.repository import LightDM 38 | 39 | from PyQt5.QtCore import QVariant 40 | 41 | # This application 42 | from browser.bridge import Bridge, BridgeObject 43 | from config import web_greeter_config 44 | 45 | from . import layout_to_dict 46 | 47 | def get_layouts(config_layouts: List[str]): 48 | """Get layouts from web-greeter's config""" 49 | layouts = LightDM.get_layouts() 50 | final_layouts: list[LightDM.Layout] = [] 51 | for ldm_lay in layouts: 52 | for conf_lay in config_layouts: 53 | if not isinstance(conf_lay, str): 54 | return [] 55 | conf_lay = conf_lay.replace(" ", "\t") 56 | if ldm_lay.get_name() == conf_lay: 57 | final_layouts.append(layout_to_dict(ldm_lay)) 58 | return final_layouts 59 | 60 | 61 | class Config(BridgeObject): 62 | # pylint: disable=no-self-use,missing-function-docstring,too-many-public-methods,invalid-name 63 | """Config bridge class, known as `greeter_config` in javascript""" 64 | 65 | noop_signal = Bridge.signal() 66 | 67 | def __init__(self, *args, **kwargs): 68 | super().__init__(name='Config', *args, **kwargs) 69 | 70 | _config = web_greeter_config["config"] 71 | self._branding = _config["branding"] 72 | self._greeter = _config["greeter"] 73 | self._features = _config["features"] 74 | self._layouts = get_layouts(_config["layouts"]) 75 | 76 | @Bridge.prop(QVariant, notify=noop_signal) 77 | def branding(self): 78 | return self._branding 79 | 80 | @Bridge.prop(QVariant, notify=noop_signal) 81 | def greeter(self): 82 | return self._greeter 83 | 84 | @Bridge.prop(QVariant, notify=noop_signal) 85 | def features(self): 86 | return self._features 87 | 88 | @Bridge.prop(QVariant, notify=noop_signal) 89 | def layouts(self): 90 | return self._layouts 91 | -------------------------------------------------------------------------------- /src/bridge/GreeterComm.py: -------------------------------------------------------------------------------- 1 | # 2 | # Greeter.py 3 | # 4 | # Copyright © 2021 JezerM 5 | # 6 | # This file is part of Web Greeter. 7 | # 8 | # Web Greeter is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Web Greeter is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # The following additional terms are in effect as per Section 7 of the license: 19 | # 20 | # The preservation of all legal notices and author attributions in 21 | # the material or in the Appropriate Legal Notices displayed 22 | # by works containing it is required. 23 | # 24 | # You should have received a copy of the GNU General Public License 25 | # along with Web Greeter; If not, see . 26 | 27 | # pylint: disable=wrong-import-position 28 | 29 | 30 | from typing import List 31 | 32 | from PyQt5.QtCore import QVariant, QTimer 33 | 34 | # This Application 35 | from browser.window import WindowAbstract 36 | from browser.bridge import Bridge, BridgeObject 37 | from bridge import window_metadata_to_dict 38 | 39 | import globales 40 | 41 | communications: List = [] 42 | 43 | def communication_emit(window, data): 44 | """Emit broadcast_signal for each GreeterComm element in communications""" 45 | for comm in communications: 46 | # print(window) 47 | comm.broadcast_signal.emit(window, data) 48 | 49 | class GreeterComm(BridgeObject): 50 | # pylint: disable=missing-function-docstring,too-many-public-methods,invalid-name 51 | """Greeter Communication bridge class, known as `greeter_comm` in javascript""" 52 | 53 | broadcast_signal = Bridge.signal(QVariant, QVariant, arguments=("window", "data")) 54 | metadata_signal = Bridge.signal(QVariant, arguments=("metadata")) 55 | 56 | property_changed = Bridge.signal() 57 | window: WindowAbstract 58 | 59 | def __init__(self, window, *args, **kwargs): 60 | super().__init__(name='Comm', *args, **kwargs) 61 | self.window = window 62 | 63 | communications.append(self) 64 | 65 | @Bridge.prop(QVariant, notify=property_changed) 66 | def window_metadata(self): 67 | for win in globales.greeter.windows: 68 | if self.window.meta.id == win.meta.id: 69 | meta = window_metadata_to_dict(win.meta) 70 | # print(meta) 71 | return meta 72 | 73 | return {} 74 | 75 | @Bridge.method(QVariant) 76 | def broadcast(self, data): 77 | self.property_changed.emit() 78 | QTimer().singleShot(60, lambda: communication_emit(self.window_metadata, data)) 79 | 80 | @Bridge.method() 81 | def requestMetadata(self): 82 | for win in globales.greeter.windows: 83 | if self.window.meta.id == win.meta.id: 84 | meta = window_metadata_to_dict(win.meta) 85 | self.metadata_signal.emit(meta) 86 | 87 | def destroy(self): 88 | global communications 89 | comms = [] 90 | for obj in communications: 91 | if obj != self: 92 | comms.append(obj) 93 | 94 | communications = comms 95 | -------------------------------------------------------------------------------- /src/bridge/ThemeUtils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # ThemeUtils.py 4 | # 5 | # Copyright © 2017 Antergos 6 | # Copyright © 2021 JezerM 7 | # 8 | # This file is part of Web Greeter. 9 | # 10 | # Web Greeter is free software; you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation; either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # Web Greeter is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # The following additional terms are in effect as per Section 7 of the license: 21 | # 22 | # The preservation of all legal notices and author attributions in 23 | # the material or in the Appropriate Legal Notices displayed 24 | # by works containing it is required. 25 | # 26 | # You should have received a copy of the GNU General Public License 27 | # along with Web Greeter; If not, see . 28 | 29 | # pylint: disable=wrong-import-position 30 | 31 | # Standard Lib 32 | import os 33 | import re 34 | import tempfile 35 | 36 | # 3rd-Party Libs 37 | from PyQt5.QtCore import QVariant 38 | 39 | # This application 40 | from browser.bridge import Bridge, BridgeObject 41 | 42 | from config import web_greeter_config 43 | from logger import logger 44 | 45 | class ThemeUtils(BridgeObject): 46 | # pylint: disable=no-self-use,missing-function-docstring,too-many-public-methods,invalid-name 47 | """ThemeUtils bridge class, known as `theem_utils` in javascript""" 48 | 49 | def __init__(self, greeter_object, *args, **kwargs): 50 | super().__init__(name='ThemeUtils', *args, **kwargs) 51 | 52 | self._config = web_greeter_config 53 | self._greeter = greeter_object 54 | 55 | self._allowed_dirs = ( 56 | os.path.dirname( 57 | os.path.realpath(self._config["config"]["greeter"]["theme"]) 58 | ), 59 | self._config["app"]["theme_dir"], 60 | self._config["config"]["branding"]["background_images_dir"], 61 | self._greeter.shared_data_directory, 62 | tempfile.gettempdir(), 63 | ) 64 | 65 | @Bridge.method(str, bool, result=QVariant) 66 | def dirlist(self, dir_path, only_images=True): 67 | if not dir_path or not isinstance(dir_path, str) or '/' == dir_path: 68 | return [] 69 | 70 | if dir_path.startswith("./"): 71 | dir_path = os.path.join( 72 | os.path.dirname(self._config["config"]["greeter"]["theme"]), 73 | dir_path 74 | ) 75 | 76 | dir_path = os.path.realpath(os.path.normpath(dir_path)) 77 | 78 | if not os.path.isabs(dir_path) or not os.path.isdir(dir_path): 79 | return [] 80 | 81 | allowed = False 82 | 83 | for allowed_dir in self._allowed_dirs: 84 | if dir_path.startswith(allowed_dir): 85 | allowed = True 86 | break 87 | 88 | if not allowed: 89 | logger.error("Path \"%s\" is not allowed", dir_path) 90 | return [] 91 | 92 | result = [] 93 | if only_images: 94 | for f in os.scandir(dir_path): 95 | if f.is_file() and re.match(r".+\.(jpe?g|png|gif|bmp|webp)", f.name): 96 | result.append(f.path) 97 | 98 | else: 99 | result = [os.path.join(dir_path, f) for f in os.listdir(dir_path)] 100 | 101 | result.sort() 102 | return result 103 | -------------------------------------------------------------------------------- /src/bridge/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # __init__.py 5 | # 6 | # Copyright © 2017 Antergos 7 | # 8 | # This file is part of Web Greeter. 9 | # 10 | # Web Greeter is free software; you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation; either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # Web Greeter is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # The following additional terms are in effect as per Section 7 of the license: 21 | # 22 | # The preservation of all legal notices and author attributions in 23 | # the material or in the Appropriate Legal Notices displayed 24 | # by works containing it is required. 25 | # 26 | # You should have received a copy of the GNU General Public License 27 | # along with Web Greeter; If not, see . 28 | 29 | import math 30 | import sys 31 | from typing import Literal 32 | from logger import logger 33 | 34 | def language_to_dict(lang): 35 | """Returns a dict from LightDMLanguage object""" 36 | if not lang: 37 | return {} 38 | try: 39 | return { 40 | "code": lang.get_code(), 41 | "name": lang.get_name(), 42 | "territory": lang.get_territory() 43 | } 44 | except Exception as e: 45 | logger.warn(e) 46 | return {} 47 | 48 | 49 | def layout_to_dict(layout): 50 | """Returns a dict from LightDMLayout object""" 51 | if not layout: 52 | return {} 53 | try: 54 | return { 55 | "description": layout.get_description(), 56 | "name": layout.get_name(), 57 | "short_description": layout.get_short_description() 58 | } 59 | except Exception as e: 60 | logger.warn(e) 61 | return {} 62 | 63 | 64 | def session_to_dict(session): 65 | """Returns a dict from LightDMSession object""" 66 | if not session: 67 | return {} 68 | try: 69 | return { 70 | "comment": session.get_comment(), 71 | "key": session.get_key(), 72 | "name": session.get_name(), 73 | "type": session.get_session_type(), 74 | } 75 | except Exception as e: 76 | logger.warn(e) 77 | return {} 78 | 79 | 80 | def user_to_dict(user): 81 | """Returns a dict from LightDMUser object""" 82 | if not user: 83 | return {} 84 | try: 85 | return { 86 | "background": user.get_background(), 87 | "display_name": user.get_display_name(), 88 | "home_directory": user.get_home_directory(), 89 | "image": user.get_image(), 90 | "language": user.get_language(), 91 | "layout": user.get_layout(), 92 | "layouts": user.get_layouts(), 93 | "logged_in": user.get_logged_in(), 94 | "session": user.get_session(), 95 | "username": user.get_name(), 96 | } 97 | except Exception as e: 98 | logger.warn(e) 99 | return {} 100 | 101 | 102 | def battery_to_dict(battery): 103 | """Returns a dict from Battery object""" 104 | if not battery: 105 | return {} 106 | if len(battery.batteries) == 0: 107 | return {} 108 | try: 109 | return { 110 | "name": battery.get_name(), 111 | "level": battery.get_level(), 112 | "status": battery.get_status(), 113 | "ac_status": battery.get_ac_status(), 114 | "capacity": battery.get_capacity(), 115 | "time": battery.get_time(), 116 | "watt": battery.get_watt() 117 | } 118 | except Exception as e: 119 | logger.warn(e) 120 | return {} 121 | 122 | def inf_to_infinity(num: float): 123 | """Converts a math.inf to "infinity" or "-infinity" """ 124 | if not math.isinf(num): 125 | return num 126 | if num > 0: 127 | return "infinity" 128 | return "-infinity" 129 | 130 | def window_metadata_to_dict(metadata): 131 | """Returns a dict from WindowMetadata object""" 132 | if not metadata: 133 | return {} 134 | return { 135 | "id": metadata.id, 136 | "is_primary": metadata.is_primary, 137 | "overallBoundary": { 138 | "minX": metadata.overallBoundary.minX, 139 | "maxX": inf_to_infinity(metadata.overallBoundary.maxX), 140 | "minY": metadata.overallBoundary.minY, 141 | "maxY": inf_to_infinity(metadata.overallBoundary.maxY), 142 | }, 143 | "position": { 144 | "x": metadata.position.x, 145 | "y": metadata.position.y, 146 | }, 147 | "size": { 148 | "width": metadata.size.width, 149 | "height": metadata.size.height, 150 | }, 151 | } 152 | 153 | # pylint: disable=wrong-import-position 154 | from .Greeter import Greeter 155 | from .Config import Config 156 | from .ThemeUtils import ThemeUtils 157 | from .GreeterComm import GreeterComm 158 | -------------------------------------------------------------------------------- /src/browser/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JezerM/web-greeter/0bfa7f0036b2336c4d9aa9ad35e0777ab0b41857/src/browser/__init__.py -------------------------------------------------------------------------------- /src/browser/bridge.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # bridge.py 4 | # 5 | # Copyright © 2016-2017 Antergos 6 | # 7 | # This file is part of Web Greeter. 8 | # 9 | # Web Greeter is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 3 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # Web Greeter is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # The following additional terms are in effect as per Section 7 of the license: 20 | # 21 | # The preservation of all legal notices and author attributions in 22 | # the material or in the Appropriate Legal Notices displayed 23 | # by works containing it is required. 24 | # 25 | # You should have received a copy of the GNU General Public License 26 | # along with Web Greeter; If not, see . 27 | 28 | from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty 29 | 30 | class Bridge: 31 | """Bridge class""" 32 | @staticmethod 33 | def method(*args, **kwargs): 34 | """Declare a method""" 35 | return pyqtSlot(*args, **kwargs) 36 | 37 | @staticmethod 38 | def prop(*args, **kwargs): 39 | """Declare a property""" 40 | return pyqtProperty(*args, **kwargs) 41 | 42 | @staticmethod 43 | def signal(*args, **kwargs): 44 | """Declare a signal""" 45 | return pyqtSignal(*args, **kwargs) 46 | 47 | class BridgeObject(QObject): 48 | """BridgeObject class""" 49 | def __init__(self, name: str): 50 | super().__init__(parent=None) 51 | self._name = name 52 | -------------------------------------------------------------------------------- /src/browser/browser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # browser.py 4 | # 5 | # Copyright © 2017 Antergos 6 | # Copyright © 2021 JezerM 7 | # 8 | # This file is part of Web Greeter. 9 | # 10 | # Web Greeter is free software; you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation; either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # Web Greeter is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # The following additional terms are in effect as per Section 7 of the license: 21 | # 22 | # The preservation of all legal notices and author attributions in 23 | # the material or in the Appropriate Legal Notices displayed 24 | # by works containing it is required. 25 | # 26 | # You should have received a copy of the GNU General Public License 27 | # along with Web Greeter; If not, see . 28 | 29 | # Standard lib 30 | 31 | import math 32 | import random 33 | import re 34 | import sys 35 | import os 36 | from typing import ( 37 | List, 38 | Tuple, 39 | TypeVar, 40 | ) 41 | 42 | # 3rd-Party Libs 43 | from PyQt5.QtCore import ( 44 | QUrl, 45 | Qt, 46 | QRect, 47 | ) 48 | from PyQt5.QtWebEngineCore import QWebEngineUrlScheme 49 | from PyQt5.QtWidgets import ( 50 | QApplication 51 | ) 52 | from PyQt5.QtWebEngineWidgets import ( 53 | QWebEngineProfile 54 | ) 55 | from PyQt5.QtGui import QScreen 56 | 57 | from browser.url_scheme import QtUrlSchemeHandler 58 | from browser.interceptor import QtUrlRequestInterceptor 59 | from bridge.GreeterComm import GreeterComm 60 | from browser.browser_interfaces import OverallBoundary, WindowMetadata, WindowPosition, WindowSize 61 | from browser.window import BrowserWindow, WindowAbstract 62 | import globales 63 | 64 | from logger import logger 65 | from config import load_primary_theme_path, load_secondary_theme_path, load_theme_dir, web_greeter_config 66 | from bindings.screensaver import screensaver 67 | 68 | # pylint: disable-next=unused-import 69 | # Do not ever remove this import 70 | import resources 71 | 72 | # Typing Helpers 73 | BridgeObjects = Tuple["BridgeObject"] 74 | Url = TypeVar("Url", str, QUrl) 75 | 76 | os.environ["QT_DEVICE_PIXEL_RATIO"] = "0" 77 | os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" 78 | os.environ["QT_SCREEN_SCALE_FACTORS"] = "1" 79 | os.environ["QT_SCALE_FACTOR"] = "1" 80 | 81 | def get_default_cursor(): 82 | """Gets the default cursor theme""" 83 | default_theme = "/usr/share/icons/default/index.theme" 84 | cursor_theme = "" 85 | matched = None 86 | try: 87 | with open(default_theme, "r", encoding = "utf-8") as file: 88 | matched = re.search(r"Inherits=.*", file.read()) 89 | except IOError: 90 | return "" 91 | if not matched: 92 | logger.error("Default cursor couldn't be get") 93 | return "" 94 | cursor_theme = matched.group().replace("Inherits=", "") 95 | return cursor_theme 96 | 97 | class Application: 98 | """Main application""" 99 | app: QApplication 100 | windows: List[WindowAbstract] 101 | 102 | def __init__(self, application): 103 | self.app = application 104 | 105 | self.set_protocol() 106 | 107 | self.windows = self.create_windows() 108 | 109 | timeout = web_greeter_config["config"]["greeter"]["screensaver_timeout"] 110 | screensaver.set_screensaver(timeout or 300) 111 | 112 | cursor_theme = web_greeter_config["config"]["greeter"]["icon_theme"] 113 | if cursor_theme is not None: 114 | os.environ["XCURSOR_THEME"] = cursor_theme 115 | else: 116 | os.environ["XCURSOR_THEME"] = get_default_cursor() 117 | 118 | self.app.aboutToQuit.connect(self._before_exit) 119 | 120 | def create_windows(self): 121 | """Initialize application windows""" 122 | screens: List[QScreen] = self.app.screens() 123 | primary_screen: QScreen = self.app.primaryScreen() 124 | 125 | overall_boundary: OverallBoundary = OverallBoundary( 126 | minX = math.inf, 127 | maxX = -math.inf, 128 | minY = math.inf, 129 | maxY = -math.inf 130 | ) 131 | 132 | for screen in screens: 133 | overall_boundary.minX = min(overall_boundary.minX, 134 | screen.geometry().x()) 135 | overall_boundary.minY = min(overall_boundary.minY, 136 | screen.geometry().y()) 137 | overall_boundary.maxX = max( 138 | overall_boundary.maxX, 139 | screen.geometry().x() + screen.geometry().width() 140 | ) 141 | overall_boundary.maxY = max( 142 | overall_boundary.maxY, 143 | screen.geometry().y() + screen.geometry().height() 144 | ) 145 | 146 | windows: List[WindowAbstract] = [] 147 | # screens.append(primary_screen) 148 | for screen in screens: 149 | is_primary: bool = screen == primary_screen 150 | 151 | window = BrowserWindow( 152 | QRect( 153 | screen.geometry().x(), 154 | screen.geometry().y(), 155 | screen.geometry().width(), 156 | screen.geometry().height() 157 | ), 158 | web_greeter_config["config"]["greeter"]["debug_mode"] 159 | ) 160 | 161 | abstract = WindowAbstract( 162 | is_primary = is_primary, 163 | display = screen, 164 | window = window, 165 | meta = WindowMetadata( 166 | id = random.randrange(1, 20000), 167 | is_primary = is_primary, 168 | size = WindowSize( 169 | width = screen.geometry().width(), 170 | height = screen.geometry().height(), 171 | ), 172 | position = WindowPosition( 173 | x = screen.geometry().x(), 174 | y = screen.geometry().y(), 175 | ), 176 | overallBoundary = overall_boundary 177 | ) 178 | ) 179 | window.bridge_objects.append( 180 | GreeterComm(abstract) 181 | ) 182 | windows.append(abstract) 183 | window.closeEv.connect(self._remove_window) 184 | 185 | logger.debug("Browser Window created") 186 | 187 | return windows 188 | 189 | def _remove_window(self, window): 190 | wins = [] 191 | for win in self.windows: 192 | if win.window != window: 193 | wins.append(win) 194 | else: 195 | comm: GreeterComm = win.window.bridge_objects[-1] 196 | comm.destroy() 197 | self.windows = wins 198 | 199 | def set_protocol(self): 200 | """Set protocol""" 201 | url_scheme = "web-greeter" 202 | self.url_scheme = QWebEngineUrlScheme(url_scheme.encode()) 203 | self.url_scheme.setDefaultPort(QWebEngineUrlScheme.PortUnspecified) 204 | self.url_scheme.setFlags(QWebEngineUrlScheme.SecureScheme or 205 | QWebEngineUrlScheme.LocalScheme or 206 | QWebEngineUrlScheme.LocalAccessAllowed) 207 | QWebEngineUrlScheme.registerScheme(self.url_scheme) 208 | 209 | self.profile = QWebEngineProfile.defaultProfile() 210 | self.interceptor = QtUrlRequestInterceptor(url_scheme) 211 | self.url_scheme_handler = QtUrlSchemeHandler() 212 | 213 | self.profile.installUrlSchemeHandler(url_scheme.encode(), self.url_scheme_handler) 214 | 215 | if web_greeter_config["config"]["greeter"]["secure_mode"]: 216 | if hasattr(QWebEngineProfile, "setUrlRequestInterceptor"): 217 | self.profile.setUrlRequestInterceptor(self.interceptor) 218 | else: # Older Qt5 versions 219 | self.profile.setRequestInterceptor(self.interceptor) 220 | 221 | @classmethod 222 | def _before_exit(cls): 223 | """Runs before exit""" 224 | screensaver.reset_screensaver() 225 | 226 | def show(self): 227 | """Show window""" 228 | primary: WindowAbstract | None = None 229 | for win in self.windows: 230 | if win.is_primary: 231 | primary = win 232 | continue 233 | win.window.show() 234 | logger.debug("Web Greeter started win: %s", str(win.meta.id)) 235 | 236 | if primary and primary.is_primary: 237 | primary.window.show() 238 | primary.window.activateWindow() 239 | primary.window.raise_() 240 | logger.debug("Web Greeter started win: %s", str(primary.meta.id)) 241 | 242 | def run(self) -> int: 243 | """Runs the application""" 244 | logger.debug("Web Greeter started") 245 | return self.app.exec_() 246 | 247 | class Browser(Application): 248 | # pylint: disable=too-many-instance-attributes 249 | """The main browser""" 250 | 251 | def __init__(self, application): 252 | super().__init__(application) 253 | self.init() 254 | self.load_theme() 255 | 256 | def init(self): 257 | """Initialize browser""" 258 | logger.debug("Initializing Browser Window") 259 | 260 | if web_greeter_config["config"]["greeter"]["debug_mode"]: 261 | os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = '12345' 262 | 263 | def load_theme(self): 264 | """Load theme""" 265 | load_theme_dir() 266 | primary_html = load_primary_theme_path() 267 | secondary_html = load_secondary_theme_path() 268 | 269 | primary_url = QUrl(f"web-greeter://app/{primary_html}") 270 | secondary_url = QUrl(f"web-greeter://app/{secondary_html}") 271 | 272 | for win in self.windows: 273 | if win.is_primary: 274 | win.window.win_page.setUrl(primary_url) 275 | else: 276 | win.window.win_page.setUrl(secondary_url) 277 | 278 | logger.debug("Theme loaded") 279 | 280 | def primary_window(self): 281 | """Returns the primary window""" 282 | for win in self.windows: 283 | if win.is_primary: 284 | return win.window 285 | raise Exception("No primary window initialized") 286 | -------------------------------------------------------------------------------- /src/browser/browser_interfaces.py: -------------------------------------------------------------------------------- 1 | 2 | class WindowSize: 3 | width: float 4 | height: float 5 | 6 | def __init__(self, width, height): 7 | self.width = width 8 | self.height = height 9 | 10 | class WindowPosition: 11 | x: float 12 | y: float 13 | 14 | def __init__(self, x, y): 15 | self.x = x 16 | self.y = y 17 | 18 | class OverallBoundary: 19 | minX: float 20 | maxX: float 21 | minY: float 22 | maxY: float 23 | 24 | def __init__(self, minX, maxX, minY, maxY): 25 | self.minX = minX 26 | self.maxX = maxX 27 | self.minY = minY 28 | self.maxY = maxY 29 | 30 | class WindowMetadata: 31 | id: int 32 | is_primary: bool 33 | size: WindowSize 34 | position: WindowPosition 35 | overallBoundary: OverallBoundary 36 | 37 | def __init__(self, id, is_primary, size, position, overallBoundary): 38 | self.id = id 39 | self.is_primary = is_primary 40 | self.size = size 41 | self.position = position 42 | self.overallBoundary = overallBoundary 43 | -------------------------------------------------------------------------------- /src/browser/error_prompt.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # error_prompt.py 4 | # 5 | # Copyright © 2021 JezerM 6 | # 7 | # This file is part of Web Greeter. 8 | # 9 | # Web Greeter is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 3 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # Web Greeter is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # The following additional terms are in effect as per Section 7 of the license: 20 | # 21 | # The preservation of all legal notices and author attributions in 22 | # the material or in the Appropriate Legal Notices displayed 23 | # by works containing it is required. 24 | # 25 | # You should have received a copy of the GNU General Public License 26 | # along with Web Greeter; If not, see . 27 | 28 | # Standard lib 29 | 30 | # 3rd-Party Libs 31 | from typing import List 32 | from logging import ( 33 | getLogger, 34 | DEBUG, 35 | Formatter, 36 | StreamHandler, 37 | ) 38 | from PyQt5.QtWebEngineWidgets import QWebEnginePage 39 | from PyQt5.QtWidgets import ( 40 | QAbstractButton, 41 | QDialogButtonBox, 42 | QDialog, 43 | QVBoxLayout, 44 | QLabel, 45 | QPushButton 46 | ) 47 | from PyQt5.QtGui import QWindow 48 | from config import web_greeter_config 49 | 50 | import globales 51 | 52 | LOG_FORMAT = ''.join([ 53 | '%(asctime)s [ %(levelname)s ] %(filename)s %(', 54 | 'lineno)d: %(message)s' 55 | ]) 56 | formatter = Formatter(fmt=LOG_FORMAT, datefmt="%Y-%m-%d %H:%M:%S") 57 | logger = getLogger("javascript") 58 | logger.propagate = False 59 | stream_handler = StreamHandler() 60 | stream_handler.setLevel(DEBUG) 61 | stream_handler.setFormatter(formatter) 62 | logger.setLevel(DEBUG) 63 | logger.addHandler(stream_handler) 64 | 65 | class WebPage(QWebEnginePage): 66 | """web-greeter's webpage class""" 67 | 68 | def javaScriptConsoleMessage( 69 | self, level: QWebEnginePage.JavaScriptConsoleMessageLevel, 70 | message: str, line_number: int, source_id: str 71 | ): 72 | # pylint: disable = no-self-use,missing-function-docstring,invalid-name 73 | if source_id == "": 74 | source_id = "console" 75 | 76 | log_level = 0 77 | if level == WebPage.ErrorMessageLevel: 78 | log_level = 40 79 | elif level == WebPage.WarningMessageLevel: 80 | log_level = 30 81 | elif level == WebPage.InfoMessageLevel: 82 | return 83 | else: 84 | return 85 | 86 | record = logger.makeRecord( 87 | name="javascript", 88 | level=log_level, 89 | fn="", 90 | lno=line_number, 91 | msg=message, 92 | args=(), 93 | exc_info=None 94 | ) 95 | record.filename = source_id 96 | logger.handle(record) 97 | 98 | if log_level == 40: 99 | errorMessage = f"{source_id} {line_number}: {message}" 100 | error_prompt(errorMessage) 101 | 102 | def increaseZoom(self, value = 0.1): 103 | """Increase zoom""" 104 | # pylint: disable=invalid-name 105 | self.setZoomFactor( 106 | self.zoomFactor() + (value if value else 0.1) 107 | ) 108 | 109 | def decreaseZoom(self, value = 0.1): 110 | """Increase zoom""" 111 | # pylint: disable=invalid-name 112 | self.setZoomFactor( 113 | self.zoomFactor() - (value if value else 0.1) 114 | ) 115 | 116 | class Dialog(QDialog): 117 | """Popup dialog class""" 118 | 119 | def __init__( 120 | self, parent = None, title: str = "Dialog", 121 | message: str = "Message", detail: str = "", 122 | buttons: List[str] = None 123 | ): 124 | super().__init__(parent) 125 | self.setWindowTitle(title) 126 | 127 | self.button_box = QDialogButtonBox() 128 | if buttons is not None: 129 | for i, btn in enumerate(buttons, 0): 130 | button = QPushButton(btn) 131 | button.role = i 132 | self.button_box.addButton(button, QDialogButtonBox.NoRole) 133 | 134 | self.button_box.clicked.connect(self.handle_click) 135 | 136 | self.layout = QVBoxLayout() 137 | self.layout.addWidget(QLabel(message)) 138 | self.layout.addWidget(QLabel(detail)) 139 | self.layout.addWidget(self.button_box) 140 | 141 | self.setLayout(self.layout) 142 | 143 | def handle_click(self, button: QAbstractButton): 144 | """Handle click of button""" 145 | self.done(button.role) 146 | 147 | def general_error_prompt(window: QWindow, message: str, detail: str, title: str): 148 | """General error prompt""" 149 | dialog = Dialog(parent = window, 150 | title = title, 151 | message = message, 152 | detail = detail, 153 | buttons = ["Reload theme", "Use default theme", "Cancel"]) 154 | dialog.exec() 155 | result = dialog.result() 156 | 157 | if result == 2: # Cancel 158 | pass 159 | elif result == 1: # Default theme 160 | web_greeter_config["config"]["greeter"]["theme"] = "gruvbox" 161 | globales.greeter.load_theme() 162 | elif result == 0: # Reload 163 | globales.greeter.load_theme() 164 | 165 | 166 | def error_prompt(err: str): 167 | """Prompts a popup dialog on error""" 168 | if not web_greeter_config["config"]["greeter"]["detect_theme_errors"]: 169 | return 170 | 171 | general_error_prompt(globales.greeter.primary_window(), 172 | "An error ocurred. Do you want to change to default theme?", 173 | f"{err}", 174 | "An error ocurred") 175 | -------------------------------------------------------------------------------- /src/browser/interceptor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # interceptor.py 4 | # 5 | # Copyright © 2016-2017 Antergos 6 | # 7 | # This file is part of Web Greeter. 8 | # 9 | # Web Greeter is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 3 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # Web Greeter is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # The following additional terms are in effect as per Section 7 of the license: 20 | # 21 | # The preservation of all legal notices and author attributions in 22 | # the material or in the Appropriate Legal Notices displayed 23 | # by works containing it is required. 24 | # 25 | # You should have received a copy of the GNU General Public License 26 | # along with Web Greeter; If not, see . 27 | 28 | # 3rd-Party Libs 29 | from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor, QWebEngineUrlRequestInfo 30 | 31 | class QtUrlRequestInterceptor(QWebEngineUrlRequestInterceptor): 32 | """Url request interceptor for web-greeter's protocol""" 33 | 34 | def __init__(self, url_scheme: str): 35 | super().__init__() 36 | self._url_scheme = url_scheme 37 | 38 | def intercept_request(self, info: QWebEngineUrlRequestInfo) -> None: 39 | """Intercept request""" 40 | url = info.requestUrl().toString() 41 | not_webg_uri = self._url_scheme != info.requestUrl().scheme() 42 | not_data_uri = 'data' != info.requestUrl().scheme() 43 | not_local_file = not info.requestUrl().isLocalFile() 44 | 45 | # print(url) 46 | 47 | not_devtools = ( 48 | not url.startswith('http://127.0.0.1') and 49 | not url.startswith('ws://127.0.0.1') 50 | and not url.startswith('devtools') 51 | ) 52 | 53 | block_request = ( 54 | not_devtools and not_data_uri and 55 | not_webg_uri and not_local_file 56 | ) 57 | 58 | info.block(block_request) # Block everything that is not allowed 59 | 60 | def interceptRequest(self, info: QWebEngineUrlRequestInfo) -> None: 61 | # pylint: disable=invalid-name,missing-function-docstring 62 | self.intercept_request(info) 63 | -------------------------------------------------------------------------------- /src/browser/url_scheme.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # url_scheme.py 4 | # 5 | # Copyright © 2016-2018 Antergos 6 | # Copyright © 2021 JezerM 7 | # 8 | # This file is part of Web Greeter. 9 | # 10 | # Web Greeter is free software; you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation; either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # Web Greeter is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # The following additional terms are in effect as per Section 7 of the license: 21 | # 22 | # The preservation of all legal notices and author attributions in 23 | # the material or in the Appropriate Legal Notices displayed 24 | # by works containing it is required. 25 | # 26 | # You should have received a copy of the GNU General Public License 27 | # along with Web Greeter; If not, see . 28 | 29 | """ Custom Url Scheme Handler """ 30 | 31 | # Standard Lib 32 | import os 33 | import mimetypes 34 | 35 | # 3rd-Party Libs 36 | from PyQt5.QtCore import QBuffer, QIODevice 37 | from PyQt5.QtWebEngineCore import QWebEngineUrlSchemeHandler, QWebEngineUrlRequestJob 38 | 39 | 40 | class QtUrlSchemeHandler(QWebEngineUrlSchemeHandler): 41 | """URL Scheme Handler for web-greeter's protocol""" 42 | 43 | def requestStarted(self, job: QWebEngineUrlRequestJob) -> None: 44 | # pylint: disable=invalid-name,missing-function-docstring 45 | path = job.requestUrl().path() 46 | path = os.path.realpath(path) 47 | 48 | # print("PATH", job.requestUrl().path()) 49 | 50 | if not path: 51 | # print("JOB FAIL", path) 52 | job.fail(QWebEngineUrlRequestJob.UrlInvalid) 53 | return 54 | 55 | if not os.path.exists(path): 56 | # print("NOT FOUND", path) 57 | job.fail(QWebEngineUrlRequestJob.UrlNotFound) 58 | return 59 | 60 | try: 61 | with open(path, 'rb') as file: 62 | content_type = mimetypes.guess_type(path) 63 | if content_type[0] is None: 64 | content_type = ("text/plain", None) 65 | buffer = QBuffer(parent=self) 66 | 67 | buffer.open(QIODevice.WriteOnly) 68 | buffer.write(file.read()) 69 | buffer.seek(0) 70 | buffer.close() 71 | 72 | if content_type[0] is None: 73 | job.reply("text/plain", "") 74 | else: 75 | job.reply(content_type[0].encode(), buffer) 76 | 77 | except Exception as err: 78 | raise err 79 | -------------------------------------------------------------------------------- /src/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # config.py 4 | # 5 | # Copyright © 2021 JezerM 6 | # 7 | # This file is part of Web Greeter. 8 | # 9 | # Web Greeter is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 3 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # Web Greeter is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # The following additional terms are in effect as per Section 7 of the license: 20 | # 21 | # The preservation of all legal notices and author attributions in 22 | # the material or in the Appropriate Legal Notices displayed 23 | # by works containing it is required. 24 | # 25 | # You should have received a copy of the GNU General Public License 26 | # along with Web Greeter; If not, see . 27 | # Standard lib 28 | 29 | import os 30 | from ruamel import yaml 31 | 32 | from logger import logger 33 | 34 | PATH_TO_CONFIG = "/etc/lightdm/web-greeter.yml" 35 | 36 | yaml_loader = yaml.YAML(typ='safe', pure=True) 37 | 38 | web_greeter_config = { 39 | "config": { 40 | "branding": { 41 | "background_images_dir": "/usr/share/backgrounds", 42 | "logo_image": "", 43 | "user_image": "", 44 | }, 45 | "greeter": { 46 | "debug_mode": False, 47 | "detect_theme_errors": True, 48 | "screensaver_timeout": 300, 49 | "secure_mode": True, 50 | "theme": "gruvbox", 51 | "icon_theme": None, 52 | "time_language": None, 53 | }, 54 | "layouts": ["us", "latam"], 55 | "features": { 56 | "battery": False, 57 | "backlight": { 58 | "enabled": False, 59 | "value": 10, 60 | "steps": 0, 61 | } 62 | } 63 | }, 64 | "app": { 65 | "fullscreen": True, 66 | "frame": False, 67 | "debug_mode": False, 68 | "theme_dir": "/usr/share/web-greeter/themes/", 69 | "version": { 70 | "full": "3.5.3", 71 | "major": 3, 72 | "minor": 5, 73 | "micro": 3, 74 | }, 75 | "api_version": { 76 | "full": "1.0.0", 77 | "major:": 1, 78 | "minor": 0, 79 | "micro": 0, 80 | }, 81 | }, 82 | "theme": { 83 | "primary_html": "index.html", 84 | "secondary_html": "", 85 | } 86 | } 87 | 88 | path_to_config = os.getenv("WEB_GREETER_CONFIG") or "/etc/lightdm/web-greeter.yml" 89 | 90 | theme_dir = None 91 | 92 | def load_theme_dir() -> str: 93 | """Loads the theme directory""" 94 | theme: str = web_greeter_config["config"]["greeter"]["theme"] 95 | directory: str = web_greeter_config["app"]["theme_dir"] 96 | def_theme = "gruvbox" 97 | 98 | theme_dir = os.path.join(directory, theme) 99 | 100 | if theme.startswith("/"): 101 | theme_dir = theme 102 | elif theme.__contains__(".") or theme.__contains__("/"): 103 | theme_dir = os.path.join(os.getcwd(), theme) 104 | 105 | if theme_dir.endswith(".html"): 106 | theme_dir = os.path.dirname(theme_dir) 107 | 108 | if not os.path.exists(theme_dir): 109 | logger.warn("\"%s\" theme does not exists. Using \"%s\" theme", 110 | theme, def_theme) 111 | theme_dir = os.path.join(directory, def_theme) 112 | 113 | return theme_dir 114 | 115 | def load_primary_theme_path() -> str: 116 | """ 117 | Loads the primary theme path 118 | The provided theme with `--theme` flag is preferred over index.yml 119 | """ 120 | global theme_dir 121 | if not theme_dir: 122 | theme_dir = load_theme_dir() 123 | abs_theme: str = web_greeter_config["config"]["greeter"]["theme"] 124 | abs_theme_name = abs_theme.split("/").pop() 125 | directory: str = web_greeter_config["app"]["theme_dir"] 126 | def_theme = "gruvbox" 127 | 128 | if abs_theme_name.endswith(".html"): 129 | web_greeter_config["theme"]["primary_html"] = abs_theme_name 130 | 131 | primary = web_greeter_config["theme"]["primary_html"] 132 | path_to_theme = os.path.join(theme_dir, primary) 133 | 134 | if not path_to_theme.endswith(".html"): 135 | path_to_theme = os.path.join(path_to_theme, "index.html") 136 | 137 | if not os.path.exists(path_to_theme): 138 | logger.warn("\"%s\" theme does not exists. Using \"%s\" theme", 139 | path_to_theme, def_theme) 140 | path_to_theme = os.path.join(directory, def_theme, "index.html") 141 | 142 | web_greeter_config["config"]["greeter"]["theme"] = path_to_theme 143 | return path_to_theme 144 | 145 | def load_secondary_theme_path() -> str: 146 | """ 147 | Loads the secondary theme path 148 | This can only be set with index.yml, either it defaults to primary html 149 | """ 150 | global theme_dir 151 | if not theme_dir: 152 | theme_dir = load_theme_dir() 153 | primary = web_greeter_config["theme"]["primary_html"] 154 | secondary = web_greeter_config["theme"]["secondary_html"] 155 | path_to_theme = os.path.join(theme_dir, secondary or primary) 156 | 157 | if not path_to_theme.endswith(".html"): 158 | path_to_theme = os.path.join(path_to_theme, "index.html") 159 | 160 | if not os.path.exists(path_to_theme): 161 | logger.warn("\"%s\" does not exists. Using \"%s\" for secondary monitors", 162 | secondary, primary) 163 | path_to_theme = load_primary_theme_path() 164 | 165 | return path_to_theme 166 | 167 | def load_theme_config(): 168 | """Loads the theme config inside "index.yml" """ 169 | global theme_dir 170 | if not theme_dir: 171 | theme_dir = load_theme_dir() 172 | path_to_theme_config = os.path.join(theme_dir, "index.yml") 173 | 174 | try: 175 | if not os.path.exists(path_to_theme_config): 176 | raise Exception("index.yml file not found") 177 | with open(path_to_theme_config, "r", encoding="utf-8") as file: 178 | theme_config = yaml_loader.load(file) 179 | web_greeter_config["theme"] = theme_config 180 | 181 | except Exception as err: 182 | logger.warn("Theme config was not loaded:\n\t%s", err) 183 | logger.debug("Using default theme config") 184 | 185 | def ensure_theme(): 186 | """ 187 | Ensures that the theme does exists 188 | If it doesn't, default theme (gruvbox) is used 189 | """ 190 | global theme_dir 191 | if not theme_dir: 192 | theme_dir = load_theme_dir() 193 | primary = web_greeter_config["theme"]["primary_html"] 194 | directory = web_greeter_config["app"]["theme_dir"] 195 | def_theme = "gruvbox" 196 | 197 | primary_exists = os.path.exists(os.path.join(theme_dir, primary)) 198 | 199 | if not primary_exists: 200 | theme_dir = os.path.join(directory, def_theme) 201 | load_theme_config() 202 | 203 | def load_config(): 204 | """Load web-greeter's config""" 205 | try: 206 | if not os.path.exists(PATH_TO_CONFIG): 207 | raise Exception("Config file not found") 208 | with open(PATH_TO_CONFIG, "r", encoding="utf-8") as file: 209 | web_greeter_config["config"] = yaml_loader.load(file) 210 | except Exception as err: 211 | logger.error("Config was not loaded:\n\t%s", err) 212 | 213 | load_config() 214 | -------------------------------------------------------------------------------- /src/globales.py: -------------------------------------------------------------------------------- 1 | """Global variables""" 2 | 3 | from bridge import Config, Greeter, ThemeUtils 4 | from browser.browser import Browser 5 | 6 | greeter: Browser # pylint: disable=invalid-name 7 | 8 | LDMGreeter: Greeter 9 | LDMGreeterConfig: Config 10 | LDMThemeUtils: ThemeUtils 11 | -------------------------------------------------------------------------------- /src/logger.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # logger.py 4 | # 5 | # Copyright © 2017 Antergos 6 | # Copyright © 2021 JezerM 7 | # 8 | # This file is part of Web Greeter. 9 | # 10 | # Web Greeter is free software; you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation; either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # Web Greeter is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # The following additional terms are in effect as per Section 7 of the license: 21 | # 22 | # The preservation of all legal notices and author attributions in 23 | # the material or in the Appropriate Legal Notices displayed 24 | # by works containing it is required. 25 | # 26 | # You should have received a copy of the GNU General Public License 27 | # along with Web Greeter; If not, see . 28 | 29 | from logging import ( 30 | getLogger, 31 | DEBUG, 32 | Formatter, 33 | StreamHandler 34 | ) 35 | 36 | LOG_FORMAT = ''.join([ 37 | '%(asctime)s [ %(levelname)s ] %(module)s - %(filename)s:%(', 38 | 'lineno)d : %(funcName)s | %(message)s' 39 | ]) 40 | formatter = Formatter(fmt=LOG_FORMAT, datefmt="%Y-%m-%d %H:%M:%S") 41 | stream_handler = StreamHandler() 42 | 43 | logger = getLogger("debug") 44 | 45 | stream_handler.setLevel(DEBUG) 46 | stream_handler.setFormatter(formatter) 47 | logger.propagate = False 48 | logger.setLevel(DEBUG) 49 | logger.addHandler(stream_handler) 50 | -------------------------------------------------------------------------------- /src/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JezerM/web-greeter/0bfa7f0036b2336c4d9aa9ad35e0777ab0b41857/src/requirements.txt -------------------------------------------------------------------------------- /src/resources/css/style.css: -------------------------------------------------------------------------------- 1 | QMainWindow { 2 | background: #000000; 3 | } 4 | 5 | /*QWidget {*/ 6 | /*background: #282828;*/ 7 | /*color: #ebdbb2;*/ 8 | /*}*/ 9 | 10 | /*window,*/ 11 | /*GtkWindow {*/ 12 | /*background: #000000;*/ 13 | /*}*/ 14 | 15 | /*dialog .dialog-vbox > label,*/ 16 | /*GtkDialog .dialog-vbox > GtkLabel {*/ 17 | /*margin: 20px 15px;*/ 18 | /*}*/ 19 | -------------------------------------------------------------------------------- /src/resources/js/GreeterComm.js: -------------------------------------------------------------------------------- 1 | /* 2 | * GreeterComm.js 3 | * 4 | * Copyright © 2022 JezerM 5 | * 6 | * This file is part of Web Greeter. 7 | * 8 | * Web Greeter is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * Web Greeter is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * The following additional terms are in effect as per Section 7 of the license: 19 | * 20 | * The preservation of all legal notices and author attributions in 21 | * the material or in the Appropriate Legal Notices displayed 22 | * by works containing it is required. 23 | * 24 | * You should have received a copy of the GNU General Public License 25 | * along with web-greeter; If not, see . 26 | */ 27 | 28 | function inf_to_infinity(num) { 29 | if (num === "infinity") return Infinity; 30 | else if (num === "-infinity") return -Infinity; 31 | else return num; 32 | } 33 | 34 | class GreeterComm { 35 | static _comm = null; // Bridge object 36 | static _instance = null; // GreeterComm object 37 | 38 | _windowMetadata = null; 39 | 40 | constructor(comm) { 41 | if (GreeterComm._instance !== null) { 42 | return GreeterComm._instance; 43 | } 44 | 45 | GreeterComm._comm = comm; 46 | GreeterComm._comm.broadcast_signal.connect(this._on_broadcast); 47 | 48 | // Obtain metadata and fire the whenReady promise 49 | GreeterComm._comm.metadata_signal.connect((metadata) => { 50 | let meta = metadata; 51 | meta.overallBoundary.minX = inf_to_infinity(meta.overallBoundary.minX); 52 | meta.overallBoundary.minY = inf_to_infinity(meta.overallBoundary.minY); 53 | meta.overallBoundary.maxX = inf_to_infinity(meta.overallBoundary.maxX); 54 | meta.overallBoundary.maxY = inf_to_infinity(meta.overallBoundary.maxY); 55 | this._windowMetadata = meta; 56 | if (this._ready) this._ready(); 57 | }); 58 | 59 | this._readyPromise = new Promise((resolve) => { 60 | this._ready = resolve; 61 | }); 62 | 63 | // Send initial request for metadata 64 | GreeterComm._comm.requestMetadata(); 65 | 66 | GreeterComm._instance = this; 67 | } 68 | 69 | get window_metadata() { 70 | if (this._windowMetadata) { 71 | return this._windowMetadata; 72 | } 73 | throw new Error(`window_metadata not available, did you wait for the GreeterReady event?`); 74 | } 75 | 76 | whenReady() { 77 | return this._readyPromise; 78 | } 79 | 80 | broadcast(data) { 81 | GreeterComm._comm.broadcast(data); 82 | } 83 | 84 | _on_broadcast(window_meta, data) { 85 | const event = new Event("GreeterBroadcastEvent"); 86 | event.window = window_meta; 87 | event.data = data; 88 | window.dispatchEvent(event); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/resources/js/ThemeUtils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ThemeUtils.js 3 | * 4 | * Copyright © 2017 Antergos Developers 5 | * 6 | * This file is part of Web Greeter. 7 | * 8 | * Web Greeter is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * Web Greeter is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * The following additional terms are in effect as per Section 7 of the license: 19 | * 20 | * The preservation of all legal notices and author attributions in 21 | * the material or in the Appropriate Legal Notices displayed 22 | * by works containing it is required. 23 | * 24 | * You should have received a copy of the GNU General Public License 25 | * along with web-greeter; If not, see . 26 | */ 27 | 28 | let time_language = null, 29 | _ThemeUtils = null; 30 | 31 | /** 32 | * Provides various utility methods for use in greeter themes. The greeter will automatically 33 | * create an instance of this class when it starts. The instance can be accessed 34 | * with the global variable: [`theme_utils`](#dl-window-theme_utils). 35 | * 36 | * @memberOf LightDM 37 | */ 38 | class ThemeUtils { 39 | constructor(instance) { 40 | if (null !== _ThemeUtils) { 41 | return _ThemeUtils; 42 | } 43 | 44 | _ThemeUtils = instance; 45 | } 46 | 47 | /** 48 | * Binds `this` to class, `context`, for all of the class's methods. 49 | * 50 | * @arg {object} context An ES6 class instance with at least one method. 51 | * 52 | * @return {object} `context` with `this` bound to it for all of its methods. 53 | */ 54 | bind_this(context) { 55 | let excluded_methods = ["constructor"]; 56 | 57 | function not_excluded(_method, _context) { 58 | let is_excluded = 59 | excluded_methods.findIndex((excluded_method) => _method === excluded_method) > 60 | -1, 61 | is_method = "function" === typeof _context[_method]; 62 | 63 | return is_method && !is_excluded; 64 | } 65 | 66 | for (let obj = context; obj; obj = Object.getPrototypeOf(obj)) { 67 | // Stop once we have traveled all the way up the inheritance chain 68 | if ("Object" === obj.constructor.name) { 69 | break; 70 | } 71 | 72 | for (let method of Object.getOwnPropertyNames(obj)) { 73 | if (not_excluded(method, context)) { 74 | context[method] = context[method].bind(context); 75 | } 76 | } 77 | } 78 | 79 | return context; 80 | } 81 | 82 | /** 83 | * Returns the contents of directory found at `path` provided that the (normalized) `path` 84 | * meets at least one of the following conditions: 85 | * * Is located within the greeter themes' root directory. 86 | * * Has been explicitly allowed in the greeter's config file. 87 | * * Is located within the greeter's shared data directory (`/var/lib/lightdm-data`). 88 | * * Is located in `/tmp`. 89 | * 90 | * @param {string} path The abs path to desired directory. 91 | * @param {boolean} only_images Include only images in the results. Default `true`. 92 | * @param {function(string[])} callback Callback function to be called with the result. 93 | */ 94 | dirlist(path, only_images = true, callback) { 95 | if ("" === path || "string" !== typeof path) { 96 | console.error(`theme_utils.dirlist(): path must be a non-empty string!`); 97 | return callback([]); 98 | } else if (null !== path.match(/^[^/].+/)) { 99 | return _ThemeUtils.dirlist(path, only_images, callback); 100 | } 101 | 102 | if (null !== path.match(/\/\.+(?=\/)/)) { 103 | // No special directory names allowed (eg ../../) 104 | path = path.replace(/\/\.+(?=\/)/g, ""); 105 | } 106 | 107 | try { 108 | return _ThemeUtils.dirlist(path, only_images, callback); 109 | } catch (err) { 110 | console.error(`theme_utils.dirlist(): ${err}`); 111 | return callback([]); 112 | } 113 | } 114 | 115 | /** 116 | * Get the current date in a localized format. Local language is autodetected by default, but can be set manually in the greeter config file. 117 | * * `language` defaults to the system's language, but can be set manually in the config file. 118 | * 119 | * @returns {Object} The current date. 120 | */ 121 | get_current_localized_date() { 122 | let config = greeter_config.greeter; 123 | 124 | var locale = []; 125 | 126 | if (time_language === null) { 127 | time_language = config.time_language || ""; 128 | } 129 | 130 | if (time_language != "") { 131 | locale.push(time_language); 132 | } 133 | 134 | let optionsDate = { day: "2-digit", month: "2-digit", year: "2-digit" }; 135 | 136 | let fmtDate = Intl.DateTimeFormat(locale, optionsDate); 137 | 138 | let now = new Date(); 139 | var date = fmtDate.format(now); 140 | 141 | return date; 142 | } 143 | 144 | /** 145 | * Get the current time in a localized format. Local language is autodetected by default, but can be set manually in the greeter config file. 146 | * * `language` defaults to the system's language, but can be set manually in the config file. 147 | * 148 | * @returns {Object} The current time. 149 | */ 150 | get_current_localized_time() { 151 | let config = greeter_config.greeter; 152 | 153 | var locale = []; 154 | 155 | if (time_language === null) { 156 | time_language = config.time_language || ""; 157 | } 158 | 159 | if (time_language != "") { 160 | locale.push(time_language); 161 | } 162 | 163 | let optionsTime = { hour: "2-digit", minute: "2-digit" }; 164 | 165 | let fmtTime = Intl.DateTimeFormat(locale, optionsTime); 166 | 167 | let now = new Date(); 168 | var time = fmtTime.format(now); 169 | 170 | return time; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/resources/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | /* 2 | * bootstrap.js 3 | * 4 | * Copyright © 2017 Antergos Developers 5 | * 6 | * This file is part of Web Greeter. 7 | * 8 | * Web Greeter is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * Web Greeter is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * The following additional terms are in effect as per Section 7 of the license: 19 | * 20 | * The preservation of all legal notices and author attributions in 21 | * the material or in the Appropriate Legal Notices displayed 22 | * by works containing it is required. 23 | * 24 | * You should have received a copy of the GNU General Public License 25 | * along with web-greeter; If not, see . 26 | */ 27 | 28 | (() => { 29 | let _channel; 30 | 31 | /** 32 | * Greeter Ready Event. Themes should not initialize until this event has fired. 33 | * @event window#GreeterReady 34 | * @name GreeterReady 35 | * @type Event 36 | * @memberOf window 37 | */ 38 | window._ready_event = new Event("GreeterReady"); 39 | 40 | let dispatched = false; 41 | 42 | function dispatch_channel_ready_cb() { 43 | if (window.lightdm === undefined) { 44 | console.debug( 45 | "LIGHTDM:", 46 | "Document loaded before channel is done. Won't dispatch GreeterReady" 47 | ); 48 | return; 49 | } 50 | if (dispatched) { 51 | console.debug("LIGHTDM:", "GreeterReady already dispatched"); 52 | return; 53 | } 54 | dispatched = true; 55 | setTimeout(function () { 56 | console.debug("LIGHTDM:", "Event dispatch"); 57 | window.dispatchEvent(_ready_event); 58 | }, 2); 59 | } 60 | 61 | function channel_ready_cb(channel) { 62 | _channel = channel; 63 | 64 | /** 65 | * Greeter Instance 66 | * @name lightdm 67 | * @type {LightDM.Greeter} 68 | * @memberOf window 69 | */ 70 | window.lightdm = _channel.objects.LightDMGreeter; 71 | 72 | /** 73 | * Greeter Config - Access values from the greeter's config file. 74 | * @name greeter_config 75 | * @type {LightDM.GreeterConfig} 76 | * @memberOf window 77 | */ 78 | window.greeter_config = _channel.objects.Config; 79 | 80 | /** 81 | * Theme Utils - various utility methods for use in greeter themes. 82 | * @name theme_utils 83 | * @type {LightDM.ThemeUtils} 84 | * @memberOf window 85 | */ 86 | window.theme_utils = new ThemeUtils(_channel.objects.ThemeUtils); 87 | 88 | window.greeter_comm = new GreeterComm(_channel.objects.Comm); 89 | 90 | console.debug("LIGHTDM:", "Channel is done"); 91 | console.debug("LIGHTDM:", "Document state", document.readyState); 92 | 93 | if (document.readyState === "loading") { 94 | document.addEventListener("DOMContentLoaded", dispatch_channel_ready_cb); 95 | } else if (document.readyState === "interactive") { 96 | window.addEventListener("load", dispatch_channel_ready_cb); 97 | } else { 98 | dispatch_channel_ready_cb(); 99 | } 100 | } 101 | new QWebChannel(qt.webChannelTransport, channel_ready_cb); 102 | 103 | window.addEventListener("DOMContentLoaded", dispatch_channel_ready_cb); 104 | })(); 105 | -------------------------------------------------------------------------------- /src/resources/js/docs/Greeter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * LightDMGreeter.js 3 | * 4 | * Copyright © 2017 Antergos Developers 5 | * 6 | * This file is part of Web Greeter. 7 | * 8 | * Web Greeter is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * Web Greeter is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * The following additional terms are in effect as per Section 7 of the license: 19 | * 20 | * The preservation of all legal notices and author attributions in 21 | * the material or in the Appropriate Legal Notices displayed 22 | * by works containing it is required. 23 | * 24 | * You should have received a copy of the GNU General Public License 25 | * along with web-greeter; If not, see . 26 | */ 27 | 28 | 29 | /** 30 | * Base class for the greeter's Theme JavaScript API. Greeter themes will interact 31 | * directly with an object derived from this class to facilitate the user log-in process. 32 | * The greeter will automatically create an instance when it starts. 33 | * The instance can be accessed using the global variable: [`lightdm`](#dl-window-lightdm). 34 | * 35 | * @memberOf LightDM 36 | */ 37 | class Greeter { 38 | 39 | constructor() { 40 | if ( 'lightdm' in window ) { 41 | return window.lightdm; 42 | } 43 | 44 | window.lightdm = GreeterUtils.bind_this( this ); 45 | 46 | return window.lightdm; 47 | } 48 | 49 | /** 50 | * The username of the user being authenticated or {@link null} 51 | * if there is no authentication in progress. 52 | * @type {string|null} 53 | * @readonly 54 | */ 55 | get authentication_user() {} 56 | 57 | /** 58 | * Whether or not the guest account should be automatically logged 59 | * into when the timer expires. 60 | * @type {boolean} 61 | * @readonly 62 | */ 63 | get autologin_guest() {} 64 | 65 | /** 66 | * The number of seconds to wait before automatically logging in. 67 | * @type {number} 68 | * @readonly 69 | */ 70 | get autologin_timeout() {} 71 | 72 | /** 73 | * The username with which to automatically log in when the timer expires. 74 | * @type {string} 75 | * @readonly 76 | */ 77 | get autologin_user() {} 78 | 79 | /** 80 | * Whether or not the greeter can make the system hibernate. 81 | * @type {boolean} 82 | * @readonly 83 | */ 84 | get can_hibernate() {} 85 | 86 | /** 87 | * Whether or not the greeter can make the system restart. 88 | * @type {boolean} 89 | * @readonly 90 | */ 91 | get can_restart() {} 92 | 93 | /** 94 | * Whether or not the greeter can make the system shutdown. 95 | * @type {boolean} 96 | * @readonly 97 | */ 98 | get can_shutdown() {} 99 | 100 | /** 101 | * Whether or not the greeter can make the system suspend/sleep. 102 | * @type {boolean} 103 | * @readonly 104 | */ 105 | get can_suspend() {} 106 | 107 | /** 108 | * The name of the default session. 109 | * @type {string} 110 | * @readonly 111 | */ 112 | get default_session() {} 113 | 114 | /** 115 | * Whether or not guest sessions are supported. 116 | * @type {boolean} 117 | * @readonly 118 | */ 119 | get has_guest_account() {} 120 | 121 | /** 122 | * Whether or not user accounts should be hidden. 123 | * @type {boolean} 124 | * @readonly 125 | */ 126 | get hide_users_hint() {} 127 | 128 | /** 129 | * The system's hostname. 130 | * @type {string} 131 | * @readonly 132 | */ 133 | get hostname() {} 134 | 135 | /** 136 | * Whether or not the greeter is in the process of authenticating. 137 | * @type {boolean} 138 | * @readonly 139 | */ 140 | get in_authentication() {} 141 | 142 | /** 143 | * Whether or not the greeter has successfully authenticated. 144 | * @type {boolean} 145 | * @readonly 146 | */ 147 | get is_authenticated() {} 148 | 149 | /** 150 | * The current language or {@link null} if no language. 151 | * @type {LightDM.Language|null} 152 | * @readonly 153 | */ 154 | get language() {} 155 | 156 | /** 157 | * A list of languages to present to the user. 158 | * @type {LightDM.Language[]} 159 | * @readonly 160 | */ 161 | get languages() {} 162 | 163 | /** 164 | * The currently active layout for the selected user. 165 | * @type {LightDM.Layout} 166 | */ 167 | get layout() {} 168 | 169 | /** 170 | * Set the active layout for the selected user. 171 | * @param {LightDM.Layout} value 172 | */ 173 | set layout( value ) {} 174 | 175 | /** 176 | * A list of keyboard layouts to present to the user. 177 | * @type {LightDM.Layout[]} 178 | * @readonly 179 | */ 180 | get layouts() {} 181 | 182 | /** 183 | * Whether or not the greeter was started as a lock screen. 184 | * @type {boolean} 185 | * @readonly 186 | */ 187 | get lock_hint() {} 188 | 189 | /** 190 | * The available remote sessions. 191 | * @type {LightDM.Session[]} 192 | * @readonly 193 | */ 194 | get remote_sessions() {} 195 | 196 | /** 197 | * Whether or not the guest account should be selected by default. 198 | * @type {boolean} 199 | * @readonly 200 | */ 201 | get select_guest_hint() {} 202 | 203 | /** 204 | * The username to select by default. 205 | * @type {string} 206 | * @readonly 207 | */ 208 | get select_user_hint() {} 209 | 210 | /** 211 | * List of available sessions. 212 | * @type {LightDM.Session[]} 213 | * @readonly 214 | */ 215 | get sessions() {} 216 | 217 | /** 218 | * Check if a manual login option should be shown. If {@link true}, the theme should 219 | * provide a way for a username to be entered manually. Otherwise, themes that show 220 | * a user list may limit logins to only those users. 221 | * @type {string} 222 | * @readonly 223 | */ 224 | get show_manual_login_hint() {} 225 | 226 | /** 227 | * Check if a remote login option should be shown. If {@link true}, the theme should provide 228 | * a way for a user to log into a remote desktop server. 229 | * @type {string} 230 | * @readonly 231 | * @internal 232 | */ 233 | get show_remote_login_hint() {} 234 | 235 | /** 236 | * List of available users. 237 | * @type {LightDM.User[]} 238 | * @readonly 239 | */ 240 | get users() {} 241 | 242 | 243 | /** 244 | * Starts the authentication procedure for a user. 245 | * 246 | * @arg {String|null} username A username or {@link null} to prompt for a username. 247 | */ 248 | authenticate( username = null ) {} 249 | 250 | /** 251 | * Starts the authentication procedure for the guest user. 252 | */ 253 | authenticate_as_guest() {} 254 | 255 | /** 256 | * Cancel the user authentication that is currently in progress. 257 | */ 258 | cancel_authentication() {} 259 | 260 | /** 261 | * Cancel the automatic login. 262 | */ 263 | cancel_autologin() {} 264 | 265 | /** 266 | * Triggers the system to hibernate. 267 | * @returns {boolean} {@link true} if hibernation initiated, otherwise {@link false} 268 | */ 269 | hibernate() {} 270 | 271 | /** 272 | * Provide a response to a prompt. 273 | * @arg {string} response 274 | */ 275 | respond( response ) {} 276 | 277 | /** 278 | * Triggers the system to restart. 279 | * @returns {boolean} {@link true} if restart initiated, otherwise {@link false} 280 | */ 281 | restart() {} 282 | 283 | /** 284 | * Set the language for the currently authenticated user. 285 | * @arg {string} language The language in the form of a locale specification (e.g. 'de_DE.UTF-8') 286 | * @returns {boolean} {@link true} if successful, otherwise {@link false} 287 | */ 288 | set_language( language ) {} 289 | 290 | /** 291 | * Triggers the system to shutdown. 292 | * @returns {boolean} {@link true} if shutdown initiated, otherwise {@link false} 293 | */ 294 | shutdown() {} 295 | 296 | /** 297 | * Start a session for the authenticated user. 298 | * @arg {String|null} session The session to log into or {@link null} to use the default. 299 | * @returns {boolean} {@link true} if successful, otherwise {@link false} 300 | */ 301 | start_session( session ) {} 302 | 303 | /** 304 | * Triggers the system to suspend/sleep. 305 | * @returns {boolean} {@link true} if suspend/sleep initiated, otherwise {@link false} 306 | */ 307 | suspend() {} 308 | 309 | /** 310 | * Gets the brightness 311 | * @type {Number} 312 | * @readonly 313 | */ 314 | get brightness() {} 315 | 316 | /** 317 | * Set the brightness to quantity 318 | * @arg {Number} quantity The quantity to set 319 | */ 320 | brightnessSet( quantity ) {} 321 | 322 | /** 323 | * Increase the brightness by quantity 324 | * @arg {Number} quantity The quantity to increase 325 | */ 326 | brightnessIncrease( quantity ) {} 327 | 328 | /** 329 | * Decrease the brightness by quantity 330 | * @arg {Number} quantity The quantity to decrease 331 | */ 332 | brightnessDecrease( quantity ) {} 333 | 334 | /** 335 | * Gets the battery data 336 | * @type {Object} 337 | * @readonly 338 | */ 339 | get batteryData() {} 340 | 341 | /** 342 | * Whether or not the greeter can access to battery data 343 | * @type {boolean} 344 | * @readonly 345 | */ 346 | get can_access_battery() {} 347 | 348 | /** 349 | * Updates the battery data 350 | */ 351 | batteryUpdate() {} 352 | 353 | /** 354 | * Whether or not the greeter can control display brightness 355 | * @type {boolean} 356 | * @readonly 357 | */ 358 | get can_access_brightness() {} 359 | 360 | } 361 | 362 | 363 | /** 364 | * Moment.js instance - Loaded and instantiated automatically by the greeter. 365 | * @name moment 366 | * @type {object} 367 | * @version 2.17.0 368 | * @memberOf window 369 | * @see [Moment.js Documentation](http://momentjs.com/docs) 370 | */ 371 | 372 | /** 373 | * jQuery instance - Themes must manually load the included vendor script in order to use this object. 374 | * @name jQuery 375 | * @type {object} 376 | * @version 3.1.1 377 | * @memberOf window 378 | * @see [jQuery Documentation](http://api.jquery.com) 379 | */ 380 | 381 | /** 382 | * jQuery instance 383 | * @name $ 384 | * @memberOf window 385 | * @see {@link window.jQuery} 386 | */ 387 | 388 | /** 389 | * JS-Cookie instance - Themes must manually load the included vendor script in order to use this object. 390 | * @name Cookies 391 | * @type {object} 392 | * @version 2.1.3 393 | * @memberOf window 394 | * @see [JS Cookie Documentation](https://github.com/js-cookie/js-cookie/tree/latest#readme) 395 | */ 396 | 397 | 398 | 399 | -------------------------------------------------------------------------------- /src/resources/js/docs/GreeterConfig.js: -------------------------------------------------------------------------------- 1 | /* 2 | * GreeterConfig.js 3 | * 4 | * Copyright © 2017 Antergos Developers 5 | * 6 | * This file is part of Web Greeter. 7 | * 8 | * Web Greeter is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * Web Greeter is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * The following additional terms are in effect as per Section 7 of the license: 19 | * 20 | * The preservation of all legal notices and author attributions in 21 | * the material or in the Appropriate Legal Notices displayed 22 | * by works containing it is required. 23 | * 24 | * You should have received a copy of the GNU General Public License 25 | * along with web-greeter; If not, see . 26 | */ 27 | 28 | 29 | /** 30 | * Provides greeter themes with a way to access values from the greeter's config 31 | * file located at `/etc/lightdm/web-greeter.yml`. The greeter will 32 | * create an instance of this class when it starts. The instance can be accessed 33 | * with the global variable: [`greeter_config`](#dl-window-greeter_config). 34 | * 35 | * @memberOf LightDM 36 | */ 37 | class GreeterConfig { 38 | /** 39 | * Holds keys/values from the `branding` section of the config file. 40 | * 41 | * @type {object} branding 42 | * @prop {string} background_images_dir Path to directory that contains background images 43 | * for use in greeter themes. 44 | * @prop {string} logo Path to distro logo image for use in greeter themes. 45 | * @prop {string} user_image Default user image/avatar. This is used by greeter themes 46 | * for users that have not configured a `.face` image. 47 | * @readonly 48 | */ 49 | get branding() {} 50 | 51 | /** 52 | * Holds keys/values from the `greeter` section of the config file. 53 | * 54 | * @type {object} greeter 55 | * @prop {boolean} debug_mode Greeter theme debug mode. 56 | * @prop {boolean} detect_theme_errors Provide an option to load a fallback theme when theme 57 | * errors are detected. 58 | * @prop {number} screensaver_timeout Blank the screen after this many seconds of inactivity. 59 | * @prop {boolean} secure_mode Don't allow themes to make remote http requests. 60 | * @prop {string} time_format A moment.js format string to be used by the greeter to 61 | * generate localized time for display. 62 | * @prop {string} time_language Language to use when displaying the time or `auto` 63 | * to use the system's language. 64 | * @prop {string} theme The name of the theme to be used by the greeter. 65 | * @readonly 66 | */ 67 | get greeter() {} 68 | } 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/resources/js/docs/LightDMObjects.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2015-2017 Antergos 3 | * 4 | * LightDMObjects.js 5 | * 6 | * This file is part of Web Greeter 7 | * 8 | * Web Greeter is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, 11 | * or any later version. 12 | * 13 | * Web Greeter is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * The following additional terms are in effect as per Section 7 of the license: 19 | * 20 | * The preservation of all legal notices and author attributions in 21 | * the material or in the Appropriate Legal Notices displayed 22 | * by works containing it is required. 23 | * 24 | * You should have received a copy of the GNU General Public License 25 | * along with this program. If not, see . 26 | */ 27 | 28 | /** 29 | * The global window object. 30 | * 31 | * @name window 32 | * @type {object} 33 | * @global 34 | */ 35 | 36 | /** 37 | * The greeter's Theme JavaScript API. 38 | * 39 | * @namespace LightDM 40 | */ 41 | 42 | 43 | /** 44 | * Interface for object that holds info about a session. Session objects are not 45 | * created by the theme's code, but rather by the [`LightDM.Greeter`](#dl-LightDM-Greeter) class. 46 | * 47 | * @memberOf LightDM 48 | */ 49 | class Session { 50 | constructor( { comment, key, name } ) { 51 | this._comment = comment; 52 | this._key = key; 53 | this._name = name; 54 | } 55 | 56 | /** 57 | * The name for the session. 58 | * @type {string} 59 | * @readonly 60 | */ 61 | get name() { 62 | return this._name; 63 | } 64 | 65 | /** 66 | * The key for the session. 67 | * @type {string} 68 | * @readonly 69 | */ 70 | get key() { 71 | return this._key; 72 | } 73 | 74 | /** 75 | * The comment for the session. 76 | * @type {string} 77 | * @readonly 78 | */ 79 | get comment() { 80 | return this._comment; 81 | } 82 | } 83 | 84 | 85 | /** 86 | * Interface for object that holds info about a language on the system. Language objects are not 87 | * created by the theme's code, but rather by the [`LightDM.Greeter`](#dl-LightDM-Greeter) class. 88 | * 89 | * @memberOf LightDM 90 | */ 91 | class Language { 92 | constructor( { code, name, territory } ) { 93 | this._code = code; 94 | this._name = name; 95 | this._territory = territory; 96 | } 97 | 98 | /** 99 | * The code for the language. 100 | * @type {string} 101 | * @readonly 102 | */ 103 | get code() { 104 | return this._code; 105 | } 106 | 107 | /** 108 | * The name for the layout. 109 | * @type {string} 110 | * @readonly 111 | */ 112 | get name() { 113 | return this._name; 114 | } 115 | 116 | /** 117 | * The territory for the language. 118 | * @type {string} 119 | * @readonly 120 | */ 121 | get territory() { 122 | return this._territory; 123 | } 124 | } 125 | 126 | 127 | /** 128 | * Interface for object that holds info about a keyboard layout on the system. Language 129 | * objects are not created by the theme's code, but rather by the [`LightDM.Greeter`](#dl-LightDM-Greeter) class. 130 | * 131 | * @memberOf LightDM 132 | */ 133 | class Layout { 134 | constructor( { description, name, short_description } ) { 135 | this._description = description; 136 | this._name = name; 137 | this._short_description = short_description; 138 | } 139 | 140 | /** 141 | * The description for the layout. 142 | * @type {string} 143 | * @readonly 144 | */ 145 | get description() { 146 | return this._description; 147 | } 148 | 149 | /** 150 | * The name for the layout. 151 | * @type {string} 152 | * @readonly 153 | */ 154 | get name() { 155 | return this._name; 156 | } 157 | 158 | /** 159 | * The territory for the layout. 160 | * @type {string} 161 | * @readonly 162 | */ 163 | get short_description() { 164 | return this._short_description; 165 | } 166 | } 167 | 168 | 169 | /** 170 | * Interface for object that holds info about a user account on the system. User 171 | * objects are not created by the theme's code, but rather by the [`LightDM.Greeter`](#dl-LightDM-Greeter) class. 172 | * 173 | * @memberOf LightDM 174 | */ 175 | class User { 176 | constructor( user_info ) { 177 | Object.keys(user_info).forEach( key => { 178 | this[`_${key}`] = user_info[key]; 179 | } ); 180 | } 181 | 182 | /** 183 | * The display name for the user. 184 | * @type {string} 185 | * @readonly 186 | */ 187 | get display_name() { 188 | return this._display_name; 189 | } 190 | 191 | /** 192 | * The language for the user. 193 | * @type {string} 194 | * @readonly 195 | */ 196 | get language() { 197 | return this._language; 198 | } 199 | 200 | /** 201 | * The keyboard layout for the user. 202 | * @type {string} 203 | * @readonly 204 | */ 205 | get layout() { 206 | return this._layout; 207 | } 208 | 209 | /** 210 | * The image for the user. 211 | * @type {string} 212 | * @readonly 213 | */ 214 | get image() { 215 | return this._image; 216 | } 217 | 218 | /** 219 | * The home_directory for the user. 220 | * @type {string} 221 | * @readonly 222 | */ 223 | get home_directory() { 224 | return this._home_directory; 225 | } 226 | 227 | /** 228 | * The username for the user. 229 | * @type {string} 230 | * @readonly 231 | */ 232 | get username() { 233 | return this._username; 234 | } 235 | 236 | /** 237 | * Whether or not the user is currently logged in. 238 | * @type {boolean} 239 | * @readonly 240 | */ 241 | get logged_in() { 242 | return this._logged_in; 243 | } 244 | 245 | /** 246 | * The last session that the user logged into. 247 | * @type {string|null} 248 | * @readonly 249 | */ 250 | get session() { 251 | return this._session; 252 | } 253 | } 254 | 255 | -------------------------------------------------------------------------------- /src/resources/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | css/style.css 5 | js/bundle.js 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JezerM/web-greeter/0bfa7f0036b2336c4d9aa9ad35e0777ab0b41857/src/utils/__init__.py -------------------------------------------------------------------------------- /src/utils/acpi.py: -------------------------------------------------------------------------------- 1 | 2 | import subprocess 3 | from threading import Thread 4 | from typing import List, Callable, Any 5 | from shutil import which 6 | from logger import logger 7 | 8 | Callback = Callable[[str], Any] 9 | 10 | class ACPIController: 11 | """ACPI controller""" 12 | 13 | tries = 0 14 | callbacks: List[Callback] = [] 15 | 16 | def __init__(self): 17 | if self.check_acpi(): 18 | self.listen() 19 | else: 20 | logger.error("ACPI: acpi_listen does not exists") 21 | 22 | @staticmethod 23 | def check_acpi() -> bool: 24 | """Checks if acpi_listen does exists""" 25 | if which("acpi_listen"): 26 | return True 27 | return False 28 | 29 | def connect(self, callback: Callback): 30 | """Connect callback to ACPI controller""" 31 | self.callbacks.append(callback) 32 | 33 | def disconnect(self, callback: Callback): 34 | """Disconnect callback from ACPI controller""" 35 | self.callbacks.remove(callback) 36 | 37 | def _listen(self): 38 | try: 39 | with subprocess.Popen("acpi_listen", 40 | stdout = subprocess.PIPE, 41 | text = True) as process: 42 | if not process.stdout: 43 | raise IOError("No stdout") 44 | while True: 45 | line = process.stdout.readline().strip() 46 | if not line: 47 | continue 48 | for _, callback in enumerate(self.callbacks): 49 | callback(line) 50 | except IOError as err: 51 | logger.error("ACPI: %s", err) 52 | if self.tries < 5: 53 | self.tries += 1 54 | logger.debug("Restarting acpi_listen") 55 | self._listen() 56 | 57 | def listen(self): 58 | """Listens to acpi_listen""" 59 | self.thread = Thread(target = self._listen) 60 | self.thread.daemon = True 61 | self.thread.start() 62 | 63 | ACPI = ACPIController() 64 | -------------------------------------------------------------------------------- /src/utils/battery.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import math 4 | import time 5 | from typing import Union 6 | import globales 7 | from utils.acpi import ACPI 8 | 9 | class Battery: 10 | # pylint: disable=too-many-instance-attributes 11 | """Battery controller""" 12 | 13 | batteries = [] 14 | ac_path = "AC0" 15 | pspath = "/sys/class/power_supply/" 16 | perc = -1 17 | status = "N/A" 18 | capacity = 0 19 | time = "" 20 | watt = 0 21 | running_update = False 22 | 23 | def __init__(self): 24 | if len(self.batteries) == 0: 25 | scandir_line(self.pspath, self._update_batteries) 26 | ACPI.connect(self.acpi_listen) 27 | self.full_update() 28 | 29 | def acpi_listen(self, data: str): 30 | """Listens""" 31 | if re.match(r"battery|ac_adapter", data): 32 | self.full_update() 33 | 34 | def _update_batteries(self, line): 35 | bstr = re.match(r"BAT\w+", line) 36 | if bstr: 37 | self.batteries.append(dict( 38 | name = bstr.group(), 39 | status = "N/A", 40 | perc = 0, 41 | capacity = 0, 42 | )) 43 | else: 44 | match = re.match(r"A\w+", line) 45 | self.ac_path = match.group() if match else self.ac_path 46 | 47 | # Based on "bat" widget from "lain" awesome-wm library 48 | # * (c) 2013, Luca CPZ 49 | # * (c) 2010-2012, Peter Hofmann 50 | # @see https://github.com/lcpz/lain/blob/master/widget/bat.lua 51 | def full_update(self): 52 | # pylint: disable=too-many-locals,too-many-statements,too-many-branches 53 | """Do a full update""" 54 | if self.running_update: 55 | return 56 | self.running_update = True 57 | 58 | sum_rate_current = 0 59 | sum_rate_voltage = 0 60 | sum_rate_power = 0 61 | sum_rate_energy = 0 62 | sum_energy_now = 0 63 | sum_energy_full = 0 64 | sum_charge_full = 0 65 | sum_charge_design = 0 66 | 67 | for i, battery in enumerate(self.batteries): 68 | bstr = self.pspath + battery["name"] 69 | present = read_first_line(bstr + "/present") 70 | 71 | if tonumber(present) == 1: 72 | rate_current = tonumber(read_first_line(bstr + "/current_now")) or 0 73 | rate_voltage = tonumber(read_first_line(bstr + "/voltage_now")) or 0 74 | rate_power = tonumber(read_first_line((bstr + "/power_now"))) or 0 75 | charge_full = tonumber(read_first_line(bstr + "/charge_full")) or 0 76 | charge_design = tonumber(read_first_line(bstr + "/charge_full_design")) or 0 77 | 78 | energy_now = tonumber(read_first_line(bstr + "/energy_now") 79 | or read_first_line(bstr + "/charge_now")) or 0 80 | energy_full = tonumber(read_first_line(bstr + "/energy_full") or charge_full) or 0 81 | energy_percentage = tonumber(read_first_line(bstr + "/capacity") 82 | or math.floor(energy_now / energy_full * 100)) or 0 83 | 84 | self.batteries[i]["status"] = read_first_line(bstr + "/status") or "N/A" 85 | self.batteries[i]["perc"] = energy_percentage or self.batteries[i].perc 86 | 87 | if not charge_design or charge_design == 0: 88 | self.batteries[i]["capacity"] = 0 89 | else: 90 | self.batteries[i]["capacity"] = math.floor( 91 | charge_full / charge_design * 100) 92 | 93 | sum_rate_current = sum_rate_current + rate_current 94 | sum_rate_voltage = sum_rate_voltage + rate_voltage 95 | sum_rate_power = sum_rate_power + rate_power 96 | sum_rate_energy = sum_rate_energy + ( 97 | rate_power or (rate_voltage * rate_current / 1e6) 98 | ) 99 | sum_energy_now = sum_energy_now + energy_now 100 | sum_energy_full = sum_energy_full + energy_full 101 | sum_charge_full = sum_charge_full + charge_full 102 | sum_charge_design = sum_charge_design + charge_design 103 | 104 | self.capacity = math.floor(min(100, sum_charge_full / (sum_charge_design or 1) * 100)) 105 | self.status = self.batteries[0]["status"] if len(self.batteries) > 0 else "N/A" 106 | 107 | for i, battery in enumerate(self.batteries): 108 | if battery["status"] == "Discharging" or battery["status"] == "Charging": 109 | self.status = battery["status"] 110 | 111 | self.ac_status = tonumber(read_first_line(self.pspath + self.ac_path + "/online")) or 0 112 | 113 | if self.status != "N/A": 114 | if self.status != "Full" and sum_rate_power == 0 and self.ac_status == 1: 115 | self.perc = math.floor(min(100, 116 | sum_energy_now / sum_energy_full * 100 + 0.5)) 117 | self.time = "00:00" 118 | self.watt = 0 119 | elif self.status != "Full": 120 | rate_time = 0 121 | if (sum_rate_power > 0 or sum_rate_current > 0): 122 | div = sum_rate_power > 0 or sum_rate_current 123 | 124 | if self.status == "Charging": 125 | rate_time = (sum_energy_full - sum_energy_now) / div 126 | else: 127 | rate_time = sum_energy_now / div 128 | 129 | if rate_time and rate_time < 0.01: 130 | rate_time_magnitude = tonumber(abs(math.floor(math.log10(rate_time)))) or 0 131 | rate_time = int(rate_time * 10) ^ (rate_time_magnitude - 2) 132 | 133 | hours = math.floor(rate_time) 134 | minutes = math.floor((rate_time - hours) * 60) 135 | self.perc = math.floor( 136 | min(100, (sum_energy_now / sum_energy_full) * 100) + 0.5 137 | ) 138 | self.time = f"{hours:02d}:{minutes:02d}" 139 | self.watt = f"{sum_rate_energy/1e6:.2f}" 140 | elif self.status == "Full": 141 | self.perc = 100 142 | self.time = "00:00" 143 | self.watt = 0 144 | 145 | self.perc = self.perc if self.perc is not None else 0 146 | 147 | if hasattr(globales, "greeter") and hasattr(globales, "LDMGreeter"): 148 | globales.LDMGreeter.battery_update.emit() 149 | 150 | time.sleep(0.1) 151 | 152 | self.running_update = False 153 | 154 | def get_name(self): 155 | """Get name""" 156 | return self.batteries[0]["name"] 157 | 158 | def get_level(self): 159 | """Get level""" 160 | return self.perc 161 | 162 | def get_status(self): 163 | """Get status""" 164 | return self.status 165 | 166 | def get_ac_status(self): 167 | """Get AC status""" 168 | return self.ac_status 169 | 170 | def get_capacity(self): 171 | """Get capacity""" 172 | return self.capacity 173 | 174 | def get_time(self): 175 | """Get time""" 176 | return self.time 177 | 178 | def get_watt(self): 179 | """Get watt""" 180 | return self.watt 181 | 182 | def scandir_line(path, callback): 183 | """List directory""" 184 | lines = os.listdir(path) 185 | for _, line in enumerate(lines): 186 | callback(line) 187 | 188 | def read_first_line(path) -> Union[str, None]: 189 | """Just read the first line of file""" 190 | try: 191 | first = None 192 | with open(path, "r", encoding = "utf-8") as file: 193 | first = file.readline() 194 | first = first.replace("\n", "") 195 | return first 196 | except IOError: 197 | return None 198 | 199 | def tonumber(string) -> Union[int, None]: 200 | """Converts string to int or None""" 201 | try: 202 | return int(string) 203 | except (ValueError, TypeError): 204 | return None 205 | -------------------------------------------------------------------------------- /src/utils/brightness.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # brightness.py 4 | # 5 | # Copyright © 2021 JezerM 6 | # 7 | # This file is part of Web Greeter. 8 | # 9 | # Web Greeter is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 3 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # Web Greeter is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # The following additional terms are in effect as per Section 7 of the license: 20 | # 21 | # The preservation of all legal notices and author attributions in 22 | # the material or in the Appropriate Legal Notices displayed 23 | # by works containing it is required. 24 | # 25 | # You should have received a copy of the GNU General Public License 26 | # along with Web Greeter; If not, see . 27 | 28 | import os 29 | import stat 30 | import time 31 | from typing import List 32 | from threading import Thread 33 | import pyinotify 34 | from logger import logger 35 | from config import web_greeter_config 36 | import globales 37 | 38 | sys_path = ["/sys/class/backlight/"] 39 | 40 | def get_controllers() -> List[str]: 41 | """Get brightness controllers path""" 42 | ctrls: List[str] = [] 43 | for dev in sys_path: 44 | if os.path.exists(dev) and stat.S_ISDIR(os.stat(dev).st_mode): 45 | drs = os.listdir(dev) 46 | for name in drs: 47 | ctrls.append(os.path.join(dev, name)) 48 | return ctrls 49 | 50 | class EventHandler(pyinotify.ProcessEvent): 51 | """PyInotify handler""" 52 | @classmethod 53 | def process_IN_MODIFY(cls, _): 54 | # pylint: disable=invalid-name,missing-function-docstring 55 | if hasattr(globales, "greeter") and hasattr(globales, "LDMGreeter"): 56 | globales.LDMGreeter.brightness_update.emit() 57 | 58 | 59 | # Behavior based on "acpilight" 60 | # Copyright(c) 2016-2019 by wave++ "Yuri D'Elia" 61 | # See https://gitlab.com/wavexx/acpilight 62 | class BrightnessController: 63 | # pylint: disable=too-many-instance-attributes 64 | """Brightness controller for web-greeter""" 65 | 66 | _controllers: List[str] = [] 67 | _available: bool = False 68 | _brightness_path: str 69 | _max_brightness_path: str 70 | steps: int 71 | delay: int 72 | _brightness: int 73 | _max_brightness: int = -1 74 | 75 | def __init__(self): 76 | self._controllers = get_controllers() 77 | if (len(self._controllers) == 0 or 78 | self._controllers[0] is None or 79 | not web_greeter_config["config"]["features"]["backlight"]["enabled"]): 80 | self._available = False 81 | return 82 | b_path = self._controllers[0] 83 | self._available = True 84 | self._brightness_path = os.path.join(b_path, "brightness") 85 | self._max_brightness_path = os.path.join(b_path, "max_brightness") 86 | 87 | with open(self._max_brightness_path, "r", encoding = "utf-8") as file: 88 | self._max_brightness = int(file.read()) 89 | 90 | steps = web_greeter_config["config"]["features"]["backlight"]["steps"] 91 | self.steps = 1 if steps <= 1 else steps 92 | self.delay = 200 93 | self.watch_brightness() 94 | 95 | def _watch(self): 96 | watch_manager = pyinotify.WatchManager() 97 | handler = EventHandler() 98 | # pylint: disable-next=no-member 99 | watch_manager.add_watch(self._brightness_path, pyinotify.IN_MODIFY) 100 | 101 | notifier = pyinotify.Notifier(watch_manager, handler) 102 | 103 | notifier.loop() 104 | 105 | def watch_brightness(self): 106 | """Starts a thread to watch brightness""" 107 | if not self._available: 108 | return 109 | thread = Thread(target = self._watch) 110 | thread.daemon = True 111 | thread.start() 112 | 113 | @property 114 | def max_brightness(self) -> int: 115 | """Max brightness""" 116 | return self._max_brightness 117 | 118 | @property 119 | def real_brightness(self) -> int: 120 | """Real brightness""" 121 | if not self._available: 122 | return -1 123 | try: 124 | with open(self._brightness_path, "r", encoding = "utf-8") as file: 125 | return int(file.read()) 126 | except OSError: 127 | logger.error("Couldn't read from \"%s\"", self._brightness_path) 128 | return -1 129 | 130 | @real_brightness.setter 131 | def real_brightness(self, value: int): 132 | if not self._available: 133 | return 134 | if value > self.max_brightness: 135 | value = self.max_brightness 136 | elif value <= 0: 137 | value = 0 138 | 139 | if not os.path.exists(self._brightness_path): 140 | return 141 | 142 | try: 143 | with open(self._brightness_path, "w", encoding = "utf-8") as file: 144 | file.write(str(round(value))) 145 | except OSError: 146 | logger.error("Couldn't write to \"%s\"", self._brightness_path) 147 | 148 | @property 149 | def brightness(self) -> int: 150 | """Brightness""" 151 | if not self._available: 152 | return -1 153 | return round(self.real_brightness * 100 / self.max_brightness) 154 | 155 | @brightness.setter 156 | def brightness(self, value: int): 157 | self.real_brightness = round(value * self.max_brightness / 100) 158 | 159 | def _set_brightness(self, value: int): 160 | if not self._available: 161 | return 162 | steps = self.steps or 1 163 | sleep = self.delay / steps 164 | current = self.brightness 165 | 166 | if steps <= 1: 167 | self.brightness = value 168 | return 169 | 170 | for i in range(steps + 1): 171 | time.sleep(sleep / 1000) 172 | brigh = current + ((value - current) * i) / steps 173 | self.brightness = round(brigh) 174 | 175 | def set_brightness(self, value: int): 176 | """Set brightness""" 177 | thread = Thread(target = self._set_brightness, args = (value,)) 178 | thread.start() 179 | 180 | def inc_brightness(self, value: int): 181 | """Increase brightness""" 182 | self.set_brightness(self.brightness + value) 183 | 184 | def dec_brightness(self, value: int): 185 | """Decrease brightness""" 186 | self.set_brightness(self.brightness - value) 187 | -------------------------------------------------------------------------------- /web-greeter.doap: -------------------------------------------------------------------------------- 1 | 6 | 7 | web-greeter 8 | Web Greeter for LightDM 9 | Python 10 | JavaScript 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Jezer Mejía 20 | 21 | 22 | 23 | 24 | --------------------------------------------------------------------------------