Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-3.0-or-later
2 : : // SPDX-FileCopyrightText: Andy Holmes <andrew.g.r.holmes@gmail.com>
3 : :
4 : : #define G_LOG_DOMAIN "valent-runcommand-plugin"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <glib/gi18n.h>
9 : : #include <json-glib/json-glib.h>
10 : : #include <libportal/portal.h>
11 : : #include <valent.h>
12 : :
13 : : #include "valent-runcommand-plugin.h"
14 : : #include "valent-runcommand-utils.h"
15 : :
16 : :
17 : : struct _ValentRuncommandPlugin
18 : : {
19 : : ValentDevicePlugin parent_instance;
20 : :
21 : : GSubprocessLauncher *launcher;
22 : : GHashTable *subprocesses;
23 : :
24 : : unsigned long commands_changed_id;
25 : : };
26 : :
27 [ + + + - ]: 79 : G_DEFINE_FINAL_TYPE (ValentRuncommandPlugin, valent_runcommand_plugin, VALENT_TYPE_DEVICE_PLUGIN)
28 : :
29 : : static void valent_runcommand_plugin_execute_local_command (ValentRuncommandPlugin *self,
30 : : const char *key);
31 : : static void valent_runcommand_plugin_execute_remote_command (ValentRuncommandPlugin *self,
32 : : const char *key);
33 : : static void valent_runcommand_plugin_handle_command_list (ValentRuncommandPlugin *self,
34 : : JsonObject *command_list);
35 : : static void valent_runcommand_plugin_handle_runcommand (ValentRuncommandPlugin *self,
36 : : JsonNode *packet);
37 : : static void valent_runcommand_plugin_handle_runcommand_request (ValentRuncommandPlugin *self,
38 : : JsonNode *packet);
39 : : static void valent_runcommand_plugin_send_command_list (ValentRuncommandPlugin *self);
40 : :
41 : :
42 : : /*
43 : : * Launcher Helpers
44 : : */
45 : : static void
46 : 1 : launcher_init (ValentRuncommandPlugin *self)
47 : : {
48 : 1 : ValentDevice *device;
49 : 1 : GSubprocessFlags flags;
50 : :
51 [ + - ]: 1 : if G_UNLIKELY (self->launcher != NULL)
52 : : return;
53 : :
54 : : #ifdef VALENT_ENABLE_DEBUG
55 : 1 : flags = G_SUBPROCESS_FLAGS_NONE;
56 : : #else
57 : : flags = (G_SUBPROCESS_FLAGS_STDERR_SILENCE |
58 : : G_SUBPROCESS_FLAGS_STDOUT_SILENCE);
59 : : #endif /* VALENT_ENABLE_DEBUG */
60 : :
61 : 1 : self->launcher = g_subprocess_launcher_new (flags);
62 : :
63 : 1 : device = valent_resource_get_source (VALENT_RESOURCE (self));
64 : 1 : g_subprocess_launcher_setenv (self->launcher,
65 : : "VALENT_DEVICE_ID",
66 : 1 : valent_device_get_id (device),
67 : : TRUE);
68 : 1 : g_subprocess_launcher_setenv (self->launcher,
69 : : "VALENT_DEVICE_NAME",
70 : 1 : valent_device_get_name (device),
71 : : TRUE);
72 : : }
73 : :
74 : : static void
75 : 1 : launcher_clear (ValentRuncommandPlugin *self)
76 : : {
77 : 1 : GHashTableIter iter;
78 : 1 : gpointer subprocess;
79 : :
80 : 1 : g_hash_table_iter_init (&iter, self->subprocesses);
81 : :
82 [ - + ]: 1 : while (g_hash_table_iter_next (&iter, NULL, &subprocess))
83 : : {
84 : 0 : g_subprocess_force_exit (G_SUBPROCESS (subprocess));
85 : 0 : g_hash_table_iter_remove (&iter);
86 : : }
87 : :
88 [ - + ]: 1 : g_clear_object (&self->launcher);
89 : 1 : }
90 : :
91 : : static void
92 : 0 : launcher_watch (GSubprocess *subprocess,
93 : : GAsyncResult *result,
94 : : GHashTable *subprocesses)
95 : : {
96 : 0 : g_autoptr (GError) error = NULL;
97 : :
98 [ # # # # ]: 0 : if (!g_subprocess_wait_finish (subprocess, result, &error) &&
99 : 0 : !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
100 : 0 : g_warning ("Process failed: %s", error->message);
101 : :
102 : 0 : g_hash_table_remove (subprocesses, subprocess);
103 [ # # ]: 0 : g_hash_table_unref (subprocesses);
104 : 0 : }
105 : :
106 : : static gboolean
107 : 1 : launcher_execute (ValentRuncommandPlugin *self,
108 : : GVariant *command,
109 : : GError **error)
110 : : {
111 : 2 : g_autoptr (GSubprocess) subprocess = NULL;
112 [ + - ]: 1 : g_autoptr (GString) args = NULL;
113 : 1 : g_auto (GStrv) argv = NULL;
114 [ + - ]: 1 : g_autofree char *command_quoted = NULL;
115 : 1 : const char *command_args = "";
116 : :
117 [ + - ]: 1 : g_assert (VALENT_IS_RUNCOMMAND_PLUGIN (self));
118 [ + - - + ]: 1 : g_assert (command != NULL && g_variant_is_of_type (command, G_VARIANT_TYPE_VARDICT));
119 [ + - - + ]: 1 : g_assert (error == NULL || *error == NULL);
120 : :
121 : 1 : launcher_init (self);
122 : :
123 : 1 : args = g_string_new ("");
124 : 1 : g_variant_lookup (command, "command", "&s", &command_args);
125 : :
126 : :
127 : : /* When running in a Flatpak, run the command on the host if possible */
128 [ - + - - ]: 1 : if (xdp_portal_running_under_flatpak () &&
129 : 0 : valent_runcommand_can_spawn_host ())
130 [ # # ]: 0 : g_string_append (args, "flatpak-spawn --host ");
131 : :
132 : : /* Quote the arguments for the shell */
133 : 1 : command_quoted = g_shell_quote (command_args);
134 : 1 : g_string_append_printf (args, "sh -c %s", command_quoted);
135 : :
136 [ - + ]: 1 : if (!g_shell_parse_argv (args->str, NULL, &argv, error))
137 : : return FALSE;
138 : :
139 : 1 : subprocess = g_subprocess_launcher_spawnv (self->launcher,
140 : : (const char * const *)argv,
141 : : error);
142 : :
143 [ - + ]: 1 : if (subprocess == NULL)
144 : : return FALSE;
145 : :
146 : : /* The task holds the final reference to the GSubprocess object */
147 : 1 : g_subprocess_wait_async (subprocess,
148 : : NULL,
149 : : (GAsyncReadyCallback)launcher_watch,
150 : 1 : g_hash_table_ref (self->subprocesses));
151 : 1 : g_hash_table_add (self->subprocesses, subprocess);
152 : :
153 : 1 : return TRUE;
154 : : }
155 : :
156 : : /*
157 : : * Local Commands
158 : : */
159 : : static void
160 : 1 : on_commands_changed (GSettings *settings,
161 : : const char *key,
162 : : ValentRuncommandPlugin *self)
163 : : {
164 : 1 : ValentDevice *device;
165 : 1 : ValentDeviceState state;
166 : :
167 [ + - + - : 1 : g_assert (G_IS_SETTINGS (settings));
- + - - ]
168 [ - + ]: 1 : g_assert (key != NULL);
169 [ - + ]: 1 : g_assert (VALENT_IS_RUNCOMMAND_PLUGIN (self));
170 : :
171 : 1 : device = valent_resource_get_source (VALENT_RESOURCE (self));
172 : 1 : state = valent_device_get_state (device);
173 : :
174 [ + - ]: 1 : if ((state & VALENT_DEVICE_STATE_CONNECTED) != 0 &&
175 : : (state & VALENT_DEVICE_STATE_PAIRED) != 0)
176 : 1 : valent_runcommand_plugin_send_command_list (self);
177 : 1 : }
178 : :
179 : : static void
180 : 1 : valent_runcommand_plugin_execute_local_command (ValentRuncommandPlugin *self,
181 : : const char *key)
182 : : {
183 : 1 : GSettings *settings;
184 : 1 : g_autoptr (GVariant) commands = NULL;
185 [ + - - - ]: 1 : g_autoptr (GVariant) command = NULL;
186 [ + - - - ]: 1 : g_autoptr (GError) error = NULL;
187 : :
188 [ + - ]: 1 : g_assert (VALENT_IS_RUNCOMMAND_PLUGIN (self));
189 [ - + ]: 1 : g_return_if_fail (key != NULL);
190 : :
191 : : /* Lookup the command by UUID */
192 : 1 : settings = valent_extension_get_settings (VALENT_EXTENSION (self));
193 : 1 : commands = g_settings_get_value (settings, "commands");
194 : :
195 [ - + ]: 1 : if (!g_variant_lookup (commands, key, "@a{sv}", &command))
196 : 0 : return valent_runcommand_plugin_send_command_list (self);
197 : :
198 [ - + ]: 1 : if (!launcher_execute (self, command, &error))
199 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
200 : : }
201 : :
202 : : static void
203 : 5 : valent_runcommand_plugin_send_command_list (ValentRuncommandPlugin *self)
204 : : {
205 : 5 : GSettings *settings;
206 : 10 : g_autoptr (JsonBuilder) builder = NULL;
207 [ - + ]: 5 : g_autoptr (JsonNode) packet = NULL;
208 [ + - ]: 5 : g_autoptr (GVariant) commands = NULL;
209 [ + - ]: 5 : g_autoptr (JsonBuilder) commands_builder = NULL;
210 [ + - ]: 5 : g_autoptr (JsonNode) commands_node = NULL;
211 [ + - ]: 5 : g_autofree char *commands_json = NULL;
212 : 5 : GVariantIter iter;
213 : 5 : char *key;
214 : 5 : GVariant *value;
215 : :
216 [ + - ]: 5 : g_assert (VALENT_IS_RUNCOMMAND_PLUGIN (self));
217 : :
218 : 5 : settings = valent_extension_get_settings (VALENT_EXTENSION (self));
219 : 5 : commands = g_settings_get_value (settings, "commands");
220 : :
221 : : /* The `commandList` dictionary is sent as a string of serialized JSON */
222 : 5 : commands_builder = json_builder_new ();
223 : 5 : json_builder_begin_object (commands_builder);
224 : :
225 : 5 : g_variant_iter_init (&iter, commands);
226 : :
227 [ + + ]: 7 : while (g_variant_iter_next (&iter, "{sv}", &key, &value))
228 : : {
229 : 2 : const char *name = NULL;
230 : 2 : const char *command = NULL;
231 : :
232 [ + - + - ]: 4 : if (g_variant_lookup (value, "name", "&s", &name) &&
233 : 2 : g_variant_lookup (value, "command", "&s", &command))
234 : : {
235 : 2 : json_builder_set_member_name (commands_builder, key);
236 : 2 : json_builder_begin_object (commands_builder);
237 : 2 : json_builder_set_member_name (commands_builder, "name");
238 : 2 : json_builder_add_string_value (commands_builder, name);
239 : 2 : json_builder_set_member_name (commands_builder, "command");
240 : 2 : json_builder_add_string_value (commands_builder, command);
241 : 2 : json_builder_end_object (commands_builder);
242 : : }
243 : :
244 [ + - ]: 2 : g_clear_pointer (&key, g_free);
245 [ + - ]: 2 : g_clear_pointer (&value, g_variant_unref);
246 : : }
247 : :
248 : 5 : json_builder_end_object (commands_builder);
249 : 5 : commands_node = json_builder_get_root (commands_builder);
250 : 5 : commands_json = json_to_string (commands_node, FALSE);
251 : :
252 : 5 : valent_packet_init (&builder, "kdeconnect.runcommand");
253 : 5 : json_builder_set_member_name (builder, "commandList");
254 : 5 : json_builder_add_string_value (builder, commands_json);
255 : 5 : packet = valent_packet_end (&builder);
256 : :
257 : 5 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
258 : 5 : }
259 : :
260 : : static void
261 : 2 : valent_runcommand_plugin_handle_runcommand_request (ValentRuncommandPlugin *self,
262 : : JsonNode *packet)
263 : : {
264 : 2 : const char *key;
265 : :
266 [ + - ]: 2 : g_assert (VALENT_IS_RUNCOMMAND_PLUGIN (self));
267 [ - + ]: 2 : g_assert (VALENT_IS_PACKET (packet));
268 : :
269 : : /* A request for the local command list */
270 [ + + ]: 2 : if (valent_packet_check_field (packet, "requestCommandList"))
271 : 1 : valent_runcommand_plugin_send_command_list (self);
272 : :
273 : : /* A request to execute a local command */
274 [ + + ]: 2 : if (valent_packet_get_string (packet, "key", &key))
275 : 1 : valent_runcommand_plugin_execute_local_command (self, key);
276 : 2 : }
277 : :
278 : : /*
279 : : * Remote Commands
280 : : */
281 : : static void
282 : 1 : valent_runcommand_plugin_execute_remote_command (ValentRuncommandPlugin *self,
283 : : const char *key)
284 : : {
285 : 2 : g_autoptr (JsonBuilder) builder = NULL;
286 [ - + ]: 1 : g_autoptr (JsonNode) packet = NULL;
287 : :
288 [ + - ]: 1 : g_assert (VALENT_IS_RUNCOMMAND_PLUGIN (self));
289 [ - + ]: 1 : g_assert (key != NULL);
290 : :
291 : 1 : valent_packet_init (&builder, "kdeconnect.runcommand.request");
292 : 1 : json_builder_set_member_name (builder, "key");
293 : 1 : json_builder_add_string_value (builder, key);
294 : 1 : packet = valent_packet_end (&builder);
295 : :
296 [ + - ]: 1 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
297 : 1 : }
298 : :
299 : : static void
300 : 1 : valent_runcommand_plugin_handle_command_list (ValentRuncommandPlugin *self,
301 : : JsonObject *command_list)
302 : : {
303 : 1 : JsonObjectIter iter;
304 : 1 : const char *key;
305 : 1 : JsonNode *command_node;
306 : 2 : g_autoptr (GMenuItem) cmd_item = NULL;
307 [ + - ]: 1 : g_autoptr (GIcon) cmd_icon = NULL;
308 [ + - ]: 1 : g_autoptr (GMenu) cmd_menu = NULL;
309 : 1 : GAction *commands;
310 : :
311 [ + - ]: 1 : g_assert (VALENT_IS_RUNCOMMAND_PLUGIN (self));
312 [ - + ]: 1 : g_assert (command_list != NULL);
313 : :
314 : 1 : cmd_menu = g_menu_new ();
315 : :
316 : 1 : cmd_icon = g_themed_icon_new ("system-run-symbolic");
317 : 1 : cmd_item = g_menu_item_new (_("Run Command"), "device.runcommand.commands");
318 : 1 : g_menu_item_set_icon (cmd_item, cmd_icon);
319 : 1 : g_menu_item_set_attribute (cmd_item, "hidden-when", "s", "action-disabled");
320 : 1 : g_menu_item_set_submenu (cmd_item, G_MENU_MODEL (cmd_menu));
321 : :
322 : : /* Iterate the commands */
323 : 1 : json_object_iter_init (&iter, command_list);
324 : :
325 [ + + ]: 3 : while (json_object_iter_next (&iter, &key, &command_node))
326 : : {
327 : 2 : JsonObject *cmd;
328 : 2 : const char *name;
329 : 2 : const char *command;
330 : 2 : g_autofree char *action = NULL;
331 : 2 : g_autoptr (GMenuItem) item = NULL;
332 : :
333 : 2 : cmd = json_node_get_object (command_node);
334 : 2 : name = json_object_get_string_member (cmd, "name");
335 : 2 : command = json_object_get_string_member (cmd, "command");
336 : 2 : action = g_strdup_printf ("device.runcommand.execute::%s", key);
337 : :
338 : 2 : item = g_menu_item_new (name, action);
339 : 2 : g_menu_item_set_attribute (item, "command", "s", command);
340 [ + - ]: 2 : g_menu_append_item (cmd_menu, item);
341 : : }
342 : :
343 : 1 : commands = g_action_map_lookup_action (G_ACTION_MAP (self), "commands");
344 : 1 : g_simple_action_set_enabled (G_SIMPLE_ACTION (commands),
345 : 1 : json_object_get_size (command_list) > 0);
346 [ + - ]: 1 : valent_device_plugin_set_menu_item (VALENT_DEVICE_PLUGIN (self),
347 : : "device.runcommand.commands",
348 : : cmd_item);
349 : 1 : }
350 : :
351 : : static void
352 : 1 : valent_runcommand_plugin_handle_runcommand (ValentRuncommandPlugin *self,
353 : : JsonNode *packet)
354 : : {
355 : 1 : JsonObject *body;
356 : 1 : g_autoptr (JsonNode) command_node = NULL;
357 : 1 : const char *command_json;
358 : 1 : JsonObject *command_list;
359 : :
360 [ + - ]: 1 : g_assert (VALENT_IS_RUNCOMMAND_PLUGIN (self));
361 [ - + ]: 1 : g_assert (VALENT_IS_PACKET (packet));
362 : :
363 : 1 : body = valent_packet_get_body (packet);
364 : 1 : command_json = json_object_get_string_member_with_default (body, "commandList", "{}");
365 : 1 : command_node = json_from_string (command_json, NULL);
366 : :
367 [ + - - + ]: 1 : if (command_node == NULL || !JSON_NODE_HOLDS_OBJECT (command_node))
368 : : {
369 : 0 : g_warning ("%s(): malformed commandList field", G_STRFUNC);
370 [ # # ]: 0 : return;
371 : : }
372 : :
373 : 1 : command_list = json_node_get_object (command_node);
374 : 1 : valent_runcommand_plugin_handle_command_list (self, command_list);
375 : : }
376 : :
377 : : /*
378 : : * GActions
379 : : */
380 : : static void
381 : 0 : runcommand_commands_action (GSimpleAction *action,
382 : : GVariant *parameter,
383 : : gpointer user_data)
384 : : {
385 : : // Mock action
386 : 0 : }
387 : :
388 : : static void
389 : 1 : runcommand_execute_action (GSimpleAction *action,
390 : : GVariant *parameter,
391 : : gpointer user_data)
392 : : {
393 : 1 : ValentRuncommandPlugin *self = VALENT_RUNCOMMAND_PLUGIN (user_data);
394 : 1 : const char *key;
395 : :
396 [ + - ]: 1 : g_return_if_fail (VALENT_IS_RUNCOMMAND_PLUGIN (self));
397 : :
398 : 1 : key = g_variant_get_string (parameter, NULL);
399 : 1 : valent_runcommand_plugin_execute_remote_command (self, key);
400 : : }
401 : :
402 : : static const GActionEntry actions[] = {
403 : : {"commands", runcommand_commands_action, NULL, NULL, NULL}, // "as"
404 : : {"execute", runcommand_execute_action, "s", NULL, NULL}
405 : : };
406 : :
407 : : /**
408 : : * ValentDevicePlugin
409 : : */
410 : : static void
411 : 11 : valent_runcommand_plugin_update_state (ValentDevicePlugin *plugin,
412 : : ValentDeviceState state)
413 : : {
414 : 11 : ValentRuncommandPlugin *self = VALENT_RUNCOMMAND_PLUGIN (plugin);
415 : 11 : GSettings *settings;
416 : 11 : gboolean available;
417 : :
418 [ + - ]: 11 : g_assert (VALENT_IS_RUNCOMMAND_PLUGIN (self));
419 : :
420 : 11 : available = (state & VALENT_DEVICE_STATE_CONNECTED) != 0 &&
421 : : (state & VALENT_DEVICE_STATE_PAIRED) != 0;
422 : :
423 : 11 : valent_extension_toggle_actions (VALENT_EXTENSION (plugin), available);
424 : :
425 : 11 : settings = valent_extension_get_settings (VALENT_EXTENSION (plugin));
426 : :
427 [ + + ]: 11 : if (available)
428 : : {
429 [ + - ]: 3 : if (self->commands_changed_id == 0)
430 : : {
431 : 3 : self->commands_changed_id =
432 : 3 : g_signal_connect_object (settings,
433 : : "changed::commands",
434 : : G_CALLBACK (on_commands_changed),
435 : : self, 0);
436 : : }
437 : :
438 : 3 : valent_runcommand_plugin_send_command_list (self);
439 : : }
440 : : else
441 : : {
442 [ + + ]: 8 : g_clear_signal_handler (&self->commands_changed_id, settings);
443 : : }
444 : :
445 : : /* If the device is unpaired it is no longer trusted */
446 [ + + ]: 11 : if ((state & VALENT_DEVICE_STATE_PAIRED) == 0)
447 : 1 : launcher_clear (self);
448 : 11 : }
449 : :
450 : : static void
451 : 3 : valent_runcommand_plugin_handle_packet (ValentDevicePlugin *plugin,
452 : : const char *type,
453 : : JsonNode *packet)
454 : : {
455 : 3 : ValentRuncommandPlugin *self = VALENT_RUNCOMMAND_PLUGIN (plugin);
456 : :
457 [ + - ]: 3 : g_assert (VALENT_IS_DEVICE_PLUGIN (plugin));
458 [ - + ]: 3 : g_assert (type != NULL);
459 [ - + ]: 3 : g_assert (VALENT_IS_PACKET (packet));
460 : :
461 : : /* A request for the local command list or local execution */
462 [ + + ]: 3 : if (g_str_equal (type, "kdeconnect.runcommand.request"))
463 : 2 : valent_runcommand_plugin_handle_runcommand_request (self, packet);
464 : :
465 : : /* A response to a request for the remote command list */
466 [ + - ]: 1 : else if (g_str_equal (type, "kdeconnect.runcommand"))
467 : 1 : valent_runcommand_plugin_handle_runcommand (self, packet);
468 : :
469 : : else
470 : 0 : g_assert_not_reached ();
471 : 3 : }
472 : :
473 : : /*
474 : : * ValentObject
475 : : */
476 : : static void
477 : 8 : valent_runcommand_plugin_destroy (ValentObject *object)
478 : : {
479 : 8 : ValentRuncommandPlugin *self = VALENT_RUNCOMMAND_PLUGIN (object);
480 : 8 : ValentDevicePlugin *plugin = VALENT_DEVICE_PLUGIN (object);
481 : 8 : GSettings *settings;
482 : :
483 : : /* Stop watching for command changes */
484 : 8 : settings = valent_extension_get_settings (VALENT_EXTENSION (plugin));
485 [ - + ]: 8 : g_clear_signal_handler (&self->commands_changed_id, settings);
486 : :
487 : 8 : VALENT_OBJECT_CLASS (valent_runcommand_plugin_parent_class)->destroy (object);
488 : 8 : }
489 : :
490 : : /*
491 : : * GObject
492 : : */
493 : : static void
494 : 4 : valent_runcommand_plugin_constructed (GObject *object)
495 : : {
496 : 4 : ValentDevicePlugin *plugin = VALENT_DEVICE_PLUGIN (object);
497 : :
498 : 4 : g_action_map_add_action_entries (G_ACTION_MAP (plugin),
499 : : actions,
500 : : G_N_ELEMENTS (actions),
501 : : plugin);
502 : :
503 : 4 : G_OBJECT_CLASS (valent_runcommand_plugin_parent_class)->constructed (object);
504 : 4 : }
505 : :
506 : : static void
507 : 4 : valent_runcommand_plugin_finalize (GObject *object)
508 : : {
509 : 4 : ValentRuncommandPlugin *self = VALENT_RUNCOMMAND_PLUGIN (object);
510 : :
511 [ + + ]: 4 : g_clear_object (&self->launcher);
512 [ + - ]: 4 : g_clear_pointer (&self->subprocesses, g_hash_table_unref);
513 : :
514 : 4 : G_OBJECT_CLASS (valent_runcommand_plugin_parent_class)->finalize (object);
515 : 4 : }
516 : :
517 : : static void
518 : 18 : valent_runcommand_plugin_class_init (ValentRuncommandPluginClass *klass)
519 : : {
520 : 18 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
521 : 18 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
522 : 18 : ValentDevicePluginClass *plugin_class = VALENT_DEVICE_PLUGIN_CLASS (klass);
523 : :
524 : 18 : object_class->constructed = valent_runcommand_plugin_constructed;
525 : 18 : object_class->finalize = valent_runcommand_plugin_finalize;
526 : :
527 : 18 : vobject_class->destroy = valent_runcommand_plugin_destroy;
528 : :
529 : 18 : plugin_class->handle_packet = valent_runcommand_plugin_handle_packet;
530 : 18 : plugin_class->update_state = valent_runcommand_plugin_update_state;
531 : : }
532 : :
533 : : static void
534 : 4 : valent_runcommand_plugin_init (ValentRuncommandPlugin *self)
535 : : {
536 : 4 : self->subprocesses = g_hash_table_new (NULL, NULL);
537 : 4 : }
538 : :
|