How to call a Bash script from an Gnome extension?

I’d like to call a Bash or Python script from a Gnome extension. From what I read this should be possible, I just can’t find the documentation for it. :neutral_face:

Usually you won’t want to do this. Reviewers strongly discourage spawning subprocesses from extensions unless its unavoidable, such as functionality only available in a Python module (eg. pychromecast) or spawning a CLI binary for a program that has no other interface (eg. nordvpn).

If you do determine it’s necessary, we have a guide on spawning subprocesses in GJS on the gjs.guide website: Subprocesses in GJS. Generally that looks something like this:

const {Gio} = imports.gi;

let proc = Gio.Subprocess.new(['python3', '/path/to/script.py'],
    Gio.SubprocessFlags.NONE);

proc.wait_check_async(null, (proc, result) => {
    try {
        if (proc.wait_check_finish(result))
            log('the process succeeded');
        else
            log('the process failed');
    } catch (e) {
        logError(e);
    }
});
1 Like

I understand, thanks. I’d be okay doing it in JavaScript, but I need a good library to call some dbus functions. For now I could only find a python library to do that, so I’m currently using that. If that also exists for JS directly, I’d be willing to give it a try.

Okay, I think I just found it, but I’m not sure how to translate my python script to JS using Gio.DBus.

Python:

#!/usr/bin/python3

# Toggles the scaling of the primary monitor between 1.0 and 2.0

# https://dbus.freedesktop.org/doc/dbus-python/tutorial.html
# https://github.com/GNOME/mutter/blob/b5f99bd12ebc483e682e39c8126a1b51772bc67d/data/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml
# https://ask.fedoraproject.org/t/change-scaling-resolution-of-primary-monitor-from-bash-terminal/19892

import dbus
bus = dbus.SessionBus()

display_config_well_known_name = "org.gnome.Mutter.DisplayConfig"
display_config_object_path = "/org/gnome/Mutter/DisplayConfig"

display_config_proxy = bus.get_object(display_config_well_known_name, display_config_object_path)
display_config_interface = dbus.Interface(display_config_proxy, dbus_interface=display_config_well_known_name)

serial, physical_monitors, logical_monitors, properties = display_config_interface.GetCurrentState()

updated_logical_monitors=[]
for x, y, scale, transform, primary, linked_monitors_info, props in logical_monitors:
    if primary == 1:
        scale = 2.0 if (scale == 1.0) else 1.0 # toggle scaling between 1.0 and 2.0 for the primary monitor
    physical_monitors_config = []
    for linked_monitor_connector, linked_monitor_vendor, linked_monitor_product, linked_monitor_serial in linked_monitors_info:
        for monitor_info, monitor_modes, monitor_properties in physical_monitors:
            monitor_connector, monitor_vendor, monitor_product, monitor_serial = monitor_info
            if linked_monitor_connector == monitor_connector:
                for mode_id, mode_width, mode_height, mode_refresh, mode_preferred_scale, mode_supported_scales, mode_properties in monitor_modes:
                    if mode_properties.get("is-current", False): # ( mode_properties provides is-current, is-preferred, is-interlaced, and more)
                        physical_monitors_config.append(dbus.Struct([monitor_connector, mode_id, {}]))
                        if scale not in mode_supported_scales:
                            print("Error: " + monitor_properties.get("display-name") + " doesn't support that scaling value! (" + str(scale) + ")")
                        else:
                            print("Setting scaling of: " + monitor_properties.get("display-name") + " to " + str(scale) + "!")
    updated_logical_monitor_struct = dbus.Struct([dbus.Int32(x), dbus.Int32(y), dbus.Double(scale), dbus.UInt32(transform), dbus.Boolean(primary), physical_monitors_config])
    updated_logical_monitors.append(updated_logical_monitor_struct)

properties_to_apply = { "layout_mode": properties.get("layout-mode")}
method = 2 # 2 means show a prompt before applying settings; 1 means instantly apply settings without prompt
display_config_interface.ApplyMonitorsConfig(dbus.UInt32(serial), dbus.UInt32(method), updated_logical_monitors, properties_to_apply)

