4

I can change my current workspace in various ways, e.g. by clicking on some switcher widget, with some key combination, with a command in a terminal.

Is there a way to detect that the workspace has been changed, to launch a bash script at that moment?

(I think, for example, of a monitorable signal on d-bus, something like that.)

Raffa
  • 32,237
Rodrigo
  • 143
  • See https://stackoverflow.com/q/58823860/10477326 – Daniel T Feb 04 '24 at 02:24
  • There seems not to be any distinct explicit exclusive signal for that readable via D-Bus tools … Gnome and other DEs seem to now have their internals working on their own since they got mixed with the display session with the introduction of Wayland … See for example https://askubuntu.com/a/1486643 … Comprehensive tools to interact with the new merge are not yet existent … So, take shortcuts and workarounds until then. – Raffa Feb 04 '24 at 16:42

2 Answers2

3

I did look into D-Bus and other Gnome DE specific methods that came to mind, but couldn't find a more reliable, sustainable and portable solution than what is described below ...

You can use xdotool (which also seems to work on Wayland for this purpose) in a shell loop like:

#!/bin/sh

while sleep 0.2 do v="$(xdotool get_desktop)" if [ -z "$ws" ] then ws="$v" elif [ "$ws" -ne "$v" ] then ws="$v" echo "changed to $ws" # Your command here fi done

... that tool still works on Wayland because it reads the _NET_CURRENT_DESKTOP property of the EWMHExtended Window Manager Hints which many windowing managers/systems still support and set by standards.

