Search code examples
linuxvalaaccountsservice

Why does accountsservice always return an empty user list?


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 ();

Solution

  • 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.