Print From Gio.DBus.session.call

I have a gjs script like:

#!/usr/bin/env gjs

const { GLib, Gio } = imports.gi;

Gio.DBus.session.call(
    'org.gnome.Shell.Extensions.GnomeUtilsWorkspaces',
    '/org/gnome/Shell/Extensions/GnomeUtilsWorkspaces',
    'org.gnome.Shell.Extensions.GnomeUtilsWorkspaces',
    'GetWorkspaces',
    null,
    null,
    Gio.DBusCallFlags.NONE,
    -1,
    null,
    (connection, res) => {
        print("Connection");
    }
);

print("Testing");

The problem is, print("Testing"); works., But print("Connection"); does not print anything in the stdout. I can not print anything from within the callback function of Gio.DBus.session.call.

How can I get the return value of dbus call from gjs script.

To be more clear, the dbus command is:

dbus-send --print-reply=literal --session --dest=org.gnome.Shell /org/gnome/Shell/Extensions/GnomeUtilsWorkspaces org.gnome.Shell.Extensions.GnomeUtilsWorkspaces.GetWorkspaces

And it returns a string.

We have an extensive tutorial for D-Bus, as well as a tutorial for GVariant on gjs.guide.

The answer to your question is that you need to call the “finish” function in your callback to get the result:

(connection, res) => {
  try {
    const reply = connection.call_finish(res);
    console.log(`Raw Result: ${reply.print(true)}`);

    const [string] = reply.recursiveUnpack();
    console.log(`Unpacked Result: ${string}`);
  } catch (e) {
    logError(e);
  }
}

Note that you will also need to ensure you are running a GLib.MainLoop (or iterating the main context some other way) or the callback will never be invoked. In that case, you should probably review the Asynchronous Programming tutorial as well.

1 Like

I have tried (trial and error) many combinations. And also went through the tutorial (in fact the code of my question is from the tutorial page). I think the code is now ok after your suggestions. But how to connect it to the main loop.

Here is my complete code:

#!/usr/bin/env gjs

const { GLib, Gio } = imports.gi;

const connection = Gio.DBus.session;

const loop = new GLib.MainLoop(null, false);

const sourceId = GLib.timeout_add_seconds(
    GLib.PRIORITY_DEFAULT,
    1,
    () => {
            connection.call(
            'org.gnome.Shell.Extensions.GnomeUtilsWorkspaces',
            '/org/gnome/Shell/Extensions/GnomeUtilsWorkspaces',
            'org.gnome.Shell.Extensions.GnomeUtilsWorkspaces',
            'GetWorkspaces',
            null,
            null,
            Gio.DBusCallFlags.NONE,
            -1,
            null,
            (connection, res) => {
                try {
                    const reply = connection.call_finish(res);
                    console.log(`Raw Result: ${reply.print(true)}`);
                    const [string] = reply.recursiveUnpack();
                    console.log(`Unpacked Result: ${string}`);
                } catch (e) {
                    if (e instanceof Gio.DBusError)
                        Gio.DBusError.strip_remote_error(e);

                    logError(e);
                }
            }
        )
    }
);

loop.run();

Here it is saying:

(gjs:121339): Gjs-WARNING **: 08:25:18.802: JS ERROR: Gio.DBusError: The name org.gnome.Shell.Extensions.GnomeUtilsWorkspaces was not provided by any .service files
sourceId</<@./test:25:46
@./test:40:6

In the extension where I have defined GetWorkspaces, there is a variable like:

var MR_DBUS_IFACE = `
<node>
   <interface name="org.gnome.Shell.Extensions.GnomeUtilsWorkspaces">
      <method name="GetCurrentWorkspace">
         <arg type="i" direction="out" name="workspaces" />
      </method>
      <method name="MoveFocusedWindowToWorkspace">
         <arg type="i" direction="in" name="workspace_num" />
      </method>
      <method name="MoveWindowToWorkspace">
         <arg type="u" direction="in" name="winid" />
         <arg type="i" direction="in" name="workspace_num" />
      </method>
      <method name="MoveWindowToWorkspaceAndFocus">
         <arg type="u" direction="in" name="winid" />
         <arg type="i" direction="in" name="workspace_num" />
      </method>
      <method name="GetWorkspaces">
         <arg type="s" direction="out" name="workspaces" />
      </method>
   </interface>
</node>`;

