1 /* ide-terminal-launcher.c
2  *
3  * Copyright 2019 Christian Hergert <unknown@domain.org>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * SPDX-License-Identifier: GPL-3.0-or-later
19  */
20 
21 #define G_LOG_DOMAIN "ide-terminal-launcher"
22 
23 #include "config.h"
24 
25 #include <errno.h>
26 #include <glib/gi18n.h>
27 #include <libide-foundry.h>
28 #include <libide-threading.h>
29 
30 #include "ide-private.h"
31 
32 #include "ide-terminal-launcher.h"
33 #include "ide-terminal-util.h"
34 
35 typedef enum
36 {
37   LAUNCHER_KIND_HOST = 0,
38   LAUNCHER_KIND_DEBUG,
39   LAUNCHER_KIND_RUNTIME,
40   LAUNCHER_KIND_RUNNER,
41   LAUNCHER_KIND_LAUNCHER,
42 } LauncherKind;
43 
44 struct _IdeTerminalLauncher
45 {
46   GObject                parent_instance;
47   gchar                 *cwd;
48   gchar                 *shell;
49   gchar                 *title;
50   gchar                **args;
51   IdeRuntime            *runtime;
52   IdeContext            *context;
53   IdeSubprocessLauncher *launcher;
54   LauncherKind           kind;
55 };
56 
57 G_DEFINE_FINAL_TYPE (IdeTerminalLauncher, ide_terminal_launcher, G_TYPE_OBJECT)
58 
59 enum {
60   PROP_0,
61   PROP_ARGS,
62   PROP_CWD,
63   PROP_SHELL,
64   PROP_TITLE,
65   N_PROPS
66 };
67 
68 static GParamSpec *properties [N_PROPS];
69 static const struct {
70   const gchar *key;
71   const gchar *value;
72 } default_environment[] = {
73   { "INSIDE_GNOME_BUILDER", PACKAGE_VERSION },
74   { "TERM", "xterm-256color" },
75 };
76 
77 static gboolean
shell_supports_login(const gchar * shell)78 shell_supports_login (const gchar *shell)
79 {
80   g_autofree gchar *name = NULL;
81 
82   /* Shells that support --login */
83   static const gchar *supported[] = {
84     "sh", "bash",
85   };
86 
87   if (shell == NULL)
88     return FALSE;
89 
90   if (!(name = g_path_get_basename (shell)))
91     return FALSE;
92 
93   for (guint i = 0; i < G_N_ELEMENTS (supported); i++)
94     {
95       if (g_str_equal (name, supported[i]))
96         return TRUE;
97     }
98 
99   return FALSE;
100 }
101 
102 static void
copy_envvars(gpointer instance)103 copy_envvars (gpointer instance)
104 {
105   static const gchar *copy_env[] = {
106     "AT_SPI_BUS_ADDRESS",
107     "COLORTERM",
108     "DBUS_SESSION_BUS_ADDRESS",
109     "DBUS_SYSTEM_BUS_ADDRESS",
110     "DESKTOP_SESSION",
111     "DISPLAY",
112     "LANG",
113     "SHELL",
114     "SSH_AUTH_SOCK",
115     "USER",
116     "WAYLAND_DISPLAY",
117     "XAUTHORITY",
118     "XDG_CURRENT_DESKTOP",
119 #if 0
120     /* Can't copy these as they could mess up Flatpak */
121     "XDG_DATA_DIRS",
122     "XDG_RUNTIME_DIR",
123 #endif
124     "XDG_MENU_PREFIX",
125     "XDG_SEAT",
126     "XDG_SESSION_DESKTOP",
127     "XDG_SESSION_ID",
128     "XDG_SESSION_TYPE",
129     "XDG_VTNR",
130   };
131   const gchar * const *host_environ;
132   IdeEnvironment *env = NULL;
133 
134   g_assert (IDE_IS_SUBPROCESS_LAUNCHER (instance) || IDE_IS_RUNNER (instance));
135 
136   if (IDE_IS_RUNNER (instance))
137     env = ide_runner_get_environment (instance);
138 
139   host_environ = _ide_host_environ ();
140 
141   for (guint i = 0; i < G_N_ELEMENTS (copy_env); i++)
142     {
143       const gchar *val = g_environ_getenv ((gchar **)host_environ, copy_env[i]);
144 
145       if (val != NULL)
146         {
147           if (IDE_IS_SUBPROCESS_LAUNCHER (instance))
148             ide_subprocess_launcher_setenv (instance, copy_env[i], val, FALSE);
149           else
150             ide_environment_setenv (env, copy_env[i], val);
151         }
152     }
153 }
154 
155 static void
apply_pipeline_info(gpointer instance,IdeObject * object)156 apply_pipeline_info (gpointer   instance,
157                      IdeObject *object)
158 {
159   g_autoptr(GFile) workdir = NULL;
160   IdeEnvironment *env = NULL;
161   IdeContext *context;
162 
163   g_assert (IDE_IS_SUBPROCESS_LAUNCHER (instance) || IDE_IS_RUNNER (instance));
164   g_assert (IDE_IS_OBJECT (object));
165 
166   context = ide_object_get_context (object);
167   workdir = ide_context_ref_workdir (context);
168 
169   if (IDE_IS_RUNNER (instance))
170     env = ide_runner_get_environment (instance);
171 
172   if (IDE_IS_SUBPROCESS_LAUNCHER (instance))
173     ide_subprocess_launcher_setenv (instance, "SRCDIR", g_file_peek_path (workdir), FALSE);
174   else
175     ide_environment_setenv (env, "SRCDIR", g_file_peek_path (workdir));
176 
177   if (ide_context_has_project (context))
178     {
179       IdeBuildManager *build_manager = ide_build_manager_from_context (context);
180       IdePipeline *pipeline = ide_build_manager_get_pipeline (build_manager);
181 
182       if (pipeline != NULL)
183         {
184           const gchar *builddir = ide_pipeline_get_builddir (pipeline);
185 
186           if (IDE_IS_SUBPROCESS_LAUNCHER (instance))
187             ide_subprocess_launcher_setenv (instance, "BUILDDIR", builddir, FALSE);
188           else
189             ide_environment_setenv (env, "BUILDDIR", builddir);
190         }
191     }
192 }
193 
194 static void
ide_terminal_launcher_wait_check_cb(GObject * object,GAsyncResult * result,gpointer user_data)195 ide_terminal_launcher_wait_check_cb (GObject      *object,
196                                      GAsyncResult *result,
197                                      gpointer      user_data)
198 {
199   IdeSubprocess *subprocess = (IdeSubprocess *)object;
200   g_autoptr(IdeTask) task = user_data;
201   g_autoptr(GError) error = NULL;
202 
203   g_assert (IDE_IS_SUBPROCESS (subprocess));
204   g_assert (G_IS_ASYNC_RESULT (result));
205   g_assert (IDE_IS_TASK (task));
206 
207   if (!ide_subprocess_wait_check_finish (subprocess, result, &error))
208     ide_task_return_error (task, g_steal_pointer (&error));
209   else
210     ide_task_return_boolean (task, TRUE);
211 }
212 
213 static void
spawn_host_launcher(IdeTerminalLauncher * self,IdeTask * task,gint pty_fd,gboolean run_on_host)214 spawn_host_launcher (IdeTerminalLauncher *self,
215                      IdeTask             *task,
216                      gint                 pty_fd,
217                      gboolean             run_on_host)
218 {
219   g_autoptr(IdeSubprocessLauncher) launcher = NULL;
220   g_autoptr(IdeSubprocess) subprocess = NULL;
221   g_autoptr(GError) error = NULL;
222   const gchar *shell;
223 
224   g_assert (IDE_IS_TERMINAL_LAUNCHER (self));
225   g_assert (IDE_IS_TASK (task));
226   g_assert (pty_fd >= 0);
227 
228   if (!(shell = ide_terminal_launcher_get_shell (self)))
229     shell = ide_get_user_shell ();
230 
231   /* We only have sh/bash in our flatpak */
232   if (self->kind == LAUNCHER_KIND_DEBUG && ide_is_flatpak ())
233     shell = "/bin/bash";
234 
235   launcher = ide_subprocess_launcher_new (0);
236   ide_subprocess_launcher_set_run_on_host (launcher, run_on_host);
237   ide_subprocess_launcher_set_cwd (launcher, self->cwd ? self->cwd : g_get_home_dir ());
238   ide_subprocess_launcher_set_clear_env (launcher, FALSE);
239 
240   ide_subprocess_launcher_push_argv (launcher, shell);
241   if (shell_supports_login (shell))
242     ide_subprocess_launcher_push_argv (launcher, "--login");
243 
244   ide_subprocess_launcher_take_stdin_fd (launcher, dup (pty_fd));
245   ide_subprocess_launcher_take_stdout_fd (launcher, dup (pty_fd));
246   ide_subprocess_launcher_take_stderr_fd (launcher, dup (pty_fd));
247 
248   g_assert (ide_subprocess_launcher_get_needs_tty (launcher));
249 
250   for (guint i = 0; i < G_N_ELEMENTS (default_environment); i++)
251     ide_subprocess_launcher_setenv (launcher,
252                                     default_environment[i].key,
253                                     default_environment[i].value,
254                                     FALSE);
255 
256   ide_subprocess_launcher_setenv (launcher, "SHELL", shell, TRUE);
257 
258   if (self->context != NULL)
259     {
260       g_autoptr(GFile) workdir = ide_context_ref_workdir (self->context);
261 
262       ide_subprocess_launcher_setenv (launcher,
263                                       "SRCDIR",
264                                       g_file_peek_path (workdir),
265                                       FALSE);
266     }
267 
268   if (!(subprocess = ide_subprocess_launcher_spawn (launcher, NULL, &error)))
269     ide_task_return_error (task, g_steal_pointer (&error));
270   else
271     ide_subprocess_wait_check_async (subprocess,
272                                      ide_task_get_cancellable (task),
273                                      ide_terminal_launcher_wait_check_cb,
274                                      g_object_ref (task));
275 }
276 
277 static void
spawn_launcher(IdeTerminalLauncher * self,IdeTask * task,IdeSubprocessLauncher * launcher,gint pty_fd)278 spawn_launcher (IdeTerminalLauncher   *self,
279                 IdeTask               *task,
280                 IdeSubprocessLauncher *launcher,
281                 gint                   pty_fd)
282 {
283   g_autoptr(IdeSubprocess) subprocess = NULL;
284   g_autoptr(GError) error = NULL;
285 
286   g_assert (IDE_IS_TERMINAL_LAUNCHER (self));
287   g_assert (IDE_IS_TASK (task));
288   g_assert (!launcher || IDE_IS_SUBPROCESS_LAUNCHER (launcher));
289   g_assert (pty_fd >= 0);
290 
291   if (launcher == NULL)
292     {
293       ide_task_return_new_error (task,
294                                  G_IO_ERROR,
295                                  G_IO_ERROR_FAILED,
296                                  "process may only be spawned once");
297       return;
298     }
299 
300   ide_subprocess_launcher_set_flags (launcher, 0);
301 
302   ide_subprocess_launcher_take_stdin_fd (launcher, dup (pty_fd));
303   ide_subprocess_launcher_take_stdout_fd (launcher, dup (pty_fd));
304   ide_subprocess_launcher_take_stderr_fd (launcher, dup (pty_fd));
305 
306   if (!(subprocess = ide_subprocess_launcher_spawn (launcher, NULL, &error)))
307     ide_task_return_error (task, g_steal_pointer (&error));
308   else
309     ide_subprocess_wait_check_async (subprocess,
310                                      ide_task_get_cancellable (task),
311                                      ide_terminal_launcher_wait_check_cb,
312                                      g_object_ref (task));
313 }
314 
315 static void
spawn_runtime_launcher(IdeTerminalLauncher * self,IdeTask * task,IdeRuntime * runtime,gint pty_fd)316 spawn_runtime_launcher (IdeTerminalLauncher *self,
317                         IdeTask             *task,
318                         IdeRuntime          *runtime,
319                         gint                 pty_fd)
320 {
321   g_autoptr(IdeSubprocessLauncher) launcher = NULL;
322   g_autoptr(IdeSubprocess) subprocess = NULL;
323   g_autoptr(GError) error = NULL;
324   const gchar *shell;
325 
326   g_assert (IDE_IS_TERMINAL_LAUNCHER (self));
327   g_assert (IDE_IS_TASK (task));
328   g_assert (IDE_IS_RUNTIME (runtime));
329   g_assert (pty_fd >= 0);
330 
331   if (!(shell = ide_terminal_launcher_get_shell (self)))
332     shell = ide_get_user_shell ();
333 
334   if (!(launcher = ide_runtime_create_launcher (runtime, NULL)))
335     {
336       ide_task_return_new_error (task,
337                                  G_IO_ERROR,
338                                  G_IO_ERROR_FAILED,
339                                  _("Failed to create shell within runtime “%s”"),
340                                  ide_runtime_get_display_name (runtime));
341       return;
342     }
343 
344   ide_subprocess_launcher_set_flags (launcher, 0);
345 
346   if (!ide_runtime_contains_program_in_path (runtime, shell, NULL))
347     shell = "/bin/sh";
348 
349   ide_subprocess_launcher_set_cwd (launcher, self->cwd ? self->cwd : g_get_home_dir ());
350 
351   ide_subprocess_launcher_push_argv (launcher, shell);
352   if (shell_supports_login (shell))
353     ide_subprocess_launcher_push_argv (launcher, "--login");
354 
355   ide_subprocess_launcher_take_stdin_fd (launcher, dup (pty_fd));
356   ide_subprocess_launcher_take_stdout_fd (launcher, dup (pty_fd));
357   ide_subprocess_launcher_take_stderr_fd (launcher, dup (pty_fd));
358 
359   g_assert (ide_subprocess_launcher_get_needs_tty (launcher));
360 
361   for (guint i = 0; i < G_N_ELEMENTS (default_environment); i++)
362     ide_subprocess_launcher_setenv (launcher,
363                                     default_environment[i].key,
364                                     default_environment[i].value,
365                                     FALSE);
366 
367   apply_pipeline_info (launcher, IDE_OBJECT (self->runtime));
368   copy_envvars (launcher);
369 
370   ide_subprocess_launcher_setenv (launcher, "SHELL", shell, TRUE);
371 
372   if (!(subprocess = ide_subprocess_launcher_spawn (launcher, NULL, &error)))
373     ide_task_return_error (task, g_steal_pointer (&error));
374   else
375     ide_subprocess_wait_check_async (subprocess,
376                                      ide_task_get_cancellable (task),
377                                      ide_terminal_launcher_wait_check_cb,
378                                      g_object_ref (task));
379 }
380 
381 static void
ide_terminal_launcher_run_cb(GObject * object,GAsyncResult * result,gpointer user_data)382 ide_terminal_launcher_run_cb (GObject      *object,
383                               GAsyncResult *result,
384                               gpointer      user_data)
385 {
386   IdeRunner *runner = (IdeRunner *)object;
387   g_autoptr(IdeTask) task = user_data;
388   g_autoptr(GError) error = NULL;
389 
390   g_assert (IDE_IS_RUNNER (runner));
391   g_assert (G_IS_ASYNC_RESULT (result));
392   g_assert (IDE_IS_TASK (task));
393 
394   if (!ide_runner_run_finish (runner, result, &error))
395     ide_task_return_error (task, g_steal_pointer (&error));
396   else
397     ide_task_return_boolean (task, TRUE);
398 
399   ide_object_destroy (IDE_OBJECT (runner));
400 }
401 
402 static void
spawn_runner_launcher(IdeTerminalLauncher * self,IdeTask * task,IdeRuntime * runtime,gint pty_fd)403 spawn_runner_launcher (IdeTerminalLauncher *self,
404                        IdeTask             *task,
405                        IdeRuntime          *runtime,
406                        gint                 pty_fd)
407 {
408   g_autoptr(IdeSimpleBuildTarget) build_target = NULL;
409   g_autoptr(IdeRunner) runner = NULL;
410   g_autoptr(GPtrArray) argv = NULL;
411   IdeEnvironment *env;
412   const gchar *shell;
413 
414   g_assert (IDE_IS_TERMINAL_LAUNCHER (self));
415   g_assert (IDE_IS_TASK (task));
416   g_assert (IDE_IS_RUNTIME (runtime));
417   g_assert (pty_fd >= 0);
418 
419   if (!(shell = ide_terminal_launcher_get_shell (self)))
420     shell = ide_get_user_shell ();
421 
422   if (!ide_runtime_contains_program_in_path (runtime, shell, NULL))
423     shell = "/bin/sh";
424 
425   argv = g_ptr_array_new ();
426   g_ptr_array_add (argv, (gchar *)shell);
427 
428   if (self->args == NULL)
429     {
430       if (shell_supports_login (shell))
431         g_ptr_array_add (argv, (gchar *)"--login");
432     }
433   else
434     {
435       for (guint i = 0; self->args[i]; i++)
436         g_ptr_array_add (argv, self->args[i]);
437     }
438 
439   g_ptr_array_add (argv, NULL);
440 
441   build_target = ide_simple_build_target_new (NULL);
442   ide_simple_build_target_set_argv (build_target, (const gchar * const *)argv->pdata);
443   ide_simple_build_target_set_cwd (build_target, self->cwd ? self->cwd : g_get_home_dir ());
444 
445   /* Creating runner should always succeed, but run_async() may fail */
446   runner = ide_runtime_create_runner (runtime, IDE_BUILD_TARGET (build_target));
447   env = ide_runner_get_environment (runner);
448   ide_runner_take_tty_fd (runner, dup (pty_fd));
449 
450   for (guint i = 0; i < G_N_ELEMENTS (default_environment); i++)
451     ide_environment_setenv (env,
452                             default_environment[i].key,
453                             default_environment[i].value);
454 
455   apply_pipeline_info (runner, IDE_OBJECT (self->runtime));
456   copy_envvars (runner);
457 
458   ide_environment_setenv (env, "SHELL", shell);
459 
460   ide_runner_run_async (runner,
461                         ide_task_get_cancellable (task),
462                         ide_terminal_launcher_run_cb,
463                         g_object_ref (task));
464 }
465 
466 void
ide_terminal_launcher_spawn_async(IdeTerminalLauncher * self,VtePty * pty,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)467 ide_terminal_launcher_spawn_async (IdeTerminalLauncher *self,
468                                    VtePty              *pty,
469                                    GCancellable        *cancellable,
470                                    GAsyncReadyCallback  callback,
471                                    gpointer             user_data)
472 {
473   g_autoptr(IdeTask) task = NULL;
474   gint pty_fd = -1;
475 
476   g_assert (IDE_IS_TERMINAL_LAUNCHER (self));
477   g_assert (VTE_IS_PTY (pty));
478   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
479 
480   task = ide_task_new (self, cancellable, callback, user_data);
481   ide_task_set_source_tag (task, ide_terminal_launcher_spawn_async);
482 
483   if ((pty_fd = ide_vte_pty_create_slave (pty)) == -1)
484     {
485       int errsv = errno;
486       ide_task_return_new_error (task,
487                                  G_IO_ERROR,
488                                  g_io_error_from_errno (errsv),
489                                  "%s", g_strerror (errsv));
490       return;
491     }
492 
493   switch (self->kind)
494     {
495     case LAUNCHER_KIND_RUNTIME:
496       spawn_runtime_launcher (self, task, self->runtime, pty_fd);
497       break;
498 
499     case LAUNCHER_KIND_RUNNER:
500       spawn_runner_launcher (self, task, self->runtime, pty_fd);
501       break;
502 
503     case LAUNCHER_KIND_LAUNCHER:
504       spawn_launcher (self, task, self->launcher, pty_fd);
505       g_clear_object (&self->launcher);
506       break;
507 
508     case LAUNCHER_KIND_DEBUG:
509     case LAUNCHER_KIND_HOST:
510     default:
511       spawn_host_launcher (self, task, pty_fd, self->kind == LAUNCHER_KIND_HOST);
512       break;
513     }
514 
515   if (pty_fd != -1)
516     close (pty_fd);
517 }
518 
519 /**
520  * ide_terminal_launcher_spawn_finish:
521  * @self: a #IdeTerminalLauncher
522  *
523  * Completes a request to ide_terminal_launcher_spawn_async()
524  *
525  * Returns: %TRUE if the process executed successfully; otherwise %FALSE
526  *   and @error is set.
527  *
528  * Since: 3.34
529  */
530 gboolean
ide_terminal_launcher_spawn_finish(IdeTerminalLauncher * self,GAsyncResult * result,GError ** error)531 ide_terminal_launcher_spawn_finish (IdeTerminalLauncher  *self,
532                                     GAsyncResult         *result,
533                                     GError              **error)
534 {
535   g_assert (IDE_IS_TERMINAL_LAUNCHER (self));
536   g_assert (IDE_IS_TASK (result));
537 
538   return ide_task_propagate_boolean (IDE_TASK (result), error);
539 }
540 
541 static void
ide_terminal_launcher_finalize(GObject * object)542 ide_terminal_launcher_finalize (GObject *object)
543 {
544   IdeTerminalLauncher *self = (IdeTerminalLauncher *)object;
545 
546   g_clear_pointer (&self->args, g_strfreev);
547   g_clear_pointer (&self->cwd, g_free);
548   g_clear_pointer (&self->shell, g_free);
549   g_clear_pointer (&self->title, g_free);
550   g_clear_object (&self->launcher);
551   g_clear_object (&self->runtime);
552 
553   G_OBJECT_CLASS (ide_terminal_launcher_parent_class)->finalize (object);
554 }
555 
556 static void
ide_terminal_launcher_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)557 ide_terminal_launcher_get_property (GObject    *object,
558                                     guint       prop_id,
559                                     GValue     *value,
560                                     GParamSpec *pspec)
561 {
562   IdeTerminalLauncher *self = IDE_TERMINAL_LAUNCHER (object);
563 
564   switch (prop_id)
565     {
566     case PROP_ARGS:
567       g_value_set_boxed (value, ide_terminal_launcher_get_args (self));
568       break;
569 
570     case PROP_CWD:
571       g_value_set_string (value, ide_terminal_launcher_get_cwd (self));
572       break;
573 
574     case PROP_SHELL:
575       g_value_set_string (value, ide_terminal_launcher_get_shell (self));
576       break;
577 
578     case PROP_TITLE:
579       g_value_set_string (value, ide_terminal_launcher_get_title (self));
580       break;
581 
582     default:
583       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
584     }
585 }
586 
587 static void
ide_terminal_launcher_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)588 ide_terminal_launcher_set_property (GObject      *object,
589                                     guint         prop_id,
590                                     const GValue *value,
591                                     GParamSpec   *pspec)
592 {
593   IdeTerminalLauncher *self = IDE_TERMINAL_LAUNCHER (object);
594 
595   switch (prop_id)
596     {
597     case PROP_ARGS:
598       ide_terminal_launcher_set_args (self, g_value_get_boxed (value));
599       break;
600 
601     case PROP_CWD:
602       ide_terminal_launcher_set_cwd (self, g_value_get_string (value));
603       break;
604 
605     case PROP_SHELL:
606       ide_terminal_launcher_set_shell (self, g_value_get_string (value));
607       break;
608 
609     case PROP_TITLE:
610       ide_terminal_launcher_set_title (self, g_value_get_string (value));
611       break;
612 
613     default:
614       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
615     }
616 }
617 
618 static void
ide_terminal_launcher_class_init(IdeTerminalLauncherClass * klass)619 ide_terminal_launcher_class_init (IdeTerminalLauncherClass *klass)
620 {
621   GObjectClass *object_class = G_OBJECT_CLASS (klass);
622 
623   object_class->finalize = ide_terminal_launcher_finalize;
624   object_class->get_property = ide_terminal_launcher_get_property;
625   object_class->set_property = ide_terminal_launcher_set_property;
626 
627   properties [PROP_ARGS] =
628     g_param_spec_boxed ("args",
629                          "Args",
630                          "Arguments to shell",
631                          G_TYPE_STRV,
632                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
633 
634   properties [PROP_CWD] =
635     g_param_spec_string ("cwd",
636                          "Cwd",
637                          "The cwd to spawn in the subprocess",
638                          NULL,
639                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
640 
641   properties [PROP_SHELL] =
642     g_param_spec_string ("shell",
643                          "Shell",
644                          "The shell to spawn in the subprocess",
645                          NULL,
646                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
647 
648   properties [PROP_TITLE] =
649     g_param_spec_string ("title",
650                          "Title",
651                          "The title for the subprocess launcher",
652                          NULL,
653                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
654 
655   g_object_class_install_properties (object_class, N_PROPS, properties);
656 }
657 
658 static void
ide_terminal_launcher_init(IdeTerminalLauncher * self)659 ide_terminal_launcher_init (IdeTerminalLauncher *self)
660 {
661   self->cwd = NULL;
662   self->shell = NULL;
663   self->title = g_strdup (_("Untitled Terminal"));
664 }
665 
666 const gchar *
ide_terminal_launcher_get_cwd(IdeTerminalLauncher * self)667 ide_terminal_launcher_get_cwd (IdeTerminalLauncher *self)
668 {
669   g_return_val_if_fail (IDE_IS_TERMINAL_LAUNCHER (self), NULL);
670 
671   return self->cwd;
672 }
673 
674 void
ide_terminal_launcher_set_cwd(IdeTerminalLauncher * self,const gchar * cwd)675 ide_terminal_launcher_set_cwd (IdeTerminalLauncher *self,
676                                const gchar         *cwd)
677 {
678   g_return_if_fail (IDE_IS_TERMINAL_LAUNCHER (self));
679 
680   if (g_strcmp0 (self->cwd, cwd) != 0)
681     {
682       g_free (self->cwd);
683       self->cwd = g_strdup (cwd);
684       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CWD]);
685     }
686 }
687 
688 const gchar *
ide_terminal_launcher_get_shell(IdeTerminalLauncher * self)689 ide_terminal_launcher_get_shell (IdeTerminalLauncher *self)
690 {
691   g_return_val_if_fail (IDE_IS_TERMINAL_LAUNCHER (self), NULL);
692 
693   return self->shell;
694 }
695 
696 void
ide_terminal_launcher_set_shell(IdeTerminalLauncher * self,const gchar * shell)697 ide_terminal_launcher_set_shell (IdeTerminalLauncher *self,
698                                  const gchar         *shell)
699 {
700   g_return_if_fail (IDE_IS_TERMINAL_LAUNCHER (self));
701 
702   if (g_strcmp0 (self->shell, shell) != 0)
703     {
704       g_free (self->shell);
705       self->shell = g_strdup (shell);
706       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHELL]);
707     }
708 }
709 
710 const gchar *
ide_terminal_launcher_get_title(IdeTerminalLauncher * self)711 ide_terminal_launcher_get_title (IdeTerminalLauncher *self)
712 {
713   g_return_val_if_fail (IDE_IS_TERMINAL_LAUNCHER (self), NULL);
714 
715   return self->title;
716 }
717 
718 void
ide_terminal_launcher_set_title(IdeTerminalLauncher * self,const gchar * title)719 ide_terminal_launcher_set_title (IdeTerminalLauncher *self,
720                                  const gchar         *title)
721 {
722   g_return_if_fail (IDE_IS_TERMINAL_LAUNCHER (self));
723 
724   if (g_strcmp0 (self->title, title) != 0)
725     {
726       g_free (self->title);
727       self->title = g_strdup (title);
728       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]);
729     }
730 }
731 
732 /**
733  * ide_terminal_launcher_new:
734  *
735  * Create a new #IdeTerminalLauncher that will spawn a terminal on the host.
736  *
737  * Returns: (transfer full): a newly created #IdeTerminalLauncher
738  */
739 IdeTerminalLauncher *
ide_terminal_launcher_new(IdeContext * context)740 ide_terminal_launcher_new (IdeContext *context)
741 {
742   IdeTerminalLauncher *self;
743   g_autoptr(GFile) workdir = NULL;
744 
745   g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
746 
747   workdir = ide_context_ref_workdir (context);
748 
749   self = g_object_new (IDE_TYPE_TERMINAL_LAUNCHER, NULL);
750   self->kind = LAUNCHER_KIND_HOST;
751   self->cwd = g_file_get_path (workdir);
752   self->context = g_object_ref (context);
753 
754   return g_steal_pointer (&self);
755 }
756 
757 /**
758  * ide_terminal_launcher_new_for_launcher:
759  * @launcher: an #IdeSubprocessLauncher
760  *
761  * Creates a new #IdeTerminalLauncher that can be used to launch a process
762  * using the provided #IdeSubprocessLauncher.
763  *
764  * Returns: (transfer full): an #IdeTerminalLauncher
765  *
766  * Since: 3.34
767  */
768 IdeTerminalLauncher *
ide_terminal_launcher_new_for_launcher(IdeSubprocessLauncher * launcher)769 ide_terminal_launcher_new_for_launcher (IdeSubprocessLauncher *launcher)
770 {
771   IdeTerminalLauncher *self;
772 
773   g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (launcher), NULL);
774 
775   self = g_object_new (IDE_TYPE_TERMINAL_LAUNCHER, NULL);
776   self->kind = LAUNCHER_KIND_LAUNCHER;
777   self->launcher = g_object_ref (launcher);
778 
779   return g_steal_pointer (&self);
780 }
781 
782 /**
783  * ide_terminal_launcher_new_for_debug
784  *
785  * Create a new #IdeTerminalLauncher that will spawn a terminal on the host.
786  *
787  * Returns: (transfer full): a newly created #IdeTerminalLauncher
788  */
789 IdeTerminalLauncher *
ide_terminal_launcher_new_for_debug(void)790 ide_terminal_launcher_new_for_debug (void)
791 {
792   IdeTerminalLauncher *self;
793 
794   self = g_object_new (IDE_TYPE_TERMINAL_LAUNCHER, NULL);
795   self->kind = LAUNCHER_KIND_DEBUG;
796 
797   return g_steal_pointer (&self);
798 }
799 
800 /**
801  * ide_terminal_launcher_new_for_runtime:
802  * @runtime: an #IdeRuntime
803  *
804  * Create a new #IdeTerminalLauncher that will spawn a terminal in the runtime.
805  *
806  * Returns: (transfer full): a newly created #IdeTerminalLauncher
807  */
808 IdeTerminalLauncher *
ide_terminal_launcher_new_for_runtime(IdeRuntime * runtime)809 ide_terminal_launcher_new_for_runtime (IdeRuntime *runtime)
810 {
811   IdeTerminalLauncher *self;
812 
813   g_return_val_if_fail (IDE_IS_RUNTIME (runtime), NULL);
814 
815   self = g_object_new (IDE_TYPE_TERMINAL_LAUNCHER, NULL);
816   self->runtime = g_object_ref (runtime);
817   self->kind = LAUNCHER_KIND_RUNTIME;
818 
819   ide_terminal_launcher_set_title (self, ide_runtime_get_name (runtime));
820 
821   return g_steal_pointer (&self);
822 }
823 
824 /**
825  * ide_terminal_launcher_new_for_runner:
826  * @runtime: an #IdeRuntime
827  *
828  * Create a new #IdeTerminalLauncher that will spawn a terminal in the runtime
829  * but with a "runner" context similar to how the application would execute.
830  *
831  * Returns: (transfer full): a newly created #IdeTerminalLauncher
832  */
833 IdeTerminalLauncher *
ide_terminal_launcher_new_for_runner(IdeRuntime * runtime)834 ide_terminal_launcher_new_for_runner (IdeRuntime *runtime)
835 {
836   IdeTerminalLauncher *self;
837 
838   g_return_val_if_fail (IDE_IS_RUNTIME (runtime), NULL);
839 
840   self = g_object_new (IDE_TYPE_TERMINAL_LAUNCHER, NULL);
841   self->runtime = g_object_ref (runtime);
842   self->kind = LAUNCHER_KIND_RUNNER;
843 
844   return g_steal_pointer (&self);
845 }
846 
847 gboolean
ide_terminal_launcher_can_respawn(IdeTerminalLauncher * self)848 ide_terminal_launcher_can_respawn (IdeTerminalLauncher *self)
849 {
850   g_return_val_if_fail (IDE_IS_TERMINAL_LAUNCHER (self), FALSE);
851 
852   return self->kind != LAUNCHER_KIND_LAUNCHER;
853 }
854 
855 const gchar * const *
ide_terminal_launcher_get_args(IdeTerminalLauncher * self)856 ide_terminal_launcher_get_args (IdeTerminalLauncher *self)
857 {
858   g_return_val_if_fail (IDE_IS_TERMINAL_LAUNCHER (self), NULL);
859 
860   return (const gchar * const *)self->args;
861 }
862 
863 void
ide_terminal_launcher_set_args(IdeTerminalLauncher * self,const gchar * const * args)864 ide_terminal_launcher_set_args (IdeTerminalLauncher *self,
865                                 const gchar * const *args)
866 {
867   g_return_if_fail (IDE_IS_TERMINAL_LAUNCHER (self));
868 
869   if ((gchar **)args != self->args)
870     {
871       gchar **freeme = g_steal_pointer (&self->args);
872       self->args = g_strdupv ((gchar **)args);
873       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ARGS]);
874       g_strfreev (freeme);
875     }
876 }
877