1

I'm trying to get a list of the system users in Vala, but all I get is an empty list. As Vala documentation is quite simple, I dont know how to solve this. This is what I'm trying:

var users_list = Act.UserManager.get_default ().list_users ();
Jens Mühlenhoff
  • 14,565
  • 6
  • 56
  • 113
dvilela
  • 1,200
  • 12
  • 29
  • There is additional documentation in the source tarbar for the accountservice library. Click on "Download releases" on the home page: https://www.freedesktop.org/wiki/Software/AccountsService/ – Jens Mühlenhoff Jun 19 '17 at 12:10
  • The accountsservice package contains a binary that you can run from your terminal. You might want to run that first to see if the DBus interface actually works on your system. – Jens Mühlenhoff Jun 19 '17 at 12:12
  • I have taken a look at that documentation in the source, but is seems pretty the same. It seems that's the code I should be using. How can I check that binary's name? – dvilela Jun 19 '17 at 18:24
  • Sorry, there is no user binary. I was under the impression, because there is a binary packages, but that does not contain a user binary. – Jens Mühlenhoff Jun 19 '17 at 20:03
  • One thing to note though: "Returns the user manager singleton instance. Calling this function will automatically being loading the user list if it isn't loaded already. The “is-loaded” property will be set to TRUE when the users are finished loading and then act_user_manager_list_users() can be called." – Jens Mühlenhoff Jun 19 '17 at 20:05
  • Maybe you have to wait until the "is-loaded" property is set to TRUE? – Jens Mühlenhoff Jun 19 '17 at 20:05
  • I thought so, and i set a while loop waiting for is_loaded without luck. I waited for 20 seconds or so, but the property didnt seem to change. – dvilela Jun 19 '17 at 20:12
  • Maybe use `GLib.Timeout` instead of a tight loop? – Jens Mühlenhoff Jun 19 '17 at 21:21

1 Answers1

2

It looks as though the UserManager doesn't have the data available when it is created. The data is only available when the is_loaded property is true.

In GLib a notify signal can be emitted when a property changes. So we will take advantage of that in the following working example:

int main () {
    var loop = new EventLoop ();
    var manager = new UserManager (loop);
    if (!manager.is_running) {
        print ("AccountsService is not running\n");
        return 1;
    }
    loop.run ();
    return 0;
}

class UserManager {

    private Act.UserManager manager;
    private EventLoop loop;

    public bool is_running {
        get { return !manager.no_service (); }
    }

    public UserManager (EventLoop event_loop) {
        loop = event_loop;
        manager = Act.UserManager.get_default ();
        manager.notify["is-loaded"].connect( this.loaded );
    }

    void loaded (ParamSpec property) {
        print (@"Property \"$(property.name)\" has changed\n");
        this.print_users ();
        this.loop.quit ();
    }

    void print_users () {
        if (!manager.is_loaded) { return; }
        print ("%-20s | %-20s\n", "User name", "Logged In Time");
        foreach (var user in manager.list_users ()) {
            print ("%-20s | %-20s\n",
                   user.user_name,
                   new DateTime.from_unix_local(user.login_time).to_string()
                   );
            }
    }
}

class EventLoop {

    private MainLoop loop;

    public EventLoop () {
        loop = new MainLoop ();
    }

    public void run() {
        this.loop.run ();
    }

    public void quit() {
        Idle.add (()=> { 
            this.loop.quit ();
            return Source.REMOVE;
        });
    }
}

The example creates a UserManager class to wrap the AccountsService UserManager. It is assumed that the user manager is never loaded when it is first returned by Act.UserManager.get_default () so as part of the constructor a callback is set up when the is_loaded property changes. This is the line:

manager.notify["is-loaded"].connect( this.loaded );

The manager has a notify signal that is emitted when any property changes. The example uses a signal detail to only get fired when the is-loaded property changes. For some reason this uses a dash in its name, rather than the underscore. I couldn't find any documentation on why that is. With a notify signal the callback can take a ParamSpec as an argument. This was used to find details of the property that changed, but in the example is no longer necessary because the "is-loaded" signal detail is used.

The example also creates an EventLoop class that acts as a wrapper around GLib's MainLoop. UserManager has EventLoop as a dependency so the event loop can quit and the program finish.

Another approach would be to use the org.freedesktop.Accounts D-Bus service directly from Vala.

AlThomas
  • 4,169
  • 12
  • 22
  • This is working great, but why does not work using a while loop until is_loaded changes? – dvilela Jun 20 '17 at 11:54
  • AccountsService is a client for the `org.freedesktop.Accounts` D-Bus service. The GLib implementation of D-Bus makes use of GLib's [MainLoop](https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html) for event based programming. So I think you need to allow the program to return to the main loop so it can receive messages from the D-Bus service. Using a `while` loop doesn't do that. – AlThomas Jun 20 '17 at 12:16
  • That is why I suggested using GLib.Timeout instead of a tight while loop. This solution is even better of course :). – Jens Mühlenhoff Jun 21 '17 at 11:55