summaryrefslogtreecommitdiffstats
path: root/src/process_manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/process_manager.c')
-rw-r--r--src/process_manager.c219
1 files changed, 219 insertions, 0 deletions
diff --git a/src/process_manager.c b/src/process_manager.c
new file mode 100644
index 0000000..023894a
--- /dev/null
+++ b/src/process_manager.c
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2021 Collabora Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "app_launcher.h"
+#include "process_manager.h"
+
+struct _ProcessManager {
+ GObject parent_instance;
+
+ GList *process_data;
+};
+
+G_DEFINE_TYPE(ProcessManager, process_manager, G_TYPE_OBJECT);
+
+enum {
+ STARTED,
+ TERMINATED,
+ N_SIGNALS
+};
+static guint signals[N_SIGNALS];
+
+/*
+ * Application info structure, used for storing relevant data
+ * in the `running_apps` list
+ */
+struct process_runtime_data {
+ guint watcher;
+ GPid pid;
+ const gchar *app_id;
+};
+
+/*
+ * Initialization & cleanup functions
+ */
+
+static void process_manager_dispose(GObject *object)
+{
+ ProcessManager *self = APPLAUNCHD_PROCESS_MANAGER(object);
+
+ g_return_if_fail(APPLAUNCHD_IS_PROCESS_MANAGER(self));
+
+ if (self->process_data)
+ g_list_free_full(g_steal_pointer(&self->process_data), g_free);
+
+ G_OBJECT_CLASS(process_manager_parent_class)->dispose(object);
+}
+
+static void process_manager_finalize(GObject *object)
+{
+ G_OBJECT_CLASS(process_manager_parent_class)->finalize(object);
+}
+
+static void process_manager_class_init(ProcessManagerClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *)klass;
+
+ object_class->dispose = process_manager_dispose;
+ object_class->finalize = process_manager_finalize;
+
+ signals[STARTED] = g_signal_new("started", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0 ,
+ NULL, NULL, NULL, G_TYPE_NONE,
+ 1, G_TYPE_STRING);
+
+ signals[TERMINATED] = g_signal_new("terminated", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0 ,
+ NULL, NULL, NULL, G_TYPE_NONE,
+ 1, G_TYPE_STRING);
+}
+
+static void process_manager_init(ProcessManager *self)
+{
+}
+
+/*
+ * Internal functions
+ */
+
+static const gchar *get_app_id_for_pid(ProcessManager *self, GPid pid)
+{
+ g_return_val_if_fail(APPLAUNCHD_IS_PROCESS_MANAGER(self), NULL);
+
+ AppLauncher *app_launcher = app_launcher_get_default();
+ guint len = g_list_length(self->process_data);
+
+ for (guint i = 0; i < len; i++) {
+ struct process_runtime_data *runtime_data =
+ g_list_nth_data(self->process_data, i);
+
+ if (runtime_data->pid == pid)
+ return runtime_data->app_id;
+ }
+
+ return NULL;
+}
+
+/*
+ * Internal callbacks
+ */
+
+/*
+ * This function is called when a watched process terminated, so we can:
+ * - cleanup this application's data (and reap the process so it
+ * doesn't become a zombie)
+ * - notify listeners that the process terminated
+ */
+static void process_manager_app_terminated_cb(GPid pid,
+ gint wait_status,
+ gpointer data)
+{
+ ProcessManager *self = data;
+ AppLauncher *app_launcher = app_launcher_get_default();
+ struct process_runtime_data *runtime_data;
+ const gchar *app_id;
+ AppInfo *app_info;
+
+ g_return_if_fail(APPLAUNCHD_IS_PROCESS_MANAGER(self));
+
+ app_id = get_app_id_for_pid(self, pid);
+ if (!app_id) {
+ g_warning("Unable to retrieve app id for pid %d", pid);
+ return;
+ }
+
+ app_info = app_launcher_get_app_info(app_launcher, app_id);
+ if (!app_info) {
+ g_warning("Unable to find running app with pid %d", pid);
+ return;
+ }
+
+ if (g_spawn_check_exit_status(wait_status, NULL))
+ g_debug("Application '%s' terminated with exit code %i",
+ app_id, WEXITSTATUS(wait_status));
+ else
+ g_warning("Application '%s' crashed", app_id);
+
+ g_spawn_close_pid(pid);
+
+ runtime_data = app_info_get_runtime_data(app_info);
+ g_source_remove(runtime_data->watcher);
+
+ app_info_set_status(app_info, APP_STATUS_INACTIVE);
+ app_info_set_runtime_data(app_info, NULL);
+
+ self->process_data = g_list_remove(self->process_data, runtime_data);
+ g_free(runtime_data);
+
+ g_signal_emit(self, signals[TERMINATED], 0, app_id);
+}
+
+/*
+ * Public functions
+ */
+
+ProcessManager *process_manager_new(void)
+{
+ return g_object_new(APPLAUNCHD_TYPE_PROCESS_MANAGER, NULL);
+}
+
+/*
+ * Start an application by executing the provided command line.
+ */
+gboolean process_manager_start_app(ProcessManager *self,
+ AppInfo *app_info)
+{
+ g_return_val_if_fail(APPLAUNCHD_IS_PROCESS_MANAGER(self), FALSE);
+ g_return_val_if_fail(APPLAUNCHD_IS_APP_INFO(app_info), FALSE);
+
+ gboolean success;
+ g_autofree GStrv args = NULL;
+ const gchar *app_id = app_info_get_app_id(app_info);
+ const gchar *command = app_info_get_command(app_info);
+ struct process_runtime_data *runtime_data;
+
+ runtime_data = g_new0(struct process_runtime_data, 1);
+ if (!runtime_data) {
+ g_critical("Unable to allocate runtime data structure for '%s'",
+ app_id);
+ return FALSE;
+ }
+
+ runtime_data->app_id = app_id;
+
+ args = g_strsplit(command, " ", -1);
+ success = g_spawn_async(NULL, args, NULL,
+ G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
+ NULL, NULL, &runtime_data->pid, NULL);
+ if (!success) {
+ g_critical("Unable to start application '%s'", app_id);
+ g_free(runtime_data);
+ return FALSE;
+ }
+
+ /*
+ * Add a watcher for the child PID in order to get notified when it dies
+ */
+ runtime_data->watcher = g_child_watch_add(runtime_data->pid,
+ process_manager_app_terminated_cb,
+ self);
+ self->process_data = g_list_append(self->process_data, runtime_data);
+ app_info_set_runtime_data(app_info, runtime_data);
+
+ g_signal_emit(self, signals[STARTED], 0, app_id);
+
+ return TRUE;
+}