* plugins/run-program/plugin.c,
[anjuta-git-plugin.git] / plugins / run-program / execute.c
blobcb12a0b6012ecefa3372845b6b0af1911f9291f1
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3 execute.c
4 Copyright (C) 2008 Sébastien Granjoux
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 * Run and Stop program
23 *---------------------------------------------------------------------------*/
25 #include <config.h>
27 #include "execute.h"
29 #include <libanjuta/anjuta-debug.h>
30 #include <libanjuta/interfaces/ianjuta-terminal.h>
31 #include <libanjuta/interfaces/ianjuta-builder.h>
33 #include <libgnomevfs/gnome-vfs-utils.h>
35 #include <signal.h>
37 /* Constants
38 *---------------------------------------------------------------------------*/
40 #define PREF_USE_SB "build.use_scratchbox"
41 #define PREF_SB_PATH "build.scratchbox.path"
43 #define RUN_BUILD_TARGET_QUARK_STRING "RUN_PLUGIN_BUILD_TARGET"
45 /*----------------------------------------------------------------------------
46 * Type definitions
49 struct _RunProgramChild
51 GPid pid;
52 guint source;
53 gboolean use_signal;
54 gboolean terminated;
57 /* Helper functions
58 *---------------------------------------------------------------------------*/
60 static gchar *
61 get_local_executable (GtkWindow *parent, const gchar *uri)
63 const gchar *err_msg = NULL;
64 gchar *local = NULL;
66 if (uri != NULL)
68 local = gnome_vfs_get_local_path_from_uri (uri);
69 if (local == NULL)
71 /* Only local program are supported */
72 err_msg = _("Program '%s' is not a local file");
74 else
76 if (g_file_test (local, G_FILE_TEST_EXISTS) == FALSE)
78 err_msg = _("Program '%s' does not exists");
80 else if (g_file_test (local, G_FILE_TEST_IS_EXECUTABLE) == FALSE)
82 err_msg = _("Program '%s' does not have execution permission");
87 if (err_msg)
89 anjuta_util_dialog_error (parent, err_msg, local == NULL ? uri : local);
90 g_free (local);
91 local = NULL;
94 return local;
97 static gchar *
98 get_local_directory (GtkWindow *parent, const gchar *uri)
100 const gchar *err_msg = NULL;
101 gchar *local = NULL;
103 if (uri != NULL)
105 local = gnome_vfs_get_local_path_from_uri (uri);
106 if (local == NULL)
108 /* Only local directory are supported */
109 err_msg = _("Program directory '%s' is not local");
113 if (err_msg)
115 anjuta_util_dialog_error (parent, err_msg, uri);
118 return local;
121 /* Private functions
122 *---------------------------------------------------------------------------*/
124 static void
125 on_child_terminated (GPid pid, gint status, gpointer user_data);
127 static void
128 run_plugin_child_free (RunProgramPlugin *plugin, GPid pid)
130 GList *child;
132 for (child = g_list_first (plugin->child); child != NULL; child = g_list_next (child))
134 if (((RunProgramChild *)child->data)->pid == pid)
136 if (((RunProgramChild *)child->data)->use_signal)
138 g_return_if_fail (plugin->child_exited_connection > 0);
139 plugin->child_exited_connection--;
140 if (plugin->child_exited_connection == 0)
142 IAnjutaTerminal *term;
144 term = anjuta_shell_get_interface (ANJUTA_PLUGIN (plugin)->shell,
145 IAnjutaTerminal, NULL);
146 g_signal_handlers_disconnect_by_func (term, on_child_terminated, plugin);
149 else if (((RunProgramChild *)child->data)->source)
151 g_source_remove (((RunProgramChild *)child->data)->source);
153 g_free (child->data);
154 plugin->child = g_list_delete_link (plugin->child, child);
155 break;
159 run_plugin_update_menu_sensitivity (plugin);
162 static void
163 on_child_terminated (GPid pid, gint status, gpointer user_data)
165 RunProgramPlugin *plugin = (RunProgramPlugin *)user_data;
167 run_plugin_child_free (plugin, pid);
170 static void
171 on_child_terminated_signal (IAnjutaTerminal *term, GPid pid, gint status, gpointer user_data)
173 on_child_terminated (pid, status, user_data);
176 static GPid
177 execute_with_terminal (RunProgramPlugin *plugin,
178 const gchar *dir, const gchar *cmd, gchar **env)
180 IAnjutaTerminal *term;
181 GPid pid = 0;
183 term = anjuta_shell_get_interface (ANJUTA_PLUGIN (plugin)->shell,
184 IAnjutaTerminal, NULL);
185 if (term)
187 gchar* launcher_path = g_find_program_in_path("anjuta_launcher");
188 gchar *new_cmd;
189 RunProgramChild *child;
191 if (launcher_path != NULL)
193 new_cmd = g_strconcat ("anjuta_launcher ", cmd, NULL);
194 g_free (launcher_path);
196 else
198 DEBUG_PRINT("Missing anjuta_launcher");
199 new_cmd = g_strdup (cmd);
202 if (plugin->child_exited_connection == 0)
204 g_signal_connect (term, "child-exited", G_CALLBACK (on_child_terminated_signal), plugin);
206 plugin->child_exited_connection++;
207 child = g_new0 (RunProgramChild, 1);
208 child->use_signal = TRUE;
209 plugin->child = g_list_prepend (plugin->child, child);
211 pid = ianjuta_terminal_execute_command (term, dir, new_cmd, env, NULL);
212 child->pid = pid;
213 g_free (new_cmd);
216 return pid;
219 static GPid
220 execute_without_terminal (RunProgramPlugin *plugin,
221 const gchar *dir, gchar *cmd, gchar **env)
223 char *user_shell;
224 char * argv[4];
225 GPid pid;
226 RunProgramChild *child;
227 gsize len;
228 gchar **new_env;
229 gchar **old_env;
230 gchar **p;
231 gint i;
233 /* Create environment variable array */
234 old_env = g_listenv();
235 len = old_env ? g_strv_length (old_env) : 0;
236 len += env ? g_strv_length (env) : 0;
237 len ++;
238 new_env = g_new (char *, len);
240 /* Remove some environment variables, Move other in new_env */
241 i = 0;
242 for (p = old_env; *p; p++)
244 const gchar *value;
246 value = g_getenv (*p);
247 if (env != NULL)
249 gchar **q;
251 for (q = env; *q; q++)
253 gsize len;
255 len = strlen (*p);
256 if ((strlen (*q) > len + 1) &&
257 (strncmp (*q, *p, len) == 0) &&
258 (*q[len] == '='))
260 value = *q + len + 1;
261 break;
266 new_env[i++] = g_strconcat (*p, "=", value, NULL);
268 g_strfreev (old_env);
270 /* Add new user variable */
271 if (env)
273 for (p = env; *p; p++)
275 new_env[i++] = g_strdup (*p);
277 new_env[i] = NULL;
280 /* Run user program using in a shell */
281 user_shell = gnome_util_user_shell ();
282 argv[0] = user_shell;
283 argv[1] = "-c";
284 argv[2] = cmd;
285 argv[3] = NULL;
287 child = g_new0 (RunProgramChild, 1);
288 plugin->child = g_list_prepend (plugin->child, child);
290 if (g_spawn_async_with_pipes (dir, argv, new_env, G_SPAWN_DO_NOT_REAP_CHILD,
291 NULL, NULL, &pid, NULL, NULL, NULL, NULL))
293 child->pid = pid;
294 child->source = g_child_watch_add (pid, on_child_terminated, plugin);
296 else
298 pid = 0;
301 g_free (user_shell);
302 g_strfreev (new_env);
304 return pid;
307 static gboolean
308 run_program (RunProgramPlugin *plugin)
310 gchar *target;
311 gchar *quote_target;
312 gchar *dir = NULL;
313 gchar *dir_uri = NULL;
314 gchar *args = NULL;
315 gchar **env = NULL;
316 gchar *cmd;
317 gboolean run_in_terminal = 0;
318 AnjutaPreferences *prefs;
319 GPid pid;
321 target = get_local_executable (GTK_WINDOW (ANJUTA_PLUGIN (plugin)->shell),
322 plugin->build_uri);
323 g_free (plugin->build_uri);
324 plugin->build_uri = NULL;
325 if (target == NULL) return FALSE;
327 /* Get directory from shell */
328 anjuta_shell_get (ANJUTA_PLUGIN (plugin)->shell,
329 RUN_PROGRAM_DIR, G_TYPE_STRING, &dir_uri,
330 NULL);
331 if (dir_uri != NULL)
333 dir = get_local_directory (GTK_WINDOW (ANJUTA_PLUGIN (plugin)->shell),
334 dir_uri);
335 g_free (dir_uri);
336 if (dir == NULL) return FALSE;
338 else
340 dir = g_path_get_dirname (target);
343 /* Get other parameters from shell */
344 anjuta_shell_get (ANJUTA_PLUGIN (plugin)->shell,
345 RUN_PROGRAM_ARGS, G_TYPE_STRING, &args,
346 RUN_PROGRAM_ENV, G_TYPE_STRV, &env,
347 RUN_PROGRAM_NEED_TERM, G_TYPE_BOOLEAN, &run_in_terminal,
348 NULL);
350 /* Quote target name */
351 quote_target = g_shell_quote (target);
352 g_free (target);
354 if (args && strlen (args) > 0)
355 cmd = g_strconcat (quote_target, " ", args, NULL);
356 else
357 cmd = g_strdup (quote_target);
358 g_free (args);
359 g_free (quote_target);
361 /* Take care of scratchbox */
362 prefs = anjuta_shell_get_preferences (ANJUTA_PLUGIN(plugin)->shell, NULL);
363 if (anjuta_preferences_get_int (prefs , PREF_USE_SB))
365 const gchar* sb_path = anjuta_preferences_get(prefs, PREF_SB_PATH);
366 /* we need to skip the /scratchbox/users part, maybe could be done more clever */
367 const gchar* real_dir = strstr(dir, "/home");
368 gchar* oldcmd = cmd;
369 gchar* olddir = dir;
371 cmd = g_strdup_printf("%s/login -d %s \"%s\"", sb_path,
372 real_dir, oldcmd);
373 g_free(oldcmd);
374 dir = g_strdup(real_dir);
375 g_free (olddir);
378 if (run_in_terminal)
380 pid = execute_with_terminal (plugin, dir, cmd, env);
381 if (!pid)
383 pid = execute_without_terminal (plugin, dir, cmd, env);
386 else
388 pid = execute_without_terminal (plugin, dir, cmd, env);
391 if (pid == 0)
393 anjuta_util_dialog_error (GTK_WINDOW (ANJUTA_PLUGIN (plugin)->shell),
394 "Unable to execute %s", cmd);
396 run_plugin_update_menu_sensitivity (plugin);
398 g_free (dir);
399 g_strfreev (env);
400 g_free (cmd);
402 return TRUE;
405 static void
406 on_build_finished (IAnjutaBuilder *builder, GError *err, gpointer user_data)
408 RunProgramPlugin *plugin = (RunProgramPlugin *)user_data;
410 g_signal_handler_disconnect (builder, plugin->build_handle);
412 if (err == NULL)
414 /* Up to date, run program */
415 run_program (plugin);
417 else
419 g_free (plugin->build_uri);
420 plugin->build_uri = NULL;
424 static void
425 on_is_built_finished (IAnjutaBuilder *builder, GError *err, gpointer user_data)
427 RunProgramPlugin *plugin = (RunProgramPlugin *)user_data;
429 g_signal_handler_disconnect (builder, plugin->build_handle);
431 if (err == NULL)
433 /* Up to date, run program */
434 run_program (plugin);
436 else if (err->code == IANJUTA_BUILDER_FAILED)
438 /* Target is not up to date */
439 plugin->build_handle = g_signal_connect (builder, "command-finished::" RUN_BUILD_TARGET_QUARK_STRING, G_CALLBACK (on_build_finished), plugin);
441 /* Build target */
442 ianjuta_builder_build (builder, plugin->build_uri, plugin->build_id, NULL);
444 else
446 g_free (plugin->build_uri);
447 plugin->build_uri = NULL;
451 static gboolean
452 check_target (RunProgramPlugin *plugin)
454 IAnjutaBuilder *builder;
455 gchar *prog_uri;
457 anjuta_shell_get (ANJUTA_PLUGIN (plugin)->shell,
458 RUN_PROGRAM_URI, G_TYPE_STRING, &prog_uri, NULL);
460 builder = anjuta_shell_get_interface (ANJUTA_PLUGIN (plugin)->shell, IAnjutaBuilder, NULL);
461 if (builder != NULL)
463 if (plugin->build_uri)
465 /* a build operation is currently running */
466 if (strcmp (plugin->build_uri, prog_uri) == 0)
468 /* It is the same one, just ignore */
469 return TRUE;
471 else
473 /* Cancel old operation */
474 ianjuta_builder_cancel (builder, plugin->build_id, NULL);
478 plugin->build_uri = prog_uri;
479 if (plugin->build_id == 0)
480 plugin->build_id = g_quark_from_static_string(RUN_BUILD_TARGET_QUARK_STRING);
482 plugin->build_handle = g_signal_connect (builder, "command-finished::" RUN_BUILD_TARGET_QUARK_STRING, G_CALLBACK (on_is_built_finished), plugin);
484 /* Check if target is up to date */
485 return ianjuta_builder_is_built (builder, plugin->build_uri, plugin->build_id, NULL);
487 else
489 plugin->build_uri = prog_uri;
491 /* Unable to build target, just run it */
492 return run_program (plugin);
496 /* Public functions
497 *---------------------------------------------------------------------------*/
499 gboolean
500 run_plugin_run_program (RunProgramPlugin *plugin)
502 /* Check if target is up to date */
503 return check_target (plugin);
506 gboolean
507 run_plugin_kill_program (RunProgramPlugin *plugin, gboolean terminate)
509 if (plugin->child != NULL)
511 RunProgramChild *child = (RunProgramChild *)plugin->child->data;
513 if (!child->terminated && terminate)
515 kill (child->pid, SIGTERM);
516 child->terminated = TRUE;
518 else
520 kill (child->pid, SIGKILL);
521 run_plugin_child_free (plugin, child->pid);
525 return TRUE;
528 void
529 run_free_all_children (RunProgramPlugin *plugin)
531 GList *child;
532 IAnjutaTerminal *term;
534 /* Remove terminal child-exited handle */
535 term = anjuta_shell_get_interface (ANJUTA_PLUGIN (plugin)->shell,
536 IAnjutaTerminal, NULL);
537 g_signal_handlers_disconnect_by_func (term, on_child_terminated, plugin);
538 plugin->child_exited_connection = 0;
540 /* Remove all child-exited source */
541 for (child = g_list_first (plugin->child); child != NULL; child = g_list_next (child))
543 if (!((RunProgramChild *)child->data)->use_signal)
545 g_source_remove (((RunProgramChild *)child->data)->source);
547 g_free (child->data);
549 g_list_free (plugin->child);
550 plugin->child = NULL;