/*
roxterm - VTE/GTK terminal emulator with tabs
Copyright (C) 2004-2015 Tony Houghton <h@realh.co.uk>
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 2 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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "defns.h"
#include <errno.h>
#include <stdio.h>
#include <locale.h>
#include <unistd.h>
#include <gdk/gdk.h>
#include "dlg.h"
#include "globalopts.h"
#include "multitab.h"
#include "roxterm.h"
#include "rtdbus.h"
#if ENABLE_SM
#include "session.h"
#endif
#include "session-file.h"
#include "x11support.h"
#define ROXTERM_DBUS_NAME RTDBUS_NAME ".term"
#define ROXTERM_DBUS_OBJECT_PATH RTDBUS_OBJECT_PATH "/term"
#define ROXTERM_DBUS_INTERFACE RTDBUS_INTERFACE
#define ROXTERM_DBUS_METHOD_NAME "NewTerminal"
extern char **environ;
static DBusMessage *create_dbus_message(int argc, char **argv,
const char *display)
{
DBusMessage *message;
int n;
message = rtdbus_method_new(ROXTERM_DBUS_NAME, ROXTERM_DBUS_OBJECT_PATH,
ROXTERM_DBUS_INTERFACE,
ROXTERM_DBUS_METHOD_NAME, DBUS_TYPE_INVALID);
for (n = 0; environ[n]; ++n);
message = rtdbus_append_args(message,
DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &environ, n,
DBUS_TYPE_INVALID);
if (!message)
return NULL;
for (n = 0; message && n < argc; ++n)
{
char *arg;
char *tmp = NULL;
if (!strcmp(argv[n], "--tab"))
{
if (global_options_workspace == WORKSPACE_UNDEFINED)
{
guint32 ws;
if (x11support_get_current_desktop(
gdk_get_default_root_window(), &ws))
{
global_options_workspace = ws;
}
else
{
g_warning(_("Unable to read current workspace"));
}
}
tmp = g_strdup_printf("--tab=%u", global_options_workspace);
arg = tmp;
}
else
{
arg = argv[n];
}
message = rtdbus_append_args(message,
DBUS_TYPE_STRING, &arg,
DBUS_TYPE_INVALID);
if (tmp)
g_free(tmp);
}
if (display)
{
message = rtdbus_append_args(message,
DBUS_TYPE_STRING, &display,
DBUS_TYPE_INVALID);
}
return message;
}
/* Returns 0 for OK, -1 if DBUS fails, +1 if reply is an error */
static int run_via_dbus(DBusMessage *message)
{
int result = 0;
if (!message)
return -1;
/* New roxterm command may have been run in a different directory
* from original instance */
if (!result && !global_options_directory)
{
char *cwd = GET_CURRENT_DIR();
const char *d = "-d";
message = rtdbus_append_args(message,
DBUS_TYPE_STRING, &d,
DBUS_TYPE_STRING, &cwd,
DBUS_TYPE_INVALID);
if (!message)
result = -1;
FREE_CURRENT_DIR(cwd);
}
if (!result && global_options_commandv)
{
int n;
const char *e = "-e";
message = rtdbus_append_args(message,
DBUS_TYPE_STRING, &e, DBUS_TYPE_INVALID);
if (!message)
return -1;
for (n = 0; global_options_commandv[n]; ++n)
{
message = rtdbus_append_args(message,
DBUS_TYPE_STRING, &global_options_commandv[n],
DBUS_TYPE_INVALID);
if (!message)
return -1;
}
}
if (!result)
{
/* The reply to the NewTerminal message can't include whether it
* launched successfully because we don't know that until we fork
* the command after an idle, and D-BUS won't let us defer the reply to
* after an idle.
*
* Removing the reply prevents the error message reported in
* <http://p.sf.net/roxterm/gi5kdI9QrPZ>
* at the expense of regressing
* <http://p.sf.net/roxterm/EfNoUkwW>
*/
result = rtdbus_send_message(message) ? 0 : -1;
}
return result;
}
static DBusHandlerResult new_term_listener(DBusConnection *connection,
DBusMessage *message, void *user_data)
{
DBusError derror;
char **env;
char **argv;
int argc;
const char *display = NULL;
DBusMessageIter iter;
(void) connection;
(void) user_data;
dbus_error_init(&derror);
if (!dbus_message_is_method_call(message, ROXTERM_DBUS_INTERFACE,
ROXTERM_DBUS_METHOD_NAME))
{
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
dbus_message_iter_init(message, &iter);
env = rtdbus_get_message_arg_string_array(&iter);
argv = rtdbus_get_message_args_as_strings(&iter);
for (argc = 0; argv && argv[argc]; ++argc)
{
if (g_str_has_prefix(argv[argc], "--display="))
display = argv[argc] + 10;
else if (!strcmp(argv[argc], "--display") && argv[argc + 1])
display = argv[argc + 1];
}
if (argc)
{
global_options_preparse_argv_for_execute(&argc, argv, TRUE);
global_options_init(&argc, &argv, FALSE);
}
roxterm_launch(display, env);
g_strfreev(argv);
g_strfreev(env);
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
gboolean listen_for_new_term(void)
{
return rtdbus_start_service(ROXTERM_DBUS_NAME,
ROXTERM_DBUS_OBJECT_PATH,
new_term_listener, global_options_lookup_int("replace") > 0);
}
static char *abs_bin(const char *filename)
{
if (g_path_is_absolute(filename))
{
return g_strdup(filename);
}
return g_build_filename(global_options_bindir, "roxterm", NULL);
}
static int wait_for_child(int pipe_r)
{
char result;
if (read(pipe_r, &result, 1) != 1)
{
g_error(_("Parent failed to read signal from child for --fork: %s"),
strerror(errno));
result = 1;
}
close(pipe_r);
return (int) result;
}
/* Only single-byte exit codes are supported */
static int roxterm_exit(int pipe_w, char exit_code)
{
if (global_options_fork)
{
if (write(pipe_w, &exit_code, 1) != 1)
{
g_error(_("Child failed to signal parent for --fork: %s"),
strerror(errno));
exit_code = 1;
}
close(pipe_w);
}
return exit_code;
}
static gboolean roxterm_idle_ok(gpointer ppipe)
{
roxterm_exit(*(int *) ppipe, 0);
return FALSE;
}
static DBusHandlerResult
roxterm_dbus_service_ready(DBusConnection * connection,
DBusMessage * message, void *user_data)
{
const char *service_name = NULL;
DBusError derror;
(void) connection;
dbus_error_init(&derror);
dbus_message_get_args(message, &derror,
DBUS_TYPE_STRING, &service_name,
DBUS_TYPE_INVALID);
if (!service_name)
{
rtdbus_warn(&derror, "NULL service name in service_ready handler");
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
else if (!strcmp(service_name, ROXTERM_DBUS_NAME))
{
//g_debug("We acquired service name '%s'", service_name);
//g_debug("dbus service ready, sending OK down pipe
roxterm_idle_ok(user_data);
dbus_connection_remove_filter(connection, roxterm_dbus_service_ready,
user_data);
}
return DBUS_HANDLER_RESULT_HANDLED;
}
int main(int argc, char **argv)
{
gboolean preparse_ok;
DBusMessage *message = NULL;
char *display = NULL;
const char *dpy_name = NULL;
int n;
gboolean launched = FALSE;
gboolean dbus_ok;
pid_t fork_result = 0;
static int fork_pipe[2] = { -1, -1};
gboolean defer_pipe = FALSE;
const char *session_leafname;
char *session_filename;
global_options_init_appdir(argc, argv);
global_options_init_bindir(argv[0]);
if (global_options_fork)
{
if (pipe(fork_pipe))
{
g_error(_("Unable to open pipe to implement --fork: %s"),
strerror(errno));
}
if ((fork_result = fork()) > 0)
{
/* parent doesn't write */
close(fork_pipe[1]);
return wait_for_child(fork_pipe[0]);
}
else if (!fork_result)
{
/* child doesn't read */
close(fork_pipe[0]);
}
else
{
g_error(_("Unable to fork to implement --fork: %s"),
strerror(errno));
}
}
#if ENABLE_SM
if (!global_options_disable_sm)
{
session_argc = argc;
session_argv = g_new(char *, argc + 1);
if (global_options_appdir)
{
session_argv[0] = g_build_filename(global_options_appdir,
"AppRun", NULL);
if (!g_file_test(session_argv[0], G_FILE_TEST_IS_EXECUTABLE))
{
g_free(session_argv[0]);
session_argv[0] = abs_bin(argv[0]);
}
}
else
{
session_argv[0] = abs_bin(argv[0]);
}
for (n = 1; n < argc; ++n)
{
session_argv[n] = g_strdup(argv[n]);
}
/* FIXME: NULL terminator (and argc + 1 above) only necessary if
* debugging
*/
session_argv[n] = NULL;
}
#endif
g_set_application_name(PACKAGE);
preparse_ok = global_options_preparse_argv_for_execute(&argc, argv, FALSE);
#if ENABLE_NLS
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, global_options_appdir ?
g_strdup_printf("%s/build/locale", global_options_appdir) :
LOCALEDIR);
bind_textdomain_codeset(PACKAGE, "UTF-8");
textdomain(PACKAGE);
#endif
/* Get --display option before GTK removes it */
for (n = 1; n < argc; ++n)
{
if (strlen(argv[n]) > 10 && g_str_has_prefix(argv[n], "--display="))
{
display = g_strdup(argv[n]);
dpy_name = display + 10;
break;
}
else if (n < argc - 1 && !strcmp(argv[n], "--display"))
{
display = g_strdup_printf("--display=%s", argv[n + 1]);
dpy_name = display + 10;
break;
}
}
gtk_init(&argc, &argv);
if (!preparse_ok)
{
/* Only one possible reason for failure */
dlg_critical(NULL, _("Missing command after -e/--execute option"));
return 1;
}
if (!display)
{
dpy_name = gdk_display_get_name(gdk_display_get_default());
display = g_strdup_printf("--display=%s", dpy_name);
}
/* Have to create message with args from argv before parsing them */
dbus_ok = rtdbus_ok = rtdbus_init();
if (dbus_ok)
{
if (global_options_fork)
{
rtdbus_add_rule_and_filter("type='signal',"
"interface='org.freedesktop.DBus',"
"member='NameAcquired',"
"path='/org/freedesktop/DBus',"
"sender='org.freedesktop.DBus'",
roxterm_dbus_service_ready,
&fork_pipe[1]);
defer_pipe = TRUE;
}
#if ENABLE_SM
if (!global_options_user_session_id && (global_options_disable_sm ||
(!global_options_restart_session_id &&
!global_options_clone_session_id)))
#else
if (!global_options_user_session_id)
#endif
{
message = create_dbus_message(argc, argv, display);
}
}
g_free(display);
global_options_init(&argc, &argv, TRUE);
global_options_apply_dark_theme();
if (dbus_ok)
{
dbus_ok = listen_for_new_term();
/* Only TRUE if another roxterm is providing the service */
}
dbus_ok = global_options_lookup_int("separate") <= 0 && dbus_ok
&& !global_options_user_session_id
#if ENABLE_SM
&& (global_options_disable_sm ||
(!global_options_restart_session_id
&& !global_options_clone_session_id))
#endif
;
if (dbus_ok)
{
switch (run_via_dbus(message))
{
case 0:
return roxterm_exit(fork_pipe[1], 0);
case 1:
return roxterm_exit(fork_pipe[1], 1);
case -1:
/* DBUS failure, run as if --separate */
break;
}
}
else if (message)
{
dbus_message_unref(message);
}
roxterm_init();
multi_win_set_role_prefix("roxterm");
#if ENABLE_SM
if (!global_options_disable_sm)
{
if (global_options_restart_session_id)
launched = session_load(global_options_restart_session_id);
if (!launched && global_options_clone_session_id)
launched = session_load(global_options_clone_session_id);
}
#endif
session_leafname = global_options_user_session_id ?
global_options_user_session_id : "Default";
session_filename = session_get_filename(session_leafname,
"UserSessions", FALSE);
if (g_file_test(session_filename, G_FILE_TEST_IS_REGULAR))
{
launched = load_session_from_file(session_filename, session_leafname);
}
else if (global_options_user_session_id)
{
g_critical("Session file '%s' not found", session_filename);
}
if (!launched)
{
roxterm_launch(dpy_name, environ);
}
#if ENABLE_SM
if (!global_options_disable_sm)
{
session_init(global_options_restart_session_id);
}
#endif
/* Usually this should be deferred to NameAcquired signal handler */
if (!defer_pipe)
{
g_idle_add(roxterm_idle_ok, &fork_pipe[1]);
}
SLOG("Entering main loop with %d windows", g_list_length(multi_win_all));
gtk_main();
SLOG("Exiting normally");
return 0;
}
/* vi:set sw=4 ts=4 et cindent cino= */