Spawn applications from systemd user service

Hello,

I have a systemd user service which provides a Gnome Search Provider for a 3rd party application. When the user activates a match I use g_app_info_launch to start the application with a file path to the selected match.

All in all this works fine, apart from a small nuisance: When started this way the application apparently becomes a “child” of the service, which means that when I stop the user service with “systemctl stop” systemd also stops the running application which isn’t really desired.

Is there a way to spawn an application (referred to by a DesktopAppInfo object) from within a systemd user service so that it runs “outside” of the service, perhaps as an immediate child of the shell?

Would g_app_info_launch_uris_async help?

Kind regards

It appears there is a merge request open in glib to automatically launch the program in a new systemd unit: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1596

It’s not merged though, so for now the way gnome-shell does it seems to be to use the gnome_start_systemd_scope function in libgnome-desktop.

I must admit that I had hoped for something more tractabl :pensive:

How should use that function? Call it with the PID of the new child process after spawning? If so where do I get that from? The launch function from an app info doesn’t seem to return it, and in case a dbus activatable app there wouldn’t be a new child process at all would it?

You can use the launched signal on GAppLaunchContext to retrieve the pid.

Edit: For d-bus activated services the d-bus daemon should be doing this automatically if built with support for systemd activation. It seems the launched signal will not even be emitted for those, since the process spawn is happening completely within the d-bus daemon.

@jfrancis Thanks a lot; I must admit that this looks all rather complicated :disappointed: I had really hoped for a single function I could replace g_app_info_launch with :innocent:

Your approach is rather more complex than that; I’d have to connect to a signal, meaning I’d need to spawn a Glib main loop I guess, and link to libgnome to call the function you mentioned. I don’t even have a mainloop currently, and I can’t easily add one, since I’m in rust with zbus which doesn’t have glib mainloop integration yet. And linking to libgnome from Rust doesn’t seem to be easy either :confused:

To me it’s not worth the effort just to fix a small minor issue, but many thanks for your help anyway; learned a bit along the way :pray:

Just some more notes… I don’t know if this helps when using zbus, but that libgnome-desktop function is just a wrapper around the StartTransientUnit d-bus method in systemd. You could try to call that method using zbus, that would save you from linking libgnome-desktop. I don’t think it’s much code to do that, you may just be able to copy the few properties it sets from there.

Emitting signals doesn’t require a main loop. But you may have to integrate the glib main loop anyway when using g_app_info_launch. That might be activating services using gdbus and I know there are some issues when trying to use gdbus without a main loop. Maybe you can run the glib main loop in a different thread, and ensure that g_app_info_launch is always called in that thread? You might also be able to get away with iterating the main loop after you launch the program, if you aren’t doing anything else with it and some minimal amount of blocking is okay.

1 Like

You could try to call that method using zbus, that would save you from linking libgnome-desktop. I don’t think it’s much code to do that, you may just be able to copy the few properties it sets from there.

Thanks a lot for your help, but it’s still a lot for something that’s just about 500 lines of code to glue something into Gnome :man_shrugging: And it’s not if I had much clue about all these systemd concepts :innocent:

Emitting signals doesn’t require a main loop

Oh, I wasn’t aware, thanks :slightly_smiling_face: I’m not familiar with glib.

But you may have to integrate the glib main loop anyway when using g_app_info_launch. That might be activating services using gdbus and I know there are some issues when trying to use gdbus without a main loop.

So far I haven’t seen any; perhaps I got lucky because the application I’m spawning doesn’t happen to be dbus activatable in fact.

That said, can’t help but think that there’s a lot of “maybe” in this paragraph :wink: The documentation of g_app_info_launch doesn’t say it needs a mainloop; is there a implicit assumption that just about anything in glib needs a mainloop unless explicitly said not to?

I can run the mainloop in a different thread and I guess I can also somehow bend Rust’s inter-thread communication stuff to my will so as to make sure I always launch the application from the mainloop thread while keeping the mainloop running, but that’s again a lot of code for what’s just a piece of glue between Gnome search and an external program.

I guess I could alternatively also replace zbus with gdbus and avoid the extra thread but it looks as if the gdbus server API isn’t so nice, and it doesn’t appear to have first-class support in glib.rs, likely making the whole thing rather ugly :see_no_evil:

Doing any of the above would likely double the size of my code; if all I get in return is keeping the application alive in the unlikely case I ever stop the systemd service and some protection against some “maybe” issues with g_app_info_launch then think I’ll let it pass.

I’m sorry; I appreciate the support you gave me a lot and I thank you very much for the time you’ve spent in answering, and I really hate to be ungrateful, but it’s just that I only have so much time for these things :disappointed: As such, something which works 90% with some small nuisance is good enough for me; I’d not like to spent another weekend to get it past the last 10% :innocent:

It should be much less than that, I think it’s just the one call to that one d-bus method.

Not so much in glib but in gio. There are some areas there such as GTask and GDBus that will sometimes use idle sources and timeout sources internally. These will not dispatch correctly without iterating the main loop. This unfortunately is not well documented AFAIK, I had to grep through the code to find the instances of it. I don’t think you need to replace zbus to handle this case, but I don’t have much experience with all the different async event loops in Rust so I can’t say for sure.

Another option might be to use g_app_info_get_commandline, launch the process manually, and then grab the pid from there. You could use the rust std::process if that integrates better. I think if you use the classic double fork method that may solve your problem too, that won’t make a new systemd scope but it will reparent the program to the user’s login scope. On second thought a better solution might be to skip all that and invoke the command with systemd-run. I think that will call StartTransientUnit internally too.

1 Like

Turns how that zbus exposes the DBus connection file descriptor, so I was able to hook zbus into the glib mainloop with g_unix_fd_add.

I’m looking into systemd-run next, but I’m not sure how to use g_app_info_get_commandline. There doesn’t seem to be a way to pass URIs/files to it, so I guess I have to substitute those “magic” symbols like %f myself, do I?