Skip to content

Commit 14e47b8

Browse files
committed
Implement SystemD activation
Closes #369 Closes #433
1 parent 65d9762 commit 14e47b8

6 files changed

+138
-20
lines changed

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ PACK_CONTENT += \
192192
ddterm/com.github.amezin.ddterm.Extension.xml \
193193
ddterm/com.github.amezin.ddterm.desktop.in \
194194
ddterm/com.github.amezin.ddterm.service.in \
195+
ddterm/dbus-com.github.amezin.ddterm.service.in \
195196
LICENSE \
196197

197198
PACK_CONTENT := $(sort $(PACK_CONTENT))

ddterm/app/application.js

+44-2
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ const Application = GObject.registerClass(
8383
'Ask the extension to launch the app',
8484
null
8585
);
86+
this.add_main_option(
87+
'attach-unit',
88+
0,
89+
GLib.OptionFlags.NONE,
90+
GLib.OptionArg.STRING,
91+
'Attach launched application process to the specified systemd unit',
92+
'UNIT_NAME'
93+
);
8694

8795
this.connect('startup', this.startup.bind(this));
8896
this.connect('handle-local-options', this.handle_local_options.bind(this));
@@ -228,17 +236,30 @@ const Application = GObject.registerClass(
228236
}
229237

230238
handle_local_options(_, options) {
239+
const attach_unit = options.lookup('attach-unit');
240+
231241
if (options.lookup('launch-through-extension')) {
232242
try {
233-
const iface = extensiondbus.get();
234-
iface.ServiceSync();
243+
extensiondbus.get().ServiceSync(
244+
attach_unit ? ['--attach-unit', attach_unit] : []
245+
);
246+
235247
return 0;
236248
} catch (e) {
237249
logError(e);
238250
return 1;
239251
}
240252
}
241253

254+
if (attach_unit) {
255+
try {
256+
this.attach_unit(attach_unit);
257+
} catch (e) {
258+
logError(e);
259+
return 1;
260+
}
261+
}
262+
242263
this.decorated = !options.lookup('undecorated');
243264

244265
if (!(this.flags & Gio.ApplicationFlags.IS_SERVICE))
@@ -247,6 +268,27 @@ const Application = GObject.registerClass(
247268
return -1;
248269
}
249270

271+
attach_unit(unit) {
272+
Gio.DBus.session.call_sync(
273+
'org.freedesktop.systemd1',
274+
'/org/freedesktop/systemd1',
275+
'org.freedesktop.systemd1.Manager',
276+
'AttachProcessesToUnit',
277+
GLib.Variant.new_tuple([
278+
GLib.Variant.new_string(unit),
279+
GLib.Variant.new_string(''),
280+
GLib.Variant.new_array(
281+
new GLib.VariantType('u'),
282+
[GLib.Variant.new_uint32(Gio.Credentials.new().get_unix_pid())]
283+
),
284+
]),
285+
null,
286+
Gio.DBusCallFlags.NONE,
287+
-1,
288+
null
289+
);
290+
}
291+
250292
preferences() {
251293
if (this.prefs_dialog === null) {
252294
this.prefs_dialog = new PrefsDialog({

ddterm/com.github.amezin.ddterm.Extension.xml

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
<interface name="com.github.amezin.ddterm.Extension">
66
<method name="Toggle"/>
77
<method name="Activate"/>
8-
<method name="Service"/>
8+
<method name="Service">
9+
<arg type="as" name="args" direction="in"/>
10+
</method>
911
<method name="MissingDependenciesNotification">
1012
<arg type="as" name="packages" direction="in"/>
1113
<arg type="as" name="files" direction="in"/>
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
[D-BUS Service]
22
Name=com.github.amezin.ddterm
33
Exec="@LAUNCHER@" --launch-through-extension
4+
SystemdService=dbus-com.github.amezin.ddterm.service
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[Service]
2+
Type=forking
3+
BusName=com.github.amezin.ddterm
4+
ExecStart="@LAUNCHER@" --launch-through-extension --attach-unit "%n"
5+
Delegate=

ddterm/shell/extension.js

+84-17
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ let window_connections = null;
4444
let dbus_interface = null;
4545

4646
let desktop_entry = null;
47+
let systemd_service = null;
4748
let dbus_service = null;
4849

4950
const APP_ID = 'com.github.amezin.ddterm';
@@ -127,8 +128,28 @@ class ExtensionDBusInterface {
127128
activate();
128129
}
129130

130-
Service() {
131-
spawn_app();
131+
ServiceAsync(params, invocation) {
132+
const return_error = err => {
133+
logError(err);
134+
135+
if (err instanceof GLib.Error) {
136+
invocation.return_gerror(err);
137+
} else {
138+
let name = err.name;
139+
if (!name.includes('.'))
140+
name = `org.gnome.gjs.JSError.${name}`;
141+
142+
invocation.return_dbus_error(name, err.message);
143+
}
144+
};
145+
146+
try {
147+
const [args] = params;
148+
149+
spawn_app(args).then(() => invocation.return_value(null)).catch(return_error);
150+
} catch (err) {
151+
return_error(err);
152+
}
132153
}
133154

134155
MissingDependenciesNotification(packages, files) {
@@ -146,15 +167,7 @@ class ExtensionDBusInterface {
146167
for (const filename of files)
147168
cmd.push('--file', filename);
148169

149-
const [_, pid] = GLib.spawn_async(
150-
null,
151-
cmd,
152-
null,
153-
GLib.SpawnFlags.DEFAULT,
154-
null
155-
);
156-
157-
GLib.spawn_close_pid(pid);
170+
Gio.Subprocess.new(cmd, Gio.SubprocessFlags.NONE);
158171
}
159172

160173
GetTargetRect() {
@@ -292,6 +305,31 @@ function enable() {
292305
);
293306
desktop_entry.install();
294307

308+
systemd_service = new InstallableResource(
309+
Me.dir.get_child('ddterm').get_child('dbus-com.github.amezin.ddterm.service.in'),
310+
Gio.File.new_for_path(GLib.build_filenamev(
311+
[
312+
GLib.get_user_runtime_dir(),
313+
'systemd',
314+
'user',
315+
`dbus-${APP_ID}.service`,
316+
]))
317+
);
318+
systemd_service.install();
319+
320+
Gio.DBus.session.call(
321+
'org.freedesktop.systemd1',
322+
'/org/freedesktop/systemd1',
323+
'org.freedesktop.systemd1.Manager',
324+
'Reload',
325+
null,
326+
null,
327+
Gio.DBusCallFlags.NO_AUTO_START,
328+
-1,
329+
null,
330+
null
331+
);
332+
295333
dbus_service = new InstallableResource(
296334
Me.dir.get_child('ddterm').get_child('com.github.amezin.ddterm.service.in'),
297335
Gio.File.new_for_path(GLib.build_filenamev(
@@ -372,6 +410,11 @@ function disable() {
372410
desktop_entry = null;
373411
}
374412

413+
if (systemd_service) {
414+
systemd_service.uninstall();
415+
systemd_service = null;
416+
}
417+
375418
if (dbus_service) {
376419
dbus_service.uninstall();
377420
dbus_service = null;
@@ -380,7 +423,7 @@ function disable() {
380423
settings = null;
381424
}
382425

383-
function spawn_app() {
426+
async function spawn_app(args) {
384427
if (subprocess)
385428
return;
386429

@@ -393,6 +436,7 @@ function spawn_app() {
393436
Me.dir.get_child(APP_ID).get_path(),
394437
'--undecorated',
395438
'--gapplication-service',
439+
...args,
396440
];
397441

398442
if (Meta.is_wayland_compositor()) {
@@ -407,12 +451,35 @@ function spawn_app() {
407451

408452
printerr(`Starting ddterm app: ${JSON.stringify(argv)}`);
409453

410-
if (wayland_client)
411-
subprocess = wayland_client.spawnv(global.display, argv);
412-
else
413-
subprocess = subprocess_launcher.spawnv(argv);
454+
let available_handler = null;
455+
456+
const registered = new Promise(resolve => {
457+
available_handler = app_dbus.connect('notify::available', source => {
458+
if (source.available)
459+
resolve();
460+
});
461+
});
462+
463+
try {
464+
if (wayland_client)
465+
subprocess = wayland_client.spawnv(global.display, argv);
466+
else
467+
subprocess = subprocess_launcher.spawnv(argv);
468+
469+
const terminated = new Promise(resolve => {
470+
subprocess.wait_async(null, source => {
471+
subprocess_terminated(source);
472+
resolve();
473+
});
474+
});
475+
476+
await Promise.race([terminated, registered]);
477+
} finally {
478+
app_dbus.disconnect(available_handler);
479+
}
414480

415-
subprocess.wait_async(null, subprocess_terminated);
481+
if (!subprocess)
482+
throw new Error('ddterm app exited without acquiring bus name');
416483
}
417484

418485
function subprocess_terminated(source) {

0 commit comments

Comments
 (0)