Is it talking about this? Do the gjs also need to have it?

This is telling that the service you are trying to call a method on is not exported on D-Bus at org.gnome.Shell.Extensions.GnomeUtilsWorkspaces.

You’ll want to go through tutorial, where it explains the difference between well-known names, objects and interfaces, then move on to Owning a Name if you want your own service name. I’d recommend you read the full tutorial though, if you’re new to D-Bus.

I can call the function with dbus-send command.

dbus-send --print-reply=literal --session --dest=org.gnome.Shell /org/gnome/Shell/Extensions/GnomeUtilsWorkspaces org.gnome.Shell.Extensions.GnomeUtilsWorkspaces.GetWorkspaces

Here is a screenshot.

So, how the service i am trying to call a method on is not exported on D-Bus?

The extension I am using is located in:

I have gone through the pages you have linked. Maybe i am missing something. Without working code, it is hard for me understand how the pieces ( Gio.DBus.session.call, GLib.MainLoop, …) come together.

There is a working code in bash.

#!/usr/bin/env bash

json=$(dbus-send --print-reply=literal --session --dest=org.gnome.Shell /org/gnome/Shell/Extensions/GnomeUtilsWorkspaces org.gnome.Shell.Extensions.GnomeUtilsWorkspaces.GetWorkspaces)

current_workspace=$(echo $json | jq -r .current_workspace)

number_of_workspaces=$(echo $json | jq .number_of_workspaces)

all_normal_windows_of_workspaces=$(echo $json | jq .all_normal_windows_of_workspaces)

all_normal_windows_of_current_workspaces=$(echo $json | jq ".all_normal_windows_of_workspaces .$current_workspace" | jq -c '.[]' )

But i think being able to use gjs will ultimately help me with the json (or at least it will be a good learning experience).

 connection.call(
            'org.gnome.Shell.Extensions.GnomeUtilsWorkspaces',  // <========= well-known name
            '/org/gnome/Shell/Extensions/GnomeUtilsWorkspaces',
            'org.gnome.Shell.Extensions.GnomeUtilsWorkspaces',
            'GetWorkspaces',
            null,
            null,
            Gio.DBusCallFlags.NONE,
            -1,
            null,
            (connection, res) => {}
);
dbus-send --print-reply=literal
          --session
          --dest=org.gnome.Shell   <=== well-known name
          /org/gnome/Shell/Extensions/GnomeUtilsWorkspaces 
          org.gnome.Shell.Extensions.GnomeUtilsWorkspaces.GetWorkspaces

EDIT: a second set of eyes never hurts either, so I don’t mind you asking :slight_smile:

1 Like

To be clearer about what happened here, you exported your interface (org.gnome.Shell.Extensions.GnomeUtilsWorkspaces) at an object path (/org/gnome/Shell/Extensions/GnomeUtilsWorkspaces).

Because the gnome-shell process already owns the well-known name org.gnome.Shell, any calls to org.gnome.Shell will be routed to the unique name (just like www.gnome.org gets routed to an IP address). However you can also own your own name, which would get routed to the same unique name, but might feel fancier.

If you look in D-Spy you will see that many object paths and interfaces are exported at org.gnome.Shell, and the same object paths and interfaces can be found under the name org.gnome.Mutter and many other names that process owns.

1 Like

Yes sir, This is now working. The three issues remaining are:

  1. Does not quit the program.
  2. The following code give output beginning with Gjs-Console-Message: 09:22:32.808:. i need raw json output.
const [string] = reply.recursiveUnpack();
console.log(`${string}`);
  1. it would be better if i could print to stdout instead of stderr.

print(${string}); solves point 2 and 3

If you just want that run like a CLI program, you can call loop.quit() from the callback

I’m not sure what your output looks like, but assuming the service returns something created by e.g. JSON.stringify(), that should be enough. console.log() and friends are for program development etc, otherwise print() and printerr() are the easiest ways to write to stdout/stderr respectively and shouldn’t have any extra bits.

1 Like

Actually, I’d recommend doing:

try {
  // stuff
} catch (e) {
  // in case of error
} finally {
  // so it definitely quits
  loop.quit();
}
1 Like

Thank you very much. This solves all my problems completely.

1 Like

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