Ask Ubuntu Asked on January 6, 2022
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.
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.
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 is PyGObject
python3-dbus
, which is dbus-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.
Let's get into the code.
dbus-session-lock-watcher.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from dbus.mainloop.glib import DBusGMainLoop
from gi.repository import GLib
import dbus
import logging
import sys
class ScreenSaverEventListener(object):
def __init__(self):
self.logger = logging.getLogger(self.__class__.__name__)
self.mainloop = DBusGMainLoop()
self.loop = GLib.MainLoop()
self.session_bus = dbus.SessionBus(mainloop=self.mainloop)
self.receiver_args, self.receiver_kwargs = None, None
def setup(self):
self.receiver_args = (self.on_session_activity_change,)
self.receiver_kwargs = dict(dbus_interface="org.freedesktop.DBus.Properties", path="/org/gnome/SessionManager",
signal_name="PropertiesChanged",
# callback arguments
sender_keyword="sender", destination_keyword="dest",
interface_keyword="interface", member_keyword="member", path_keyword="path",
message_keyword="message")
self.session_bus.add_signal_receiver(*self.receiver_args, **self.receiver_kwargs)
def on_session_activity_change(self, target: dbus.String, changed_properties: dbus.Dictionary, *args, **kwargs):
if target != "org.gnome.SessionManager" or "SessionIsActive" not in changed_properties:
return
if changed_properties.get("SessionIsActive"):
self.on_session_unlock()
else:
self.on_session_lock()
def on_session_lock(self):
self.logger.info("Session Locked")
def on_session_unlock(self):
self.logger.info("Session Unlocked")
def run(self):
self.logger.debug("Starting event loop.")
self.loop.run()
def shutdown(self):
self.logger.debug("Stopping event loop.")
self.session_bus.remove_signal_receiver(*self.receiver_args, **self.receiver_kwargs)
self.loop.quit()
def main():
setup_logging()
listener = ScreenSaverEventListener()
listener.setup()
try:
listener.run()
except KeyboardInterrupt:
sys.stderr.write("ctrl+c received, shutting down...n")
listener.shutdown()
def setup_logging():
console = logging.StreamHandler(sys.stderr)
console.setFormatter(
logging.Formatter("%(asctime)s [%(levelname)-5s] %(name)s: %(message)s", datefmt="%Y-%m-%dT%H:%M:%S%z"))
logging.addLevelName(logging.WARNING, "WARN")
logging.getLogger().addHandler(console)
logging.getLogger().setLevel(logging.DEBUG)
if __name__ == "__main__":
main()
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 ?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, via chmod +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
[Unit]
Description=DBus Session Lock Watcher
[Service]
ExecStart=$HOME/.local/bin/dbus-session-lock-watcher.py
[Install]
WantedBy=default.target
Next, let's enable the service:
systemctl --user enable session-lock-watcher.service
Finally, let's start it:
systemctl --user start session-lock-watcher.service
To view logs from the service:
journalctl --user-unit session-lock-watcher.service
And now we can execute custom code or scripts whenever the session is locked or unlocked!
A lot of work went into this, so I wanted to provide some of that information in case it's interesting.
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
and org.gnome.ScreenSaver
. They both support an ActiveChanged
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 exposed SessionOver
and SessionRunning
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 to org.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.
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!
Answered by Naftuli Kay on January 6, 2022
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
and RemoveWatch
from org.gnome.Mutter.IdleMonitor
interface.
Here's my script:
#!/usr/bin/env python
import dbus, gobject
from dbus.mainloop.glib import DBusGMainLoop
from subprocess import call
def filter_cb(bus,message):
if message.get_member() == "AddUserActiveWatch":
print("Monitor off")
call("/usr/bin/g810-led -dv 046d -dp c337 -a 000000", shell=True)
elif message.get_member() == "RemoveWatch":
print("Monitor on")
call("/usr/bin/g810-led -dv 046d -dp c337 -p /etc/g810-led/profile", shell=True)
return
DBusGMainLoop(set_as_default=True)
bus = dbus.SessionBus()
bus.add_match_string_non_blocking("interface='org.gnome.Mutter.IdleMonitor',eavesdrop='true'")
bus.add_message_filter(filter_cb)
mainloop = gobject.MainLoop ()
mainloop.run ()
The result is:
Disclaimers:
Startup Applications
GUI and it worksPS: found a "bug". If you interrupt the screen fade, the keyboard stays unlit.
Answered by evilpenguin on January 6, 2022
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.
_getState () {
dbus-monitor --session "type=signal,interface=org.gnome.SessionManager.Presence,member=StatusChanged" |
while read x; do
case "$x" in
*"uint32 3"*)
/home/victor/bin/devices/kasa_cntrl.sh off
echo -e "$(date)t-tTurned off" >> "$log"
;;
*"uint32 0"*)
/home/victor/bin/devices/kasa_cntrl.sh on
echo -e "$(date)t-tTurned on" >> "$log"
;;
esac
done
}
Reference: https://www.organicdesign.co.nz/PoisonTap_solution
Answered by victorbrca on January 6, 2022
Get help from others!
Recent Answers
Recent Questions
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP