-
-
Save rbreaves/257c3edfa301786e66e964d7ac036269 to your computer and use it in GitHub Desktop.
# Single Command, runs 2 calls to gdbus to get the currently active Window from Gnome 3.x | |
# Escaped so you can copy and paste into terminal directly | |
gdbus call -e -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval global.get_window_actors\(\)[`gdbus call -e -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval global.get_window_actors\(\).findIndex\(a\=\>a.meta_window.has_focus\(\)===true\) | cut -d"'" -f 2`].get_meta_window\(\).get_wm_class\(\) | cut -d'"' -f 2 | |
# Unescaped version, will not run | |
# Broken down into 2 commands. | |
# Call to Gnome to get the array location of the active Application | |
gdbus call -e -d org.gnome.Shell -o /org/gnome/Shell -m \ | |
org.gnome.Shell.Eval global.get_window_actors().findIndex(a=>a.meta_window.has_focus()===true) \ | |
| cut -d"'" -f 2 | |
# Replace the array number 2 with the one from the previous command and you will get the App Name of the actively focused Window | |
gdbus call -e -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval \ | |
global.get_window_actors()[2].get_meta_window().get_wm_class() \ | |
| cut -d'"' -f 2 |
for people that interest in this too, now, we could use filter
function then use optional js chaining method to avoid null
⟩ gjs --version
gjs 1.68.1
gdbus call -e -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval "global.get_window_actors().filter(a=>a.meta_window.has_focus()===true)[0]?.get_meta_window()?.get_wm_class()"
or if you only want to get only the title
gdbus call -e -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval "global.get_window_actors().filter(a=>a.meta_window.has_focus()===true)[0]?.get_meta_window()?.get_wm_class()" | cut -d'"' -f 2
This may be a shorter way to accomplish the goal:
gdbus call -e -d org.gnome.Shell -o /org/gnome/Shell \
--method org.gnome.Shell.Eval "global.display.focus_window.get_wm_class()"
How would this work in gnome 41? Looks like the Eval interface has been now restricted to internal calls only :-(
@dceee find where that commit occurred and fork it? I honestly don't know. Tbh - that may be what should happen to Wayland - add the feature we need directly and then just maintain a fork of it. Literally everything could then just be merged by default, but just the single addition of a feature to expose the currently focused Window - at least to authorized apps outside of just DEs.
@canadaduane That solution does look nice! I hope that dceee is wrong about the latest update in Gnome 41 or that something new & useful is replacing it - although I am not sure that the Gnome team or Wayland are paying attention just yet.
I did appreciate your article and the visibility it got - I believe that will likely help us out a lot because I imagine most devs just aren't thinking of the use cases that involves key remappers & other accessibility related apps needing to know the active window name.
Thanks @rbreaves happy to try to make sense of our ecosystem and perhaps nudge towards stronger community and interop.
@dceee The gnome 41 solution seems to be to write a gnome shell extension that adds the needed interface. Here's an example that makes it possible to move windows (something that could also only be achieved with Eval previously):
https://gist.github.com/tuberry/dc651e69d9b7044359d25f1493ee0b39
@canadaduane Thanks, this is exactly what I did. I moved all calls against the Eval interface to an extension that acts like a proxy for my python scripts.
@dceee Is that extension something you can share? I'd be interested in seeing how you did it.
Based on the example code above, I replaced the methods with the ones I need:
const { Gio } = imports.gi;
const MR_DBUS_IFACE = `
<node>
<interface name="org.gnome.Shell.Extensions.Windows">
<method name="List">
<arg type="s" direction="out" name="win"/>
</method>
<method name="Focus">
<arg type="u" direction="in" name="winid"/>
</method>
<method name="Minimize">
<arg type="u" direction="in" name="winid"/>
</method>
</interface>
</node>`;
class Extension {
enable() {
this._dbus = Gio.DBusExportedObject.wrapJSObject(MR_DBUS_IFACE, this);
this._dbus.export(Gio.DBus.session, '/org/gnome/Shell/Extensions/Windows');
}
disable() {
this._dbus.flush();
this._dbus.unexport();
delete this._dbus;
}
List() {
let win = global.get_window_actors()
.map(a => a.meta_window)
.map(w => ({ class: w.get_wm_class(), pid: w.get_pid(), id: w.get_id(), max: w.get_maximized(), focus: w.has_focus()}));
return JSON.stringify(win);
}
Focus(winid) {
let win = global.get_window_actors().map(a=>a.meta_window).find(w=>w.get_id()==winid);
if (win) {
win.activate(0);
} else {
throw new Error('Not found');
}
}
Minimize(winid) {
let win = global.get_window_actors().map(a=>a.meta_window).find(w=>w.get_id()==winid);
if (win) {
win.minimize(0);
} else {
throw new Error('Not found');
}
}
}
To get a list of windows, you can now place the DBUS call against /org/gnome/Shell/Extensions/Windows:
gdbus call --session --dest org.gnome.Shell --object-path /org/gnome/Shell/Extensions/Windows --method org.gnome.Shell.Extensions.Windows.List
Thanks!
Btw, as for the extension code above, is missing the "init" function, so the code needs to have this at the end
function init() {
return new Extension();
}
Now with that out of the way, wouldn't the best way to implement this on GNOME, be:
Implement a gdbus "client" inside xkeysnail, this would have to be async I guess, then, we could have our extension, but what we could try to do (I don't know is GNOME has an API or an event for this), but we can detect if the user changed windows, after that, we can emit a dbus event (This example if from the command line, but I could make one programatically) and since we are connected to the system/user bus via our client, we could just "listen" for this signal, the moment the signal is emited, we could just grab the window that is marked as "active" and return that as the window title
Maybe I’d have to look into it more. Xremap has solved it apparently for gnome & wayland. Kinto could be redone to use xremap or we can yes patch xkeysnail & do a PR on it.
@rbreaves Xremap seem to do a similar dbus Eval call according to the README
busctl --user call org.gnome.Shell /org/gnome/Shell org.gnome.Shell Eval s 'global.get_window_actors().map(a => a.get_meta_window().get_wm_class());'
@rbreaves I like how xremap looks and I think it is a little bit more modern than xkeysnail, however, I don't know how much effort it would take to rewrite or redo Kinto to use xremap.
@DiegoMagdaleno probably not as much as you think. The syntax for it looks quite similar to a yaml config format I’ve been wanting to make part of either Kinto or xkeysnail actually. That would have been a simple translation layer though.
Just wanted to highlight some cool work that rvaiya is doing in keyd here: https://github.com/rvaiya/keyd/blob/master/scripts/keyd-application-mapper
In this latest beta, keyd
now offers its hot-swappable keyboard layouts and layers with application focus conveyed by that python script via a socket to the keyd
daemon. Works on X, Wayland+Sway, and Wayland+Gnome.
That is very interesting, I've not used sway any.. and sadly my distro of choice, Ubuntu Budgie, does not have access to gnome-extensions even though it works with gnome-shell still.
This was very helpful for me! Thank you!
I was easily able to write a second query identical to the first, substituting only
get_title()
forget_wm_class()
to get the title of the currently open webpage in Firefox, Opera, or Chromium as well as the app name, while knowing virtually nothing about bash scripting.However, if
.get_wm_class()
(orget_title()
) is a string with an apostrophe or double-quote character, the name of the app will get messed up. I had to remove| cut -d'"' -f 2
from the end of your command to get something I found usable, which I was then able to parse in the python app that I was actually using your command in. FWIW, here are the basics of the python code I used (in case someone else happens to be in a similar boat)...