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