├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yaml │ └── feature_request.md └── workflows │ └── build.yml ├── .gitignore ├── AUTHORS ├── COPYING ├── COPYING.LIB ├── HACKING ├── NEWS ├── README.md ├── backup-locker ├── cinnamon-unlock-desktop ├── cs-backup-locker.c └── meson.build ├── cinnamon-screensaver.pot ├── data ├── cinnamon-screensaver ├── cinnamon-screensaver.pam.debian ├── icons │ ├── hicolor │ │ └── scalable │ │ │ ├── actions │ │ │ ├── screensaver-switch-users-symbolic.svg │ │ │ └── screensaver-unlock-symbolic.svg │ │ │ ├── apps │ │ │ └── csr-backup-locker-icon.svg │ │ │ └── status │ │ │ ├── cinnamon-screensaver-view-conceal.svg │ │ │ ├── cinnamon-screensaver-view-reveal.svg │ │ │ ├── screensaver-blank.svg │ │ │ └── screensaver-notification-symbolic.svg │ └── meson.build ├── meson.build ├── org.cinnamon.ScreenSaver.desktop └── org.cinnamon.ScreenSaver.service.in ├── debian ├── changelog ├── cinnamon-screensaver.install ├── cinnamon-screensaver.lintian-overrides ├── control ├── copyright ├── not-installed ├── rules └── source │ └── format ├── doc └── dbus-interface.html ├── install-scripts ├── meson.build └── meson_compile_python.py ├── libcscreensaver ├── cs-auth-pam.c ├── cs-auth.h ├── cs-event-grabber.c ├── cs-event-grabber.h ├── cs-gdk-event-filter-x11.c ├── cs-gdk-event-filter.h ├── cs-notification-watcher.c ├── cs-notification-watcher.h ├── cs-screen-x11.c ├── cs-screen.h ├── meson.build ├── org.Cinnamon.xml ├── org.cinnamon.Muffin.DisplayConfig.xml ├── org.cinnamon.ScreenSaver.xml ├── org.cinnamon.SettingsDaemon.KeybindingHandler.xml ├── org.freedesktop.Accounts.User.xml ├── org.freedesktop.Accounts.xml ├── org.freedesktop.ConsoleKit.Manager.xml ├── org.freedesktop.ConsoleKit.Session.xml ├── org.freedesktop.UPower.Device.xml ├── org.freedesktop.UPower.xml ├── org.freedesktop.login1.Manager.xml ├── org.freedesktop.login1.Session.xml ├── org.gnome.SessionManager.Presence.xml ├── org.mpris.MediaPlayer2.Player.xml ├── setuid.c ├── setuid.h ├── subprocs.c ├── subprocs.h └── test-passwd.c ├── makepot ├── meson.build ├── meson_options.txt └── src ├── __init__.py ├── albumArt.py ├── audioPanel.py ├── baseWindow.py ├── binfile.in ├── cinnamon-screensaver-command.py ├── cinnamon-screensaver-main.py ├── cinnamon-screensaver.css ├── clock.py ├── config.py.in ├── constants.py ├── dbusdepot ├── __init__.py ├── accountsServiceClient.py ├── baseClient.py ├── cinnamonClient.py ├── consoleKitClient.py ├── keybindingHandlerClient.py ├── loginInterface.py ├── logindClient.py ├── mediaPlayerWatcher.py ├── meson.build ├── muffinClient.py ├── nameBlocker.py ├── sessionClient.py └── uPowerClient.py ├── floating.py ├── infoPanel.py ├── manager.py ├── meson.build ├── monitorView.py ├── osk.py ├── pamhelper ├── authClient.py ├── cinnamon-screensaver-pam-helper.c └── meson.build ├── passwordEntry.py ├── playerControl.py ├── service.py ├── singletons.py ├── stage.py ├── status.py ├── tests ├── test-auth ├── test-layouts ├── test-notifications └── test-osk ├── unlock.py ├── util ├── __init__.py ├── eventHandler.py ├── fader.py ├── focusNavigator.py ├── keybindings.py ├── meson.build ├── settings.py ├── trackers.py └── utils.py ├── volumeControl.py └── widgets ├── __init__.py ├── framedImage.py ├── marqueeLabel.py ├── meson.build ├── notificationWidget.py ├── powerWidget.py ├── transparentButton.py └── volumeSlider.py /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | ### STOP! Before continuing: 8 | - Please try searching for any existing report that may match the behavior you're seeing. *If you find one, add to that issue, instead of opening a new one*. Include any relevant details that may differ from the original author's. 9 | - Check your logs - there may be some obvious cause for your trouble - in Linux Mint you can check ~/.xsession-errors (a hidden file in your Home folder). 10 | - Run cinnamon-screensaver from a terminal to see if any errors or warnings are printed while reproducing the issue: `cinnamon-screensaver --debug --hold` 11 | - If this is a *crash*, provide information about it if possible (CoreDump file, stack trace, etc...). In Linux Mint you can check the System Reports program. Entries may show as 'backup-locker' as well. 12 | --- 13 | 14 | Thank you for taking the time to report this issue. To allow us to work as efficiently as possible at resolving this, we need some information from you. 15 | 16 | - type: input 17 | id: distro 18 | attributes: 19 | label: Distribution 20 | description: Which Linux distribution are you using? Please be as specific as possible. 21 | placeholder: "example: Mint 21.1" 22 | validations: 23 | required: true 24 | 25 | - type: input 26 | id: pkgver 27 | attributes: 28 | label: Package version 29 | description: Please provide the cinnamon-screensaver version. You can get this by running `cinnamon-screensaver --version` in a terminal. 30 | placeholder: "example: 5.8.0" 31 | validations: 32 | required: true 33 | 34 | - type: input 35 | id: graphics 36 | attributes: 37 | label: Graphics hardware in use 38 | description: Please provide information about your graphics hardware, if known. If you are using a dual-gpu system please specify that also. 39 | placeholder: "example: NVIDIA GeForce GTX 1660 TI" 40 | validations: 41 | required: false 42 | 43 | - type: dropdown 44 | id: frequency 45 | attributes: 46 | label: Frequency 47 | description: How often does this behavior occur? 48 | options: 49 | - Always 50 | - Quite often 51 | - Only occasionally 52 | validations: 53 | required: true 54 | 55 | - type: textarea 56 | id: current-behavior 57 | attributes: 58 | label: Bug description 59 | description: Please describe what is happening 60 | validations: 61 | required: true 62 | 63 | - type: textarea 64 | id: steps 65 | attributes: 66 | label: Steps to reproduce 67 | description: Please try to provide **detailed** steps on the most direct way to reproduce this issue. The chances of a bug being fixed go up **considerably** if we are able to duplicate the behavior ourselves. 68 | validations: 69 | required: true 70 | 71 | - type: textarea 72 | id: expected-behavior 73 | attributes: 74 | label: Expected behavior 75 | description: Describe what you think should happen instead of the current behavior. 76 | validations: 77 | required: true 78 | 79 | - type: textarea 80 | id: more-info 81 | attributes: 82 | label: Additional information 83 | description: You can add any other information you think may be relevant. 84 | validations: 85 | required: false 86 | 87 | - type: markdown 88 | attributes: 89 | value: | 90 | #### By submitting this report you agree to behave respectfully and in a mature manner. If in doubt, refer to the [Golden Rule](https://en.wikipedia.org/wiki/Golden_Rule) and [Github's Community Guidelines](https://docs.github.com/en/site-policy/github-terms/github-community-guidelines). 91 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request (OBSOLETE) 3 | about: Feature requests are no longer accepted here, please use https://github.com/orgs/linuxmint/discussions instead. 4 | title: "" 5 | labels: ["FEATURE REQUEST"] 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please use: 11 | 12 | https://github.com/orgs/linuxmint/discussions -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | workflow_dispatch: 11 | inputs: 12 | debug_enabled: 13 | type: boolean 14 | description: 'Start an SSH server on failure.' 15 | required: false 16 | default: false 17 | 18 | jobs: 19 | build: 20 | uses: linuxmint/github-actions/.github/workflows/do-builds.yml@master 21 | with: 22 | commit_id: master 23 | ############################## Comma separated list - like 'linuxmint/xapp, linuxmint/cinnamon-desktop' 24 | dependencies: linuxmint/xapp, linuxmint/cinnamon-desktop 25 | ############################## 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | obj-* 2 | debian/tmp 3 | debian/libcscreensaver0 4 | debian/libcscreensaver-dbg 5 | debian/cinnamon-screensaver 6 | *.debhelper* 7 | *.substvars 8 | debian/debhelper-build-stamp 9 | debian/files 10 | 11 | 12 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Michael Webster 2 | -------------------------------------------------------------------------------- /HACKING: -------------------------------------------------------------------------------- 1 | HACKING 2 | ======= 3 | 4 | Cinnamon-Screensaver is normally build using dpkg. If using Linux Mint, Ubuntu, 5 | or any other member of the Debian family, install the `devscripts` package with 6 | `sudo apt-get install devscripts`. 7 | 8 | Other dependencies that may be needed to get it to build: 9 | `sudo apt install dh-make dh-python gobject-introspection meson build-essential libgtk-3-dev libxdo-dev libgirepository1.0-dev` 10 | 11 | 12 | 13 | Once you have devscripts, run `debuild -us -uc` to build cinnamon-screensaver 14 | (or any other cinnamon package) and `sudo dpkg -i ../cinnamon-screensaver_{version}_{arch}.deb` 15 | to install. 16 | -------------------------------------------------------------------------------- /backup-locker/cinnamon-unlock-desktop: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | killall cs-backup-locker 3 | killall cinnamon-screensaver -------------------------------------------------------------------------------- /backup-locker/meson.build: -------------------------------------------------------------------------------- 1 | backup_locker_data = configuration_data() 2 | backup_locker_data.set_quoted('GETTEXT_PACKAGE', 'cinnamon-screensaver') 3 | backup_locker_data.set_quoted('LOCALEDIR', join_paths(get_option('prefix'), get_option('localedir'))) 4 | backup_locker_data.set_quoted('VERSION', meson.project_version()) 5 | 6 | bl_config = configure_file(output : 'config.h', 7 | configuration : backup_locker_data 8 | ) 9 | 10 | bl_sources = [ 11 | 'cs-backup-locker.c', 12 | ] 13 | 14 | backup_locker = executable('cs-backup-locker', 15 | bl_sources, 16 | include_directories: inc, 17 | dependencies: [x11, gtk, glib], 18 | link_with: libcscreensaver, 19 | install_rpath: pkglibdir, 20 | install_dir: pkglibdir, 21 | install: true 22 | ) 23 | 24 | install_data('cinnamon-unlock-desktop', 25 | install_dir: bindir, 26 | install_mode: 'rwxr-xr-x' 27 | ) 28 | -------------------------------------------------------------------------------- /cinnamon-screensaver.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2022-12-02 21:29+0000\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=CHARSET\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" 20 | 21 | #: src/cinnamon-screensaver-command.py:41 22 | msgid "Causes the screensaver to exit gracefully" 23 | msgstr "" 24 | 25 | #: src/cinnamon-screensaver-command.py:43 26 | msgid "Query the state of the screensaver" 27 | msgstr "" 28 | 29 | #: src/cinnamon-screensaver-command.py:45 30 | msgid "Query the length of time the screensaver has been active" 31 | msgstr "" 32 | 33 | #: src/cinnamon-screensaver-command.py:47 34 | msgid "Tells the running screensaver process to lock the screen immediately" 35 | msgstr "" 36 | 37 | #: src/cinnamon-screensaver-command.py:49 38 | msgid "Turn the screensaver on (blank the screen)" 39 | msgstr "" 40 | 41 | #: src/cinnamon-screensaver-command.py:51 42 | msgid "If the screensaver is active then deactivate it (un-blank the screen)" 43 | msgstr "" 44 | 45 | #: src/cinnamon-screensaver-command.py:53 46 | msgid "Version of this application" 47 | msgstr "" 48 | 49 | #: src/cinnamon-screensaver-command.py:55 50 | msgid "Message to be displayed in lock screen" 51 | msgstr "" 52 | 53 | #: src/cinnamon-screensaver-command.py:105 54 | msgid "The screensaver is active\n" 55 | msgstr "" 56 | 57 | #: src/cinnamon-screensaver-command.py:107 58 | msgid "The screensaver is inactive\n" 59 | msgstr "" 60 | 61 | #: src/cinnamon-screensaver-command.py:111 62 | msgid "The screensaver is not currently active.\n" 63 | msgstr "" 64 | 65 | #: src/cinnamon-screensaver-command.py:113 66 | #, python-format 67 | msgid "The screensaver has been active for %d second.\n" 68 | msgid_plural "The screensaver has been active for %d seconds.\n" 69 | msgstr[0] "" 70 | msgstr[1] "" 71 | 72 | #: src/manager.py:315 73 | msgid "Cinnamon Screensaver has experienced an error" 74 | msgstr "" 75 | 76 | #: src/manager.py:317 77 | msgid "" 78 | "The 'cs-backup-locker' process terminated before the screensaver did. Please " 79 | "report this issue and try to describe any actions you may have performed " 80 | "prior to this occurring." 81 | msgstr "" 82 | 83 | #: src/passwordEntry.py:23 src/unlock.py:216 84 | msgid "Please enter your password..." 85 | msgstr "" 86 | 87 | #: src/unlock.py:96 88 | msgid "Unlock" 89 | msgstr "" 90 | 91 | #: src/unlock.py:107 92 | msgid "Switch User" 93 | msgstr "" 94 | 95 | #: src/unlock.py:189 96 | msgid "Incorrect password" 97 | msgstr "" 98 | 99 | #: src/unlock.py:206 100 | msgid "Checking..." 101 | msgstr "" 102 | 103 | #: src/unlock.py:250 104 | msgid "You have the Caps Lock key on." 105 | msgstr "" 106 | 107 | #. This is the first line of text for the backup-locker, explaining how to switch to tty 108 | #. and run 'cinnamon-unlock-desktop' command. This appears if the screensaver crashes. 109 | #: backup-locker/cs-backup-locker.c:255 110 | msgid "Something went wrong with the screensaver." 111 | msgstr "" 112 | 113 | #. (continued) This is a subtitle 114 | #: backup-locker/cs-backup-locker.c:265 115 | msgid "We'll help you get your desktop back" 116 | msgstr "" 117 | 118 | #. (new section) Bulleted list of steps to take to unlock the desktop; 119 | #: backup-locker/cs-backup-locker.c:276 120 | #, c-format 121 | msgid "Switch to a console using ." 122 | msgstr "" 123 | 124 | #. (list continued) 125 | #: backup-locker/cs-backup-locker.c:278 126 | msgid "Log in by typing your user name followed by your password." 127 | msgstr "" 128 | 129 | #. (list continued) 130 | #: backup-locker/cs-backup-locker.c:280 131 | msgid "At the prompt, type 'cinnamon-unlock-desktop' and press Enter." 132 | msgstr "" 133 | 134 | #. (list continued) 135 | #: backup-locker/cs-backup-locker.c:282 136 | #, c-format 137 | msgid "Switch back to your unlocked desktop using ." 138 | msgstr "" 139 | 140 | #. (end section) Final words after the list of steps 141 | #: backup-locker/cs-backup-locker.c:287 142 | msgid "If you can reproduce this behavior, please file a report here:" 143 | msgstr "" 144 | -------------------------------------------------------------------------------- /data/cinnamon-screensaver: -------------------------------------------------------------------------------- 1 | #%PAM-1.0 2 | 3 | # Fedora & Arch 4 | -auth sufficient pam_selinux_permit.so 5 | auth include system-auth 6 | -auth optional pam_gnome_keyring.so 7 | account include system-auth 8 | password include system-auth 9 | session include system-auth 10 | 11 | # SuSE/Novell 12 | #auth include common-auth 13 | #auth optional pam_gnome_keyring.so 14 | #account include common-account 15 | #password include common-password 16 | #session include common-session 17 | -------------------------------------------------------------------------------- /data/cinnamon-screensaver.pam.debian: -------------------------------------------------------------------------------- 1 | @include common-auth 2 | auth optional pam_gnome_keyring.so 3 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/screensaver-switch-users-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | Gnome Symbolic Icon Theme 49 | 50 | 51 | 52 | Gnome Symbolic Icon Theme 54 | 57 | 61 | 66 | 70 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/screensaver-unlock-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | Gnome Symbolic Icon Theme 49 | 50 | 51 | 52 | Gnome Symbolic Icon Theme 54 | 58 | 63 | 64 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/apps/csr-backup-locker-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 30 | 50 | 53 | 54 | 59 | 60 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/status/cinnamon-screensaver-view-conceal.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 35 | 40 | 41 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/status/cinnamon-screensaver-view-reveal.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 35 | 40 | 41 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/status/screensaver-blank.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 41 | 43 | 44 | 46 | image/svg+xml 47 | 49 | 50 | 51 | 52 | 53 | 58 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/status/screensaver-notification-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | Gnome Symbolic Icon Theme 49 | 50 | 51 | 52 | Gnome Symbolic Icon Theme 54 | 58 | 59 | -------------------------------------------------------------------------------- /data/icons/meson.build: -------------------------------------------------------------------------------- 1 | install_subdir('hicolor', install_dir: join_paths(datadir, 'icons')) 2 | -------------------------------------------------------------------------------- /data/meson.build: -------------------------------------------------------------------------------- 1 | subdir('icons') 2 | 3 | pamdir = get_option('pam-prefix') 4 | if pamdir == '' 5 | pamdir = sysconfdir 6 | endif 7 | 8 | dbus_service = configure_file( 9 | output: 'org.cinnamon.ScreenSaver.service', 10 | input: 'org.cinnamon.ScreenSaver.service.in', 11 | configuration: misc_conf 12 | ) 13 | 14 | if get_option('use-debian-pam') 15 | install_data( 16 | 'cinnamon-screensaver.pam.debian', 17 | rename: 'cinnamon-screensaver', 18 | install_dir: join_paths(pamdir, 'pam.d') 19 | ) 20 | else 21 | install_data( 22 | 'cinnamon-screensaver', 23 | install_dir: join_paths(pamdir, 'pam.d') 24 | ) 25 | endif 26 | 27 | install_data('org.cinnamon.ScreenSaver.desktop', install_dir: join_paths(datadir, 'applications')) 28 | install_data(dbus_service, install_dir: dbus_services_dir) 29 | -------------------------------------------------------------------------------- /data/org.cinnamon.ScreenSaver.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name=Screensaver 4 | Comment=Launch screensaver and locker program 5 | Exec=cinnamon-screensaver 6 | OnlyShowIn=X-Cinnamon; 7 | NoDisplay=true 8 | DBusActivatable=true -------------------------------------------------------------------------------- /data/org.cinnamon.ScreenSaver.service.in: -------------------------------------------------------------------------------- 1 | [D-BUS Service] 2 | Name=org.cinnamon.ScreenSaver 3 | Exec=@bindir@/cinnamon-screensaver 4 | -------------------------------------------------------------------------------- /debian/cinnamon-screensaver.install: -------------------------------------------------------------------------------- 1 | etc/pam.d/cinnamon-screensaver 2 | usr/bin 3 | usr/share/applications 4 | usr/share/cinnamon-screensaver/*.css 5 | usr/share/cinnamon-screensaver/*.py 6 | usr/share/cinnamon-screensaver/*/*.py 7 | usr/share/dbus-1 8 | usr/share/icons 9 | usr/libexec/cinnamon-screensaver -------------------------------------------------------------------------------- /debian/cinnamon-screensaver.lintian-overrides: -------------------------------------------------------------------------------- 1 | cinnamon-screensaver: binary-without-manpage usr/bin/cinnamon-screensaver 2 | cinnamon-screensaver: binary-without-manpage usr/bin/cinnamon-screensaver-command 3 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: cinnamon-screensaver 2 | Section: x11 3 | Priority: optional 4 | Maintainer: Linux Mint 5 | Build-Depends: 6 | debhelper-compat (= 13), 7 | dh-sequence-python3, 8 | gnome-pkg-tools, 9 | gobject-introspection, 10 | intltool, 11 | libdbus-1-dev, 12 | libgirepository1.0-dev, 13 | libglib2.0-dev, 14 | libgtk-3-dev, 15 | libpam0g-dev, 16 | libxdo-dev, 17 | libxext-dev, 18 | meson, 19 | python3, 20 | Standards-Version: 3.9.6 21 | 22 | Package: cinnamon-screensaver 23 | Architecture: any 24 | Pre-Depends: ${misc:Pre-Depends} 25 | Depends: 26 | cinnamon-desktop-data (>= 6.0), 27 | gir1.2-caribou-1.0, 28 | gir1.2-cinnamondesktop-3.0 (>= 6.0), 29 | gir1.2-gdk-3.0, 30 | gir1.2-gdkpixbuf-2.0, 31 | gir1.2-gio-2.0, 32 | gir1.2-gkbd-3.0, 33 | gir1.2-glib-2.0, 34 | gir1.2-gobject-2.0, 35 | gir1.2-gtk-3.0, 36 | gir1.2-pango-1.0, 37 | gir1.2-xapp-1.0, 38 | iso-flag-png, 39 | libxdo3, 40 | python3, 41 | python3-gi, 42 | python3-gi-cairo, 43 | python3-setproctitle, 44 | python3-xapp, 45 | python3-xlib, 46 | x11-utils, 47 | ${misc:Depends}, 48 | ${python3:Depends}, 49 | ${shlibs:Depends}, 50 | Recommends: libpam-gnome-keyring 51 | Breaks: libcscreensaver0 (<< ${source:Version}) 52 | Description: Cinnamon screen saver and locker 53 | cinnamon-screensaver is a screen saver and locker that aims to have simple, 54 | sane and secure defaults, and be well integrated with the Cinnamon desktop. 55 | 56 | Package: libcscreensaver-dbg 57 | Section: debug 58 | Priority: extra 59 | Architecture: any 60 | Multi-Arch: same 61 | Pre-Depends: ${misc:Pre-Depends} 62 | Depends: cinnamon-screensaver (= ${binary:Version}), ${misc:Depends} 63 | Description: Cinnamon Screensaver library - debug symbols 64 | This package contains the symbols files needed to debug the Cinnamon Screensaver library. 65 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: cinnamon-screensaver 3 | Upstream-Contact: Linux Mint Project 4 | Source: https://github.com/linuxmint/cinnamon-screensaver.git 5 | 6 | Files: * 7 | Copyright: 2003, Bill Nottingham 8 | 1989-1991, Free Software Foundation, Inc 9 | 1991-2004, Jamie Zawinski 10 | 2016, Michael Webster 11 | 2006, Ray Strode 12 | 2002, Sun Microsystems 13 | 2004-2006, William Jon McCann 14 | License: GPL-2+ 15 | 16 | Files: debian/* 17 | Copyright: 2014-2016, Maximiliano Curia 18 | 2013-2024, Linux Mint Project 19 | License: LGPL-2+ 20 | 21 | Files: libcscreensaver/setuid.c 22 | libcscreensaver/setuid.h 23 | libcscreensaver/subprocs.c 24 | libcscreensaver/subprocs.h 25 | Copyright: 1991-2004, Jamie Zawinski 26 | 2004, William Jon McCann 27 | License: MIT/X11 28 | 29 | License: GPL-2+ 30 | This program is free software; you can redistribute it and/or 31 | modify it under the terms of the GNU General Public License as 32 | published by the Free Software Foundation; either version 2 of the 33 | License, or (at your option) any later version. 34 | . 35 | This program is distributed in the hope that it will be useful, but 36 | WITHOUT ANY WARRANTY; without even the implied warranty of 37 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 38 | General Public License for more details. 39 | . 40 | On Debian systems, the complete text of the GNU General Public License 41 | version 2 can be found in `/usr/share/common-licenses/GPL-2'. 42 | 43 | License: LGPL-2+ 44 | This program is free software; you can redistribute it and/or modify it under 45 | the terms of the GNU Library General Public License as published by the Free 46 | Software Foundation; either version 2 of the License, or (at your option) any 47 | later version. 48 | . 49 | This program is distributed in the hope that it will be useful, but WITHOUT 50 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 51 | FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for 52 | more details. 53 | . 54 | On Debian systems, the complete text of the GNU Library General Public License 55 | version 2 can be found in `/usr/share/common-licenses/LGPL-2'. 56 | 57 | License: MIT/X11 58 | Permission to use, copy, modify, distribute, and sell this software and its 59 | documentation for any purpose is hereby granted without fee, provided that 60 | the above copyright notice appear in all copies and that both that 61 | copyright notice and this permission notice appear in supporting 62 | documentation. No representations are made about the suitability of this 63 | software for any purpose. It is provided "as is" without express or 64 | implied warranty. 65 | -------------------------------------------------------------------------------- /debian/not-installed: -------------------------------------------------------------------------------- 1 | # Internal lib, no need for the dev parts 2 | usr/lib/*/libcscreensaver.la 3 | usr/lib/*/libcscreensaver.so 4 | usr/lib/*/pkgconfig/cscreensaver.pc 5 | usr/include/cinnamon-screensaver/ 6 | # precompiled python files 7 | */__pycache__/ 8 | 9 | usr/share/gir-1.0/CScreensaver-1.0.gir 10 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) 4 | 5 | %: 6 | dh $@ 7 | 8 | override_dh_strip: 9 | dh_strip --dbg-package=libcscreensaver-dbg 10 | 11 | override_dh_auto_configure: 12 | dh_auto_configure -- \ 13 | --libexecdir=libexec \ 14 | --buildtype=debugoptimized \ 15 | -D use-debian-pam=true \ 16 | 17 | execute_after_dh_fixperms: 18 | : # fix executable libraries 19 | chmod 0644 debian/cinnamon-screensaver/usr/libexec/cinnamon-screensaver/girepository-1.0/CScreensaver-1.0.typelib 20 | chmod 0644 debian/cinnamon-screensaver/usr/libexec/cinnamon-screensaver/libcscreensaver.so 21 | 22 | # there has never been a testsuite, so don't try running it and breaking 23 | # just because there isn't one 24 | override_dh_auto_test: 25 | 26 | override_dh_python3: 27 | dh_python3 usr/share/cinnamon-screensaver 28 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /doc/dbus-interface.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/cinnamon-screensaver/d7e415936b6516e45e9e073a77c050abfb38b192/doc/dbus-interface.html -------------------------------------------------------------------------------- /install-scripts/meson.build: -------------------------------------------------------------------------------- 1 | # These scripts run as post-installation scripts. 2 | 3 | # They're designed to do nothing if DESTDIR is set, which happens 4 | # during debian builds for instance - there's a fake install target 5 | # so running these would be pointless. 6 | 7 | # When using deb packaging, these aren't needed, as these operations 8 | # are run automatically by the package manager. 9 | 10 | # They're really only necessary in straight builds where 'ninja install' 11 | # will be run directly, to install the program onto the system. 12 | 13 | 14 | # Generate python bytecode 15 | meson.add_install_script('meson_compile_python.py') 16 | 17 | -------------------------------------------------------------------------------- /install-scripts/meson_compile_python.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | import subprocess 5 | 6 | pythondir = os.path.join(os.environ['MESON_INSTALL_PREFIX'], 'share', 'cinnamon-screensaver') 7 | 8 | if not os.environ.get('DESTDIR'): 9 | print('Generating python bytecode...') 10 | subprocess.call(['sh', '-c', 'python3 -m compileall "%s"' % pythondir]) 11 | -------------------------------------------------------------------------------- /libcscreensaver/cs-auth.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2006 William Jon McCann 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License as 7 | * published by the Free Software Foundation; either version 2 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street - Suite 500, Boston, MA 18 | * 02110-1335, USA. 19 | * 20 | */ 21 | 22 | #ifndef __CS_AUTH_H 23 | #define __CS_AUTH_H 24 | 25 | #include 26 | 27 | G_BEGIN_DECLS 28 | 29 | typedef enum { 30 | CS_AUTH_MESSAGE_PROMPT_ECHO_ON, 31 | CS_AUTH_MESSAGE_PROMPT_ECHO_OFF, 32 | CS_AUTH_MESSAGE_ERROR_MSG, 33 | CS_AUTH_MESSAGE_TEXT_INFO 34 | } CsAuthMessageStyle; 35 | 36 | typedef enum { 37 | CS_AUTH_ERROR_GENERAL, 38 | CS_AUTH_ERROR_AUTH_ERROR, 39 | CS_AUTH_ERROR_USER_UNKNOWN, 40 | CS_AUTH_ERROR_AUTH_DENIED 41 | } CsAuthError; 42 | 43 | #define PAM_SERVICE_NAME "cinnamon-screensaver" 44 | 45 | typedef gboolean (* CsAuthMessageFunc) (CsAuthMessageStyle style, 46 | const char *msg, 47 | char **response, 48 | gpointer data); 49 | 50 | #define CS_AUTH_ERROR cs_auth_error_quark () 51 | 52 | GQuark cs_auth_error_quark (void); 53 | 54 | void cs_auth_set_verbose (gboolean verbose); 55 | gboolean cs_auth_get_verbose (void); 56 | 57 | gboolean cs_auth_priv_init (void); 58 | gboolean cs_auth_init (void); 59 | gboolean cs_auth_verify_user (const char *username, 60 | const char *display, 61 | CsAuthMessageFunc func, 62 | gpointer data, 63 | GError **error); 64 | 65 | G_END_DECLS 66 | 67 | #endif /* __CS_AUTH_H */ 68 | -------------------------------------------------------------------------------- /libcscreensaver/cs-event-grabber.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- 2 | * 3 | * Copyright (C) 2004-2006 William Jon McCann 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street - Suite 500, Boston, MA 02110-1335, USA. 18 | * 19 | * Authors: William Jon McCann 20 | * 21 | */ 22 | 23 | #ifndef __CS_EVENT_GRABBER_H 24 | #define __CS_EVENT_GRABBER_H 25 | 26 | #include 27 | #include 28 | 29 | G_BEGIN_DECLS 30 | 31 | #define CS_TYPE_EVENT_GRABBER (cs_event_grabber_get_type ()) 32 | G_DECLARE_FINAL_TYPE (CsEventGrabber, cs_event_grabber, CS, EVENT_GRABBER, GObject) 33 | 34 | CsEventGrabber * cs_event_grabber_new (gboolean debug); 35 | 36 | void cs_event_grabber_release (CsEventGrabber *grab); 37 | gboolean cs_event_grabber_release_mouse (CsEventGrabber *grab); 38 | 39 | gboolean cs_event_grabber_grab_window (CsEventGrabber *grab, 40 | GdkWindow *window, 41 | GdkScreen *screen, 42 | gboolean hide_cursor); 43 | 44 | gboolean cs_event_grabber_grab_root (CsEventGrabber *grab, 45 | gboolean hide_cursor); 46 | gboolean cs_event_grabber_grab_offscreen (CsEventGrabber *grab, 47 | gboolean hide_cursor); 48 | 49 | void cs_event_grabber_move_to_window (CsEventGrabber *grab, 50 | GdkWindow *window, 51 | GdkScreen *screen, 52 | gboolean hide_cursor); 53 | 54 | void cs_event_grabber_mouse_reset (CsEventGrabber *grab); 55 | void cs_event_grabber_keyboard_reset (CsEventGrabber *grab); 56 | 57 | G_END_DECLS 58 | 59 | #endif /* __CS_EVENT_GRABBER_H */ 60 | -------------------------------------------------------------------------------- /libcscreensaver/cs-gdk-event-filter.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __CS_GDK_EVENT_FILTER_H 3 | #define __CS_GDK_EVENT_FILTER_H 4 | 5 | #include 6 | #include 7 | 8 | G_BEGIN_DECLS 9 | 10 | #define CS_TYPE_GDK_EVENT_FILTER (cs_gdk_event_filter_get_type ()) 11 | #define CS_GDK_EVENT_FILTER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CS_TYPE_GDK_EVENT_FILTER, CsGdkEventFilter)) 12 | #define CS_GDK_EVENT_FILTER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), CS_TYPE_GDK_EVENT_FILTER, CsGdkEventFilterClass)) 13 | #define CS_IS_GDK_EVENT_FILTER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CS_TYPE_GDK_EVENT_FILTER)) 14 | #define CS_IS_GDK_EVENT_FILTER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CS_TYPE_GDK_EVENT_FILTER)) 15 | #define CS_GDK_EVENT_FILTER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CS_TYPE_GDK_EVENT_FILTER, CsGdkEventFilterClass)) 16 | 17 | typedef struct 18 | { 19 | GObject obj; 20 | 21 | GdkDisplay *display; 22 | GtkWidget *managed_window; 23 | gulong my_xid; 24 | 25 | /* Using XID/Window here would complicate introspection. */ 26 | gulong pretty_xid; 27 | gboolean we_are_backup_window; 28 | 29 | int shape_event_base; 30 | } CsGdkEventFilter; 31 | 32 | typedef struct 33 | { 34 | GObjectClass parent_class; 35 | } CsGdkEventFilterClass; 36 | 37 | GType cs_gdk_event_filter_get_type (void); 38 | 39 | CsGdkEventFilter *cs_gdk_event_filter_new (GtkWidget *managed_window, gulong pretty_xid); 40 | 41 | void cs_gdk_event_filter_start (CsGdkEventFilter *filter, gboolean fractional_scaling, gboolean debug); 42 | 43 | void cs_gdk_event_filter_stop (CsGdkEventFilter *filter); 44 | 45 | G_END_DECLS 46 | 47 | #endif /* __CS_GDK_EVENT_FILTER_H */ 48 | -------------------------------------------------------------------------------- /libcscreensaver/cs-notification-watcher.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __CS_NOTIFICATION_WATCHER_H 3 | #define __CS_NOTIFICATION_WATCHER_H 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | G_BEGIN_DECLS 10 | 11 | #define CS_TYPE_NOTIFICATION_WATCHER (cs_notification_watcher_get_type ()) 12 | #define CS_NOTIFICATION_WATCHER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CS_TYPE_NOTIFICATION_WATCHER, CsNotificationWatcher)) 13 | #define CS_NOTIFICATION_WATCHER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), CS_TYPE_NOTIFICATION_WATCHER, CsNotificationWatcherClass)) 14 | #define CS_IS_NOTIFICATION_WATCHER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CS_TYPE_NOTIFICATION_WATCHER)) 15 | #define CS_IS_NOTIFICATION_WATCHER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CS_TYPE_NOTIFICATION_WATCHER)) 16 | #define CS_NOTIFICATION_WATCHER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CS_TYPE_NOTIFICATION_WATCHER, CsNotificationWatcherClass)) 17 | 18 | typedef struct 19 | { 20 | GObject obj; 21 | 22 | GDBusConnection *connection; 23 | gint filter_id; 24 | gboolean debug; 25 | } CsNotificationWatcher; 26 | 27 | typedef struct 28 | { 29 | GObjectClass parent_class; 30 | 31 | void (* notification_received) (CsNotificationWatcher *watcher, const gchar *sender); 32 | } CsNotificationWatcherClass; 33 | 34 | GType cs_notification_watcher_get_type (void); 35 | 36 | CsNotificationWatcher *cs_notification_watcher_new (gboolean debug); 37 | 38 | G_END_DECLS 39 | 40 | #endif /* __CS_NOTIFICATION_WATCHER_H */ 41 | -------------------------------------------------------------------------------- /libcscreensaver/cs-screen.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __CS_SCREEN_H 3 | #define __CS_SCREEN_H 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | G_BEGIN_DECLS 10 | 11 | #define CS_TYPE_SCREEN (cs_screen_get_type ()) 12 | #define CS_SCREEN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CS_TYPE_SCREEN, CsScreen)) 13 | #define CS_SCREEN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), CS_TYPE_SCREEN, CsScreenClass)) 14 | #define CS_IS_SCREEN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CS_TYPE_SCREEN)) 15 | #define CS_IS_SCREEN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CS_TYPE_SCREEN)) 16 | #define CS_SCREEN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CS_TYPE_SCREEN, CsScreenClass)) 17 | 18 | typedef struct _CsMonitorInfo CsMonitorInfo; 19 | 20 | struct _CsMonitorInfo 21 | { 22 | int number; 23 | GdkRectangle rect; 24 | gboolean is_primary; 25 | XID output; /* The primary or first output for this crtc, None if no xrandr */ 26 | }; 27 | 28 | typedef struct 29 | { 30 | GObject obj; 31 | 32 | GdkRectangle rect; 33 | 34 | GdkScreen *gdk_screen; 35 | 36 | CsMonitorInfo *monitor_infos; 37 | 38 | gint primary_monitor_index; 39 | gint n_monitor_infos; 40 | 41 | gulong monitors_changed_id; 42 | gulong screen_size_changed_id; 43 | gulong composited_changed_id; 44 | 45 | gboolean low_res; 46 | gint smallest_width; 47 | gint smallest_height; 48 | } CsScreen; 49 | 50 | typedef struct 51 | { 52 | GObjectClass parent_class; 53 | } CsScreenClass; 54 | 55 | GType cs_screen_get_type (void); 56 | 57 | CsScreen *cs_screen_new (gboolean debug); 58 | 59 | void cs_screen_get_monitor_geometry (CsScreen *screen, 60 | gint monitor, 61 | GdkRectangle *geometry); 62 | 63 | void cs_screen_get_screen_geometry (CsScreen *screen, 64 | GdkRectangle *geometry); 65 | 66 | gint cs_screen_get_primary_monitor (CsScreen *screen); 67 | 68 | gint cs_screen_get_n_monitors (CsScreen *screen); 69 | 70 | gint cs_screen_get_mouse_monitor (CsScreen *screen); 71 | 72 | gboolean cs_screen_get_low_res_mode (CsScreen *screen); 73 | 74 | void cs_screen_get_smallest_monitor_sizes (CsScreen *screen, 75 | gint *width, 76 | gint *height); 77 | 78 | void cs_screen_place_pointer_in_primary_monitor (CsScreen *screen); 79 | 80 | void cs_screen_set_net_wm_name (GdkWindow *window, 81 | const gchar *name); 82 | 83 | gchar *cs_screen_get_net_wm_name (gulong xwindow); 84 | 85 | void cs_screen_reset_screensaver (void); 86 | 87 | gint cs_screen_get_global_scale (void); 88 | 89 | G_END_DECLS 90 | 91 | #endif /* __CS_SCREEN_H */ 92 | -------------------------------------------------------------------------------- /libcscreensaver/meson.build: -------------------------------------------------------------------------------- 1 | gnome = import('gnome') 2 | pkgconfig = import('pkgconfig') 3 | 4 | dbus_files = [ 5 | [ 6 | 'cs-cinnamon-proxy', 7 | [ 8 | ['org.Cinnamon', 'org.gtk.GDBus.C.Name', 'Cinnamon'] 9 | ], 10 | 'org.Cinnamon' 11 | ], 12 | [ 13 | 'cs-session-presence-proxy', 14 | [ 15 | ['org.gnome.SessionManager.Presence', 'org.gtk.GDBus.C.Name', 'SessionPresence'] 16 | ], 17 | 'org.gnome.SessionManager.Presence' 18 | ], 19 | [ 20 | 'cs-upower-proxy', 21 | [ 22 | ['org.freedesktop.UPower', 'org.gtk.GDBus.C.Name', 'UPower'], 23 | ['org.freedesktop.UPower.EnumerateDevices()[devices]', 'org.gtk.GDBus.C.ForceGVariant', 'true'] 24 | ], 25 | 'org.freedesktop.UPower' 26 | ], 27 | [ 28 | 'cs-upower-device-proxy', 29 | [ 30 | ['org.freedesktop.UPower.Device', 'org.gtk.GDBus.C.Name', 'UPowerDevice'] 31 | ], 32 | 'org.freedesktop.UPower.Device' 33 | ], 34 | [ 35 | 'cs-login-manager-proxy', 36 | [ 37 | ['org.freedesktop.login1.Manager', 'org.gtk.GDBus.C.Name', 'LogindManager'] 38 | ], 39 | 'org.freedesktop.login1.Manager' 40 | ], 41 | [ 42 | 'cs-logind-session-proxy', 43 | [ 44 | ['org.freedesktop.login1.Session', 'org.gtk.GDBus.C.Name', 'LogindSession'] 45 | ], 46 | 'org.freedesktop.login1.Session' 47 | ], 48 | [ 49 | 'cs-consolekit-manager-proxy', 50 | [ 51 | ['org.freedesktop.ConsoleKit.Manager', 'org.gtk.GDBus.C.Name', 'ConsoleKitManager'] 52 | ], 53 | 'org.freedesktop.ConsoleKit.Manager' 54 | ], 55 | [ 56 | 'cs-consolekit-session-proxy', 57 | [ 58 | ['org.freedesktop.ConsoleKit.Session', 'org.gtk.GDBus.C.Name', 'ConsoleKitSession'] 59 | ], 60 | 'org.freedesktop.ConsoleKit.Session' 61 | ], 62 | [ 63 | 'cs-screensaver-proxy', 64 | [ 65 | ['org.cinnamon.ScreenSaver', 'org.gtk.GDBus.C.Name', 'ScreenSaver'] 66 | ], 67 | 'org.cinnamon.ScreenSaver' 68 | ], 69 | [ 70 | 'cs-keybinding-handler-proxy', 71 | [ 72 | ['org.cinnamon.SettingsDaemon.KeybindingHandler', 'org.gtk.GDBus.C.Name', 'KeybindingHandler'] 73 | ], 74 | 'org.cinnamon.SettingsDaemon.KeybindingHandler' 75 | ], 76 | [ 77 | 'cs-media-player-proxy', 78 | [ 79 | ['org.mpris.MediaPlayer2.Player', 'org.gtk.GDBus.C.Name', 'MediaPlayer'] 80 | ], 81 | 'org.mpris.MediaPlayer2.Player' 82 | ], 83 | [ 84 | 'cs-muffin-displayconfig-proxy', 85 | [ 86 | ['org.cinnamon.Muffin.DisplayConfig', 'org.gtk.GDBus.C.Name', 'MuffinDisplayConfig'] 87 | ], 88 | 'org.cinnamon.Muffin.DisplayConfig' 89 | ], 90 | [ 91 | 'cs-accounts-service-proxy', 92 | [ 93 | ['org.freedesktop.Accounts', 'org.gtk.GDBus.C.Name', 'AccountsService'] 94 | ], 95 | 'org.freedesktop.Accounts' 96 | ], 97 | [ 98 | 'cs-accounts-user-proxy', 99 | [ 100 | ['org.freedesktop.Accounts.User', 'org.gtk.GDBus.C.Name', 'AccountsUser'] 101 | ], 102 | 'org.freedesktop.Accounts.User' 103 | ], 104 | ] 105 | 106 | dbus_built = [] 107 | foreach dbus: dbus_files 108 | dbus_built += gnome.gdbus_codegen(dbus[0], '@0@.xml'.format(dbus[2]), 109 | namespace: 'Cs', 110 | annotations: dbus[1] 111 | ) 112 | endforeach 113 | 114 | # non-pam auth implementations are not implemented at this time 115 | auth_impl = 'pam' 116 | cscreensaver_sources = [ 117 | 'cs-auth-@0@.c'.format(auth_impl), 118 | 'subprocs.c', 119 | 'subprocs.h', 120 | 'setuid.c' 121 | ] 122 | 123 | gir_sources = [ 124 | 'cs-event-grabber.h', 125 | 'cs-event-grabber.c', 126 | 'cs-gdk-event-filter.h', 127 | 'cs-gdk-event-filter-x11.c', 128 | 'cs-notification-watcher.h', 129 | 'cs-notification-watcher.c', 130 | 'cs-screen.h', 131 | 'cs-screen-x11.c', 132 | dbus_built 133 | ] 134 | 135 | libcscreensaver_deps = [gobject, gtk, gdk, x11, xrandr, xext, glib, gio, gio_unix, gthread, pam, m, xdo] 136 | if use_xinerama 137 | libcscreensaver_deps += xinerama 138 | endif 139 | 140 | libcscreensaver = library( 141 | 'cscreensaver', 142 | cscreensaver_sources + gir_sources, 143 | include_directories: inc, 144 | cpp_args: '-DG_LOG_DOMAIN="CScreensaver"', 145 | dependencies: libcscreensaver_deps, 146 | install_dir: pkglibdir, 147 | install: true 148 | ) 149 | 150 | cscreensaver_gir = gnome.generate_gir( 151 | libcscreensaver, 152 | sources: gir_sources, 153 | namespace: 'CScreensaver', 154 | nsversion: '1.0', 155 | identifier_prefix: 'Cs', 156 | symbol_prefix: 'cs_', 157 | includes: 'Gtk-3.0', 158 | install_dir_typelib: typelibdir, 159 | install: true 160 | ) 161 | 162 | test_passwd = executable( 163 | 'test-passwd', 164 | 'test-passwd.c', 165 | include_directories: inc, 166 | link_with: libcscreensaver, 167 | dependencies: [gobject, gtk, gdk], 168 | build_by_default: false 169 | ) 170 | -------------------------------------------------------------------------------- /libcscreensaver/org.Cinnamon.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /libcscreensaver/org.cinnamon.Muffin.DisplayConfig.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /libcscreensaver/org.cinnamon.ScreenSaver.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /libcscreensaver/org.cinnamon.SettingsDaemon.KeybindingHandler.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /libcscreensaver/org.freedesktop.Accounts.User.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | The users real name. 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | The filename of a png file containing the users icon. 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | The users home directory. 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /libcscreensaver/org.freedesktop.Accounts.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | The username to look up 13 | 14 | 15 | Object path of user 16 | 17 | 18 | 19 | 20 | 21 | Finds a user by its username. 22 | 23 | 24 | 25 | if no user with the given username exists 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /libcscreensaver/org.freedesktop.ConsoleKit.Manager.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | The object identifier for the current session 12 | 13 | 14 | 15 | 16 | Attempts to determine the session ID that the caller belongs to. 17 | 18 | See this example of using dbus-send: 19 | 20 | dbus-send --system --dest=org.freedesktop.ConsoleKit \ 21 | --type=method_call --print-reply --reply-timeout=2000 \ 22 | /org/freedesktop/ConsoleKit/Manager \ 23 | org.freedesktop.ConsoleKit.Manager.GetCurrentSession 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /libcscreensaver/org.freedesktop.ConsoleKit.Session.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Session objects represent and store information 8 | related to a user session. 9 | 10 | The properties associated with the Session 11 | specifically refer to the properties of the "session leader". 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | This will cause a Lock 20 | signal to be emitted for this session. 21 | 22 | 23 | This method is restricted to privileged users by D-Bus policy. 24 | Lock signal 25 | 26 | 27 | 28 | 29 | 30 | 31 | This will cause an Unlock 32 | signal to be emitted for this session. 33 | 34 | This can be used by login managers to unlock a session before it is 35 | re-activated during fast-user-switching. 36 | 37 | 38 | This method is restricted to privileged users by D-Bus policy. 39 | Unlock signal 40 | 41 | 42 | 43 | 44 | 45 | 46 | TRUE if the session is active, otherwise FALSE 47 | 48 | 49 | 50 | 51 | Emitted when the active property has changed. 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | Lock the desktop. 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | Unlock the desktop. 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /libcscreensaver/org.freedesktop.UPower.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | The UPower service is available via the system message 11 | bus. To access the service, use 12 | the org.freedesktop.UPower interface on 13 | the /org/freedesktop/UPower object on 14 | the D-Bus system bus service with the well-known 15 | name org.freedesktop.UPower. 16 | 17 | 18 | 19 | 20 | $ dbus-send --print-reply \ 21 | --system \ 22 | --dest=org.freedesktop.UPower \ 23 | /org/freedesktop/UPower \ 24 | org.freedesktop.UPower.EnumerateDevices 25 | 26 | method return sender=:1.386 -> dest=:1.451 reply_serial=2 27 | array [ 28 | object path "/org/freedesktop/UPower/devices/line_power_AC" 29 | object path "/org/freedesktop/UPower/devices/battery_BAT0" 30 | ] 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | An array of object paths for devices. 43 | 44 | 45 | 46 | 47 | 48 | Enumerate all power objects on the system. 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | Object path of device that was added. 59 | 60 | 61 | 62 | 63 | 64 | Emitted when a device is added. 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | Object path of device that was removed. 75 | 76 | 77 | 78 | 79 | 80 | Emitted when a device is removed. 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | Indicates whether the system is running on battery power. 91 | This property is provided for convenience. 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /libcscreensaver/org.freedesktop.login1.Manager.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /libcscreensaver/org.freedesktop.login1.Session.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /libcscreensaver/org.gnome.SessionManager.Presence.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | The status of the session. 11 | 12 | 13 | The status parameter must be one of the following: 14 | 15 | 16 | 0 17 | Available 18 | 19 | 20 | 1 21 | Invisible 22 | 23 | 24 | 2 25 | Busy 26 | 27 | 28 | 3 29 | Idle 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | The new status value 41 | 42 | 43 | 44 | 45 | Indicates that the session status value has changed. 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /libcscreensaver/setuid.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- 2 | * 3 | * xscreensaver, Copyright (c) 1993-2004 Jamie Zawinski 4 | * 5 | * Permission to use, copy, modify, distribute, and sell this software and its 6 | * documentation for any purpose is hereby granted without fee, provided that 7 | * the above copyright notice appear in all copies and that both that 8 | * copyright notice and this permission notice appear in supporting 9 | * documentation. No representations are made about the suitability of this 10 | * software for any purpose. It is provided "as is" without express or 11 | * implied warranty. 12 | */ 13 | 14 | #ifndef __GS_SETUID_H 15 | #define __GS_SETUID_H 16 | 17 | #include 18 | 19 | G_BEGIN_DECLS 20 | 21 | gboolean hack_uid (char **nolock_reason, 22 | char **orig_uid, 23 | char **uid_message); 24 | 25 | G_END_DECLS 26 | 27 | #endif /* __GS_SETUID_H */ 28 | -------------------------------------------------------------------------------- /libcscreensaver/subprocs.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- 2 | * 3 | * subprocs.c --- choosing, spawning, and killing screenhacks. 4 | * 5 | * xscreensaver, Copyright (c) 1991-2003 Jamie Zawinski 6 | * Modified: Copyright (c) 2004 William Jon McCann 7 | * 8 | * Permission to use, copy, modify, distribute, and sell this software and its 9 | * documentation for any purpose is hereby granted without fee, provided that 10 | * the above copyright notice appear in all copies and that both that 11 | * copyright notice and this permission notice appear in supporting 12 | * documentation. No representations are made about the suitability of this 13 | * software for any purpose. It is provided "as is" without express or 14 | * implied warranty. 15 | */ 16 | 17 | #include "config.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #ifndef ESRCH 25 | # include 26 | #endif 27 | 28 | #include /* sys/resource.h needs this for timeval */ 29 | # include /* for waitpid() and associated macros */ 30 | 31 | #ifdef VMS 32 | # include 33 | # include /* for close */ 34 | # include /* for getpid */ 35 | # define pid_t int 36 | # define fork vfork 37 | #endif /* VMS */ 38 | 39 | #include /* for the signal names */ 40 | 41 | #include 42 | #include "subprocs.h" 43 | 44 | #if !defined(SIGCHLD) && defined(SIGCLD) 45 | # define SIGCHLD SIGCLD 46 | #endif 47 | 48 | /* Semaphore to temporarily turn the SIGCHLD handler into a no-op. 49 | Don't alter this directly -- use block_sigchld() / unblock_sigchld(). 50 | */ 51 | static int block_sigchld_handler = 0; 52 | 53 | 54 | #ifdef HAVE_SIGACTION 55 | sigset_t 56 | #else /* !HAVE_SIGACTION */ 57 | int 58 | #endif /* !HAVE_SIGACTION */ 59 | block_sigchld (void) 60 | { 61 | #ifdef HAVE_SIGACTION 62 | sigset_t child_set; 63 | sigemptyset (&child_set); 64 | sigaddset (&child_set, SIGCHLD); 65 | sigaddset (&child_set, SIGPIPE); 66 | sigprocmask (SIG_BLOCK, &child_set, 0); 67 | #endif /* HAVE_SIGACTION */ 68 | 69 | block_sigchld_handler++; 70 | 71 | #ifdef HAVE_SIGACTION 72 | return child_set; 73 | #else /* !HAVE_SIGACTION */ 74 | return 0; 75 | #endif /* !HAVE_SIGACTION */ 76 | } 77 | 78 | void 79 | unblock_sigchld (void) 80 | { 81 | #ifdef HAVE_SIGACTION 82 | sigset_t child_set; 83 | sigemptyset (&child_set); 84 | sigaddset (&child_set, SIGCHLD); 85 | sigaddset (&child_set, SIGPIPE); 86 | sigprocmask (SIG_UNBLOCK, &child_set, 0); 87 | #endif /* HAVE_SIGACTION */ 88 | 89 | block_sigchld_handler--; 90 | } 91 | 92 | int 93 | signal_pid (int pid, 94 | int signal) 95 | { 96 | int status = -1; 97 | gboolean verbose = TRUE; 98 | 99 | if (block_sigchld_handler) 100 | /* This function should not be called from the signal handler. */ 101 | abort(); 102 | 103 | block_sigchld (); /* we control the horizontal... */ 104 | 105 | status = kill (pid, signal); 106 | 107 | if (verbose && status < 0) { 108 | if (errno == ESRCH) 109 | g_message ("Child process %lu was already dead.", 110 | (unsigned long) pid); 111 | else { 112 | char buf [1024]; 113 | snprintf (buf, sizeof (buf), "Couldn't kill child process %lu", 114 | (unsigned long) pid); 115 | perror (buf); 116 | } 117 | } 118 | 119 | unblock_sigchld (); 120 | 121 | if (block_sigchld_handler < 0) 122 | abort (); 123 | 124 | return status; 125 | } 126 | 127 | #ifndef VMS 128 | 129 | void 130 | await_dying_children (int pid, 131 | gboolean debug) 132 | { 133 | while (1) { 134 | int wait_status = 0; 135 | pid_t kid; 136 | 137 | errno = 0; 138 | kid = waitpid (-1, &wait_status, WNOHANG|WUNTRACED); 139 | 140 | if (debug) { 141 | if (kid < 0 && errno) 142 | g_message ("waitpid(%d) ==> %ld (%d)", pid, (long) kid, errno); 143 | else if (kid != 0) 144 | g_message ("waitpid(%d) ==> %ld", pid, (long) kid); 145 | } 146 | 147 | /* 0 means no more children to reap. 148 | -1 means error -- except "interrupted system call" isn't a "real" 149 | error, so if we get that, we should just try again. */ 150 | if (kid < 0 && errno != EINTR) 151 | break; 152 | } 153 | } 154 | 155 | 156 | #else /* VMS */ 157 | static void await_dying_children (saver_info *si) { return; } 158 | #endif /* VMS */ 159 | 160 | -------------------------------------------------------------------------------- /libcscreensaver/subprocs.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- 2 | * 3 | * subprocs.c --- choosing, spawning, and killing screenhacks. 4 | * 5 | * xscreensaver, Copyright (c) 1991-2003 Jamie Zawinski 6 | * 7 | * Permission to use, copy, modify, distribute, and sell this software and its 8 | * documentation for any purpose is hereby granted without fee, provided that 9 | * the above copyright notice appear in all copies and that both that 10 | * copyright notice and this permission notice appear in supporting 11 | * documentation. No representations are made about the suitability of this 12 | * software for any purpose. It is provided "as is" without express or 13 | * implied warranty. 14 | */ 15 | 16 | #ifndef __GS_SUBPROCS_H 17 | #define __GS_SUBPROCS_H 18 | 19 | #include 20 | 21 | G_BEGIN_DECLS 22 | 23 | void unblock_sigchld (void); 24 | 25 | #ifdef HAVE_SIGACTION 26 | sigset_t 27 | #else /* !HAVE_SIGACTION */ 28 | int 29 | #endif /* !HAVE_SIGACTION */ 30 | block_sigchld (void); 31 | 32 | int signal_pid (int pid, 33 | int signal); 34 | void await_dying_children (int pid, 35 | gboolean debug); 36 | 37 | G_END_DECLS 38 | 39 | #endif /* __GS_SUBPROCS_H */ 40 | -------------------------------------------------------------------------------- /makepot: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | xgettext --language=Python --from-code=UTF-8 --keyword=_ --output=cinnamon-screensaver.pot src/*.py 3 | xgettext --language=C --from-code=UTF-8 --add-comments --keyword=_ --keyword=N_ --join-existing --output=cinnamon-screensaver.pot backup-locker/*.c 4 | 5 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('cinnamon-screensaver', 'c', version : '6.4.0', meson_version : '>=0.56.0') 2 | 3 | cc = meson.get_compiler('c') 4 | 5 | conf = configuration_data() 6 | conf.set_quoted('PACKAGE', meson.project_name()) 7 | conf.set_quoted('GETTEXT_PACKAGE', meson.project_name()) 8 | 9 | # project options 10 | 11 | # commandline options 12 | prefix = get_option('prefix') 13 | bindir = join_paths(prefix, get_option('bindir')) 14 | datadir = join_paths(prefix, get_option('datadir')) 15 | pkgdatadir = join_paths(datadir, meson.project_name()) 16 | libexecdir = join_paths(prefix, get_option('libexecdir')) 17 | pkglibdir = join_paths(libexecdir, meson.project_name()) 18 | typelibdir = join_paths(pkglibdir, 'girepository-1.0') 19 | 20 | libdir = join_paths(prefix, get_option('libdir')) 21 | #needed? 22 | sysconfdir = join_paths(prefix, get_option('sysconfdir')) 23 | localstatedir = join_paths(prefix, get_option('localstatedir')) 24 | localedir = join_paths(prefix, get_option('localedir')) 25 | 26 | # object used for configure_file because meson 0.47 is needed for dicts 27 | # and 0.49 for using them in configure_file 28 | misc_conf = configuration_data() 29 | misc_conf.set('prefix', prefix) 30 | misc_conf.set('bindir', bindir) 31 | misc_conf.set('datadir', datadir) 32 | misc_conf.set('pkgdatadir', pkgdatadir) 33 | misc_conf.set('pkglibdir', pkglibdir) 34 | misc_conf.set('libdir', libdir) 35 | misc_conf.set('PACKAGE', meson.project_name()) 36 | misc_conf.set('VERSION', meson.project_version()) 37 | misc_conf.set('GETTEXT_PACKAGE', meson.project_name()) 38 | 39 | gtk = dependency('gtk+-3.0') 40 | glib = dependency('glib-2.0') 41 | gio = dependency('gio-2.0') 42 | gio_unix = dependency('gio-unix-2.0') 43 | gthread = dependency('gthread-2.0') 44 | gobject = dependency('gobject-2.0') 45 | gdk = dependency('gdk-x11-3.0') 46 | x11 = dependency('x11') 47 | xext = dependency('xext') 48 | xrandr = dependency('xrandr', required: false) 49 | m = cc.find_library('m') 50 | 51 | xdo = dependency('libxdo', required: false) 52 | if not xdo.found() 53 | xdo = cc.find_library('xdo') 54 | endif 55 | 56 | dbus_services_dir = dependency('dbus-1').get_variable(pkgconfig: 'session_bus_services_dir', pkgconfig_define: ['datadir', datadir]) 57 | 58 | # check for symbols and headers 59 | foreach header : [ 60 | 'unistd.h' 61 | ] 62 | if cc.has_header(header) 63 | conf.set('HAVE_' + header.underscorify().to_upper(), true) 64 | endif 65 | endforeach 66 | foreach sym : [ 67 | 'sigaction' 68 | ] 69 | if cc.has_function(sym, args : '-D_GNU_SOURCE') 70 | conf.set('HAVE_' + sym.to_upper(), true) 71 | endif 72 | endforeach 73 | 74 | use_xinerama = get_option('xinerama') 75 | if use_xinerama 76 | if host_machine.system() == 'solaris' 77 | xinerama = cc.find_library('Xext') 78 | xinerama_h = cc.has_header('X11/extensions/xinerama.h') 79 | if not xinerama.found() or xinerama_h 80 | error('could not find usable xinerama library') 81 | endif 82 | conf.set('HAVE_SOLARIS_XINERAMA', 1) 83 | else 84 | xinerama = dependency('xinerama') 85 | conf.set('HAVE_XFREE_XINERAMA', 1) 86 | endif 87 | conf.set('HAVE_XINERAMA', 1) 88 | endif 89 | 90 | pam_compile = '''#include 91 | #include 92 | #include 93 | int main () 94 | { 95 | pam_handle_t *pamh = 0; 96 | char *s = pam_strerror(pamh, PAM_SUCCESS); 97 | return 0; 98 | }''' 99 | 100 | pam = cc.find_library('pam') 101 | if not cc.has_function('sigtimedwait') 102 | pam = [pam, cc.find_library('rt')] 103 | endif 104 | if cc.compiles(pam_compile) 105 | conf.set('PAM_STRERROR_TWO_ARGS', 1) 106 | endif 107 | # this check is not used anywhere 108 | if cc.has_function('pam_syslog', dependencies: pam) 109 | conf.set('HAVE_PAM_SYSLOG', 1) 110 | endif 111 | # Sun-type pam, this check is not used anywhere 112 | if not cc.has_header_symbol('security/pam_appl.h', 'const struct pam_message') 113 | conf.set('PAM_MESSAGE_NONCONST', 1) 114 | endif 115 | 116 | # do we care if the header exists? Just use pkg-config 117 | if cc.has_header('X11/extensions/Xrandr.h') 118 | xrandr = 'Xrandr' 119 | else 120 | xrandr = '' 121 | endif 122 | xrandr = cc.find_library(xrandr, required: false) 123 | 124 | if xrandr.found() 125 | conf.set('HAVE_RANDR', 1) 126 | endif 127 | if cc.has_header('X11/extensions/shape.h') 128 | conf.set('HAVE_SHAPE_EXT', 1) 129 | endif 130 | 131 | conf_h = configure_file( 132 | output : 'config.h', 133 | configuration : conf) 134 | 135 | inc = include_directories('.') 136 | 137 | if not get_option('deprecated-warnings') 138 | add_global_arguments([ 139 | '-Wno-deprecated-declarations', 140 | '-Wno-deprecated', 141 | '-Wno-declaration-after-statement', 142 | '-DGLIB_DISABLE_DEPRECATION_WARNINGS', 143 | ], 144 | language: 'c', 145 | ) 146 | endif 147 | 148 | # add_global_arguments( 149 | # [ 150 | # '-Wall', 151 | # '-Wextra', 152 | # '-Wno-unused-parameter', 153 | # '-Wmissing-prototypes', 154 | # '-Wstrict-prototypes', 155 | # '-pedantic' 156 | # ], 157 | # language: 'c' 158 | # ) 159 | 160 | subdir('install-scripts') 161 | subdir('libcscreensaver') 162 | subdir('data') 163 | subdir('src') 164 | subdir('backup-locker') 165 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('setres', type : 'boolean', value : false, description: 'Use setresuid/setresgid in the setuid.c helper') 2 | option('locking', type : 'boolean', value : true, description: 'Compile in support for locking the display') 3 | option('xinerama', type : 'boolean', value : true, description: 'Use of the Xinerama extension') 4 | option('pam-prefix', type : 'string', value : '', description: 'specify where pam files go') 5 | option('use-debian-pam', type : 'boolean', value : false, description: 'use the debian pam file') 6 | option('deprecated-warnings', type : 'boolean', value : false, description: 'show deprecated warnings') 7 | 8 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/cinnamon-screensaver/d7e415936b6516e45e9e073a77c050abfb38b192/src/__init__.py -------------------------------------------------------------------------------- /src/albumArt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import Gio, Gtk 4 | 5 | from util import trackers, settings 6 | from baseWindow import BaseWindow 7 | from floating import Floating 8 | from widgets.framedImage import FramedImage 9 | 10 | import singletons 11 | import status 12 | 13 | class AlbumArt(Floating, BaseWindow): 14 | """ 15 | AlbumArt 16 | 17 | It is a child of the Stage's GtkOverlay, and its placement is 18 | controlled by the overlay's child positioning function. 19 | 20 | When not Awake, it positions itself around all monitors 21 | using a timer which randomizes its halign and valign properties 22 | as well as its current monitor. 23 | """ 24 | def __init__(self, initial_monitor=0, low_res=False): 25 | super(AlbumArt, self).__init__(initial_monitor, Gtk.Align.END, Gtk.Align.CENTER) 26 | self.get_style_context().add_class("albumart") 27 | 28 | if not settings.get_show_albumart(): 29 | return 30 | 31 | self.watcher = singletons.MediaPlayerWatcher 32 | self.player = self.watcher.get_best_player() 33 | 34 | self.current_url = None 35 | 36 | self.image = FramedImage(low_res, scale_up=True) 37 | self.image.show() 38 | self.image.set_opacity(0.0) 39 | self.add(self.image) 40 | 41 | trackers.con_tracker_get().connect(self.image, 42 | "surface-changed", 43 | self.on_surface_changed) 44 | 45 | if self.player is not None: 46 | trackers.con_tracker_get().connect(self.player, 47 | "metadata-changed", 48 | self.on_metadata_changed) 49 | self.on_metadata_changed(self.player) 50 | 51 | def on_surface_changed(self, image, surface): 52 | if surface is not None: 53 | self.image.set_opacity(1.0) 54 | else: 55 | self.image.set_opacity(0.0) 56 | 57 | def on_metadata_changed(self, player): 58 | self.update_image() 59 | 60 | def update_image(self): 61 | url = self.player.get_albumart_url() 62 | 63 | if self.player.get_identity() == "spotify": 64 | url = url.replace("open.spotify.com", "i.scdn.co") 65 | 66 | if url == self.current_url: 67 | return 68 | 69 | self.current_url = url 70 | 71 | if url == "": 72 | self.image.clear_image() 73 | 74 | f = Gio.File.new_for_uri(url) 75 | 76 | if f.get_uri_scheme() == "file": 77 | self.image.set_from_path(f.get_path()) 78 | elif f.get_uri_scheme() == "http": 79 | self.image.set_from_file(f) 80 | -------------------------------------------------------------------------------- /src/audioPanel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import Gtk 4 | 5 | from baseWindow import BaseWindow 6 | from volumeControl import VolumeControl 7 | from playerControl import PlayerControl 8 | from util import utils, settings 9 | import status 10 | 11 | class AudioPanel(BaseWindow): 12 | def __init__(self): 13 | """ 14 | Upper left panel - only shows when Awake. Will always show the 15 | volume slider, and will only show the player controls if there is 16 | a controllable mpris player available. 17 | """ 18 | super(AudioPanel, self).__init__() 19 | 20 | self.monitor_index = status.screen.get_primary_monitor() 21 | 22 | self.update_geometry() 23 | 24 | if not settings.get_allow_media_control(): 25 | self.disabled = True 26 | return 27 | 28 | self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 29 | self.box.set_halign(Gtk.Align.FILL) 30 | self.box.get_style_context().add_class("toppanel") 31 | self.box.get_style_context().add_class("audiopanel") 32 | 33 | self.add(self.box) 34 | 35 | hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) 36 | self.box.pack_start(hbox, True, True, 6) 37 | 38 | self.volume_widget = VolumeControl() 39 | hbox.pack_start(self.volume_widget, False, False, 0) 40 | 41 | self.player_widget = PlayerControl() 42 | hbox.pack_start(self.player_widget, False, False, 0) 43 | 44 | should_show = self.player_widget.should_show() 45 | 46 | if not self.player_widget.should_show(): 47 | self.disabled = True 48 | 49 | def show_panel(self): 50 | if not self.disabled: 51 | self.show_all() -------------------------------------------------------------------------------- /src/baseWindow.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import Gtk, GObject, Gdk 4 | 5 | from util import trackers 6 | import status 7 | 8 | class BaseWindow(Gtk.Bin): 9 | """ 10 | BaseWindow is the base class for all of the Stage GtkOverlay's immediate 11 | children. 12 | """ 13 | 14 | def __init__(self, *args): 15 | super(BaseWindow, self).__init__() 16 | 17 | self.disabled = False 18 | 19 | c = Gdk.RGBA(0, 0, 0, 0) 20 | self.override_background_color (Gtk.StateFlags.NORMAL, c) 21 | 22 | def destroy_window(self): 23 | self.destroy() 24 | 25 | def update_geometry(self): 26 | if status.Spanned: 27 | self.rect = status.screen.get_screen_geometry() 28 | else: 29 | self.rect = status.screen.get_monitor_geometry(self.monitor_index) 30 | -------------------------------------------------------------------------------- /src/binfile.in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "$XDG_SESSION_TYPE" = "wayland" ]; then 4 | echo "cinnamon-screensaver is disabled in wayland sessions. Exiting." 5 | exit 1 6 | fi 7 | 8 | export GI_TYPELIB_PATH="@typelibdir@" 9 | export LD_LIBRARY_PATH="@pkglibdir@" 10 | 11 | exec @install_dir@/@target@ "$@" 12 | -------------------------------------------------------------------------------- /src/cinnamon-screensaver-command.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import gi 4 | gi.require_version('CScreensaver', '1.0') 5 | 6 | from gi.repository import GLib, CScreensaver, Gio 7 | import os 8 | import sys 9 | import signal 10 | import argparse 11 | import gettext 12 | import shlex 13 | from enum import IntEnum 14 | from subprocess import Popen, DEVNULL 15 | 16 | import config 17 | from util import settings 18 | import constants as c 19 | 20 | signal.signal(signal.SIGINT, signal.SIG_DFL) 21 | gettext.install("cinnamon-screensaver", "/usr/share/locale") 22 | 23 | class Action(IntEnum): 24 | EXIT = 1 25 | QUERY = 2 26 | TIME = 3 27 | LOCK = 4 28 | ACTIVATE = 5 29 | DEACTIVATE = 6 30 | VERSION = 7 31 | 32 | class ScreensaverCommand: 33 | """ 34 | This is a standalone executable that provides a simple way 35 | of controlling the screensaver via its dbus interface. 36 | """ 37 | def __init__(self, mainloop): 38 | self.mainloop = mainloop 39 | self.proxy = None 40 | 41 | parser = argparse.ArgumentParser(description='Cinnamon Screensaver Command') 42 | parser.add_argument('--exit', '-e', dest="action_id", action='store_const', const=Action.EXIT, 43 | help=_('Causes the screensaver to exit gracefully')) 44 | parser.add_argument('--query', '-q', dest="action_id", action='store_const', const=Action.QUERY, 45 | help=_('Query the state of the screensaver')) 46 | parser.add_argument('--time', '-t', dest="action_id", action='store_const', const=Action.TIME, 47 | help=_('Query the length of time the screensaver has been active')) 48 | parser.add_argument('--lock', '-l', dest="action_id", action='store_const', const=Action.LOCK, 49 | help=_('Tells the running screensaver process to lock the screen immediately')) 50 | parser.add_argument('--activate', '-a', dest="action_id", action='store_const', const=Action.ACTIVATE, 51 | help=_('Turn the screensaver on (blank the screen)')) 52 | parser.add_argument('--deactivate', '-d', dest="action_id", action='store_const', const=Action.DEACTIVATE, 53 | help=_('If the screensaver is active then deactivate it (un-blank the screen)')) 54 | parser.add_argument('--version', '-V', dest="action_id", action='store_const', const=Action.VERSION, 55 | help=_('Version of this application')) 56 | parser.add_argument('--away-message', '-m', dest="message", action='store', default="", 57 | help=_('Message to be displayed in lock screen')) 58 | args = parser.parse_args() 59 | 60 | if not args.action_id: 61 | parser.print_help() 62 | quit() 63 | 64 | if args.action_id == Action.VERSION: 65 | print("cinnamon-screensaver %s" % config.VERSION) 66 | quit() 67 | 68 | self.action_id = args.action_id 69 | self.message = args.message 70 | 71 | custom_saver = settings.get_custom_screensaver() 72 | if custom_saver != '': 73 | self.handle_custom_saver(custom_saver) 74 | quit() 75 | 76 | CScreensaver.ScreenSaverProxy.new_for_bus(Gio.BusType.SESSION, 77 | Gio.DBusProxyFlags.NONE, 78 | c.SS_SERVICE, 79 | c.SS_PATH, 80 | None, 81 | self._on_proxy_ready) 82 | 83 | def handle_custom_saver(self, custom_saver): 84 | if self.action_id in [Action.LOCK, Action.ACTIVATE]: 85 | try: 86 | Popen(shlex.split(custom_saver), stdin=DEVNULL) 87 | except OSError as e: 88 | print("Error %d running %s: %s" % (e.errno, custom_saver, 89 | e.strerror)) 90 | else: 91 | print("Action not supported with custom screensaver.") 92 | 93 | def _on_proxy_ready(self, object, result, data=None): 94 | try: 95 | self.proxy = CScreensaver.ScreenSaverProxy.new_for_bus_finish(result) 96 | self.perform_action() 97 | except GLib.Error as e: 98 | print("Can't connect to screensaver: %d - %s" % (e.code, e.message)) 99 | self.mainloop.quit() 100 | 101 | def perform_action(self): 102 | if self.action_id == Action.EXIT: 103 | self.proxy.call_quit_sync() 104 | elif self.action_id == Action.QUERY: 105 | if self.proxy.call_get_active_sync(): 106 | print(_("The screensaver is active\n")) 107 | else: 108 | print(_("The screensaver is inactive\n")) 109 | elif self.action_id == Action.TIME: 110 | time = self.proxy.call_get_active_time_sync() 111 | if time == 0: 112 | print(_("The screensaver is not currently active.\n")) 113 | else: 114 | print(gettext.ngettext ("The screensaver has been active for %d second.\n", "The screensaver has been active for %d seconds.\n", time) % time) 115 | elif self.action_id == Action.LOCK: 116 | self.proxy.call_lock_sync(self.message) 117 | elif self.action_id == Action.ACTIVATE: 118 | self.proxy.call_set_active_sync(True) 119 | elif self.action_id == Action.DEACTIVATE: 120 | self.proxy.call_set_active_sync(False) 121 | 122 | self.mainloop.quit() 123 | 124 | if __name__ == "__main__": 125 | ml = GLib.MainLoop.new(None, True) 126 | main = ScreensaverCommand(ml) 127 | 128 | ml.run() 129 | -------------------------------------------------------------------------------- /src/cinnamon-screensaver-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import gi 4 | gi.require_version('Gtk', '3.0') 5 | gi.require_version('GdkX11', '3.0') 6 | gi.require_version('CScreensaver', '1.0') 7 | 8 | from gi.repository import Gtk, Gdk, CScreensaver, Gio 9 | 10 | import signal 11 | import gettext 12 | import argparse 13 | import os 14 | import setproctitle 15 | import sys 16 | 17 | import config 18 | import status 19 | from util import utils, settings 20 | from service import ScreensaverService 21 | 22 | signal.signal(signal.SIGINT, signal.SIG_DFL) 23 | gettext.install("cinnamon-screensaver", "/usr/share/locale") 24 | 25 | class Main(Gtk.Application): 26 | """ 27 | This is the main entry point to the program, and it shows up 28 | in the process list. We do any theme preparation here as well. 29 | 30 | We start the ScreensaverService from here. 31 | """ 32 | def __init__(self): 33 | super(Main, self).__init__(application_id="org.cinnamon.ScreenSaver", 34 | inactivity_timeout=30000, 35 | flags=Gio.ApplicationFlags.IS_SERVICE) 36 | 37 | # Our service must be set up before we register with the session manager. 38 | ScreensaverService() 39 | 40 | def do_activate(self): 41 | pass 42 | 43 | def do_startup(self): 44 | print("Starting screensaver...", flush=True) 45 | Gtk.Application.do_startup(self) 46 | 47 | parser = argparse.ArgumentParser(description='Cinnamon Screensaver') 48 | parser.add_argument('--debug', dest='debug', action='store_true', 49 | help='Print out some extra debugging info') 50 | parser.add_argument('--interactive-debug', dest='interactive', action='store_true', 51 | help='If multiple monitors are in use, only cover one monitor, and launch GtkInspector') 52 | parser.add_argument('--disable-locking', dest='lock_disabled', action='store_true', 53 | help='Disable the lock screen') 54 | parser.add_argument('--version', dest='version', action='store_true', 55 | help='Display the current version') 56 | parser.add_argument('--hold', dest='hold', action='store_true', 57 | help="Keep the process running." \ 58 | "Normally cinnamon-screensaver will exit after being idle for 30 seconds.") 59 | parser.add_argument('--no-fallback', dest='no_fallback', action='store_true', 60 | help="Don't spawn a fallback window when locking the screen.") 61 | args = parser.parse_args() 62 | 63 | if settings.get_custom_screensaver() != '': 64 | print("custom screensaver selected, exiting cinnamon-screensaver.", flush=True) 65 | quit() 66 | 67 | if args.version: 68 | print("cinnamon-screensaver %s" % config.VERSION) 69 | quit() 70 | 71 | status.LockEnabled = not args.lock_disabled 72 | status.Debug = args.debug 73 | status.InteractiveDebug = args.interactive 74 | status.UseFallback = not args.no_fallback 75 | # The inactivity-timeout will be ignored until there's been an initial hold. Simply 76 | # starting the app and letting it idle will end up with it exiting after 10s no matter 77 | # what the timeout. 78 | self.hold() 79 | 80 | if not args.hold: 81 | self.release() 82 | 83 | if status.Debug: 84 | print("Debug mode active", flush=True) 85 | 86 | if args.lock_disabled: 87 | print("Locking disabled", flush=True) 88 | 89 | # This is here mainly to allow the notification watcher to have a valid status.Debug value 90 | import singletons 91 | Gtk.Settings.get_default().connect("notify::gtk-theme-name", self.on_theme_changed) 92 | self.do_style_overrides() 93 | 94 | def on_theme_changed(self, settings, pspec, data=None): 95 | self.do_style_overrides() 96 | 97 | def do_style_overrides(self): 98 | """ 99 | Here we try to check for theme support in the current system's gtk theme. 100 | 101 | We do this by retrieving a string of the current theme's final style sheet, 102 | then searching for the .csstage style class. If it's found, we return, otherwise 103 | we add our own application-priority provider as a fallback. While we have the 104 | theme string, we check for a variable name we can use for the fallback experience, 105 | and adjust it in our application stylesheet if necessary before adding it as a 106 | provider. 107 | """ 108 | theme_name = Gtk.Settings.get_default().get_property("gtk-theme-name") 109 | provider = Gtk.CssProvider.get_named(theme_name) 110 | 111 | css = provider.to_string() 112 | 113 | if ".csstage" not in css: 114 | print("Cinnamon Screensaver support not found in current theme - adding some...", flush=True) 115 | 116 | path = os.path.join(config.pkgdatadir, "cinnamon-screensaver.css") 117 | 118 | f = open(path, 'r') 119 | fallback_css = f.read() 120 | f.close() 121 | 122 | if "@define-color theme_selected_bg_color" in css: 123 | pass 124 | elif "@define-color selected_bg_color" in css: 125 | print("replacing theme_selected_bg_color with selected_bg_color", flush=True) 126 | fallback_css = fallback_css.replace("@theme_selected_bg_color", "@selected_bg_color") 127 | else: 128 | print("replacing theme_selected_bg_color with Adwaita blue", flush=True) 129 | fallback_css = fallback_css.replace("@selected_bg_color", "#4a90d9") 130 | 131 | fallback_prov = Gtk.CssProvider() 132 | 133 | try: 134 | fallback_prov.load_from_data(fallback_css.encode()) 135 | Gtk.StyleContext.add_provider_for_screen (Gdk.Screen.get_default(), fallback_prov, 600) 136 | Gtk.StyleContext.reset_widgets(Gdk.Screen.get_default()) 137 | except Exception as e: 138 | print("Could not parse fallback css: %s" % str(e)) 139 | 140 | if __name__ == "__main__": 141 | setproctitle.setproctitle('cinnamon-screensaver') 142 | 143 | main = Main() 144 | main.run() 145 | -------------------------------------------------------------------------------- /src/cinnamon-screensaver.css: -------------------------------------------------------------------------------- 1 | .csstage { 2 | } 3 | 4 | .csstage .unlockbox { 5 | color: #eeeeee; 6 | font-size: 20px; 7 | text-shadow: 1px 1px alpha(black, 0.8); 8 | } 9 | 10 | .csstage .clock { 11 | color: #eeeeee; 12 | text-shadow: 1px 1px alpha(black, 0.8); 13 | } 14 | 15 | .csstage .toppanel { 16 | border-color: alpha(white, .2); 17 | border-style: solid; 18 | color: white; 19 | background-color: transparent; 20 | background-image: linear-gradient(to bottom, rgba(0, 0, 0, .2), rgba(0, 0, 0, 0)); 21 | } 22 | 23 | .csstage .audiopanel { 24 | border-width: 0 1px 1px 0; 25 | border-radius: 0 0 3px 0; 26 | background-color: rgba(255, 255, 255, .05); 27 | } 28 | 29 | .csstage .infopanel { 30 | border-width: 0 0 1px 1px; 31 | border-radius: 0 0 0 3px; 32 | background-color: rgba(255, 255, 255, .05); 33 | } 34 | 35 | .csstage .notificationwidget, 36 | .csstage .powerwidget { 37 | font-size: 12px; 38 | font-weight: bold; 39 | color: white; 40 | background-color: transparent; 41 | background-image: none; 42 | text-shadow: 1px 1px alpha(black, 0.8); 43 | padding: 6px; 44 | } 45 | 46 | .csstage .auth-message { 47 | font-size: 15px; 48 | color: red; 49 | } 50 | 51 | .csstage .caps-message { 52 | font-size: 15px; 53 | color: orange; 54 | } 55 | 56 | .csstage .framedimage { 57 | border-radius: 4px; 58 | border: 4px solid; 59 | background-color: alpha(grey, .25); 60 | background-clip: border-box; 61 | border-color: @theme_selected_bg_color; 62 | box-shadow: 1px 1px alpha(black, 0.8); 63 | } 64 | 65 | .csstage .passwordentry { 66 | font-size: 15px; 67 | box-shadow: none; 68 | border-color: alpha(white, .2); 69 | border-style: solid; 70 | color: white; 71 | background-image: none; 72 | background-color: rgba(0, 0, 0, .6); 73 | border-image: none; 74 | border-width: 1px; 75 | box-shadow: none; 76 | } 77 | 78 | .csstage .passwordentry progress { 79 | margin: 2px; 80 | padding: 0; 81 | border-radius: 0; 82 | border-width: 0 0 2px; 83 | border-color: alpha(@theme_selected_bg_color, .7); 84 | } 85 | 86 | .csstage .passwordentry progress:focus { 87 | background-color: transparent; 88 | } 89 | 90 | .csstage .passwordentry image.left { 91 | padding-right: 14px; 92 | } 93 | 94 | /* Use :backdrop for alt-text keyboard layout */ 95 | .csstage .passwordentry:backdrop { 96 | font-family: monospace; 97 | font-size: 14px; 98 | color: @theme_selected_bg_color; 99 | } 100 | 101 | .csstage .transparentbutton { 102 | -gtk-icon-style: requested; 103 | border-color: alpha(white, .2); 104 | border-image: none; 105 | box-shadow: none; 106 | background-image: none; 107 | background-color: rgba(0, 0, 0, .6); 108 | border-radius: 20px; 109 | -gtk-outline-radius: 20px; 110 | outline-color: transparent; 111 | color: alpha(white, .7); 112 | border-width: 1px; 113 | padding: 4px; 114 | } 115 | 116 | .csstage .transparentbutton image { 117 | color: alpha(white, .7); 118 | } 119 | 120 | .csstage .transparentbutton:disabled image { 121 | color: #333333; 122 | } 123 | 124 | .csstage .passwordentry:focus, 125 | .csstage .transparentbutton:focus { 126 | border-color: @theme_selected_bg_color; 127 | box-shadow: 1px 1px alpha(white, 0.1); 128 | } 129 | 130 | .csstage .passwordentry:hover, 131 | .csstage .transparentbutton:hover { 132 | border-color: alpha(white, .2); 133 | background-color: alpha(grey, .2); 134 | box-shadow: 1px 1px alpha(white, 0.1); 135 | } 136 | 137 | .csstage .passwordentry:hover:focus, 138 | .csstage .transparentbutton:hover:focus { 139 | border-color: @theme_selected_bg_color; 140 | box-shadow: 1px 1px alpha(white, 0.1); 141 | } 142 | 143 | .csstage .passwordentry:active, 144 | .csstage .transparentbutton:active { 145 | border-color: @theme_selected_bg_color; 146 | box-shadow: 1px 1px alpha(white, 0.1); 147 | } 148 | 149 | .csstage .passwordentry:active:focus, 150 | .csstage .transparentbutton:active:focus { 151 | border-color: @theme_selected_bg_color; 152 | box-shadow: 1px 1px alpha(white, 0.1); 153 | } 154 | 155 | .csstage .volumeslider { 156 | min-height: 24px; 157 | min-width: 100px; 158 | background-color: rgba(255, 255, 255, .1); 159 | color: @theme_selected_bg_color; 160 | padding: 3px 0px 3px 0px; 161 | } 162 | 163 | .csstage scale.volumeslider slider { 164 | min-height: 24px; 165 | min-width: 4px; 166 | margin: -9px; } 167 | 168 | .csstage .volumeslider:disabled { 169 | background-color: alpha(white, .5); 170 | } 171 | 172 | .csstage .trackname { 173 | font-family: ubuntu; 174 | font-size: 14px; 175 | text-shadow: 1px 1px alpha(black, 0.8); 176 | background-image: none; 177 | background-color: transparent; 178 | } 179 | 180 | .csstage .albumartist { 181 | font-family: ubuntu; 182 | font-size: 10px; 183 | text-shadow: 1px 1px alpha(black, 0.8); 184 | background-image: none; 185 | background-color: transparent; 186 | } 187 | 188 | .csstage GtkViewport { 189 | background-color: transparent; 190 | background-image: none; 191 | } 192 | 193 | .csstage .osk-button { 194 | -gtk-icon-style: requested; 195 | border-color: alpha(white, .2); 196 | border-image: none; 197 | box-shadow: none; 198 | background-image: none; 199 | background-color: transparent; 200 | border-radius: 4px; 201 | -gtk-outline-radius: 4px; 202 | outline-color: transparent; 203 | color: alpha(white, .7); 204 | border-width: 1px; 205 | padding: 4px; 206 | } 207 | 208 | .csstage .osk-button image { 209 | color: alpha(white, .7); 210 | } 211 | 212 | .csstage .osk-button:disabled image { 213 | color: #333333; 214 | } 215 | 216 | .csstage .osk-button:focus { 217 | background-color: alpha(grey, .3); 218 | border-color: @theme_selected_bg_color; 219 | box-shadow: 1px 1px alpha(white, 0.1); 220 | } 221 | 222 | .csstage .osk-button:hover { 223 | border-color: alpha(white, .2); 224 | background-color: alpha(grey, .2); 225 | box-shadow: 1px 1px alpha(white, 0.1); 226 | } 227 | 228 | .csstage .osk-button:hover:focus { 229 | border-color: @theme_selected_bg_color; 230 | background-color: alpha(grey, .4); 231 | box-shadow: 1px 1px alpha(white, 0.1); 232 | } 233 | 234 | .csstage .osk-button:active { 235 | border-color: @theme_selected_bg_color; 236 | background-color: alpha(grey, .5); 237 | box-shadow: 1px 1px alpha(white, 0.1); 238 | } 239 | 240 | .csstage .osk-button:active:focus { 241 | border-color: @theme_selected_bg_color; 242 | background-color: alpha(grey, .55); 243 | box-shadow: 1px 1px alpha(white, 0.1); 244 | } 245 | 246 | .csstage .osk-popover { 247 | background-color: black; 248 | } 249 | -------------------------------------------------------------------------------- /src/clock.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import CinnamonDesktop, GLib, Gtk, Gio, Pango 4 | 5 | from util import utils, trackers, settings 6 | from baseWindow import BaseWindow 7 | from floating import Floating 8 | 9 | MAX_WIDTH = 320 10 | MAX_WIDTH_LOW_RES = 200 11 | 12 | class ClockWidget(Floating, BaseWindow): 13 | """ 14 | ClockWidget displays the time and away message on the screen. 15 | 16 | It is a child of the Stage's GtkOverlay, and its placement is 17 | controlled by the overlay's child positioning function. 18 | 19 | When not Awake, it positions itself around all monitors 20 | using a timer which randomizes its halign and valign properties 21 | as well as its current monitor. 22 | """ 23 | def __init__(self, away_message=None, initial_monitor=0, low_res=False): 24 | super(ClockWidget, self).__init__(initial_monitor, Gtk.Align.START, Gtk.Align.CENTER) 25 | self.get_style_context().add_class("clock") 26 | self.set_property("margin", 6) 27 | 28 | self.clock = None 29 | self.low_res = low_res 30 | 31 | if not settings.get_show_clock(): 32 | return 33 | 34 | self.away_message = away_message 35 | 36 | box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 37 | self.add(box) 38 | box.show() 39 | 40 | self.label = Gtk.Label() 41 | self.label.show() 42 | self.label.set_line_wrap(True) 43 | self.label.set_alignment(0.5, 0.5) 44 | 45 | box.pack_start(self.label, True, False, 6) 46 | 47 | self.msg_label = Gtk.Label() 48 | self.msg_label.show() 49 | self.msg_label.set_line_wrap(True) 50 | self.msg_label.set_alignment(0.5, 0.5) 51 | 52 | if self.low_res: 53 | self.msg_label.set_max_width_chars(50) 54 | else: 55 | self.msg_label.set_max_width_chars(80) 56 | 57 | box.pack_start(self.msg_label, True, True, 6) 58 | 59 | self.clock = CinnamonDesktop.WallClock() 60 | self.set_clock_format() 61 | 62 | trackers.con_tracker_get().connect(self.clock, 63 | "notify::clock", 64 | self.on_clock_changed) 65 | 66 | tz = Gio.File.new_for_path(path="/etc/localtime") 67 | self.tz_monitor = tz.monitor_file(Gio.FileMonitorFlags.NONE, None) 68 | 69 | trackers.con_tracker_get().connect(self.tz_monitor, 70 | "changed", 71 | self.on_tz_changed) 72 | 73 | trackers.con_tracker_get().connect(self, 74 | "destroy", 75 | self.on_destroy) 76 | 77 | self.update_clock() 78 | 79 | def set_clock_format(self): 80 | date_format = "" 81 | time_format = "" 82 | 83 | if settings.get_use_custom_format(): 84 | date_format = settings.get_custom_date_format() 85 | time_format = settings.get_custom_time_format() 86 | else: 87 | date_format = self.clock.get_default_date_format() 88 | time_format = self.clock.get_default_time_format() 89 | 90 | # %l is 12-hr hours, but it adds a space to 0-9, which looks bad here. 91 | # The '-' modifier tells the GDateTime formatter not to pad the value. 92 | time_format = time_format.replace('%l', '%-l') 93 | 94 | time_font = Pango.FontDescription.from_string(settings.get_time_font()) 95 | date_font = Pango.FontDescription.from_string(settings.get_date_font()) 96 | 97 | if self.low_res: 98 | time_size = time_font.get_size() * .66 99 | date_size = date_font.get_size() * .66 100 | time_font.set_size(int(time_size)) 101 | date_font.set_size(int(date_size)) 102 | 103 | time_format = ('%s\n' + \ 104 | '%s') \ 105 | % (time_font.to_string(), time_format, date_font.to_string(), date_format) 106 | 107 | self.clock.set_format_string(time_format) 108 | 109 | def on_clock_changed(self, clock, pspec): 110 | self.update_clock() 111 | 112 | def on_tz_changed(self, monitor, file, other, event): 113 | self.update_clock() 114 | 115 | def update_clock(self): 116 | default_message = GLib.markup_escape_text (settings.get_default_away_message(), -1) 117 | font_message = Pango.FontDescription.from_string(settings.get_message_font()) 118 | 119 | if self.low_res: 120 | msg_size = font_message.get_size() * .66 121 | font_message.set_size(int(msg_size)) 122 | 123 | if self.away_message and self.away_message != "": 124 | user_name = utils.get_user_display_name() 125 | markup = ('' 126 | + '{1}' 127 | + '\n ~ {2}' 128 | + '\n ').format( 129 | font_message.to_string(), self.away_message, user_name) 130 | else: 131 | markup = '%s\n ' %\ 132 | (font_message.to_string(), default_message) 133 | 134 | self.label.set_markup(self.clock.get_clock()) 135 | self.msg_label.set_markup(markup) 136 | 137 | def set_message(self, msg=""): 138 | if not self.clock: 139 | return 140 | 141 | self.away_message = msg 142 | self.update_clock() 143 | 144 | def on_destroy(self, data=None): 145 | trackers.con_tracker_get().disconnect(self.clock, 146 | "notify::clock", 147 | self.on_clock_changed) 148 | 149 | trackers.con_tracker_get().disconnect(self.tz_monitor, 150 | "changed", 151 | self.on_tz_changed) 152 | 153 | trackers.con_tracker_get().disconnect(self, 154 | "destroy", 155 | self.on_destroy) 156 | 157 | self.clock = None 158 | self.tz_monitor = None 159 | -------------------------------------------------------------------------------- /src/config.py.in: -------------------------------------------------------------------------------- 1 | # Generated file - DO NOT EDIT. Edit config.py.in instead. 2 | 3 | prefix="@prefix@" 4 | datadir="@datadir@" 5 | localedir=datadir+"/locale" 6 | pkgdatadir="@pkgdatadir@" 7 | libdir="@libdir@" 8 | pkglibdir="@pkglibdir@" 9 | PACKAGE="@PACKAGE@" 10 | VERSION="@VERSION@" 11 | GETTEXT_PACKAGE="@GETTEXT_PACKAGE@" 12 | -------------------------------------------------------------------------------- /src/constants.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Idle time in seconds before the unlock dialog will disappear and we go back to sleep. 4 | UNLOCK_TIMEOUT = 30 5 | 6 | # Time in ms to wait before releasing the keyboard and mouse grabs 7 | # after an idle-activation is canceled. 8 | GRAB_RELEASE_TIMEOUT = 1 * 1000 9 | 10 | # Used by powerWidget - the level a battery must be below before the battery icon widget in the infopanel 11 | # will show even when asleep (active but not awake.) 12 | BATTERY_CRITICAL_PERCENT = 20 13 | 14 | # Cinnamon Screensaver 15 | SS_SERVICE = "org.cinnamon.ScreenSaver" 16 | SS_PATH = "/org/cinnamon/ScreenSaver" 17 | SS_INTERFACE = "org.cinnamon.ScreenSaver" 18 | -------------------------------------------------------------------------------- /src/dbusdepot/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/cinnamon-screensaver/d7e415936b6516e45e9e073a77c050abfb38b192/src/dbusdepot/__init__.py -------------------------------------------------------------------------------- /src/dbusdepot/accountsServiceClient.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import gi 4 | from gi.repository import GObject, CScreensaver, Gio, GLib 5 | import os 6 | import time 7 | 8 | from util import utils, trackers 9 | 10 | class AccountsServiceClient(GObject.Object): 11 | """ 12 | Singleton for working with the AccountsService, which we use 13 | to retrieve the user's face image and their real name. 14 | """ 15 | ACCOUNTS_SERVICE = "org.freedesktop.Accounts" 16 | ACCOUNTS_PATH = "/org/freedesktop/Accounts" 17 | 18 | __gsignals__ = { 19 | 'accounts-ready': (GObject.SignalFlags.RUN_LAST, None, ()), 20 | } 21 | 22 | def __init__(self): 23 | super(AccountsServiceClient, self).__init__() 24 | 25 | self.accounts = None 26 | self.user = None 27 | 28 | print("Loading AccountsService") 29 | 30 | CScreensaver.AccountsServiceProxy.new_for_bus(Gio.BusType.SYSTEM, 31 | Gio.DBusProxyFlags.DO_NOT_AUTO_START, 32 | self.ACCOUNTS_SERVICE, 33 | self.ACCOUNTS_PATH, 34 | None, 35 | self.on_accounts_connected) 36 | 37 | def on_accounts_connected(self, source, res): 38 | try: 39 | self.accounts = CScreensaver.AccountsServiceProxy.new_for_bus_finish(res) 40 | except GLib.Error as e: 41 | print(f"Could not connect to AccountsService: {e}", flush=True) 42 | return 43 | 44 | self.accounts.call_find_user_by_name(utils.get_user_name(), None, self.got_user_proxy) 45 | 46 | def got_user_proxy(self, source, res): 47 | try: 48 | proxy_path = self.accounts.call_find_user_by_name_finish(res) 49 | except GLib.Error as e: 50 | print(f"Could not get AccountsService User object path: {e}", flush=True) 51 | return 52 | 53 | CScreensaver.AccountsUserProxy.new_for_bus(Gio.BusType.SYSTEM, 54 | Gio.DBusProxyFlags.NONE, 55 | self.ACCOUNTS_SERVICE, 56 | proxy_path, 57 | None, 58 | self.on_user_loaded) 59 | 60 | def on_user_loaded(self, source, res): 61 | try: 62 | self.user = CScreensaver.AccountsUserProxy.new_for_bus_finish(res) 63 | except GLib.Error as e: 64 | print(f"Could not create AccountsService.User: {e}", flush=True) 65 | 66 | print("AccountsService ready") 67 | self.emit("accounts-ready") 68 | 69 | def get_real_name(self): 70 | if self.user is not None: 71 | return self.user.get_property("real-name") 72 | 73 | return None 74 | 75 | def get_face_path(self): 76 | face = os.path.join(GLib.get_home_dir(), ".face") 77 | if os.path.exists(face): 78 | return face 79 | 80 | if self.user is not None: 81 | accounts_path = self.user.get_property("icon-file") 82 | if os.path.exists(accounts_path): 83 | return accounts_path 84 | 85 | return None 86 | -------------------------------------------------------------------------------- /src/dbusdepot/baseClient.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import Gio, GObject, GLib 4 | 5 | class BaseClient(GObject.GObject): 6 | """ 7 | The base constructor for all of our generated GDBusProxies. 8 | 9 | This initializes and sets self.proxy, then fires on_client_setup_complete() 10 | or on_failure(), which the subclasses implement, depending on the outcome. 11 | 12 | These clients are technically one more level of abstraction than would be 13 | needed in a perfect world, where all dbus proxies work as they should and 14 | are supported properly by the actual providers they proxy for, but they 15 | provide a convenient and relatively clean way of implementing workarounds 16 | and alternate ways of retrieving or calculating values when the interface 17 | itself is broken in some way (this is common.) 18 | 19 | They also provide a bit of fault tolerance in cases where one or more of 20 | these providers do not exist on the bus, and we can provide sane default 21 | values for our widgets. 22 | """ 23 | def __init__(self, bustype, proxy_class, service, path): 24 | """ 25 | Asynchronously initialize the GDBusProxy - we'll call 26 | on_client_setup_complete() or on_failure() depending on this outcome. 27 | """ 28 | super(BaseClient, self).__init__() 29 | 30 | self.proxy_class = proxy_class 31 | self.path = path 32 | 33 | self.proxy = None 34 | 35 | self.watch_name_id = Gio.bus_watch_name(bustype, 36 | service, 37 | Gio.BusNameWatcherFlags.NONE, 38 | self._on_appeared, 39 | self.on_failure) 40 | 41 | def _on_appeared(self, connection, name, name_owner, data=None): 42 | try: 43 | self.proxy_class.new(connection, 44 | Gio.DBusProxyFlags.NONE, 45 | name, 46 | self.path, 47 | None, 48 | self._on_proxy_ready) 49 | except GLib.Error: 50 | self.proxy = None 51 | self.on_failure() 52 | 53 | Gio.bus_unwatch_name(self.watch_name_id) 54 | 55 | def _on_proxy_ready(self, object, result, data=None): 56 | self.proxy = self.proxy_class.new_finish(result) 57 | 58 | self.on_client_setup_complete() 59 | 60 | def ensure_proxy_alive(self): 61 | """ 62 | Use this as a safety check to see if a given proxy is valid 63 | and owned. 64 | """ 65 | return self.proxy and self.proxy.get_name_owner() is not None 66 | 67 | def on_client_setup_complete(self): 68 | """ 69 | Subclasses must implement this - to complete setup after self.proxy is 70 | successfully initialized. 71 | """ 72 | print("You need to implement on_client_setup_complete(self) in your real client class") 73 | raise NotImplementedError 74 | 75 | def on_failure(self, *args): 76 | """ 77 | Can be implemented by subclasses, but not necessary. Nothing further is done anyhow 78 | if on_client_setup_complete() is never called. 79 | """ 80 | pass 81 | -------------------------------------------------------------------------------- /src/dbusdepot/cinnamonClient.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import Gio, CScreensaver 4 | 5 | from dbusdepot.baseClient import BaseClient 6 | 7 | class CinnamonClient(BaseClient): 8 | """ 9 | Simple client to talk to Cinnamon's dbus interface. Currently 10 | its only use is for attempting to force an exit from overview 11 | and expo mode (both of which do a fullscreen grab and would prevent 12 | the screensaver from acquiring one.) 13 | """ 14 | CINNAMON_SERVICE = "org.Cinnamon" 15 | CINNAMON_PATH = "/org/Cinnamon" 16 | 17 | def __init__(self): 18 | super(CinnamonClient, self).__init__(Gio.BusType.SESSION, 19 | CScreensaver.CinnamonProxy, 20 | self.CINNAMON_SERVICE, 21 | self.CINNAMON_PATH) 22 | 23 | def on_client_setup_complete(self): 24 | pass 25 | 26 | def exit_expo_and_overview(self): 27 | if self.ensure_proxy_alive(): 28 | self.proxy.set_property("overview-active", False) 29 | self.proxy.set_property("expo-active", False) 30 | 31 | def on_failure(self, *args): 32 | print("Failed to connect to Cinnamon - screensaver will not activate when expo or overview modes are active.", flush=True) 33 | -------------------------------------------------------------------------------- /src/dbusdepot/consoleKitClient.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import Gio, GLib, CScreensaver 4 | 5 | from dbusdepot.baseClient import BaseClient 6 | from dbusdepot.loginInterface import LoginInterface 7 | 8 | 9 | class ConsoleKitClient(LoginInterface, BaseClient): 10 | """ 11 | Client for communicating with ConsoleKit - this is a backup 12 | to logind, if it's not available. 13 | """ 14 | CK_SERVICE = "org.freedesktop.ConsoleKit" 15 | CK_MANAGER_PATH = "/org/freedesktop/ConsoleKit/Manager" 16 | 17 | def __init__(self): 18 | """ 19 | We first try to connect to the ConsoleKit manager. 20 | """ 21 | super(ConsoleKitClient, self).__init__(Gio.BusType.SYSTEM, 22 | CScreensaver.ConsoleKitManagerProxy, 23 | self.CK_SERVICE, 24 | self.CK_MANAGER_PATH) 25 | 26 | self.session_proxy = None 27 | self.session_id = None 28 | 29 | def on_client_setup_complete(self): 30 | """ 31 | If our manager connection succeeds, we ask it for the current session id and 32 | then attempt to connect to its session interface. 33 | """ 34 | self.session_id = self.proxy.call_get_current_session_sync() 35 | 36 | try: 37 | self.session_proxy = CScreensaver.ConsoleKitSessionProxy.new_for_bus(Gio.BusType.SYSTEM, 38 | Gio.DBusProxyFlags.NONE, 39 | self.CK_SERVICE, 40 | self.session_id, 41 | None, 42 | self.on_session_ready, 43 | None) 44 | except GLib.Error: 45 | self.session_proxy = None 46 | self.on_failure() 47 | 48 | def on_session_ready(self, object, result, data=None): 49 | """ 50 | Once we're connected to the session interface, we can respond to signals sent from 51 | it - used primarily when returning from suspend, hibernation or the login screen. 52 | """ 53 | self.session_proxy = CScreensaver.ConsoleKitSessionProxy.new_for_bus_finish(result) 54 | 55 | self.session_proxy.connect("unlock", lambda proxy: self.emit("unlock")) 56 | self.session_proxy.connect("lock", lambda proxy: self.emit("lock")) 57 | self.session_proxy.connect("active-changed", self.on_active_changed) 58 | 59 | self.emit("startup-status", True) 60 | 61 | def on_active_changed(self, proxy, active, data=None): 62 | if active: 63 | self.emit("active") 64 | 65 | def on_failure(self, *args): 66 | self.emit("startup-status", False) 67 | -------------------------------------------------------------------------------- /src/dbusdepot/keybindingHandlerClient.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import Gio, CScreensaver 4 | 5 | from dbusdepot.baseClient import BaseClient 6 | 7 | class KeybindingHandlerClient(BaseClient): 8 | """ 9 | Connects to the media key interface of cinnamon-settings-daemon. 10 | This calls the keybinding handler for shortcuts received by the 11 | Keybindings object. 12 | """ 13 | KEYBINDING_HANDLER_SERVICE = "org.cinnamon.SettingsDaemon.KeybindingHandler" 14 | KEYBINDING_HANDLER_PATH = "/org/cinnamon/SettingsDaemon/KeybindingHandler" 15 | 16 | def __init__(self): 17 | super(KeybindingHandlerClient, self).__init__(Gio.BusType.SESSION, 18 | CScreensaver.KeybindingHandlerProxy, 19 | self.KEYBINDING_HANDLER_SERVICE, 20 | self.KEYBINDING_HANDLER_PATH) 21 | 22 | def on_client_setup_complete(self): 23 | pass 24 | 25 | def handle_keybinding(self, mk_type): 26 | if self.proxy: 27 | self.proxy.call_handle_keybinding(mk_type, None, None) 28 | 29 | def on_failure(self, *args): 30 | print("Failed to connect to the keybinding handler - media key shortcuts will not work.") 31 | -------------------------------------------------------------------------------- /src/dbusdepot/loginInterface.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import GObject 4 | 5 | class LoginInterface(GObject.Object): 6 | """ 7 | A common signal interface for our Logind and ConsoleKit clients. 8 | """ 9 | __gsignals__ = { 10 | 'startup-status': (GObject.SignalFlags.RUN_LAST, None, (bool, )), 11 | 'lock': (GObject.SignalFlags.RUN_LAST, None, ()), 12 | 'unlock': (GObject.SignalFlags.RUN_LAST, None, ()), 13 | 'active': (GObject.SignalFlags.RUN_LAST, None, ()), 14 | } 15 | 16 | def __init__(self, *args): 17 | super(LoginInterface, self).__init__(*args) 18 | -------------------------------------------------------------------------------- /src/dbusdepot/logindClient.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import Gio, GLib, CScreensaver 4 | import os 5 | import subprocess 6 | 7 | import status 8 | from dbusdepot.baseClient import BaseClient 9 | from dbusdepot.loginInterface import LoginInterface 10 | from util.utils import DEBUG 11 | 12 | class LogindClient(LoginInterface, BaseClient): 13 | """ 14 | A client for communicating with logind. At startup we check 15 | for its availability, falling back to ConsoleKit if it's not 16 | available. 17 | """ 18 | LOGIND_SERVICE = "org.freedesktop.login1" 19 | LOGIND_PATH = "/org/freedesktop/login1" 20 | 21 | def __init__(self): 22 | """ 23 | We first try to connect to the logind manager. 24 | """ 25 | super(LogindClient, self).__init__(Gio.BusType.SYSTEM, 26 | CScreensaver.LogindManagerProxy, 27 | self.LOGIND_SERVICE, 28 | self.LOGIND_PATH) 29 | 30 | self.pid = os.getpid() 31 | 32 | self.session_path = None 33 | self.session_proxy = None 34 | 35 | def on_client_setup_complete(self): 36 | """ 37 | If our manager connection succeeds, we get the current session path and attempt 38 | to connect to its interface. 39 | """ 40 | try: 41 | current_user = GLib.get_user_name() 42 | 43 | cmd = "loginctl show-user %s -pDisplay --value" % current_user 44 | current_session_id = subprocess.check_output(cmd, shell=True).decode().replace("\n", "") 45 | 46 | self.session_path = self.proxy.call_get_session_sync(current_session_id, None) 47 | DEBUG("login client: found session path for user '%s' (session_id: %s): %s" % (current_user, current_session_id, self.session_path)) 48 | except GLib.Error as e: 49 | print("login client: could not get session path: %s" % e, flush=True) 50 | self.on_failure() 51 | return 52 | 53 | CScreensaver.LogindSessionProxy.new_for_bus(Gio.BusType.SYSTEM, 54 | Gio.DBusProxyFlags.NONE, 55 | self.LOGIND_SERVICE, 56 | self.session_path, 57 | None, 58 | self.on_session_ready, 59 | None) 60 | 61 | def on_session_ready(self, object, result, data=None): 62 | """ 63 | Once we're connected to the session interface, we can respond to signals sent from 64 | it - used primarily when returning from suspend, hibernation or the login screen. 65 | """ 66 | self.session_proxy = CScreensaver.LogindSessionProxy.new_for_bus_finish(result) 67 | 68 | self.session_proxy.connect("unlock", lambda proxy: self.emit("unlock")) 69 | self.session_proxy.connect("lock", lambda proxy: self.emit("lock")) 70 | self.session_proxy.connect("notify::active", self.on_active_changed) 71 | 72 | self.emit("startup-status", True) 73 | 74 | def on_active_changed(self, proxy, pspec, data=None): 75 | if self.session_proxy.get_property("active"): 76 | self.emit("active") 77 | 78 | def on_failure(self, *args): 79 | self.emit("startup-status", False) 80 | -------------------------------------------------------------------------------- /src/dbusdepot/meson.build: -------------------------------------------------------------------------------- 1 | app_py = [ 2 | '__init__.py', 3 | 'accountsServiceClient.py', 4 | 'baseClient.py', 5 | 'cinnamonClient.py', 6 | 'consoleKitClient.py', 7 | 'keybindingHandlerClient.py', 8 | 'logindClient.py', 9 | 'loginInterface.py', 10 | 'mediaPlayerWatcher.py', 11 | 'muffinClient.py', 12 | 'nameBlocker.py', 13 | 'sessionClient.py', 14 | 'uPowerClient.py' 15 | ] 16 | 17 | install_data(app_py, install_dir: join_paths(pkgdatadir, 'dbusdepot')) 18 | -------------------------------------------------------------------------------- /src/dbusdepot/muffinClient.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import gi 4 | gi.require_version('CScreensaver', '1.0') 5 | from gi.repository import GLib, Gio, GObject, CScreensaver 6 | 7 | # TODO 8 | # self.monitors, etc.. replace or at least prefer this over CsScreen, as it will be more accurate. 9 | # Nothing currently listens to muffin-config-changed. This class is only used to initialize the event filters. 10 | 11 | class MuffinClient(GObject.Object): 12 | MUFFIN_SERVICE = "org.cinnamon.Muffin.DisplayConfig" 13 | MUFFIN_PATH = "/org/cinnamon/Muffin/DisplayConfig" 14 | 15 | __gsignals__ = { 16 | 'muffin-config-changed': (GObject.SignalFlags.RUN_LAST, None, ()), 17 | } 18 | 19 | def __init__(self): 20 | GObject.Object.__init__(self) 21 | 22 | self.proxy = None 23 | self.using_fractional_scaling = False 24 | 25 | try: 26 | self.proxy = CScreensaver.MuffinDisplayConfigProxy.new_for_bus_sync(Gio.BusType.SESSION, 27 | Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES | 28 | Gio.DBusProxyFlags.DO_NOT_AUTO_START, 29 | self.MUFFIN_SERVICE, 30 | self.MUFFIN_PATH, 31 | None) 32 | self.proxy.connect("monitors-changed", self.on_monitors_changed) 33 | # cinnamon restart (monitors-changed isn't emitted at muffin startup) 34 | self.proxy.connect("notify::g-name-owner", self.on_name_owner_changed) 35 | self.update() 36 | except GLib.Error as e: 37 | print(f"Could not connect to Muffin's DisplayConfig service: {e}", flush=True) 38 | 39 | def on_monitors_changed(self, proxy): 40 | self.update() 41 | 42 | def on_name_owner_changed(self, proxy, pspec): 43 | if proxy.get_name_owner() is not None: 44 | self.update() 45 | 46 | def update(self): 47 | if self.read_current_state(): 48 | self.emit("muffin-config-changed") 49 | 50 | def read_current_state(self, *args): 51 | old_scaling = self.using_fractional_scaling 52 | 53 | if self.proxy.get_name_owner() is None: 54 | print("Muffin not running, skipping fractional scaling check.") 55 | return False 56 | 57 | try: 58 | logical_monitors = self.proxy.call_get_current_state_sync(None)[2] 59 | except GLib.Error as e: 60 | print(f"Could not read current state from Muffin: {e}", flush=True) 61 | self.using_fractional_scaling = False 62 | return self.using_fractional_scaling != old_scaling 63 | 64 | fractional = False 65 | previous_scale = -1 66 | 67 | for monitor in logical_monitors.unpack(): 68 | scale = monitor[2] 69 | 70 | # one or more monitors using some non-integer scale. 71 | if int(scale) != scale: 72 | fractional = True 73 | break 74 | 75 | # multiple monitors with non-identical scales 76 | if previous_scale > 0 and scale != previous_scale: 77 | fractional = True 78 | break 79 | 80 | previous_scale = scale 81 | 82 | self.using_fractional_scaling = fractional 83 | print(f"Fractional scaling active: {self.using_fractional_scaling}", flush=True) 84 | return self.using_fractional_scaling != old_scaling 85 | 86 | def get_using_fractional_scaling(self): 87 | return self.using_fractional_scaling 88 | -------------------------------------------------------------------------------- /src/dbusdepot/nameBlocker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import Gio, GObject, GLib 4 | 5 | import status 6 | from util.utils import DEBUG 7 | 8 | class NameBlocker(GObject.GObject): 9 | """ 10 | Terminates other screensavers. Ideally we'd just take ownership of their 11 | dbus names, but due to how DE detection works in xdg-screensaver, that script 12 | incorrectly assumes we're a gnome or mate session if their names are owned. 13 | """ 14 | def __init__(self): 15 | super(NameBlocker, self).__init__() 16 | 17 | self.owned_names = [] 18 | 19 | self.watch("org.gnome.ScreenSaver") 20 | self.watch("org.mate.ScreenSaver") 21 | 22 | def watch(self, name): 23 | handle = Gio.bus_watch_name(Gio.BusType.SESSION, 24 | name, 25 | Gio.BusNameWatcherFlags.NONE, 26 | self.on_name_appeared, 27 | self.on_name_lost) 28 | 29 | self.owned_names.append((handle, "name")) 30 | 31 | def unwatch_all(self): 32 | for (handle, name) in self.owned_names: 33 | DEBUG("Releasing dbus name: %s" % name) 34 | 35 | Gio.bus_unown_name(handle) 36 | 37 | self.owned_names = [] 38 | 39 | def on_name_appeared(self, connection, name, name_owner, data=None): 40 | DEBUG("%s appeared on the session bus, killing it" % name) 41 | 42 | connection.call(name_owner, 43 | "/" + name.replace(".", "/"), 44 | name, 45 | "Quit", 46 | None, 47 | None, 48 | Gio.DBusCallFlags.NONE, 49 | -1, 50 | None) 51 | 52 | def on_name_lost(self, connection, name, data=None): 53 | DEBUG("%s is gone from the session bus" % name) 54 | 55 | def do_dispose(self): 56 | DEBUG("nameBlocker do_dispose") 57 | 58 | self.unwatch_all() 59 | 60 | super(NameBlocker, self).do_dispose() 61 | -------------------------------------------------------------------------------- /src/dbusdepot/sessionClient.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import Gio, GObject, CScreensaver 4 | 5 | from dbusdepot.baseClient import BaseClient 6 | 7 | class SessionClient(BaseClient): 8 | """ 9 | This client is for connecting to the session manager's Presence 10 | interface - it is responsible for triggering activation of the 11 | screensaver when the session goes into an idle state. 12 | """ 13 | __gsignals__ = { 14 | 'idle-changed': (GObject.SignalFlags.RUN_LAST, None, (bool, )), 15 | } 16 | 17 | GSM_SERVICE = "org.gnome.SessionManager" 18 | GSM_PRESENCE_PATH = "/org/gnome/SessionManager/Presence" 19 | 20 | def __init__(self): 21 | super(SessionClient, self).__init__(Gio.BusType.SESSION, 22 | CScreensaver.SessionPresenceProxy, 23 | self.GSM_SERVICE, 24 | self.GSM_PRESENCE_PATH) 25 | 26 | self.idle = False 27 | 28 | def on_client_setup_complete(self): 29 | self.proxy.connect("status-changed", self.on_status_changed) 30 | 31 | def on_status_changed(self, proxy, status): 32 | new_idle = status == 3 33 | if new_idle != self.idle: 34 | self.idle = new_idle 35 | self.emit("idle-changed", self.idle) 36 | 37 | def on_failure(self, *args): 38 | print("Failed to connect to session manager - idle detection will not work.") 39 | -------------------------------------------------------------------------------- /src/floating.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import Gtk 4 | import random 5 | import status 6 | 7 | from util import trackers 8 | from util import settings 9 | 10 | class PositionInfo(): 11 | def __init__(self, monitor, halign, valign): 12 | self.monitor = monitor 13 | self.halign = halign 14 | self.valign = valign 15 | 16 | class Floating: 17 | def __init__(self, initial_monitor=0, halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER): 18 | super(Floating, self).__init__() 19 | self.awake_position = PositionInfo(initial_monitor, halign, valign) 20 | self.next_position = self.awake_position 21 | self.current_monitor = initial_monitor 22 | self.apply_next_position() 23 | 24 | def start_positioning(self): 25 | self.apply_next_position() 26 | self.show() 27 | 28 | def set_next_position(self, monitor, halign, valign): 29 | self.next_position = PositionInfo(monitor, halign, valign) 30 | 31 | def apply_next_position(self): 32 | self.current_monitor = self.next_position.monitor 33 | self.set_halign(self.next_position.halign) 34 | self.set_valign(self.next_position.valign) 35 | 36 | def set_awake_position(self, monitor): 37 | self.awake_position.monitor = monitor 38 | self.next_position = self.awake_position 39 | -------------------------------------------------------------------------------- /src/infoPanel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import Gtk 4 | 5 | import status 6 | from util import utils, trackers, settings 7 | from baseWindow import BaseWindow 8 | from widgets.notificationWidget import NotificationWidget 9 | from widgets.powerWidget import PowerWidget 10 | 11 | class InfoPanel(BaseWindow): 12 | """ 13 | Upper right corner panel - contains the notification counter and any 14 | battery indicator(s) - this panel will generally show if it has anything 15 | relevant to say, regardless of our Awake state. 16 | """ 17 | def __init__(self): 18 | super(InfoPanel, self).__init__() 19 | self.monitor_index = status.screen.get_primary_monitor() 20 | 21 | self.update_geometry() 22 | 23 | if not settings.get_show_info_panel(): 24 | self.disabled = True 25 | return 26 | 27 | self.show_power = False 28 | self.show_notifications = False 29 | 30 | self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 31 | self.box.set_halign(Gtk.Align.FILL) 32 | self.box.get_style_context().add_class("toppanel") 33 | self.box.get_style_context().add_class("infopanel") 34 | self.add(self.box) 35 | 36 | hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) 37 | self.box.pack_start(hbox, False, False, 6) 38 | 39 | self.notification_widget = NotificationWidget() 40 | self.notification_widget.set_no_show_all(True) 41 | hbox.pack_start(self.notification_widget, True, True, 2) 42 | self.notification_widget.connect("notification", self.on_notification_received) 43 | 44 | self.power_widget = PowerWidget() 45 | self.power_widget.set_no_show_all(True) 46 | self.power_widget.connect("power-state-changed",self.on_power_state_changed) 47 | hbox.pack_start(self.power_widget, True, True, 2) 48 | 49 | self.show_all() 50 | 51 | def refresh_power_state(self): 52 | if self.disabled: 53 | return 54 | 55 | self.power_widget.refresh() 56 | 57 | def on_notification_received(self, obj): 58 | self.update_visibility() 59 | 60 | def on_power_state_changed(self, obj): 61 | self.update_visibility() 62 | 63 | def update_visibility(self): 64 | """ 65 | Determines whether or not to show the panel, depending on: 66 | - Whether the power widget should show (are we on battery?) 67 | - Whether the notification widget should show (are there any?) 68 | 69 | The panel will show if either of its child indicators has useful info. 70 | """ 71 | if self.disabled: 72 | return 73 | 74 | do_show = False 75 | battery_critical = False 76 | 77 | self.show_power = self.power_widget.should_show() 78 | if self.show_power: 79 | battery_critical = self.power_widget.battery_critical 80 | 81 | self.show_notifications = self.notification_widget.should_show() 82 | 83 | # Determine if we want to show all the time or only when status.Awake 84 | if status.Awake: 85 | if self.show_power or self.show_notifications: 86 | do_show = True 87 | elif self.show_notifications or battery_critical: 88 | do_show = True 89 | 90 | self.set_visible(do_show) 91 | self.power_widget.set_visible(self.show_power) 92 | self.notification_widget.set_visible(self.show_notifications) 93 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | subdir('dbusdepot') 2 | subdir('pamhelper') 3 | subdir('util') 4 | subdir('widgets') 5 | 6 | # will not subdir the binfiles directory as it only contains symlinked scripts 7 | 8 | config_py = configure_file( 9 | output: 'config.py', 10 | input: 'config.py.in', 11 | configuration: misc_conf 12 | ) 13 | 14 | app_py = [ 15 | '__init__.py', 16 | 'albumArt.py', 17 | 'audioPanel.py', 18 | 'baseWindow.py', 19 | 'clock.py', 20 | config_py, 21 | 'constants.py', 22 | 'floating.py', 23 | 'infoPanel.py', 24 | 'manager.py', 25 | 'monitorView.py', 26 | 'osk.py', 27 | 'passwordEntry.py', 28 | 'playerControl.py', 29 | 'service.py', 30 | 'singletons.py', 31 | 'stage.py', 32 | 'status.py', 33 | 'unlock.py', 34 | 'volumeControl.py', 35 | ] 36 | 37 | app_css = [ 38 | 'cinnamon-screensaver.css', 39 | ] 40 | 41 | install_data(app_py + app_css, 42 | install_dir: pkgdatadir 43 | ) 44 | 45 | app_scripts = [ 46 | ['cinnamon-screensaver-main.py', 'cinnamon-screensaver'], 47 | ['cinnamon-screensaver-command.py', 'cinnamon-screensaver-command'] 48 | ] 49 | 50 | foreach script : app_scripts 51 | prefix_info = configuration_data() 52 | prefix_info.set('install_dir', pkgdatadir) 53 | prefix_info.set('target', script[0]) 54 | prefix_info.set('pkglibdir', pkglibdir) 55 | prefix_info.set('typelibdir', typelibdir) 56 | 57 | bin_file = configure_file( 58 | input : 'binfile.in', 59 | output: script[1], 60 | configuration: prefix_info, 61 | install_dir: get_option('bindir'), 62 | install_mode: 'rwxr-xr-x' 63 | ) 64 | 65 | install_data( 66 | script[0], 67 | install_dir: pkgdatadir, 68 | install_mode: 'rwxr-xr-x' 69 | ) 70 | endforeach 71 | -------------------------------------------------------------------------------- /src/monitorView.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import Gtk, Gio, GLib, GObject 4 | import re 5 | import cairo 6 | import signal 7 | 8 | import status 9 | from baseWindow import BaseWindow 10 | from util import settings, utils, trackers 11 | 12 | class WallpaperStack(Gtk.Stack): 13 | """ 14 | WallpaperStack implements a crossfade when changing backgrounds. 15 | 16 | An initial image is made and added to the GtkStack. When a new 17 | image is requested, it is created and added to the stack, then 18 | a crossfade transition is made to the new child. The former 19 | visible stack child is then destroyed. And this repeats. 20 | """ 21 | def __init__(self): 22 | super(WallpaperStack, self).__init__() 23 | 24 | self.set_transition_type(Gtk.StackTransitionType.NONE) 25 | self.set_transition_duration(1000) 26 | 27 | self.initialized = False 28 | self.current = None 29 | self.queued = None 30 | 31 | def transition_to_image(self, image): 32 | """ 33 | Queues a new image in the stack, and begins the transition to it. 34 | """ 35 | self.queued = image 36 | self.queued.set_visible(True) 37 | 38 | trackers.con_tracker_get().connect_after(image, 39 | "draw", 40 | self.shade_wallpaper) 41 | 42 | self.add(self.queued) 43 | 44 | if not self.initialized: 45 | self.visible_image_changed() 46 | self.set_transition_type(Gtk.StackTransitionType.CROSSFADE) 47 | self.initialized = True 48 | return 49 | 50 | self.set_visible_child(self.queued) 51 | GObject.timeout_add(2000, self.visible_image_changed) 52 | 53 | def visible_image_changed(self, data=None): 54 | if self.current is not None: 55 | tmp = self.current 56 | 57 | self.remove(tmp) 58 | tmp.destroy() 59 | 60 | self.current = self.queued 61 | self.queued = None 62 | 63 | return False 64 | 65 | def shade_wallpaper(self, widget, cr): 66 | """ 67 | This draw callback adds a shade mask over the current 68 | image. It is uniform when not Awake, and acquires a 69 | significant gradient vertically framing the unlock dialog 70 | when Awake. 71 | """ 72 | cr.set_source_rgba(0.0, 0.0, 0.0, 0.7) 73 | cr.paint() 74 | return False 75 | 76 | class MonitorView(BaseWindow): 77 | """ 78 | A monitor-sized child of the stage that is responsible for displaying 79 | the currently-selected wallpaper or appropriate plug-in. 80 | """ 81 | def __init__(self, index): 82 | super(MonitorView, self).__init__() 83 | 84 | self.monitor_index = index 85 | 86 | self.update_geometry() 87 | 88 | self.wallpaper_stack = WallpaperStack() 89 | self.wallpaper_stack.show() 90 | self.wallpaper_stack.set_halign(Gtk.Align.FILL) 91 | self.wallpaper_stack.set_valign(Gtk.Align.FILL) 92 | self.add(self.wallpaper_stack) 93 | 94 | self.show_all() 95 | 96 | def set_next_wallpaper_image(self, image): 97 | self.wallpaper_stack.transition_to_image(image) 98 | -------------------------------------------------------------------------------- /src/pamhelper/meson.build: -------------------------------------------------------------------------------- 1 | app_py = [ 2 | 'authClient.py', 3 | ] 4 | 5 | install_data(app_py, 6 | install_dir: join_paths(pkgdatadir, 'pamhelper')) 7 | 8 | executable('cinnamon-screensaver-pam-helper', 9 | 'cinnamon-screensaver-pam-helper.c', 10 | dependencies: [gio_unix, glib], 11 | include_directories: inc, 12 | link_with: libcscreensaver, 13 | install_rpath: pkglibdir, 14 | install: true, 15 | install_dir: pkglibdir 16 | ) 17 | -------------------------------------------------------------------------------- /src/service.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import gi 4 | gi.require_version('CScreensaver', '1.0') 5 | 6 | from gi.repository import Gtk, CScreensaver, Gio, GObject 7 | 8 | import constants as c 9 | from manager import ScreensaverManager 10 | import status 11 | from util.utils import DEBUG 12 | 13 | class ScreensaverService(GObject.Object): 14 | """ 15 | This is the dbus service we run ourselves. It is the owner 16 | of our ScreensaverManager, and implements the org.cinnamon.Screensaver 17 | dbus interface. It is through this service that the screensaver 18 | is controlled. 19 | """ 20 | def __init__(self): 21 | super(ScreensaverService, self).__init__() 22 | 23 | try: 24 | self.bus = Gio.bus_get_sync(Gio.BusType.SESSION, None) 25 | except: 26 | print("Unable to get session connection, fatal!") 27 | exit(1) 28 | 29 | self.interface = CScreensaver.ScreenSaverSkeleton.new() 30 | 31 | self.interface.connect("handle-lock", self.handle_lock) 32 | self.interface.connect("handle-quit", self.handle_quit) 33 | self.interface.connect("handle-set-active", self.handle_set_active) 34 | self.interface.connect("handle-get-active", self.handle_get_active) 35 | self.interface.connect("handle-get-active-time", self.handle_get_active_time) 36 | self.interface.connect("handle-simulate-user-activity", self.handle_simulate_user_activity) 37 | 38 | self.manager = ScreensaverManager() 39 | self.manager.connect("active-changed", self.on_active_changed) 40 | 41 | """ 42 | The stage constructs itself and fades in asynchronously, and most importantly, 43 | as an idle callback. This can cause the screensaver to not be fully active when 44 | a call to suspend is made. Cinnamon-session calls to lock the screensaver 45 | synchronously, and if we don't completely finish construction before returning 46 | the dbus call completion, there's a chance the idle callback won't run until 47 | after the computer is resumed. 48 | 49 | We get an active-changed signal whenever the screensaver becomes completely active 50 | or inactive, so we'll queue up running iface.complete_lock() until we receive that signal. 51 | 52 | This allows the screensaver to be fully activated prior to cinnamon-session allowing 53 | the suspend/hibernate/whatever process to continue. 54 | 55 | For reference, this is called in cinnamon-session's csm-manager.c "manager_perhaps_lock" 56 | method. 57 | """ 58 | self.lock_queue = [] 59 | 60 | self.interface.export(self.bus, c.SS_PATH) 61 | 62 | def poke_process(self, method_name): 63 | if not status.Awake: 64 | DEBUG("service: '%s' received, poking application." % method_name) 65 | 66 | app = Gio.Application.get_default() 67 | app.hold() 68 | app.release() 69 | 70 | # Interface handlers 71 | def handle_lock(self, iface, inv, msg): 72 | """ 73 | We want to be able to respond to locking synchronously for security reasons. 74 | Things like cinnamon-settings-daemon (power) and cinnamon-session use the 75 | cinnamon-screensaver-command helper script to lock the screen during certain events 76 | like sleep, hibernate, user switching, etc... 77 | 78 | If we receive a lock request, we forward it to the manager. It returns True if we were 79 | already active (or active and locked.) If so, we can complete the invocation immediately. 80 | Otherwise, we queue the invocation, and wait for an "active-changed" signal from the manager, 81 | and then complete the invocations (in the same order they were received.) 82 | """ 83 | 84 | self.poke_process("Lock") 85 | 86 | if self.manager.lock(msg): 87 | iface.complete_lock(inv) 88 | else: 89 | self.lock_queue.append(inv) 90 | 91 | return True 92 | 93 | def handle_quit(self, iface, inv): 94 | self.manager.unlock() 95 | 96 | iface.complete_quit(inv) 97 | 98 | Gio.Application.get_default().quit() 99 | 100 | return True 101 | 102 | def handle_set_active(self, iface, inv, active): 103 | self.poke_process("SetActive") 104 | 105 | if active: 106 | self.manager.set_active(active) 107 | else: 108 | self.manager.unlock() 109 | 110 | iface.complete_set_active(inv) 111 | 112 | return True 113 | 114 | def handle_get_active(self, iface, inv): 115 | self.poke_process("GetActive") 116 | 117 | active = self.manager.get_active() 118 | 119 | iface.complete_get_active(inv, active) 120 | 121 | return True 122 | 123 | def handle_get_active_time(self, iface, inv): 124 | self.poke_process("GetActiveTime") 125 | 126 | atime = self.manager.get_active_time() 127 | 128 | iface.complete_get_active_time(inv, atime) 129 | 130 | return True 131 | 132 | def handle_simulate_user_activity(self, iface, inv): 133 | self.poke_process("SimulateUserActivity") 134 | 135 | if self.manager.is_locked(): 136 | self.manager.simulate_user_activity() 137 | else: 138 | DEBUG("Calling XResetScreenSaver") 139 | 140 | CScreensaver.Screen.reset_screensaver() 141 | 142 | iface.complete_simulate_user_activity(inv) 143 | 144 | return True 145 | 146 | def on_active_changed(self, manager, state, data=None): 147 | GObject.idle_add(self.on_active_changed_idle, state) 148 | 149 | def on_active_changed_idle(self, state): 150 | self.lock_queue.reverse() 151 | 152 | while len(self.lock_queue) > 0: 153 | invocation = self.lock_queue.pop() 154 | self.interface.complete_lock(invocation) 155 | 156 | self.lock_queue = [] 157 | 158 | self.interface.emit_active_changed(state) 159 | -------------------------------------------------------------------------------- /src/status.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Our global state vars 4 | Active = False # Screensaver visible or not - False when it's completely idle. 5 | Locked = False # Independent of Active, whether the unlock dialog will show when we become Awake. 6 | Awake = False # Whether the unlock dialog is visible or not. 7 | 8 | # A list of focusable widgets that the user can tab between in the unlock screen. See FocusNavigator. 9 | focusWidgets = [] 10 | 11 | # This is different than the preference that turns off locking - that only prevents idle locking. The 12 | # user can still lock explicitly. The function checks for the existence of correct PAM files, 13 | # as well as adjusting the UID if this process is started as root. 14 | LockEnabled = True 15 | UseFallback = True 16 | 17 | # Enables extra PAM/authentication/notification debugging 18 | # TODO: We do a *lot* of logging now, we should just use a debug() function that checks 19 | # for debug mode internally, instead of 'if status.Debug' everywhere. 20 | Debug = False 21 | 22 | # Forces the Stage to only cover a single monitor and launch a GtkInspector window. 23 | InteractiveDebug = False 24 | 25 | # If the wallpaper aspect is 'spanned' we will only create one MonitorView and manage it slightly 26 | # differently. This is an easy place to keep track of that. This is set in singletons.py. 27 | Spanned = False 28 | 29 | screen = None 30 | -------------------------------------------------------------------------------- /src/tests/test-auth: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import gi 4 | gi.require_version('CinnamonDesktop', '3.0') 5 | 6 | from gi.repository import CinnamonDesktop, GLib 7 | import getpass 8 | 9 | name = input("Username: ") 10 | password = getpass.getpass() 11 | 12 | def callback(success): 13 | if success: 14 | print("\n\nAccepted!") 15 | else: 16 | print("\n\nFailed!") 17 | 18 | ml.quit() 19 | 20 | CinnamonDesktop.desktop_check_user_password(name, password, callback) 21 | 22 | ml = GLib.MainLoop.new(None, True) 23 | ml.run() 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/tests/test-layouts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import sys 4 | import signal 5 | import gettext 6 | 7 | import gi 8 | gi.require_version('Gtk', '3.0') 9 | gi.require_version('CScreensaver', '1.0') 10 | 11 | sys.path.append("/usr/share/cinnamon-screensaver") 12 | gettext.install("cinnamon-screensaver", "/usr/share/locale") 13 | 14 | from gi.repository import Gtk 15 | 16 | from unlock import PasswordEntry 17 | 18 | signal.signal(signal.SIGINT, signal.SIG_DFL) 19 | 20 | class Main: 21 | def __init__(self): 22 | win = Gtk.Window() 23 | 24 | box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) 25 | win.add(box) 26 | 27 | self.entry = PasswordEntry() 28 | box.pack_start(self.entry, True, True, 2) 29 | box.show_all() 30 | 31 | win.connect("delete-event", lambda w, e: Gtk.main_quit()) 32 | win.present() 33 | 34 | Gtk.main() 35 | 36 | if __name__ == "__main__": 37 | main = Main() 38 | -------------------------------------------------------------------------------- /src/tests/test-notifications: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import subprocess 4 | import time 5 | import os 6 | 7 | print("Assertion: Notifications should not appear while the screensaver is active") 8 | print("Test: Activate the screensaver, then send a test notification a couple seconds later.") 9 | print("Expected outcome: Notification should *not* appear over the screensaver window.\n") 10 | 11 | print("Starting cinnamon-screensaver if necessary...") 12 | ps_output = subprocess.check_output(["ps", "-A"]) 13 | if "cinnamon-screen" not in ps_output.decode("utf-8"): 14 | os.system("cinnamon-screensaver &") 15 | time.sleep(2) 16 | print("Ok\n") 17 | 18 | print("Locking screen...") 19 | os.system("cinnamon-screensaver-command --lock") 20 | print("Ok\n") 21 | 22 | print("waiting a few seconds...") 23 | time.sleep(5) 24 | print("Ok\n") 25 | 26 | print("Sending a notification...") 27 | os.system("notify-send 'Test notification' 'You should not see this while the screen is locked'") 28 | print("Ok\n") 29 | 30 | print("waiting a few more seconds...") 31 | time.sleep(5) 32 | os.system("cinnamon-screensaver-command --deactivate") 33 | print("Unlocked. Terminating.") 34 | quit() 35 | 36 | -------------------------------------------------------------------------------- /src/tests/test-osk: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import sys 4 | import signal 5 | import gettext 6 | 7 | import gi 8 | gi.require_version('Gtk', '3.0') 9 | gi.require_version('CScreensaver', '1.0') 10 | 11 | sys.path.append("/usr/share/cinnamon-screensaver") 12 | gettext.install("cinnamon-screensaver", "/usr/share/locale") 13 | 14 | from gi.repository import Gtk, CScreensaver 15 | 16 | from osk import OnScreenKeyboard 17 | import status 18 | 19 | signal.signal(signal.SIGINT, signal.SIG_DFL) 20 | 21 | class Main: 22 | def __init__(self): 23 | status.screen = CScreensaver.Screen.new(True) 24 | 25 | win = Gtk.Window() 26 | 27 | box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) 28 | win.add(box) 29 | win.get_style_context().add_class("csstage") 30 | 31 | self.osk = OnScreenKeyboard() 32 | box.pack_start(self.osk, True, True, 2) 33 | box.show_all() 34 | 35 | win.connect("delete-event", lambda w, e: Gtk.main_quit()) 36 | win.present() 37 | 38 | Gtk.main() 39 | 40 | if __name__ == "__main__": 41 | main = Main() 42 | -------------------------------------------------------------------------------- /src/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/cinnamon-screensaver/d7e415936b6516e45e9e073a77c050abfb38b192/src/util/__init__.py -------------------------------------------------------------------------------- /src/util/eventHandler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import Gdk 4 | 5 | import status 6 | from util.keybindings import KeyBindings 7 | 8 | MOTION_THRESHOLD = 100 9 | 10 | class EventHandler: 11 | """ 12 | The EventHandler receives all user key, button and motion events 13 | for the application. At various points it can be receiving them 14 | from the Stage window, an offscreen window, or the root GdkWindow. 15 | 16 | All events are stopped from passing beyond this point, to prevent 17 | any unintended events from propagating to Cinnamon or Muffin. 18 | """ 19 | def __init__(self, manager): 20 | self.manager = manager 21 | self.keybindings_handler = KeyBindings(manager) 22 | 23 | self.last_x = -1 24 | self.last_y = -1 25 | 26 | 27 | def on_user_activity(self): 28 | """ 29 | Any user event is a 'wake' event, and is propagated to the stage 30 | in order to reset our unlock cancellation timer. 31 | """ 32 | self.manager.simulate_user_activity() 33 | 34 | def on_motion_event(self, event): 35 | """ 36 | Any mouse movement is sent here - there is a threshold to reach when 37 | asleep, so that inadvertent motion doesn't wake the system unintentionally. 38 | """ 39 | if status.Awake: 40 | self.on_user_activity() 41 | return Gdk.EVENT_PROPAGATE 42 | 43 | if self.last_x == -1 or self.last_y == -1: 44 | self.last_x = event.x 45 | self.last_y = event.y 46 | return Gdk.EVENT_PROPAGATE 47 | 48 | distance = max(abs(self.last_x - event.x), abs(self.last_y - event.y)) 49 | 50 | if distance > MOTION_THRESHOLD: 51 | self.on_user_activity() 52 | 53 | return Gdk.EVENT_PROPAGATE 54 | 55 | def on_button_press_event(self, event): 56 | """ 57 | Any button presses are swallowed after interacting 58 | with their receiving widgets (in the case of buttons, entry, etc...) 59 | """ 60 | self.on_user_activity() 61 | 62 | return Gdk.EVENT_STOP 63 | 64 | def on_key_press_event(self, event): 65 | """ 66 | Any key events are checked with the KeybindingHandler in case 67 | a media shortcut is being used, or it's a special keystroke such 68 | as Escape or tab. 69 | 70 | Any other presses are sent to the password entry or swallowed. 71 | """ 72 | if self.keybindings_handler.maybe_handle_event(event): 73 | return Gdk.EVENT_STOP 74 | 75 | if status.Active: 76 | if status.Locked: 77 | self.manager.queue_dialog_key_event(event) 78 | 79 | self.on_user_activity() 80 | 81 | return Gdk.EVENT_STOP 82 | -------------------------------------------------------------------------------- /src/util/fader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import GLib, GObject 4 | 5 | class Fader: 6 | """ 7 | The Fader is used by the Stage to fade in from the desktop. 8 | 9 | It uses a widget tick callback to change the opacity over time. 10 | """ 11 | def __init__(self, widget): 12 | self.widget = widget 13 | self.finished_cb = None 14 | 15 | self.repositioned = False 16 | 17 | self.starting_opacity = 0.0 18 | self.current_opacity = 0.0 19 | self.target_opacity = 0.0 20 | 21 | self.tick_id = 0 22 | 23 | self.start_time = 0 24 | self.end_time = 0 25 | 26 | def fade_in(self, ms, reposition_cb=None, finished_cb=None): 27 | GObject.idle_add(self._fade_in_idle, ms, reposition_cb, finished_cb) 28 | 29 | def fade_out(self, ms, finished_cb=None): 30 | GObject.idle_add(self._fade_out_idle, ms, finished_cb) 31 | 32 | def cancel(self): 33 | if self.tick_id > 0: 34 | self.widget.remove_tick_callback(self.tick_id) 35 | self.tick_id = 0 36 | 37 | def _fade_in_idle(self, ms, reposition_cb=None, finished_cb=None): 38 | self.finished_cb = finished_cb 39 | self.reposition_cb = reposition_cb 40 | self.current_opacity = self.starting_opacity = self.widget.get_opacity() 41 | self.target_opacity = 1.0 42 | 43 | if self.widget.get_mapped(): 44 | self.start_time = self.widget.get_frame_clock().get_frame_time() 45 | self.end_time = self.start_time + (ms * 1000) # ms to microsec 46 | 47 | if self.tick_id == 0: 48 | self.tick_id = self.widget.add_tick_callback(self._on_frame_tick_fade_in) 49 | 50 | self._fade_in_step(self.start_time) 51 | else: 52 | self.finished_cb() 53 | 54 | return GLib.SOURCE_REMOVE 55 | 56 | def _fade_out_idle(self, ms, finished_cb=None): 57 | self.finished_cb = finished_cb 58 | self.current_opacity = self.starting_opacity = self.widget.get_opacity() 59 | self.target_opacity = 0.0 60 | 61 | if self.widget.get_mapped(): 62 | self.start_time = self.widget.get_frame_clock().get_frame_time() 63 | self.end_time = self.start_time + (ms * 1000) # ms to microsec 64 | 65 | if self.tick_id == 0: 66 | self.tick_id = self.widget.add_tick_callback(self._on_frame_tick_fade_out) 67 | 68 | self._fade_out_step(self.start_time) 69 | else: 70 | self.finished_cb() 71 | 72 | return GLib.SOURCE_REMOVE 73 | 74 | def _on_frame_tick_fade_in(self, widget, clock, data=None): 75 | now = clock.get_frame_time() 76 | 77 | self._fade_in_step(now) 78 | 79 | if not self.repositioned and self.current_opacity > .03: 80 | self.repositioned = True 81 | self.reposition_cb() 82 | 83 | if self.current_opacity == self.target_opacity: 84 | self.tick_id = 0 85 | self.finished_cb() 86 | return GLib.SOURCE_REMOVE 87 | 88 | return GLib.SOURCE_CONTINUE 89 | 90 | def _fade_in_step(self, now): 91 | if now < self.end_time: 92 | t = ((now - self.start_time) / (self.end_time - self.start_time) * self.target_opacity) 93 | else: 94 | t = self.target_opacity 95 | 96 | self.current_opacity = t 97 | 98 | self.widget.set_opacity(self.current_opacity) 99 | 100 | def _on_frame_tick_fade_out(self, widget, clock, data=None): 101 | now = clock.get_frame_time() 102 | 103 | self._fade_out_step(now) 104 | 105 | if self.current_opacity == self.target_opacity: 106 | self.tick_id = 0 107 | self.finished_cb() 108 | return GLib.SOURCE_REMOVE 109 | 110 | return GLib.SOURCE_CONTINUE 111 | 112 | def _fade_out_step(self, now): 113 | if now < self.end_time: 114 | t = self.starting_opacity - (((now - self.start_time) / (self.end_time - self.start_time)) * self.starting_opacity) 115 | else: 116 | t = self.target_opacity 117 | 118 | self.current_opacity = t 119 | 120 | self.widget.set_opacity(self.current_opacity) 121 | -------------------------------------------------------------------------------- /src/util/focusNavigator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # coding: utf-8 3 | 4 | from gi.repository import Gtk 5 | 6 | import status 7 | 8 | class FocusNavigator: 9 | """ 10 | FocusNavigator helps with tab navigation between 11 | widgets in our status.focusWidgets list. 12 | 13 | Since we handle most user events ourselves, we also 14 | need to handle Tab events correctly. 15 | """ 16 | def __init__(self, widgets=[]): 17 | status.focusWidgets = widgets 18 | 19 | def _get_focus_index(self): 20 | widgets = status.focusWidgets 21 | focus_index = -1 22 | for widget in widgets: 23 | if widget.has_focus(): 24 | focus_index = widgets.index(widget) 25 | break 26 | 27 | return focus_index 28 | 29 | def _focus_first_possible(self): 30 | widgets = status.focusWidgets 31 | 32 | for widget in widgets: 33 | if widget.get_sensitive(): 34 | widget.grab_focus() 35 | widget.grab_default() 36 | break 37 | 38 | def _focus_next(self, current): 39 | widgets = status.focusWidgets 40 | new = current + 1 41 | 42 | if new >= len(widgets): 43 | new = 0 44 | 45 | if not widgets[new].get_sensitive(): 46 | self._focus_next(new) 47 | return 48 | 49 | widgets[new].grab_focus() 50 | widgets[new].grab_default() 51 | 52 | def _focus_previous(self, current): 53 | widgets = status.focusWidgets 54 | new = current - 1 55 | 56 | if new < 0: 57 | new = len(widgets) - 1 58 | 59 | if not widgets[new].get_sensitive(): 60 | self._focus_previous(new) 61 | return 62 | 63 | widgets[new].grab_focus() 64 | widgets[new].grab_default() 65 | 66 | def navigate(self, reverse): 67 | current_index = self._get_focus_index() 68 | 69 | if current_index == -1: 70 | self._focus_first_possible() 71 | if reverse: 72 | self._focus_previous(current_index) 73 | else: 74 | self._focus_next(current_index) 75 | 76 | def activate_focus(self): 77 | widgets = status.focusWidgets 78 | 79 | focus_index = self._get_focus_index() 80 | 81 | if focus_index == -1: 82 | return 83 | 84 | widget = widgets[focus_index] 85 | 86 | if isinstance(widget, Gtk.Button): 87 | widget.clicked() 88 | elif isinstance(widget, Gtk.Entry): 89 | widget.activate() 90 | 91 | def get_focused_widget(self): 92 | widgets = status.focusWidgets 93 | 94 | focus_index = self._get_focus_index() 95 | 96 | if focus_index == -1: 97 | return None 98 | 99 | return widgets[focus_index] 100 | -------------------------------------------------------------------------------- /src/util/keybindings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import gi 4 | gi.require_version('CDesktopEnums', '3.0') 5 | from gi.repository import Gtk, GObject, Gdk, Gio, CinnamonDesktop 6 | from gi.repository.CDesktopEnums import MediaKeyType as MK 7 | 8 | import status 9 | import singletons 10 | from util import settings 11 | 12 | ALLOWED_ACTIONS = [MK.MUTE, 13 | MK.VOLUME_UP, 14 | MK.VOLUME_UP_QUIET, 15 | MK.VOLUME_DOWN, 16 | MK.VOLUME_DOWN_QUIET, 17 | MK.MIC_MUTE, 18 | MK.EJECT, 19 | MK.PLAY, 20 | MK.PAUSE, 21 | MK.STOP, 22 | MK.PREVIOUS, 23 | MK.NEXT, 24 | MK.REWIND, 25 | MK.FORWARD, 26 | MK.REPEAT, 27 | MK.RANDOM, 28 | MK.TOUCHPAD, 29 | MK.TOUCHPAD_ON, 30 | MK.TOUCHPAD_OFF, 31 | MK.SHUTDOWN, 32 | MK.SUSPEND, 33 | MK.HIBERNATE, 34 | MK.SCREEN_BRIGHTNESS_UP, 35 | MK.SCREEN_BRIGHTNESS_DOWN, 36 | MK.ROTATE_VIDEO, 37 | MK.KEYBOARD_BRIGHTNESS_UP, 38 | MK.KEYBOARD_BRIGHTNESS_DOWN, 39 | MK.KEYBOARD_BRIGHTNESS_TOGGLE] 40 | 41 | class ShortcutAction(GObject.GObject): 42 | """ 43 | A class representing a group of keybindings for a specific 44 | media key action. 45 | """ 46 | def __init__(self, action, bindings): 47 | super(ShortcutAction, self).__init__() 48 | 49 | self.action = action 50 | self.bindings = bindings 51 | 52 | self.parsed = [] 53 | 54 | for binding in self.bindings: 55 | key, codes, mods = Gtk.accelerator_parse_with_keycode(binding) 56 | 57 | self.parsed.append((key, codes, mods)) 58 | 59 | def activate(self, key, keycode, mods): 60 | for binding in self.parsed: 61 | if (key == binding[0] or keycode in binding[1]) and mods == binding[2]: 62 | return self.action 63 | 64 | return -1 65 | 66 | class KeyBindings(GObject.GObject): 67 | """ 68 | Receives keystrokes from the EventHandler - it checks for media key 69 | activation, as well as special keys like Escape or Tab, and performs the 70 | appropriate actions. 71 | """ 72 | def __init__(self, manager): 73 | super(KeyBindings, self).__init__() 74 | 75 | self.manager = manager 76 | 77 | self.client = singletons.KeybindingHandlerClient 78 | 79 | self.keymap = Gdk.Keymap.get_default() 80 | 81 | self.media_key_settings = Gio.Settings(schema_id="org.cinnamon.desktop.keybindings.media-keys") 82 | self.shortcut_actions = [] 83 | 84 | self.load_bindings() 85 | 86 | def load_bindings(self): 87 | self.shortcut_actions = [] 88 | 89 | for action_id in ALLOWED_ACTIONS: 90 | bindings = self.media_key_settings.get_strv(CinnamonDesktop.desktop_get_media_key_string(action_id)) 91 | 92 | action = ShortcutAction(action_id, bindings) 93 | 94 | self.shortcut_actions.append(action) 95 | 96 | def maybe_handle_event(self, event): 97 | if event.type != Gdk.EventType.KEY_PRESS: 98 | return False 99 | 100 | filtered_state = Gdk.ModifierType(event.state & ~(Gdk.ModifierType.MOD2_MASK | Gdk.ModifierType.LOCK_MASK)) 101 | 102 | if filtered_state == 0 and event.keyval == Gdk.KEY_Escape: 103 | if status.Awake: 104 | self.manager.cancel_unlocking() 105 | return True 106 | 107 | if event.keyval in (Gdk.KEY_Menu, Gdk.KEY_F10): 108 | return True 109 | 110 | if status.Awake: 111 | if event.keyval in (Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab): 112 | if event.keyval == Gdk.KEY_ISO_Left_Tab: 113 | self.manager.propagate_tab_event(True) 114 | else: 115 | self.manager.propagate_tab_event(False) 116 | return True 117 | elif (event.keyval in (Gdk.KEY_space, Gdk.KEY_Return, Gdk.KEY_KP_Enter)) and \ 118 | isinstance(self.manager.get_focused_widget(), Gtk.Button): 119 | self.manager.propagate_activation() 120 | return True 121 | 122 | if settings.get_allow_shortcuts(): 123 | for entry in self.shortcut_actions: 124 | res = entry.activate(event.keyval, event.hardware_keycode, filtered_state) 125 | 126 | if res == -1: 127 | continue 128 | else: 129 | self.client.handle_keybinding(res) 130 | return True 131 | 132 | return False 133 | -------------------------------------------------------------------------------- /src/util/meson.build: -------------------------------------------------------------------------------- 1 | app_py = [ 2 | '__init__.py', 3 | 'eventHandler.py', 4 | 'fader.py', 5 | 'focusNavigator.py', 6 | 'keybindings.py', 7 | 'settings.py', 8 | 'trackers.py', 9 | 'utils.py' 10 | ] 11 | 12 | install_data(app_py, install_dir: join_paths(pkgdatadir, 'util')) 13 | -------------------------------------------------------------------------------- /src/util/settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import Gio 4 | 5 | # Background settings, the only one interested in this is Backgrounds in 6 | # singletons.py, and then only for the "changed" signal - it reloads 7 | # any individual gsettings internally. 8 | 9 | bg_settings = Gio.Settings(schema_id="org.cinnamon.desktop.background") 10 | 11 | # Screensaver settings - we have no need to listen to changes here, 12 | # They're read anew each time they're used, and while the screensaver 13 | # is active, the user won't be changing them. 14 | 15 | ss_settings = Gio.Settings(schema_id="org.cinnamon.desktop.screensaver") 16 | DEFAULT_MESSAGE_KEY = "default-message" 17 | SCREENSAVER_NAME_KEY = "screensaver-name" 18 | CUSTOM_SCREENSAVER_KEY = "custom-screensaver-command" 19 | USER_SWITCH_ENABLED_KEY = "user-switch-enabled" 20 | IDLE_ACTIVATE_KEY = "idle-activation-enabled" 21 | LOCK_ENABLED_KEY = "lock-enabled" 22 | LOCK_DELAY_KEY = "lock-delay" 23 | USE_CUSTOM_FORMAT_KEY = "use-custom-format" 24 | DATE_FORMAT_KEY = "date-format" 25 | TIME_FORMAT_KEY = "time-format" 26 | FONT_DATE_KEY = "font-date" 27 | FONT_MESSAGE_KEY = "font-message" 28 | FONT_TIME_KEY = "font-time" 29 | KB_LAYOUT_KEY = "layout-group" 30 | 31 | SHOW_CLOCK_KEY = "show-clock" 32 | SHOW_ALBUMART = "show-album-art" 33 | ALLOW_SHORTCUTS = "allow-keyboard-shortcuts" 34 | ALLOW_MEDIA_CONTROL = "allow-media-control" 35 | SHOW_INFO_PANEL = "show-info-panel" 36 | FLOATING_WIDGETS = "floating-widgets" 37 | 38 | # Interface settings - the same logic applies here as above - we don't 39 | # need to listen to changes to these. 40 | if_settings = Gio.Settings(schema_id="org.cinnamon.desktop.interface") 41 | KBD_LAYOUT_SHOW_FLAGS = "keyboard-layout-show-flags" 42 | KBD_LAYOUT_USE_CAPS = "keyboard-layout-use-upper" 43 | KBD_LAYOUT_PREFER_VARIANT = "keyboard-layout-prefer-variant-names" 44 | 45 | osk_settings = Gio.Settings(schema_id="org.cinnamon.keyboard") 46 | OSK_TYPE = "keyboard-type" 47 | OSK_SIZE = "keyboard-size" 48 | OSK_ACTIVATION = "activation-mode" 49 | 50 | a11y_settings = Gio.Settings(schema_id="org.cinnamon.desktop.a11y.applications") 51 | OSK_A11Y_ENABLED = "screen-keyboard-enabled" 52 | 53 | # Every setting has a getter (and setter, rarely). This is mainly for 54 | # organizational purposes and cleanliness - it's easier to read in the 55 | # main code if you see "settings.get_default_away_message()" than seeing 56 | # "settings.ss_settings.get_string(settings.DEFAULT_MESSAGE_KEY)" or keeping 57 | # instances of GioSettings wherever we need them. 58 | 59 | def _check_string(string): 60 | if string and string != "": 61 | return string 62 | 63 | return "" 64 | 65 | def get_default_away_message(): 66 | msg = ss_settings.get_string(DEFAULT_MESSAGE_KEY) 67 | 68 | return _check_string(msg) 69 | 70 | def get_custom_screensaver(): 71 | cmd = ss_settings.get_string(CUSTOM_SCREENSAVER_KEY) 72 | 73 | return _check_string(cmd) 74 | 75 | def get_user_switch_enabled(): 76 | return ss_settings.get_boolean(USER_SWITCH_ENABLED_KEY) 77 | 78 | def get_idle_activate(): 79 | return ss_settings.get_boolean(IDLE_ACTIVATE_KEY) 80 | 81 | def get_idle_lock_enabled(): 82 | return ss_settings.get_boolean(LOCK_ENABLED_KEY) 83 | 84 | def get_idle_lock_delay(): 85 | return ss_settings.get_uint(LOCK_DELAY_KEY) 86 | 87 | def get_use_custom_format(): 88 | return ss_settings.get_boolean(USE_CUSTOM_FORMAT_KEY) 89 | 90 | def get_custom_date_format(): 91 | date_format = ss_settings.get_string(DATE_FORMAT_KEY) 92 | 93 | return _check_string(date_format) 94 | 95 | def get_custom_time_format(): 96 | time_format = ss_settings.get_string(TIME_FORMAT_KEY) 97 | 98 | return _check_string(time_format) 99 | 100 | def get_date_font(): 101 | date_font = ss_settings.get_string(FONT_DATE_KEY) 102 | 103 | return _check_string(date_font) 104 | 105 | def get_message_font(): 106 | message_font = ss_settings.get_string(FONT_MESSAGE_KEY) 107 | 108 | return _check_string(message_font) 109 | 110 | def get_time_font(): 111 | time_font = ss_settings.get_string(FONT_TIME_KEY) 112 | 113 | return _check_string(time_font) 114 | 115 | def get_show_flags(): 116 | return if_settings.get_boolean(KBD_LAYOUT_SHOW_FLAGS) 117 | 118 | def get_show_upper_case_layout(): 119 | return if_settings.get_boolean(KBD_LAYOUT_USE_CAPS) 120 | 121 | def get_use_layout_variant_names(): 122 | return if_settings.get_boolean(KBD_LAYOUT_PREFER_VARIANT) 123 | 124 | def get_kb_group(): 125 | return ss_settings.get_int(KB_LAYOUT_KEY) 126 | 127 | def set_kb_group(group): 128 | return ss_settings.set_int(KB_LAYOUT_KEY, group) 129 | 130 | def get_show_clock(): 131 | return ss_settings.get_boolean(SHOW_CLOCK_KEY) 132 | 133 | def get_show_albumart(): 134 | return ss_settings.get_boolean(SHOW_ALBUMART) 135 | 136 | def get_allow_shortcuts(): 137 | return ss_settings.get_boolean(ALLOW_SHORTCUTS) 138 | 139 | def get_allow_media_control(): 140 | return ss_settings.get_boolean(ALLOW_MEDIA_CONTROL) 141 | 142 | def get_show_info_panel(): 143 | return ss_settings.get_boolean(SHOW_INFO_PANEL) 144 | 145 | def get_allow_floating(): 146 | return ss_settings.get_boolean(FLOATING_WIDGETS) 147 | 148 | def get_osk_type(): 149 | return osk_settings.get_string(OSK_TYPE) 150 | 151 | def get_osk_a11y_active(): 152 | return a11y_settings.get_boolean(OSK_A11Y_ENABLED) and osk_settings.get_string(OSK_ACTIVATION) == 'accessible' -------------------------------------------------------------------------------- /src/util/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import GLib, Gio, Gdk, Gtk 4 | import os 5 | import grp 6 | import subprocess 7 | import xapp.os 8 | 9 | import config 10 | import status 11 | 12 | # Various utility functions that are used in multiple places. 13 | 14 | def nofail_locale_to_utf8(string): 15 | try: 16 | ret = GLib.locale_to_utf8(string, -1, 0, 0) 17 | except: 18 | try: 19 | ret, br, bw = GLib.locale_to_utf8(string, -1) 20 | except: 21 | ret = string 22 | 23 | return ret 24 | 25 | def get_user_name(): 26 | name = GLib.get_user_name() 27 | 28 | utf8_name = None 29 | 30 | if name: 31 | utf8_name = nofail_locale_to_utf8(name) 32 | 33 | return utf8_name 34 | 35 | def get_user_display_name(): 36 | name = GLib.get_real_name() 37 | 38 | if not name or name == "Unknown": 39 | name = get_user_name() 40 | 41 | utf8_name = None 42 | 43 | if name: 44 | utf8_name = nofail_locale_to_utf8(name) 45 | 46 | return utf8_name 47 | 48 | def get_host_name(): 49 | name = GLib.get_host_name() 50 | 51 | utf8_name = None 52 | 53 | if name: 54 | utf8_name = nofail_locale_to_utf8(name) 55 | 56 | return utf8_name 57 | 58 | def user_can_lock(): 59 | if not status.LockEnabled: 60 | return False 61 | 62 | name = get_user_name() 63 | 64 | # KeyError is generated if group doesn't exist, ignore it and allow lock 65 | try: 66 | group = grp.getgrnam("nopasswdlogin") 67 | if name in group.gr_mem: 68 | return False 69 | except KeyError: 70 | pass 71 | 72 | # Don't lock the screensaver in guest or live sessions 73 | if xapp.os.is_live_session() or xapp.os.is_guest_session(): 74 | return False 75 | 76 | return True 77 | 78 | def process_is_running(name): 79 | res = "" 80 | 81 | try: 82 | res = subprocess.check_output(["pidof", name]) 83 | except subprocess.CalledProcessError: 84 | pass 85 | 86 | return res != "" 87 | 88 | def do_user_switch(): 89 | GLib.idle_add(do_user_switch_timeout) 90 | 91 | def do_user_switch_timeout(data=None): 92 | if process_is_running("mdm"): 93 | command = "%s %s" % ("mdmflexiserver", "--startnew Standard") 94 | ctx = Gdk.Display.get_default().get_app_launch_context() 95 | 96 | app = Gio.AppInfo.create_from_commandline(command, "mdmflexiserver", Gio.AppInfoCreateFlags.NONE) 97 | if app: 98 | app.launch(None, ctx) 99 | elif process_is_running("gdm"): 100 | command = "%s %s" % ("gdmflexiserver", "--startnew Standard") 101 | ctx = Gdk.Display.get_default().get_app_launch_context() 102 | 103 | app = Gio.AppInfo.create_from_commandline(command, "gdmflexiserver", Gio.AppInfoCreateFlags.NONE) 104 | if app: 105 | app.launch(None, ctx) 106 | elif process_is_running("gdm3"): 107 | ctx = Gdk.Display.get_default().get_app_launch_context() 108 | 109 | app = Gio.AppInfo.create_from_commandline("gdmflexiserver", None, Gio.AppInfoCreateFlags.NONE) 110 | if app: 111 | app.launch(None, ctx) 112 | elif os.getenv("XDG_SEAT_PATH") is not None: 113 | try: 114 | bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None) 115 | bus.call_sync("org.freedesktop.DisplayManager", 116 | os.getenv("XDG_SEAT_PATH"), 117 | "org.freedesktop.DisplayManager.Seat", 118 | "SwitchToGreeter", 119 | None, 120 | None, 121 | Gio.DBusCallFlags.NONE, 122 | -1, 123 | None) 124 | 125 | except GLib.Error as err: 126 | print("Switch user failed: " + err.message) 127 | 128 | def session_is_cinnamon(): 129 | if "cinnamon" in GLib.getenv("DESKTOP_SESSION"): 130 | if GLib.find_program_in_path("cinnamon-session-quit"): 131 | return True 132 | 133 | return False 134 | 135 | def override_user_time(window): 136 | ev_time = Gtk.get_current_event_time() 137 | 138 | window.set_user_time(ev_time) 139 | 140 | def debug_allocation(alloc): 141 | print("x:%d, y:%d, width:%d, height:%d" % (alloc.x, alloc.y, alloc.width, alloc.height)) 142 | 143 | def CLAMP(value, low, high): 144 | return max(low, min(value, high)) 145 | 146 | def clear_clipboards(widget): 147 | clipboard = widget.get_clipboard(Gdk.SELECTION_PRIMARY) 148 | clipboard.clear() 149 | clipboard.set_text("", -1) 150 | 151 | clipboard = widget.get_clipboard(Gdk.SELECTION_CLIPBOARD) 152 | clipboard.clear() 153 | clipboard.set_text("", -1) 154 | 155 | def do_quit(): 156 | Gtk.main_quit() 157 | 158 | def DEBUG(message): 159 | if status.Debug: 160 | print(message, flush=True) -------------------------------------------------------------------------------- /src/volumeControl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import gi 4 | gi.require_version('Cvc', '1.0') 5 | 6 | from gi.repository import Gtk, Cvc, Gdk 7 | 8 | from widgets.volumeSlider import VolumeSlider 9 | from util import trackers, utils 10 | 11 | class VolumeControl(Gtk.Box): 12 | """ 13 | A direct child of the AudioPanel, it provides the volume slider. This shows 14 | any time we are Awake, regardless of any media player connections. We use libcvc 15 | to interact with the audio subsystem. 16 | """ 17 | def __init__(self): 18 | super(VolumeControl, self).__init__(orientation=Gtk.Orientation.HORIZONTAL) 19 | 20 | self.output = None 21 | self.controller = None 22 | 23 | self.volume_slider = VolumeSlider() 24 | self.volume_slider.show_all() 25 | 26 | self.set_no_show_all(True) 27 | 28 | self.pack_start(self.volume_slider, False, False, 6) 29 | 30 | self.initialize_sound_controller() 31 | 32 | def initialize_sound_controller(self): 33 | self.controller = Cvc.MixerControl(name="cinnamon-screensaver") 34 | trackers.con_tracker_get().connect(self.controller, 35 | "state-changed", 36 | self.on_state_changed) 37 | 38 | trackers.con_tracker_get().connect(self.controller, 39 | "default-sink-changed", 40 | self.on_state_changed) 41 | 42 | self.controller.open() 43 | self.on_state_changed() 44 | 45 | def on_state_changed(self, controller=None, state=0): 46 | if controller and controller != self.controller: 47 | old = self.controller 48 | self.controller = controller 49 | del old 50 | if self.controller.get_state() == Cvc.MixerControlState.READY: 51 | new = self.controller.get_default_sink() 52 | if new is not None: 53 | if self.output and self.output != new: 54 | old = self.output 55 | self.output = new 56 | del old 57 | else: 58 | self.output = new 59 | trackers.con_tracker_get().connect(self.output, 60 | "notify::is-muted", 61 | self.on_volume_changed) 62 | trackers.con_tracker_get().connect(self.output, 63 | "notify::volume", 64 | self.on_volume_changed) 65 | trackers.con_tracker_get().connect(self.volume_slider, 66 | "value-changed", 67 | self.on_volume_slider_changed) 68 | trackers.con_tracker_get().connect(self.volume_slider, 69 | "button-press-event", 70 | self.on_button_press_event) 71 | 72 | self.on_volume_changed(None, None) 73 | self.show() 74 | return 75 | 76 | self.hide() 77 | 78 | def on_volume_changed(self, output, pspec): 79 | vol = self.output.props.volume 80 | muted = self.output.get_is_muted() 81 | max_vol = self.controller.get_vol_max_norm() 82 | 83 | normalized_volume = int(min((vol / max_vol * 100), 100)) 84 | 85 | self.update_slider(normalized_volume, muted) 86 | 87 | def update_slider(self, volume, muted): 88 | 89 | trackers.con_tracker_get().handler_block(self.volume_slider, 90 | "value-changed", 91 | self.on_volume_slider_changed) 92 | 93 | self.volume_slider.set_muted(muted) 94 | self.volume_slider.set_value(volume) 95 | 96 | trackers.con_tracker_get().handler_unblock(self.volume_slider, 97 | "value-changed", 98 | self.on_volume_slider_changed) 99 | 100 | def on_volume_slider_changed(self, range, data=None): 101 | value = self.volume_slider.get_value() 102 | max_norm = self.controller.get_vol_max_norm() 103 | 104 | denormalized_volume = utils.CLAMP((value / 100) * max_norm, 0, max_norm) 105 | 106 | trackers.con_tracker_get().handler_block(self.output, 107 | "notify::volume", 108 | self.on_volume_changed) 109 | trackers.con_tracker_get().handler_block(self.output, 110 | "notify::is-muted", 111 | self.on_volume_changed) 112 | self.output.set_volume(denormalized_volume) 113 | self.output.push_volume() 114 | self.output.change_is_muted(False) 115 | trackers.con_tracker_get().handler_unblock(self.output, 116 | "notify::volume", 117 | self.on_volume_changed) 118 | trackers.con_tracker_get().handler_unblock(self.output, 119 | "notify::is-muted", 120 | self.on_volume_changed) 121 | 122 | def on_button_press_event(self, widget, event): 123 | if event.button == 2: 124 | self.output.set_is_muted(not self.volume_slider.muted) 125 | 126 | return Gdk.EVENT_PROPAGATE 127 | 128 | def on_scroll_event(self, widget, event): 129 | success, dx, dy = event.get_scroll_deltas() 130 | step = self.volume_slider.get_adjustment().get_step_increment() 131 | 132 | if dy < 0: 133 | self.volume_slider.set_value(self.volume_slider.get_value() + step) 134 | elif dy > 0: 135 | self.volume_slider.set_value(self.volume_slider.get_value() - step) 136 | 137 | return Gdk.EVENT_STOP 138 | -------------------------------------------------------------------------------- /src/widgets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxmint/cinnamon-screensaver/d7e415936b6516e45e9e073a77c050abfb38b192/src/widgets/__init__.py -------------------------------------------------------------------------------- /src/widgets/framedImage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # coding: utf-8 3 | 4 | import gi 5 | 6 | gi.require_version('CinnamonDesktop', '3.0') 7 | from gi.repository import Gtk, GdkPixbuf, Gio, GLib, GObject, Gdk 8 | 9 | from util import utils, trackers 10 | 11 | MAX_IMAGE_SIZE = 320 12 | MAX_IMAGE_SIZE_LOW_RES = 200 13 | 14 | class FramedImage(Gtk.Image): 15 | """ 16 | Widget to hold the user face image. It attempts to display an image at 17 | its native size, up to a max sane size. 18 | """ 19 | __gsignals__ = { 20 | "surface-changed": (GObject.SignalFlags.RUN_LAST, None, (object,)) 21 | } 22 | def __init__(self, low_res=False, scale_up=False): 23 | super(FramedImage, self).__init__() 24 | self.get_style_context().add_class("framedimage") 25 | 26 | self.cancellable = None 27 | 28 | self.file = None 29 | self.path = None 30 | self.scale_up = scale_up 31 | 32 | if low_res: 33 | self.max_size = MAX_IMAGE_SIZE_LOW_RES 34 | else: 35 | self.max_size = MAX_IMAGE_SIZE 36 | 37 | trackers.con_tracker_get().connect(self, "realize", self.on_realized) 38 | 39 | def on_realized(self, widget): 40 | self.generate_image() 41 | 42 | def clear_image(self): 43 | self.set_from_surface(None) 44 | self.emit("surface-changed", None) 45 | 46 | def set_from_path(self, path): 47 | self.path = path 48 | self.file = None 49 | 50 | if self.get_realized(): 51 | self.generate_image() 52 | 53 | def set_from_file(self, file): 54 | self.file = file 55 | self.path = None 56 | 57 | if self.get_realized(): 58 | self.generate_image() 59 | 60 | def set_image_internal(self, path): 61 | pixbuf = None 62 | scaled_max_size = self.max_size * self.get_scale_factor() 63 | 64 | try: 65 | pixbuf = GdkPixbuf.Pixbuf.new_from_file(path) 66 | except GLib.Error as e: 67 | message = "Could not load pixbuf from '%s' for FramedImage: %s" % (path, e.message) 68 | error = True 69 | 70 | if pixbuf is not None: 71 | if (pixbuf.get_height() > scaled_max_size or pixbuf.get_width() > scaled_max_size) or \ 72 | (self.scale_up and (pixbuf.get_height() < scaled_max_size / 2 or pixbuf.get_width() < scaled_max_size / 2)): 73 | try: 74 | pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(path, scaled_max_size, scaled_max_size) 75 | except GLib.Error as e: 76 | message = "Could not scale pixbuf from '%s' for FramedImage: %s" % (path, e.message) 77 | error = True 78 | 79 | if pixbuf: 80 | surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, 81 | self.get_scale_factor(), 82 | self.get_window()) 83 | self.set_from_surface(surface) 84 | self.emit("surface-changed", surface) 85 | else: 86 | print(message) 87 | self.clear_image() 88 | 89 | def generate_image(self): 90 | if self.path: 91 | self.set_image_internal(self.path) 92 | elif self.file: 93 | if self.cancellable is not None: 94 | self.cancellable.cancel() 95 | self.cancellable = None 96 | 97 | self.cancellable = Gio.Cancellable() 98 | self.file.load_contents_async(self.cancellable, self.load_contents_async_callback) 99 | 100 | def load_contents_async_callback(self, file, result, data=None): 101 | try: 102 | success, contents, etag_out = file.load_contents_finish(result) 103 | except GLib.Error: 104 | self.clear_image() 105 | return 106 | 107 | if contents: 108 | cache_name = GLib.build_filenamev([GLib.get_user_cache_dir(), "cinnamon-screensaver-albumart-temp"]) 109 | cache_file = Gio.File.new_for_path(cache_name) 110 | 111 | cache_file.replace_contents_async(contents, 112 | None, 113 | False, 114 | Gio.FileCreateFlags.REPLACE_DESTINATION, 115 | self.cancellable, 116 | self.on_file_written) 117 | 118 | def on_file_written(self, file, result, data=None): 119 | try: 120 | if file.replace_contents_finish(result): 121 | self.set_image_internal(file.get_path()) 122 | except GLib.Error: 123 | pass 124 | -------------------------------------------------------------------------------- /src/widgets/marqueeLabel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import Gtk, GObject, GLib 4 | 5 | from util import trackers 6 | 7 | class _fixedViewport(Gtk.Viewport): 8 | """ 9 | This is needed by MarqueeLabel to restrict the size of 10 | our label, and cause the viewport to actually be functional. 11 | Otherwise, the text is trimmed, but no scrolling occurs. 12 | """ 13 | def __init__(self): 14 | super(_fixedViewport, self).__init__() 15 | 16 | self.set_shadow_type(Gtk.ShadowType.NONE) 17 | 18 | def do_get_preferred_width(self): 19 | return 400, 400 20 | 21 | class MarqueeLabel(Gtk.Stack): 22 | """ 23 | A scrolling label for the PlayerControl - it uses the defined 24 | pattern as a mapping between elapsed time and the hadjustment 25 | of the _fixedViewport. If the label text is wider than the 26 | widget's actual width, we will scroll according to this map 27 | over the course of 15 seconds. 28 | 29 | It roughly translates to: 30 | 0.0 - 2.0 seconds: no movement. 31 | 2.0 - 10.0 seconds: gradually scroll to max adjustment of the viewport. 32 | 10.0 - 12.0 seconds: no movement. 33 | 12.0 - 15.0 seconds: scroll back to the starting position. 34 | 35 | This widget also implements a stack (similar to the one in MonitorView) which 36 | provides for a smooth label crossfade when track info changes. 37 | """ 38 | PATTERN = [( 0.0, 0.0), 39 | ( 2.0, 0.0), 40 | (10.0, 1.0), 41 | (12.0, 1.0), 42 | (15.0, 0.0)] 43 | 44 | LENGTH = len(PATTERN) 45 | 46 | def __init__(self, text): 47 | super(MarqueeLabel, self).__init__() 48 | 49 | self.set_transition_type(Gtk.StackTransitionType.CROSSFADE) 50 | self.set_transition_duration(300) 51 | 52 | self.tick_id = 0 53 | 54 | self.current = self._make_label(text) 55 | 56 | self.add(self.current) 57 | self.set_visible_child(self.current) 58 | 59 | def _make_label(self, text): 60 | vp = _fixedViewport() 61 | 62 | label = Gtk.Label(text) 63 | label.set_halign(Gtk.Align.START) 64 | 65 | vp.add(label) 66 | vp.show_all() 67 | 68 | return vp 69 | 70 | def set_text(self, text): 71 | if self.current.get_child().get_text() == text: 72 | return 73 | 74 | self.cancel_tick() 75 | 76 | self.queued = self._make_label(text) 77 | 78 | self.add(self.queued) 79 | self.set_visible_child(self.queued) 80 | 81 | tmp = self.current 82 | self.current = self.queued 83 | self.queued = None 84 | 85 | GObject.idle_add(tmp.destroy) 86 | 87 | if not self.current.get_realized(): 88 | trackers.con_tracker_get().connect(self.current, 89 | "realize", 90 | self.on_current_realized) 91 | else: 92 | GObject.idle_add(self._marquee_idle) 93 | 94 | def on_current_realized(self, widget, data=None): 95 | GObject.idle_add(self._marquee_idle) 96 | 97 | trackers.con_tracker_get().disconnect(widget, 98 | "realize", 99 | self.on_current_realized) 100 | 101 | def cancel_tick(self): 102 | if self.tick_id > 0: 103 | self.remove_tick_callback(self.tick_id) 104 | self.tick_id = 0 105 | 106 | def _marquee_idle(self): 107 | self.hadjust = self.current.get_hadjustment() 108 | 109 | if (self.hadjust.get_upper() == self.hadjust.get_page_size()) == self.get_allocated_width(): 110 | return False 111 | 112 | self.start_time = self.get_frame_clock().get_frame_time() 113 | self.end_time = self.start_time + (self.PATTERN[self.LENGTH - 1][0] * 1000 * 1000) # sec to ms to μs 114 | 115 | if self.tick_id == 0: 116 | self.tick_id = self.add_tick_callback(self._on_marquee_tick) 117 | 118 | self._marquee_step(self.start_time) 119 | 120 | return GLib.SOURCE_REMOVE 121 | 122 | def _on_marquee_tick(self, widget, clock, data=None): 123 | now = clock.get_frame_time() 124 | 125 | self._marquee_step(now) 126 | 127 | if now >= self.end_time: 128 | self.start_time = self.end_time 129 | self.end_time += (self.PATTERN[self.LENGTH - 1][0] * 1000 * 1000) # sec to ms to μs 130 | 131 | return GLib.SOURCE_CONTINUE 132 | 133 | def interpolate_point(self, now): 134 | point = ((now - self.start_time) / 1000 / 1000) 135 | 136 | i = 0 137 | while i < self.LENGTH: 138 | cindex, cval = self.PATTERN[i] 139 | 140 | if point > cindex: 141 | i += 1 142 | continue 143 | 144 | if point == cindex: 145 | return cval 146 | 147 | pindex, pval = self.PATTERN[i - 1] 148 | diff = cval - pval 149 | duration = cindex - pindex 150 | 151 | ratio = diff / duration 152 | additive = (point - pindex) * ratio 153 | return pval + additive 154 | 155 | def _marquee_step(self, now): 156 | if now < self.end_time: 157 | t = self.interpolate_point(now) 158 | else: 159 | t = self.PATTERN[self.LENGTH - 1][1] 160 | 161 | new_position = ((self.hadjust.get_upper() - self.hadjust.get_page_size()) * t) 162 | 163 | self.hadjust.set_value(new_position) 164 | self.queue_draw() 165 | -------------------------------------------------------------------------------- /src/widgets/meson.build: -------------------------------------------------------------------------------- 1 | app_py = [ 2 | '__init__.py', 3 | 'framedImage.py', 4 | 'marqueeLabel.py', 5 | 'notificationWidget.py', 6 | 'powerWidget.py', 7 | 'transparentButton.py', 8 | 'volumeSlider.py', 9 | ] 10 | 11 | install_data(app_py, install_dir: join_paths(pkgdatadir, 'widgets')) 12 | -------------------------------------------------------------------------------- /src/widgets/notificationWidget.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import Gtk, GObject 4 | 5 | import singletons 6 | import status 7 | from util import trackers 8 | 9 | class NotificationWidget(Gtk.Frame): 10 | """ 11 | Notification widget is a child of the InfoPanel - it is 12 | only shown if we've received intercepted a non-transient 13 | notification that hasn't been sent from a media player. 14 | """ 15 | __gsignals__ = { 16 | "notification": (GObject.SignalFlags.RUN_LAST, None, ()), 17 | } 18 | def __init__(self): 19 | super(NotificationWidget, self).__init__() 20 | self.set_shadow_type(Gtk.ShadowType.NONE) 21 | self.get_style_context().add_class("notificationwidget") 22 | 23 | self.set_size_request(50, -1) 24 | 25 | self.notification_count = 0 26 | 27 | box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) 28 | self.add(box) 29 | 30 | self.label = Gtk.Label.new("0") 31 | box.pack_start(self.label, False, False, 4) 32 | 33 | self.image = Gtk.Image.new_from_icon_name("screensaver-notification-symbolic", Gtk.IconSize.LARGE_TOOLBAR) 34 | box.pack_end(self.image, False, False, 4) 35 | 36 | box.show_all() 37 | 38 | self.notification_watcher = singletons.NotificationWatcher 39 | 40 | trackers.con_tracker_get().connect(self.notification_watcher, 41 | "notification-received", 42 | self.on_notification_received) 43 | 44 | def on_notification_received(self, proxy, sender, data=None): 45 | mp_watcher = singletons.MediaPlayerWatcher 46 | 47 | players = mp_watcher.get_all_player_names() 48 | 49 | if sender.lower() in players: 50 | return 51 | 52 | for ignored in ("network",): 53 | if ignored in sender.lower(): 54 | return 55 | 56 | self.notification_count += 1 57 | 58 | self.update_label() 59 | 60 | self.emit("notification") 61 | 62 | def should_show(self): 63 | return self.notification_count > 0 64 | 65 | def update_label(self): 66 | self.label.set_text(str(self.notification_count)) 67 | -------------------------------------------------------------------------------- /src/widgets/powerWidget.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import Gtk, GObject, Gio 4 | 5 | from util import trackers 6 | import singletons 7 | import constants as c 8 | import status 9 | from util.utils import DEBUG 10 | 11 | UPOWER_STATE_CHARGING = 1 12 | UPOWER_STATE_DISCHARGING = 2 13 | UPOWER_STATE_FULLY_CHARGED = 4 14 | UPOWER_STATE_PENDING_CHARGE = 5 15 | UPOWER_STATE_PENDING_DISCHARGE = 6 16 | 17 | class PowerWidget(Gtk.Frame): 18 | """ 19 | PowerWidget is a child of InfoPanel, and is only shown if we're on 20 | a system that can run on battery power. It is usually only visible 21 | if the system is actually currently running on battery power. 22 | """ 23 | __gsignals__ = { 24 | 'power-state-changed': (GObject.SignalFlags.RUN_LAST, None, ()), 25 | } 26 | 27 | def __init__(self): 28 | super(PowerWidget, self).__init__() 29 | self.set_shadow_type(Gtk.ShadowType.NONE) 30 | self.get_style_context().add_class("powerwidget") 31 | 32 | self.path_widget_pairs = [] 33 | 34 | self.box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) 35 | self.add(self.box) 36 | 37 | self.box.show_all() 38 | 39 | self.power_client = singletons.UPowerClient 40 | 41 | self.battery_critical = False 42 | 43 | trackers.con_tracker_get().connect(self.power_client, 44 | "power-state-changed", 45 | self.on_power_state_changed) 46 | 47 | trackers.con_tracker_get().connect(self.power_client, 48 | "percentage-changed", 49 | self.on_percentage_changed) 50 | 51 | def refresh(self): 52 | self.on_power_state_changed(self.power_client) 53 | 54 | def on_power_state_changed(self, client): 55 | for widget in self.box.get_children(): 56 | widget.destroy() 57 | 58 | self.path_widget_pairs = [] 59 | self.battery_critical = False 60 | 61 | self.construct_icons() 62 | 63 | self.emit("power-state-changed") 64 | 65 | def on_percentage_changed(self, client, battery): 66 | battery_path = battery.get_object_path() 67 | 68 | for path, widget in self.path_widget_pairs: 69 | if path == battery_path: 70 | self.update_battery_tooltip(widget, battery) 71 | break 72 | 73 | def construct_icons(self): 74 | """ 75 | The upower dbus interface actually tells us what icon name to use. 76 | """ 77 | batteries = self.power_client.get_batteries() 78 | 79 | for path, battery in batteries: 80 | percentage = battery.get_property("percentage") 81 | gicon = self.get_gicon_for_current_level(battery) 82 | 83 | DEBUG("powerWidget: Updating battery info: %s - icon: %s - percentage: %s" % 84 | (path, gicon.to_string(), percentage)) 85 | 86 | image = Gtk.Image.new_from_gicon(gicon, Gtk.IconSize.LARGE_TOOLBAR) 87 | self.update_battery_tooltip(image, battery) 88 | 89 | self.box.pack_start(image, False, False, 4) 90 | self.path_widget_pairs.append((path, image)) 91 | 92 | self._should_show = True 93 | self.box.show_all() 94 | 95 | def get_gicon_for_current_level(self, battery): 96 | percentage = battery.get_property("percentage") 97 | state = battery.get_property("state") 98 | 99 | names = None 100 | 101 | if state in (UPOWER_STATE_CHARGING, UPOWER_STATE_DISCHARGING, 102 | UPOWER_STATE_PENDING_CHARGE, UPOWER_STATE_PENDING_DISCHARGE): 103 | if percentage < 10: 104 | names = ["battery-level-0", "battery-caution"] 105 | elif percentage < 20: 106 | names = ["battery-level-10", "battery-low"] 107 | elif percentage < 30: 108 | names = ["battery-level-20", "battery-low"] 109 | elif percentage < 40: 110 | names = ["battery-level-30", "battery-good"] 111 | elif percentage < 50: 112 | names = ["battery-level-40", "battery-good"] 113 | elif percentage < 60: 114 | names = ["battery-level-50", "battery-good"] 115 | elif percentage < 70: 116 | names = ["battery-level-60", "battery-full"] 117 | elif percentage < 80: 118 | names = ["battery-level-70", "battery-full"] 119 | elif percentage < 90: 120 | names = ["battery-level-80", "battery-full"] 121 | elif percentage < 99: 122 | names = ["battery-level-90", "battery-full"] 123 | else: 124 | names = ["battery-level-100", "battery-full"] 125 | 126 | if state in (UPOWER_STATE_CHARGING, UPOWER_STATE_PENDING_CHARGE): 127 | names[0] += "-charging" 128 | names[1] += "-charging" 129 | 130 | names[0] += "-symbolic" 131 | names[1] += "-symbolic" 132 | elif state == UPOWER_STATE_FULLY_CHARGED: 133 | names = ["battery-level-100-charged-symbolic", 134 | "battery-full-charged-symbolic", 135 | "battery-full-charging-symbolic"] 136 | else: 137 | names = (battery.get_property("icon-name"),) 138 | 139 | return Gio.ThemedIcon.new_from_names(names) 140 | 141 | def update_battery_tooltip(self, widget, battery): 142 | text = "" 143 | 144 | try: 145 | pct = int(battery.get_property("percentage")) 146 | 147 | if pct > 0: 148 | text = _("%d%%" % pct) 149 | if pct < c.BATTERY_CRITICAL_PERCENT: 150 | self.battery_critical = True 151 | except Exception as e: 152 | pass 153 | 154 | widget.set_tooltip_text(text) 155 | 156 | def should_show(self): 157 | return not self.power_client.full_and_on_ac_or_no_batteries() 158 | -------------------------------------------------------------------------------- /src/widgets/transparentButton.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import Gtk 4 | 5 | class TransparentButton(Gtk.Button): 6 | """ 7 | Custom button widget used throughout. 8 | """ 9 | def __init__(self, name, size): 10 | super(TransparentButton, self).__init__() 11 | self.get_style_context().add_class("transparentbutton") 12 | image = Gtk.Image.new_from_icon_name(name, size) 13 | self.set_can_default(True) 14 | self.set_can_focus(True) 15 | self.set_image(image) 16 | -------------------------------------------------------------------------------- /src/widgets/volumeSlider.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from gi.repository import Gtk, Gdk 4 | 5 | from util import trackers 6 | 7 | class VolumeSlider(Gtk.Scale): 8 | """ 9 | Custom GtkScale widget for controlling the volume. 10 | """ 11 | def __init__(self): 12 | super(VolumeSlider, self).__init__(orientation=Gtk.Orientation.HORIZONTAL) 13 | 14 | self.set_can_focus(False) 15 | 16 | self.muted = False 17 | 18 | self.set_range(0, 100.0) 19 | self.set_increments(5.0, 5.0) 20 | self.get_style_context().remove_class("scale") 21 | self.get_style_context().add_class("volumeslider") 22 | self.set_round_digits(0) 23 | self.set_draw_value(False) 24 | self.set_size_request(130, -1) 25 | self.set_halign(Gtk.Align.CENTER) 26 | self.set_valign(Gtk.Align.CENTER) 27 | 28 | trackers.con_tracker_get().connect(self, 29 | "draw", 30 | self.on_draw) 31 | 32 | def set_muted(self, muted): 33 | if muted != self.muted: 34 | self.muted = muted 35 | self.queue_draw() 36 | 37 | def on_draw(self, widget, cr): 38 | ctx = widget.get_style_context() 39 | alloc = self.get_allocation() 40 | 41 | padding = ctx.get_padding(Gtk.StateFlags.NORMAL) 42 | border = ctx.get_border(Gtk.StateFlags.NORMAL) 43 | 44 | x = padding.left + border.left 45 | y = padding.top + border.top 46 | width = alloc.width - padding.left - padding.right - border.left - border.right 47 | height = alloc.height - padding.top - padding.bottom - border.top - border.bottom 48 | floor = y + height 49 | end = x + width 50 | value = round(self.get_value()) 51 | value_x = x + ((value / 100) * width) 52 | value_y = floor - ((value / 100) * height) 53 | 54 | if self.muted: 55 | fill_color = ctx.get_color(Gtk.StateFlags.INSENSITIVE) 56 | bg_color = ctx.get_background_color(Gtk.StateFlags.INSENSITIVE) 57 | else: 58 | fill_color = ctx.get_color(Gtk.StateFlags.NORMAL) 59 | bg_color = ctx.get_background_color(Gtk.StateFlags.NORMAL) 60 | 61 | cr.save() 62 | 63 | cr.new_sub_path() 64 | cr.move_to(x, floor) 65 | cr.line_to(end, floor) 66 | cr.line_to(end, y) 67 | cr.close_path() 68 | 69 | Gdk.cairo_set_source_rgba(cr, bg_color) 70 | cr.fill() 71 | 72 | cr.restore() 73 | cr.save() 74 | 75 | cr.new_sub_path() 76 | cr.move_to(x, floor) 77 | cr.line_to(value_x, floor) 78 | cr.line_to(value_x, value_y) 79 | cr.close_path() 80 | 81 | Gdk.cairo_set_source_rgba(cr, fill_color) 82 | cr.fill() 83 | 84 | cr.restore() 85 | 86 | return True 87 | --------------------------------------------------------------------------------