TransWikia.com

Subscribe for DBUS event of screen power off

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.

3 Answers

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:

  • A recent Python 3, I wrote this on Python 3.8.5.
  • Eggs:
    • 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.

Code

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 ?

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, 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!


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 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.

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!

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:

  • when the display starts dimming off, my keyboard back-light is shut down
  • the keyboard lights up only AFTER I successfully log in, not in the login screen (but it's close enough)

Disclaimers:

  1. The word monitor in org.gnome.Mutter.IdleMonitor comes from the monitoring action, not from the monitor aka screen. So basically these seem to be methods that are called when the user is declared idle and not idle by gnome. As a matter of fact, in my case, it coincides with the screen being powerd off. In your case, it might not.
  2. Apparently you can't add this as a systemd entry, because it needs a screen. However, you can add it in the Startup Applications GUI and it works
  3. I'm using g810-led executable to turn on and off my keyboard back-light, that should be treated as just an example, as it obviously won't work on other keyboards

PS: 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

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP