1 /*
2 * fm-terminal.c
3 *
4 * Copyright 2012-2016 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
5 *
6 * This file is a part of the Libfm library.
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22
23 /**
24 * SECTION:fm-terminal
25 * @short_description: Terminals representation for libfm.
26 * @title: FmTerminal
27 *
28 * @include: libfm/fm.h
29 *
30 * The FmTerminal object represents description how applications which
31 * require start in terminal should be started.
32 */
33
34 #ifdef HAVE_CONFIG_H
35 #include <config.h>
36 #endif
37
38 #include <glib.h>
39 #include <glib/gi18n-lib.h>
40 #include <gio/gdesktopappinfo.h>
41 #include <string.h>
42 #include <unistd.h>
43 #if !GLIB_CHECK_VERSION(2, 28, 0) && !HAVE_DECL_ENVIRON
44 extern char **environ;
45 #endif
46
47 #include "fm-terminal.h"
48 #include "fm-config.h"
49
50 struct _FmTerminalClass
51 {
52 GObjectClass parent;
53 };
54
55 static void fm_terminal_finalize(GObject *object);
56
57 G_DEFINE_TYPE(FmTerminal, fm_terminal, G_TYPE_OBJECT);
58
fm_terminal_class_init(FmTerminalClass * klass)59 static void fm_terminal_class_init(FmTerminalClass *klass)
60 {
61 GObjectClass *g_object_class;
62
63 g_object_class = G_OBJECT_CLASS(klass);
64 g_object_class->finalize = fm_terminal_finalize;
65 }
66
fm_terminal_finalize(GObject * object)67 static void fm_terminal_finalize(GObject *object)
68 {
69 FmTerminal* self;
70 g_return_if_fail(object != NULL);
71 g_return_if_fail(FM_IS_TERMINAL(object));
72
73 self = (FmTerminal*)object;
74 g_free(self->program);
75 g_free(self->open_arg);
76 g_free(self->noclose_arg);
77 g_free(self->launch);
78 g_free(self->desktop_id);
79 g_free(self->custom_args);
80
81 G_OBJECT_CLASS(fm_terminal_parent_class)->finalize(object);
82 }
83
fm_terminal_init(FmTerminal * self)84 static void fm_terminal_init(FmTerminal *self)
85 {
86 }
87
fm_terminal_new(void)88 static FmTerminal* fm_terminal_new(void)
89 {
90 return (FmTerminal*)g_object_new(FM_TERMINAL_TYPE, NULL);
91 }
92
93
94 static GSList *terminals = NULL;
95 static FmTerminal *default_terminal = NULL;
96 G_LOCK_DEFINE_STATIC(terminal);
97
on_terminal_changed(FmConfig * cfg,gpointer unused)98 static void on_terminal_changed(FmConfig *cfg, gpointer unused)
99 {
100 FmTerminal *term = NULL;
101 gsize n;
102 GSList *l;
103 gchar *name, *basename;
104
105 if(cfg->terminal == NULL)
106 goto _end;
107
108 for(n = 0; cfg->terminal[n] && cfg->terminal[n] != ' '; n++);
109 name = g_strndup(cfg->terminal, n);
110 basename = strrchr(name, '/');
111 if(basename)
112 basename++;
113 else
114 basename = name;
115 /* g_debug("terminal in FmConfig: %s, args=%s", name, &cfg->terminal[n]); */
116 for(l = terminals; l; l = l->next)
117 if(strcmp(basename, ((FmTerminal*)l->data)->program) == 0)
118 break;
119 /* don't change existing object to be thread-safe */
120 term = fm_terminal_new();
121 if(l)
122 {
123 if(name[0] != '/') /* not full path; call by basename */
124 {
125 term->program = g_strdup(basename);
126 g_free(name);
127 }
128 else /* call by full path */
129 term->program = name;
130 term->open_arg = g_strdup(((FmTerminal*)l->data)->open_arg);
131 term->noclose_arg = g_strdup(((FmTerminal*)l->data)->noclose_arg);
132 term->launch = g_strdup(((FmTerminal*)l->data)->launch);
133 term->desktop_id = g_strdup(((FmTerminal*)l->data)->desktop_id);
134 }
135 else /* unknown terminal */
136 {
137 if (strcmp(basename, "x-terminal-emulator") == 0)
138 g_message("x-terminal-emulator has very limited support, consider choosing another terminal");
139 else
140 g_warning("terminal %s isn't known, consider report it to LibFM developers",
141 basename);
142 term->program = name;
143 term->open_arg = g_strdup("-e"); /* assume it is default */
144 }
145 if(cfg->terminal[n] == ' ' && cfg->terminal[n+1])
146 {
147 term->custom_args = g_strdup(&cfg->terminal[n+1]);
148 /* support for old style terminal line alike 'xterm -e %s' */
149 name = strchr(term->custom_args, '%');
150 if(name)
151 {
152 /* skip end spaces */
153 while(name > term->custom_args && name[-1] == ' ')
154 name--;
155 /* drop '-e' or '-x' */
156 if(name > term->custom_args + 1 && name[-2] == '-')
157 {
158 name -= 2;
159 /* skip end spaces */
160 while(name > term->custom_args && name[-1] == ' ')
161 name--;
162 }
163 if(name > term->custom_args)
164 *name = '\0'; /* cut the line */
165 else
166 {
167 g_free(term->custom_args);
168 term->custom_args = NULL;
169 }
170 }
171 }
172 _end:
173 G_LOCK(terminal);
174 if(default_terminal)
175 g_object_unref(default_terminal);
176 default_terminal = term;
177 G_UNLOCK(terminal);
178 }
179
180 /* init terminal list from config */
_fm_terminal_init(void)181 void _fm_terminal_init(void)
182 {
183 GKeyFile *kf;
184 gsize i, n;
185 gchar **programs;
186 FmTerminal *term;
187
188 /* read system terminals file */
189 kf = g_key_file_new();
190 if(g_key_file_load_from_file(kf, PACKAGE_DATA_DIR "/terminals.list", 0, NULL))
191 {
192 programs = g_key_file_get_groups(kf, &n);
193 if(programs)
194 {
195 for(i = 0; i < n; ++i)
196 {
197 /* g_debug("found terminal configuration: %s", programs[i]); */
198 term = fm_terminal_new();
199 term->program = programs[i];
200 term->open_arg = g_key_file_get_string(kf, programs[i],
201 "open_arg", NULL);
202 term->noclose_arg = g_key_file_get_string(kf, programs[i],
203 "noclose_arg", NULL);
204 term->launch = g_key_file_get_string(kf, programs[i],
205 "launch", NULL);
206 term->desktop_id = g_key_file_get_string(kf, programs[i],
207 "desktop_id", NULL);
208 terminals = g_slist_append(terminals, term);
209 }
210 g_free(programs); /* strings in the vector are stolen by objects */
211 }
212 }
213 g_key_file_free(kf);
214 /* TODO: read user terminals file? */
215 /* read from config */
216 on_terminal_changed(fm_config, NULL);
217 /* monitor the config */
218 g_signal_connect(fm_config, "changed::terminal",
219 G_CALLBACK(on_terminal_changed), NULL);
220 }
221
222 /* free all resources */
_fm_terminal_finalize(void)223 void _fm_terminal_finalize(void)
224 {
225 /* cancel monitor of config */
226 g_signal_handlers_disconnect_by_func(fm_config, on_terminal_changed, NULL);
227 /* free the data */
228 g_slist_foreach(terminals, (GFunc)g_object_unref, NULL);
229 g_slist_free(terminals);
230 terminals = NULL;
231 if(default_terminal)
232 g_object_unref(default_terminal);
233 default_terminal = NULL;
234 }
235
236 /**
237 * fm_terminal_dup_default
238 * @error: (allow-none): location of error to set
239 *
240 * Retrieves description of terminal which is defined in libfm config.
241 * Returned data should be freed with g_object_unref() after usage.
242 *
243 * Returns: (transfer full): terminal descriptor or %NULL if no terminal is set.
244 *
245 * Since: 1.2.0
246 */
fm_terminal_dup_default(GError ** error)247 FmTerminal* fm_terminal_dup_default(GError **error)
248 {
249 FmTerminal *term = NULL;
250 G_LOCK(terminal);
251 if(default_terminal)
252 term = g_object_ref(default_terminal);
253 G_UNLOCK(terminal);
254 if(!term)
255 g_set_error_literal(error, G_SHELL_ERROR, G_SHELL_ERROR_EMPTY_STRING,
256 _("No terminal emulator is set in libfm config"));
257 return term;
258 }
259
child_setup(gpointer user_data)260 static void child_setup(gpointer user_data)
261 {
262 /* Move child to grandparent group so it will not die with parent */
263 setpgid(0, (pid_t)(gsize)user_data);
264 }
265
266 /**
267 * fm_terminal_launch
268 * @dir: (allow-none): a directory to launch
269 * @error: (allow-none): location of error to set
270 *
271 * Spawns a terminal window in requested @dir. If @dir is %NULL then it
272 * will be spawned in current working directory.
273 *
274 * Returns: %TRUE if spawn was succesful.
275 *
276 * Since: 1.2.0
277 */
fm_terminal_launch(const gchar * dir,GError ** error)278 gboolean fm_terminal_launch(const gchar *dir, GError **error)
279 {
280 FmTerminal *term;
281 GDesktopAppInfo *appinfo = NULL;
282 const gchar *cmd;
283 gchar *_cmd = NULL;
284 gchar **argv;
285 gchar **envp;
286 gint argc;
287 gboolean ret;
288
289 term = fm_terminal_dup_default(error);
290 if(!term)
291 return FALSE;
292 if(term->desktop_id)
293 appinfo = g_desktop_app_info_new(term->desktop_id);
294 if(appinfo)
295 /* FIXME: is it possible to have some %U there? */
296 cmd = g_app_info_get_commandline(G_APP_INFO(appinfo));
297 else if(term->launch)
298 cmd = _cmd = g_strdup_printf("%s %s", term->program, term->launch);
299 else
300 cmd = term->program;
301 if (term->custom_args)
302 {
303 cmd = g_strdup_printf("%s %s", cmd, term->custom_args);
304 g_free(_cmd);
305 _cmd = (char *)cmd;
306 }
307 if(!g_shell_parse_argv(cmd, &argc, &argv, error))
308 argv = NULL;
309 g_free(_cmd);
310 if(appinfo)
311 g_object_unref(appinfo);
312 g_object_unref(term);
313 if(!argv) /* parsing failed */
314 return FALSE;
315 #if GLIB_CHECK_VERSION(2, 28, 0)
316 envp = g_get_environ();
317 #else
318 envp = g_strdupv(environ);
319 #endif
320 if (dir)
321 #if GLIB_CHECK_VERSION(2, 32, 0)
322 envp = g_environ_setenv(envp, "PWD", dir, TRUE);
323 #else
324 {
325 char **env = envp;
326
327 if (env) while (*env != NULL)
328 {
329 if (strncmp(*env, "PWD=", 4) == 0)
330 break;
331 env++;
332 }
333 if (env == NULL || *env == NULL)
334 {
335 gint length;
336
337 length = envp ? g_strv_length(envp) : 0;
338 envp = g_renew(gchar *, envp, length + 2);
339 env = &envp[length];
340 env[1] = NULL;
341 }
342 else
343 g_free(*env);
344 *env = g_strdup_printf ("PWD=%s", dir);
345 }
346 #endif
347 ret = g_spawn_async(dir, argv, envp, G_SPAWN_SEARCH_PATH,
348 child_setup, (gpointer)(gsize)getpgid(getppid()), NULL, error);
349 g_strfreev(argv);
350 g_strfreev(envp);
351 return ret;
352 }
353