What I got so far in JavaScript (which doesn’t work):

const { DBus, DBusProxy } = imports.gi.Gio;

const display_config_well_known_name = "org.gnome.Mutter.DisplayConfig"
const display_config_object_path = "/org/gnome/Mutter/DisplayConfig"
const display_config_interface = `<interface name="${display_config_well_known_name}">
    <method name="GetCurrentState" />
    <method name="ApplyMonitorsConfig" />
</interface>`;

const display_config_proxy = DBusProxy.makeProxyWrapper(MyIface);
const display_config_interface = new display_config_proxy(DBus.session, display_config_well_known_name, display_config_object_path);

const { serial, physical_monitors, logical_monitors, properties } = display_config_interface.GetCurrentState()

const updated_logical_monitors = []
for (let { x, y, scale, transform, primary, linked_monitors_info, props } of logical_monitors) {
    if (primary === 1)
        scale = (scale === 1.0) ? 2.0 : 1.0 # toggle scaling between 1.0 and 2.0 for the primary monitor
    const physical_monitors_config = []
    for (const { linked_monitor_connector, linked_monitor_vendor, linked_monitor_product, linked_monitor_serial } of linked_monitors_info) {
        for (const { monitor_info, monitor_modes, monitor_properties } of physical_monitors) {
            const { monitor_connector, monitor_vendor, monitor_product, monitor_serial } = monitor_info
            if (linked_monitor_connector === monitor_connector) {
                for (const { mode_id, mode_width, mode_height, mode_refresh, mode_preferred_scale, mode_supported_scales, mode_properties } of monitor_modes) {
                    if (mode_properties.get("is-current", False)) { # ( mode_properties provides is-current, is-preferred, is-interlaced, and more)
                        physical_monitors_config.push(DBus.Struct([monitor_connector, mode_id, {}]))
                        if (!scale.includes(mode_supported_scales)) {
                            console.log("Error: " + monitor_properties.get("display-name") + " doesn't support that scaling value! (" + scale + ")")
                        } else {
                            console.log("Setting scaling of: " + monitor_properties.get("display-name") + " to " + scale + "!")
                        }
    const updated_logical_monitor_struct = DBus.Struct([DBus.Int32(x), DBus.Int32(y), DBus.Double(scale), DBus.UInt32(transform), DBus.Boolean(primary), physical_monitors_config])
    updated_logical_monitors.append(updated_logical_monitor_struct)

const properties_to_apply = { "layout_mode": properties.get("layout-mode")}
const method = 2 # 2 means show a prompt before applying settings; 1 means instantly apply settings without prompt
display_config_interface.ApplyMonitorsConfig(DBus.UInt32(serial), DBus.UInt32(method), updated_logical_monitors, properties_to_apply)

Mainly, I don’t know how to call my interface methods and how to create data types like Structs, UInt32 etc., but I can’t find any examples. :confused:

but I can’t find any examples.

A code search for makeProxyWrapper should bring up plenty of examples, not least in gnome-shell itself.

There will also be an extensive gjs.guide section on the topic soon (link goes to the corresponding merge request).

Wait, why are you calling Mutter’s D-Bus API in a GNOME Shell extension? You have access to the internals, you don’t need to use a remote procedure call to get them.

It’s the only way I could find change the display scaling on recent versions of wayland/gnome. How would I call it directly?

You have access to the internals, you don’t need to use a remote procedure call to get them.

In this case the internals are in mutter and not exposed to gnome-shell.

That’s kind of unfortunate that you have to run IPC to the same process to get that data.

Yes, I agree.

But then not everything has to be designed around the possibility that an extension would want to use it. In this case we ended up with a private interface between mutter and gnome-control-center, and that’s OK as far as GNOME itself is concerned :person_shrugging:

The guide for D-Bus in GJS is now live. Developers may also find the GVariant guide useful when working with D-Bus in GJS.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.