My MacBook Pro has a keyboard backlight which is pretty awesome, but there's a slight bug: the screen turns off after a given amount of time, but the keyboard backlight stays on.
I've copied over and messed with a small DBUS Python script to monitor when changes occur with the screen saver state, but it isn't triggered when the screen goes off, only when the screensaver activates or deactivates:
from dbus.mainloop.glib import DBusGMainLoop
import dbus
import gobject
import logging
logging.basicConfig()
logger = logging.getLogger(__name__)
dbus_loop = DBusGMainLoop(set_as_default=True)
def message_callback(bus, message):
if message.get_interface() == "org.gnome.ScreenSaver":
if message.get_member() == "ActiveChanged":
screensaver_enabled = bool(message.get_args_list()[0])
logger.info("Screen saver changed. Active: %s", screensaver_enabled)
session = dbus.SessionBus(mainloop=dbus_loop)
session.add_match_string_non_blocking("interface='org.gnome.ScreenSaver'")
session.add_message_filter(message_callback)
logger.info("Starting up.")
loop = gobject.MainLoop()
loop.run()
This works great whenever the screensaver is activated, but doesn't get alterted if the screen power state changes, which can happen independently of the screen saver. By going to Brightness and Lock in Settings, you can set the screens to power off after 1 minute and not lock. You can then set the screensaver time to a different amount of time, say 10 minutes.
I've tried listening on the org.gnome.SettingsDaemon.Power.Screen
interface for the Changed
signal, but this only happens when the screen brightness is changed manually.
What can I listen to in order to determine when the screen power state has changed? I want to write a script that runs whenever the screen power goes off so I can disable my keyboard backlight.
Well, it sucks that I can't leave a comment because I don't have the "reputation". This is more of a comment than a solution.
I have been looking for something similar, and I'm monitoring 'org.gnome.SessionManager.Presence' instead. I have LEDs behind my monitor for bias lighting, and I want to turn them off/on with the monitor.
This works if I lock my computer manually, however if I leave the "screen off" and the "lock screen after" settings at different intervals, the LEDs turn off when the monitor turns off, however when the screensaver lock kicks in it turns the LEDs on again.
Reference: https://www.organicdesign.co.nz/PoisonTap_solution
I've just installed Ubuntu 18.04 and it turns out there's no screensaver present by default. And honestly, I don't want one, so I won't bother installing one.
However, I found some method calls from gnome that seem to do the trick:
AddUserActiveWatch
andRemoveWatch
fromorg.gnome.Mutter.IdleMonitor
interface.Here's my script:
The result is:
Disclaimers:
Startup Applications
GUI and it worksPS: found a "bug". If you interrupt the screen fade, the keyboard stays unlit.
Okay so after years of being frustrated with this, I finally did something about it and wrote a Python utility script that monitors DBus and properly receives session lock/unlock events.
The code is hosted here, but I'll include it below as well. My goal is to rewrite this in Rust for a number of reasons, but largely to make it easier for folks to use without having to install packages to get the right Python libraries installed.
Prerequisites
To run this code, you'll need:
dbus-python >=1.2,<2
PyGObject >=3.36,<4
These Python eggs are provided by certain Ubuntu packages, but might not be the right versions. On 16.04, I believe the required packages are:
python3-gi
, which isPyGObject
python3-dbus
, which isdbus-python
On different distribution versions, these packages may be different. This is one of many reasons why I want to rewrite this in Rust, I'll list my other motivations at the end of this answer.
Code
Let's get into the code.
dbus-session-lock-watcher.py
To make this code do anything interesting, edit:
ScreenSaverEventListener.on_session_lock
, which will execute when the screen has locked ?ScreenSaverEventListener.on_session_unlock
, which will execute when the screen has been unlocked ?Installation
First, save the script above into a file, preferably in
~/.local/bin
. Next, of course, is to make sure that it's marked as executable, viachmod +x ~/.local/bin/dbus-session-lock-watcher.py
.I'd advise running this as a service, so let's create a systemd user unit to run the service.
Here's a sample unit:
~/.config/systemd/user/session-lock-watcher.service
Next, let's enable the service:
Finally, let's start it:
To view logs from the service:
And now we can execute custom code or scripts whenever the session is locked or unlocked!
Other Info
A lot of work went into this, so I wanted to provide some of that information in case it's interesting.
Learnings
D-Bus is complicated, but I'll try to summarize it. D-Bus is a plumbing service between different applications and provides a lot of bells and whistles. If you're familiar with JMX for the JVM, it's kind of like that, except as a service bus as an external process.
There are generally two D-Bus buses: the system bus, and the session bus. The system bus is for OS-wide things, whereas the session bus is a per-user bus and the primary thing you'll need to interact with, most likely.
D-Bus contains a registry of names, each of which has a number of object paths it supports, and each of these object paths has one or more interfaces. Interfaces have methods, properties, and signals. You can call methods with or without arguments and get results if the method returns values. You can read and sometimes write properties, and signals are events which you can subscribe to that carry data.
The exact relationship between names, object paths, and interfaces is still unclear to me so I'm just going to move on. In short, you have a bunch of registered names/applications that implement certain interfaces which define methods, signals, and properties, and that's how you communicate between applications using D-Bus.
I'll try to find more documentation to understand the logic behind all of the D-Bus machinery, but this suffices for now.
What was exceptionally frustrating is how many different things I tried to simply receive a callback when the screen was locked or unlocked.
I tried
org.gnome.ScreenSaver
, which supports multiple interfaces that have the same attributes,org.freedesktop.ScreenSaver
andorg.gnome.ScreenSaver
. They both support anActiveChanged
signal which carries a boolean, seemingly to indicate whether the screensaver is active or not. I never caught this signal, ever.I tried
org.gnome.SessionManager
and its corresponding interface, which exposedSessionOver
andSessionRunning
signals. I never caught either of these signals.After trudging through tons of different names and interfaces and signals, I had all but given up on finding a signal to indicate when the session was locked and unlocked. I setup a generic signal receiver that caught all signals, set a breakpoint, and stepped through all of them until I found a property change signal for the
SessionIsActive
property belonging toorg.gnome.SessionManager
. Bingo.Finally, I tuned my signal receiver to filter as well as it can on receiving only relevant signals for when this property is changed.
I'm on an Ubuntu derivative, so this code may not work on every Ubuntu distribution. Run the script, lock and unlock your session, and see if the script emits logs on lock and unlock.
Future Work
I'd really like to rewrite all this in a simple Rust daemon, as a binary will be much easier to distribute, with few or no runtime dependencies. I'd also like to provide some sort of directory where one can stash as many scripts as they'd like to run on lock and unlock events, perhaps something like
~/.config/nosleep/on-{lock,unlock}/*
. A sorted list of files will be obtained and each executable will be run in sorted order, making it much easier to write scripts around lock and unlock events.I hope this helps someone and is informative, I know I learned a lot writing it!