/* ide-terminal-launcher.c * * Copyright 2019 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * SPDX-License-Identifier: GPL-3.0-or-later */ #define G_LOG_DOMAIN "ide-terminal-launcher" #include "config.h" #include #include #include #include #include "ide-private.h" #include "ide-terminal-launcher.h" #include "ide-terminal-util.h" typedef enum { LAUNCHER_KIND_HOST = 0, LAUNCHER_KIND_DEBUG, LAUNCHER_KIND_RUNTIME, LAUNCHER_KIND_RUNNER, LAUNCHER_KIND_LAUNCHER, LAUNCHER_KIND_CONFIG, } LauncherKind; struct _IdeTerminalLauncher { GObject parent_instance; gchar *cwd; gchar *shell; gchar *title; gchar **args; IdeRuntime *runtime; IdeContext *context; IdeConfig *config; IdeSubprocessLauncher *launcher; LauncherKind kind; }; G_DEFINE_FINAL_TYPE (IdeTerminalLauncher, ide_terminal_launcher, G_TYPE_OBJECT) enum { PROP_0, PROP_ARGS, PROP_CWD, PROP_SHELL, PROP_TITLE, N_PROPS }; static GParamSpec *properties [N_PROPS]; static const struct { const gchar *key; const gchar *value; } default_environment[] = { { "INSIDE_GNOME_BUILDER", PACKAGE_VERSION }, { "TERM", "xterm-256color" }, }; static gboolean shell_supports_login (const gchar *shell) { g_autofree gchar *name = NULL; /* Shells that support --login */ static const gchar *supported[] = { "sh", "bash", }; if (shell == NULL) return FALSE; if (!(name = g_path_get_basename (shell))) return FALSE; for (guint i = 0; i < G_N_ELEMENTS (supported); i++) { if (g_str_equal (name, supported[i])) return TRUE; } return FALSE; } static void copy_envvars (gpointer instance) { static const gchar *copy_env[] = { "AT_SPI_BUS_ADDRESS", "COLORTERM", "DBUS_SESSION_BUS_ADDRESS", "DBUS_SYSTEM_BUS_ADDRESS", "DESKTOP_SESSION", "DISPLAY", "LANG", "SHELL", "SSH_AUTH_SOCK", "USER", "WAYLAND_DISPLAY", "XAUTHORITY", "XDG_CURRENT_DESKTOP", #if 0 /* Can't copy these as they could mess up Flatpak */ "XDG_DATA_DIRS", "XDG_RUNTIME_DIR", #endif "XDG_MENU_PREFIX", "XDG_SEAT", "XDG_SESSION_DESKTOP", "XDG_SESSION_ID", "XDG_SESSION_TYPE", "XDG_VTNR", }; const gchar * const *host_environ; IdeEnvironment *env = NULL; g_assert (IDE_IS_SUBPROCESS_LAUNCHER (instance) || IDE_IS_RUNNER (instance)); if (IDE_IS_RUNNER (instance)) env = ide_runner_get_environment (instance); host_environ = _ide_host_environ (); for (guint i = 0; i < G_N_ELEMENTS (copy_env); i++) { const gchar *val = g_environ_getenv ((gchar **)host_environ, copy_env[i]); if (val != NULL) { if (IDE_IS_SUBPROCESS_LAUNCHER (instance)) ide_subprocess_launcher_setenv (instance, copy_env[i], val, FALSE); else ide_environment_setenv (env, copy_env[i], val); } } } static void apply_pipeline_info (gpointer instance, IdeObject *object) { g_autoptr(GFile) workdir = NULL; IdeEnvironment *env = NULL; IdeContext *context; g_assert (IDE_IS_SUBPROCESS_LAUNCHER (instance) || IDE_IS_RUNNER (instance)); g_assert (IDE_IS_OBJECT (object)); context = ide_object_get_context (object); workdir = ide_context_ref_workdir (context); if (IDE_IS_RUNNER (instance)) env = ide_runner_get_environment (instance); if (IDE_IS_SUBPROCESS_LAUNCHER (instance)) ide_subprocess_launcher_setenv (instance, "SRCDIR", g_file_peek_path (workdir), FALSE); else ide_environment_setenv (env, "SRCDIR", g_file_peek_path (workdir)); if (ide_context_has_project (context)) { IdeBuildManager *build_manager = ide_build_manager_from_context (context); IdePipeline *pipeline = ide_build_manager_get_pipeline (build_manager); if (pipeline != NULL) { const gchar *builddir = ide_pipeline_get_builddir (pipeline); if (IDE_IS_SUBPROCESS_LAUNCHER (instance)) ide_subprocess_launcher_setenv (instance, "BUILDDIR", builddir, FALSE); else ide_environment_setenv (env, "BUILDDIR", builddir); } } } static void ide_terminal_launcher_wait_check_cb (GObject *object, GAsyncResult *result, gpointer user_data) { IdeSubprocess *subprocess = (IdeSubprocess *)object; g_autoptr(IdeTask) task = user_data; g_autoptr(GError) error = NULL; g_assert (IDE_IS_SUBPROCESS (subprocess)); g_assert (G_IS_ASYNC_RESULT (result)); g_assert (IDE_IS_TASK (task)); if (!ide_subprocess_wait_check_finish (subprocess, result, &error)) ide_task_return_error (task, g_steal_pointer (&error)); else ide_task_return_boolean (task, TRUE); } static void spawn_host_launcher (IdeTerminalLauncher *self, IdeTask *task, gint pty_fd, gboolean run_on_host) { g_autoptr(IdeSubprocessLauncher) launcher = NULL; g_autoptr(IdeSubprocess) subprocess = NULL; g_autoptr(GError) error = NULL; const gchar *shell; g_assert (IDE_IS_TERMINAL_LAUNCHER (self)); g_assert (IDE_IS_TASK (task)); g_assert (pty_fd >= 0); if (!(shell = ide_terminal_launcher_get_shell (self))) shell = ide_get_user_shell (); /* We only have sh/bash in our flatpak */ if (self->kind == LAUNCHER_KIND_DEBUG && ide_is_flatpak ()) shell = "/bin/bash"; launcher = ide_subprocess_launcher_new (0); ide_subprocess_launcher_set_run_on_host (launcher, run_on_host); ide_subprocess_launcher_set_cwd (launcher, self->cwd ? self->cwd : g_get_home_dir ()); ide_subprocess_launcher_set_clear_env (launcher, FALSE); ide_subprocess_launcher_push_argv (launcher, shell); if (shell_supports_login (shell)) ide_subprocess_launcher_push_argv (launcher, "--login"); ide_subprocess_launcher_take_stdin_fd (launcher, dup (pty_fd)); ide_subprocess_launcher_take_stdout_fd (launcher, dup (pty_fd)); ide_subprocess_launcher_take_stderr_fd (launcher, dup (pty_fd)); g_assert (ide_subprocess_launcher_get_needs_tty (launcher)); for (guint i = 0; i < G_N_ELEMENTS (default_environment); i++) ide_subprocess_launcher_setenv (launcher, default_environment[i].key, default_environment[i].value, FALSE); ide_subprocess_launcher_setenv (launcher, "SHELL", shell, TRUE); if (self->context != NULL) { g_autoptr(GFile) workdir = ide_context_ref_workdir (self->context); ide_subprocess_launcher_setenv (launcher, "SRCDIR", g_file_peek_path (workdir), FALSE); } if (!(subprocess = ide_subprocess_launcher_spawn (launcher, NULL, &error))) ide_task_return_error (task, g_steal_pointer (&error)); else ide_subprocess_wait_check_async (subprocess, ide_task_get_cancellable (task), ide_terminal_launcher_wait_check_cb, g_object_ref (task)); } static void spawn_launcher (IdeTerminalLauncher *self, IdeTask *task, IdeSubprocessLauncher *launcher, gint pty_fd) { g_autoptr(IdeSubprocess) subprocess = NULL; g_autoptr(GError) error = NULL; g_assert (IDE_IS_TERMINAL_LAUNCHER (self)); g_assert (IDE_IS_TASK (task)); g_assert (!launcher || IDE_IS_SUBPROCESS_LAUNCHER (launcher)); g_assert (pty_fd >= 0); if (launcher == NULL) { ide_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "process may only be spawned once"); return; } ide_subprocess_launcher_set_flags (launcher, 0); ide_subprocess_launcher_take_stdin_fd (launcher, dup (pty_fd)); ide_subprocess_launcher_take_stdout_fd (launcher, dup (pty_fd)); ide_subprocess_launcher_take_stderr_fd (launcher, dup (pty_fd)); if (!(subprocess = ide_subprocess_launcher_spawn (launcher, NULL, &error))) ide_task_return_error (task, g_steal_pointer (&error)); else ide_subprocess_wait_check_async (subprocess, ide_task_get_cancellable (task), ide_terminal_launcher_wait_check_cb, g_object_ref (task)); } static void spawn_runtime_launcher (IdeTerminalLauncher *self, IdeTask *task, IdeRuntime *runtime, IdeConfig *config, gint pty_fd) { g_autoptr(IdeSubprocessLauncher) launcher = NULL; g_autoptr(IdeSubprocess) subprocess = NULL; g_autoptr(GError) error = NULL; const gchar *shell; g_assert (IDE_IS_TERMINAL_LAUNCHER (self)); g_assert (IDE_IS_TASK (task)); g_assert (!runtime || IDE_IS_RUNTIME (runtime)); g_assert (pty_fd >= 0); if (runtime == NULL) { ide_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, _("Requested runtime is not installed")); return; } if (!(shell = ide_terminal_launcher_get_shell (self))) shell = ide_get_user_shell (); if (!(launcher = ide_runtime_create_launcher (runtime, NULL))) { ide_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, _("Failed to create shell within runtime ā€œ%sā€"), ide_runtime_get_display_name (runtime)); return; } ide_subprocess_launcher_set_flags (launcher, 0); if (!ide_runtime_contains_program_in_path (runtime, shell, NULL)) shell = "/bin/sh"; ide_subprocess_launcher_set_cwd (launcher, self->cwd ? self->cwd : g_get_home_dir ()); ide_subprocess_launcher_push_argv (launcher, shell); if (shell_supports_login (shell)) ide_subprocess_launcher_push_argv (launcher, "--login"); ide_subprocess_launcher_take_stdin_fd (launcher, dup (pty_fd)); ide_subprocess_launcher_take_stdout_fd (launcher, dup (pty_fd)); ide_subprocess_launcher_take_stderr_fd (launcher, dup (pty_fd)); g_assert (ide_subprocess_launcher_get_needs_tty (launcher)); for (guint i = 0; i < G_N_ELEMENTS (default_environment); i++) ide_subprocess_launcher_setenv (launcher, default_environment[i].key, default_environment[i].value, FALSE); apply_pipeline_info (launcher, IDE_OBJECT (self->runtime)); copy_envvars (launcher); ide_subprocess_launcher_setenv (launcher, "SHELL", shell, TRUE); if (config != NULL) ide_config_apply_path (config, launcher); if (!(subprocess = ide_subprocess_launcher_spawn (launcher, NULL, &error))) ide_task_return_error (task, g_steal_pointer (&error)); else ide_subprocess_wait_check_async (subprocess, ide_task_get_cancellable (task), ide_terminal_launcher_wait_check_cb, g_object_ref (task)); } static void ide_terminal_launcher_run_cb (GObject *object, GAsyncResult *result, gpointer user_data) { IdeRunner *runner = (IdeRunner *)object; g_autoptr(IdeTask) task = user_data; g_autoptr(GError) error = NULL; g_assert (IDE_IS_RUNNER (runner)); g_assert (G_IS_ASYNC_RESULT (result)); g_assert (IDE_IS_TASK (task)); if (!ide_runner_run_finish (runner, result, &error)) ide_task_return_error (task, g_steal_pointer (&error)); else ide_task_return_boolean (task, TRUE); ide_object_destroy (IDE_OBJECT (runner)); } static void spawn_runner_launcher (IdeTerminalLauncher *self, IdeTask *task, IdeRuntime *runtime, gint pty_fd) { g_autoptr(IdeSimpleBuildTarget) build_target = NULL; g_autoptr(IdeRunner) runner = NULL; g_autoptr(GPtrArray) argv = NULL; IdeEnvironment *env; const gchar *shell; g_assert (IDE_IS_TERMINAL_LAUNCHER (self)); g_assert (IDE_IS_TASK (task)); g_assert (!runtime || IDE_IS_RUNTIME (runtime)); g_assert (pty_fd >= 0); if (runtime == NULL) { ide_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, _("Requested runtime is not installed")); return; } if (!(shell = ide_terminal_launcher_get_shell (self))) shell = ide_get_user_shell (); if (!ide_runtime_contains_program_in_path (runtime, shell, NULL)) shell = "/bin/sh"; argv = g_ptr_array_new (); g_ptr_array_add (argv, (gchar *)shell); if (self->args == NULL) { if (shell_supports_login (shell)) g_ptr_array_add (argv, (gchar *)"--login"); } else { for (guint i = 0; self->args[i]; i++) g_ptr_array_add (argv, self->args[i]); } g_ptr_array_add (argv, NULL); build_target = ide_simple_build_target_new (NULL); ide_simple_build_target_set_argv (build_target, (const gchar * const *)argv->pdata); ide_simple_build_target_set_cwd (build_target, self->cwd ? self->cwd : g_get_home_dir ()); /* Creating runner should always succeed, but run_async() may fail */ runner = ide_runtime_create_runner (runtime, IDE_BUILD_TARGET (build_target)); env = ide_runner_get_environment (runner); ide_runner_take_tty_fd (runner, dup (pty_fd)); for (guint i = 0; i < G_N_ELEMENTS (default_environment); i++) ide_environment_setenv (env, default_environment[i].key, default_environment[i].value); apply_pipeline_info (runner, IDE_OBJECT (self->runtime)); copy_envvars (runner); ide_environment_setenv (env, "SHELL", shell); ide_runner_run_async (runner, ide_task_get_cancellable (task), ide_terminal_launcher_run_cb, g_object_ref (task)); } void ide_terminal_launcher_spawn_async (IdeTerminalLauncher *self, VtePty *pty, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_autoptr(IdeTask) task = NULL; gint pty_fd = -1; g_assert (IDE_IS_TERMINAL_LAUNCHER (self)); g_assert (VTE_IS_PTY (pty)); g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); task = ide_task_new (self, cancellable, callback, user_data); ide_task_set_source_tag (task, ide_terminal_launcher_spawn_async); if ((pty_fd = ide_vte_pty_create_slave (pty)) == -1) { int errsv = errno; ide_task_return_new_error (task, G_IO_ERROR, g_io_error_from_errno (errsv), "%s", g_strerror (errsv)); return; } switch (self->kind) { case LAUNCHER_KIND_RUNTIME: spawn_runtime_launcher (self, task, self->runtime, NULL, pty_fd); break; case LAUNCHER_KIND_CONFIG: spawn_runtime_launcher (self, task, self->runtime, self->config, pty_fd); break; case LAUNCHER_KIND_RUNNER: spawn_runner_launcher (self, task, self->runtime, pty_fd); break; case LAUNCHER_KIND_LAUNCHER: spawn_launcher (self, task, self->launcher, pty_fd); g_clear_object (&self->launcher); break; case LAUNCHER_KIND_DEBUG: case LAUNCHER_KIND_HOST: default: spawn_host_launcher (self, task, pty_fd, self->kind == LAUNCHER_KIND_HOST); break; } if (pty_fd != -1) close (pty_fd); } /** * ide_terminal_launcher_spawn_finish: * @self: a #IdeTerminalLauncher * * Completes a request to ide_terminal_launcher_spawn_async() * * Returns: %TRUE if the process executed successfully; otherwise %FALSE * and @error is set. * * Since: 3.34 */ gboolean ide_terminal_launcher_spawn_finish (IdeTerminalLauncher *self, GAsyncResult *result, GError **error) { g_assert (IDE_IS_TERMINAL_LAUNCHER (self)); g_assert (IDE_IS_TASK (result)); return ide_task_propagate_boolean (IDE_TASK (result), error); } static void ide_terminal_launcher_finalize (GObject *object) { IdeTerminalLauncher *self = (IdeTerminalLauncher *)object; g_clear_pointer (&self->args, g_strfreev); g_clear_pointer (&self->cwd, g_free); g_clear_pointer (&self->shell, g_free); g_clear_pointer (&self->title, g_free); g_clear_object (&self->launcher); g_clear_object (&self->runtime); g_clear_object (&self->config); G_OBJECT_CLASS (ide_terminal_launcher_parent_class)->finalize (object); } static void ide_terminal_launcher_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { IdeTerminalLauncher *self = IDE_TERMINAL_LAUNCHER (object); switch (prop_id) { case PROP_ARGS: g_value_set_boxed (value, ide_terminal_launcher_get_args (self)); break; case PROP_CWD: g_value_set_string (value, ide_terminal_launcher_get_cwd (self)); break; case PROP_SHELL: g_value_set_string (value, ide_terminal_launcher_get_shell (self)); break; case PROP_TITLE: g_value_set_string (value, ide_terminal_launcher_get_title (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void ide_terminal_launcher_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { IdeTerminalLauncher *self = IDE_TERMINAL_LAUNCHER (object); switch (prop_id) { case PROP_ARGS: ide_terminal_launcher_set_args (self, g_value_get_boxed (value)); break; case PROP_CWD: ide_terminal_launcher_set_cwd (self, g_value_get_string (value)); break; case PROP_SHELL: ide_terminal_launcher_set_shell (self, g_value_get_string (value)); break; case PROP_TITLE: ide_terminal_launcher_set_title (self, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void ide_terminal_launcher_class_init (IdeTerminalLauncherClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = ide_terminal_launcher_finalize; object_class->get_property = ide_terminal_launcher_get_property; object_class->set_property = ide_terminal_launcher_set_property; properties [PROP_ARGS] = g_param_spec_boxed ("args", "Args", "Arguments to shell", G_TYPE_STRV, (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); properties [PROP_CWD] = g_param_spec_string ("cwd", "Cwd", "The cwd to spawn in the subprocess", NULL, (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); properties [PROP_SHELL] = g_param_spec_string ("shell", "Shell", "The shell to spawn in the subprocess", NULL, (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); properties [PROP_TITLE] = g_param_spec_string ("title", "Title", "The title for the subprocess launcher", NULL, (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_properties (object_class, N_PROPS, properties); } static void ide_terminal_launcher_init (IdeTerminalLauncher *self) { self->cwd = NULL; self->shell = NULL; self->title = g_strdup (_("Untitled Terminal")); } const gchar * ide_terminal_launcher_get_cwd (IdeTerminalLauncher *self) { g_return_val_if_fail (IDE_IS_TERMINAL_LAUNCHER (self), NULL); return self->cwd; } void ide_terminal_launcher_set_cwd (IdeTerminalLauncher *self, const gchar *cwd) { g_return_if_fail (IDE_IS_TERMINAL_LAUNCHER (self)); if (g_strcmp0 (self->cwd, cwd) != 0) { g_free (self->cwd); self->cwd = g_strdup (cwd); g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CWD]); } } const gchar * ide_terminal_launcher_get_shell (IdeTerminalLauncher *self) { g_return_val_if_fail (IDE_IS_TERMINAL_LAUNCHER (self), NULL); return self->shell; } void ide_terminal_launcher_set_shell (IdeTerminalLauncher *self, const gchar *shell) { g_return_if_fail (IDE_IS_TERMINAL_LAUNCHER (self)); if (g_strcmp0 (self->shell, shell) != 0) { g_free (self->shell); self->shell = g_strdup (shell); g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHELL]); } } const gchar * ide_terminal_launcher_get_title (IdeTerminalLauncher *self) { g_return_val_if_fail (IDE_IS_TERMINAL_LAUNCHER (self), NULL); return self->title; } void ide_terminal_launcher_set_title (IdeTerminalLauncher *self, const gchar *title) { g_return_if_fail (IDE_IS_TERMINAL_LAUNCHER (self)); if (g_strcmp0 (self->title, title) != 0) { g_free (self->title); self->title = g_strdup (title); g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]); } } /** * ide_terminal_launcher_new: * * Create a new #IdeTerminalLauncher that will spawn a terminal on the host. * * Returns: (transfer full): a newly created #IdeTerminalLauncher */ IdeTerminalLauncher * ide_terminal_launcher_new (IdeContext *context) { IdeTerminalLauncher *self; g_autoptr(GFile) workdir = NULL; g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL); workdir = ide_context_ref_workdir (context); self = g_object_new (IDE_TYPE_TERMINAL_LAUNCHER, NULL); self->kind = LAUNCHER_KIND_HOST; self->cwd = g_file_get_path (workdir); self->context = g_object_ref (context); return g_steal_pointer (&self); } /** * ide_terminal_launcher_new_for_launcher: * @launcher: an #IdeSubprocessLauncher * * Creates a new #IdeTerminalLauncher that can be used to launch a process * using the provided #IdeSubprocessLauncher. * * Returns: (transfer full): an #IdeTerminalLauncher * * Since: 3.34 */ IdeTerminalLauncher * ide_terminal_launcher_new_for_launcher (IdeSubprocessLauncher *launcher) { IdeTerminalLauncher *self; g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (launcher), NULL); self = g_object_new (IDE_TYPE_TERMINAL_LAUNCHER, NULL); self->kind = LAUNCHER_KIND_LAUNCHER; self->launcher = g_object_ref (launcher); return g_steal_pointer (&self); } /** * ide_terminal_launcher_new_for_debug * * Create a new #IdeTerminalLauncher that will spawn a terminal on the host. * * Returns: (transfer full): a newly created #IdeTerminalLauncher */ IdeTerminalLauncher * ide_terminal_launcher_new_for_debug (void) { IdeTerminalLauncher *self; self = g_object_new (IDE_TYPE_TERMINAL_LAUNCHER, NULL); self->kind = LAUNCHER_KIND_DEBUG; return g_steal_pointer (&self); } /** * ide_terminal_launcher_new_for_config: * @config: an #IdeConfig * * Create a new #IdeTerminalLauncher that will spawn a terminal in the runtime * of the configuration with various build options applied. * * Returns: (transfer full): a newly created #IdeTerminalLauncher */ IdeTerminalLauncher * ide_terminal_launcher_new_for_config (IdeConfig *config) { IdeTerminalLauncher *self; IdeRuntime *runtime; g_return_val_if_fail (IDE_IS_CONFIG (config), NULL); runtime = ide_config_get_runtime (config); self = g_object_new (IDE_TYPE_TERMINAL_LAUNCHER, NULL); self->runtime = g_object_ref (runtime); self->config = g_object_ref (config); self->kind = LAUNCHER_KIND_CONFIG; ide_terminal_launcher_set_title (self, ide_runtime_get_name (runtime)); return g_steal_pointer (&self); } /** * ide_terminal_launcher_new_for_runtime: * @runtime: an #IdeRuntime * * Create a new #IdeTerminalLauncher that will spawn a terminal in the runtime. * * Returns: (transfer full): a newly created #IdeTerminalLauncher */ IdeTerminalLauncher * ide_terminal_launcher_new_for_runtime (IdeRuntime *runtime) { IdeTerminalLauncher *self; g_return_val_if_fail (IDE_IS_RUNTIME (runtime), NULL); self = g_object_new (IDE_TYPE_TERMINAL_LAUNCHER, NULL); self->runtime = g_object_ref (runtime); self->kind = LAUNCHER_KIND_RUNTIME; ide_terminal_launcher_set_title (self, ide_runtime_get_name (runtime)); return g_steal_pointer (&self); } /** * ide_terminal_launcher_new_for_runner: * @runtime: an #IdeRuntime * * Create a new #IdeTerminalLauncher that will spawn a terminal in the runtime * but with a "runner" context similar to how the application would execute. * * Returns: (transfer full): a newly created #IdeTerminalLauncher */ IdeTerminalLauncher * ide_terminal_launcher_new_for_runner (IdeRuntime *runtime) { IdeTerminalLauncher *self; g_return_val_if_fail (IDE_IS_RUNTIME (runtime), NULL); self = g_object_new (IDE_TYPE_TERMINAL_LAUNCHER, NULL); self->runtime = g_object_ref (runtime); self->kind = LAUNCHER_KIND_RUNNER; return g_steal_pointer (&self); } gboolean ide_terminal_launcher_can_respawn (IdeTerminalLauncher *self) { g_return_val_if_fail (IDE_IS_TERMINAL_LAUNCHER (self), FALSE); return self->kind != LAUNCHER_KIND_LAUNCHER; } const gchar * const * ide_terminal_launcher_get_args (IdeTerminalLauncher *self) { g_return_val_if_fail (IDE_IS_TERMINAL_LAUNCHER (self), NULL); return (const gchar * const *)self->args; } void ide_terminal_launcher_set_args (IdeTerminalLauncher *self, const gchar * const *args) { g_return_if_fail (IDE_IS_TERMINAL_LAUNCHER (self)); if ((gchar **)args != self->args) { gchar **freeme = g_steal_pointer (&self->args); self->args = g_strdupv ((gchar **)args); g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ARGS]); g_strfreev (freeme); } }