Needless to say that there are other tools which support reading that property along with builtin capability to continuously monitor it (If you don't like a continuous loop with a sleep call) like xprop and you can similarly use it like so:

#!/bin/bash

xprop -root -spy _NET_CURRENT_DESKTOP | while IFS=" " read -r p s n do if ! [[ "$ws" =~ ^[0-9]+$ ]] then ws="$n" elif [ "$n" -ne "$ws" ] then ws="$n" echo "changed to $ws" # Your command here fi done

... or even xev like so:

#!/bin/sh

xev -root -event property | grep --line-buffered -o '_NET_CURRENT_DESKTOP' | while read -r p do echo "Workspace changed." # Your command here done

... and of course you can, if you wish, alternatively implement your own app to read the _NET_CURRENT_DESKTOP property in any language C, Python ... etc. and not necessarily in a shell script using those tools.

Notice:

  • The above first loop with xdotool and the last one with xev is not Bash specific and can run with other POSIX shells with sh for example.

  • When using xprop on Wayland, as reported by @Daniel T (Thank you Daniel), then the workspace has to be switched at least once before xprop starts catching it and as a result your first switch between workspaces will be out of scope for the script ... So, if that is critical, use xdotool instead.

Notice as well that (goes without saying) those loops need to be run while a user graphical session is running i.e. after login and before logout ... and therefore the best implementation for either is as a startup application be it they are contained in script file or even laid out in the Command: field as a single shell command string like this:

 sh -c 'while sleep 0.2; do if [ -z "$ws" ]; then ws="$(xdotool get_desktop)"; elif [ "$ws" -ne "$(xdotool get_desktop)" ]; then ws="$(xdotool get_desktop)"; echo "changed to $ws"; fi done'

... or like this:

bash -c 'xprop -root -spy _NET_CURRENT_DESKTOP | while IFS=" " read -r p s n; do if ! [[ "$ws" =~ ^[0-9]+$ ]]; then ws="$n"; elif [ "$n" -ne "$ws" ]; then ws="$n"; echo "changet to $ws"; fi done'

... or like this:

sh -c 'xev -root -event property | grep --line-buffered -o '_NET_CURRENT_DESKTOP' | while read -r p; do  echo "Workspace changed."; done'

... your custom command in those command strings should go in-place of the echo "..." part.

Raffa
  • 32,237
  • This is a good workaround, but not the best option, since the script runs constantly, with whatever limitations/implications that can pose to a user's system. IMO Daniel T's answer is a better solution (although many people don't like using extensions for supposedly simple things like that) since the script only runs on trigger (i.e. workspace change). Still +1 because this workaround can be used for systems other than GNOME as well. – BeastOfCaerbannog Feb 05 '24 at 13:31
  • @BeastOfCaerbannog I appreciate your feedback, but disagree with you on the script affecting user's system (absolutely a history thing and all modern machines should handle a loop even with as little as sleep 0.01 ... a sleep call is actually like heaven for the CPU see @muru's answer here https://askubuntu.com/q/524916 ... It's what's inside the loop that matters) ... I also included another approach for you :-) – Raffa Feb 05 '24 at 14:12
  • 1
    @Raffa NVM. I had to switch desktops at least once after logging in for the xprop to exist. Before switching desktops, it gives a "not found" error. Seems like I'm getting this because I have my KDE logged in for weeks and switched desktops often, while my GNOME is in a new test user I created just for this question. – Daniel T Feb 05 '24 at 14:29
  • @DanielT No, actually you are absolutely right ... It's not xprop it's the arithmetic evaluation syntax part in the loop ... Fixing it in a moment ... Thanks for testing and letting me know :-) – Raffa Feb 05 '24 at 14:39
  • Tested with a new KDE account, and it doesn't happen after login without switching like the "not found" problem with GNOME. +1 Even though the first solution's busy wait loop is a code smell, learning the second solution saves me the effort of having to remember/find a million parameters for dbus-monitor on KDE – Daniel T Feb 05 '24 at 14:53
  • @Raffa I never said that there was an issue is with the sleep command, I rather implied that the issue is with the script/loop running constantly (also thanks for the excellent explanation by @muru for sleep). I agree that the consumption of system resources of this specific loop should be negligible, but I just consider a solution that runs on trigger better. It's not that I don't like yours too, which has the benefits of running in all DEs, as I already mentioned. Your second solution also works fine! :D – BeastOfCaerbannog Feb 05 '24 at 14:58
  • @DanielT Edited ... Thanks again ... Please see the edit and let me know if you think I missed something. – Raffa Feb 05 '24 at 15:50
  • @Raffa The first switch after login is silently ignored now. I would have made the simpler change of reverting to the original solution, and just replaced -ne with != to avoid mixing string and arithmetic operations. – Daniel T Feb 05 '24 at 16:02
  • @DanielT Changing to string comparison will trigger the action when comparing for example found to 0 when xprop later emits it out even without the workspace being actually changed ... So, better keep it accurate comparing only integers with an arithmetic operator I think. – Raffa Feb 05 '24 at 16:19
  • @DanielT The first solution should be less of a "code smell" :D now (reduced calls to xdotool to one … @BeastOfCaerbannog I totally agree with you in the concept of trigger based solutions but, things are mixed now with Wayland "security" things so reducing native simple solutions more and more so we should rely only on the standardized things that shouldn’t change much I think – Raffa Feb 05 '24 at 18:03
  • @Raffa yes, I agree that Bash solutions are more straightforward and easy to implement and customize. I had also implemented something similar for another case where a user had raised a concern about having a script running constantly on the background and I didn't know of a trigger-based solution. So that's why I was impressed by Daniel's answer and the simple way they presented it, which makes it a nice backbone on which you can implement an extension/trigger-based solution. – BeastOfCaerbannog Feb 05 '24 at 20:37
  • 1
    @BeastOfCaerbannog I looked at your linked answer … It looks like the third solution above with xev would provide a reasonable trigger for your while loop so it only runs on workspace switch … Although as discussed earlier a sleep for one second is abundant … The problem with Gnome extensions/things that are not standardized is they tend to break or get disabled with updates so need constant maintenance and the outcome performance wise is not noticeable. – Raffa Feb 06 '24 at 15:24
3

There is a more energy-efficient gnome-shell extension solution. It will actually "launch a bash script at that moment", without you having to start anything beforehand. Here is the installation procedure:

  1. Create the script you want to run at ~/script.sh
  2. Make sure it is executable
  3. Paste the correct version into ~/.local/share/gnome-shell/extensions/workspaceChangeDetect_1502368_1004020@askubuntu.com/metadata.json
  4. Paste the correct version into ~/.local/share/gnome-shell/extensions/workspaceChangeDetect_1502368_1004020@askubuntu.com/extension.js
  5. Relog
  6. Run gnome-extensions enable workspaceChangeDetect_1502368_1004020@askubuntu.com

I tested with a script.sh that just created a file. I tested Ctrl+Alt+/ and confirmed that the script runs at that time and not at other times such as login. Gnome-shell does not expose the signal to DBus, and the org.gnome.Shell.Eval DBus method is locked down. However, gnome-shell extensions can listen to the gobject signal WorkspaceManager::active-workspace-changed.

Gnome-shell ≤ 42 (22.04)

metadata.json

{
    "uuid": "workspaceChangeDetect_1502368_1004020@askubuntu.com",
    "name": "Workspace change detect script launcher",
    "description": "Detect that the workspace has been changed, to launch a bash script at that moment",
    "shell-version": [ "42" ],
    "url": "https://askubuntu.com/q/1502368/1004020"
}

extension.js

const {GLib} = imports.gi;
let handlerId;

function enable() { handlerId = global.workspaceManager.connect('active-workspace-changed', () => { // Put the script to launch at ~/script.sh , or change the string on the next line: GLib.spawn_command_line_async('./script.sh'); }); }

function disable() { global.workspaceManager.disconnect(handlerId); }

Gnome-shell ≥ 45 (23.10)

metadata.json

{
    "uuid": "workspaceChangeDetect_1502368_1004020@askubuntu.com",
    "name": "Workspace change detect script launcher",
    "description": "Detect that the workspace has been changed, to launch a bash script at that moment",
    "shell-version": [ "45" ],
    "url": "https://askubuntu.com/q/1502368/1004020"
}

extension.js

import GLib from 'gi://GLib';
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';

export default class WorkspaceChangeDetectExtension extends Extension { enable() { global.workspaceManager.connectObject('active-workspace-changed', () => { // Put the script to launch at ~/script.sh , or change the string on the next line: GLib.spawn_command_line_async('./script.sh'); }, this); }

disable() {
    global.workspaceManager.disconnectObject(this);
}

}

Daniel T
  • 4,594
  • This is an excellent option! Have you considered making it an actual GNOME Shell extension? I think it would be useful to many people! – BeastOfCaerbannog Feb 05 '24 at 13:23
  • Idk if I'm going to bother uploading. One of the advantages of this solution is that it's nice simple and readable. If I had to upload, I would want to add a customization menu for the script path at least. Then I think this should belong in an extension that listened to a variety of events and could do various outputs. I skimmed the basics of GJS just for this question, so those tasks would be difficult, and also that would remove the advantage of simplicity. If my solution loses readability after uploading, it would become preferable to use Raffa's xprop solution – Daniel T Feb 05 '24 at 14:33