diff --git a/acconfig.h b/acconfig.h index 7e0cc1656..5623e5743 100644 --- a/acconfig.h +++ b/acconfig.h @@ -9,3 +9,4 @@ #undef GETTEXT_PACKAGE #undef HAVE_SHAPE_EXT #undef HAVE_XFT +#undef HAVE_SM diff --git a/configure.in b/configure.in index b4631cc98..7150d9236 100644 --- a/configure.in +++ b/configure.in @@ -43,6 +43,25 @@ AM_GNU_GETTEXT ## here we get the flags we'll actually use PKG_CHECK_MODULES(METACITY, gtk+-2.0 >= 1.3.6) +CFLAGS="$METACITY_CFLAGS $CFLAGS" + +found_sm=false +case "$METACITY_LIBS" in + *-lSM*) + found_sm=true + ;; + *) + AC_CHECK_LIB(SM, SmcSaveYourselfDone, + AC_CHECK_HEADERS(X11/SM/SMlib.h, + METACITY_LIBS="-lSM -lICE $METACITY_LIBS" found_sm=true), + , $METACITY_LIBS) + ;; +esac + +if test "$found_sm" = "true"; then + AC_DEFINE(HAVE_SM) +fi + # Check for shaped window extension AC_CHECK_LIB(Xext, XShapeCombineMask, AC_DEFINE(HAVE_SHAPE_EXT),,$METACITY_LIBS) diff --git a/src/main.c b/src/main.c index 6a5577af1..2793e7e61 100644 --- a/src/main.c +++ b/src/main.c @@ -24,6 +24,7 @@ #include "display.h" #include "errors.h" #include "ui.h" +#include "session.h" #include @@ -37,13 +38,24 @@ static MetaExitCode meta_exit_code = META_EXIT_SUCCESS; static GMainLoop *meta_main_loop = NULL; +static void +usage (void) +{ + g_print ("metacity [--disable-sm] [--sm-client-id=ID] [--display=DISPLAY]\n"); + exit (0); +} + int main (int argc, char **argv) { struct sigaction act; sigset_t empty_mask; char *display_name; - + int i; + const char *client_id; + gboolean disable_sm; + const char *prev_arg; + sigemptyset (&empty_mask); act.sa_handler = SIG_IGN; act.sa_mask = empty_mask; @@ -52,34 +64,125 @@ main (int argc, char **argv) g_set_prgname (PACKAGE); - meta_main_loop = g_main_loop_new (NULL, FALSE); - meta_set_verbose (TRUE); meta_set_debugging (TRUE); meta_set_syncing (g_getenv ("METACITY_SYNC") != NULL); - if (g_getenv ("METACITY_DISPLAY")) + /* Parse options lamely */ + + display_name = NULL; + client_id = NULL; + disable_sm = FALSE; + prev_arg = NULL; + i = 1; + while (i < argc) + { + const char *arg = argv[i]; + + if (strcmp (arg, "--help") == 0 || + strcmp (arg, "-h") == 0 || + strcmp (arg, "-?") == 0) + usage (); + else if (strcmp (arg, "--sm-disable") == 0) + disable_sm = TRUE; + else if (strstr (arg, "--display=") == arg) + { + const char *disp; + + if (display_name != NULL) + meta_fatal ("Can't specify display twice\n"); + + disp = strchr (arg, '='); + ++disp; + + display_name = + g_strconcat ("DISPLAY=", disp, NULL); + } + else if (prev_arg && + strcmp (prev_arg, "--display") == 0) + { + if (display_name != NULL) + meta_fatal ("Can't specify display twice\n"); + + display_name = g_strconcat ("DISPLAY=", arg, NULL); + } + else if (strcmp (arg, "--display") == 0) + ; /* wait for next arg */ + else if (strstr (arg, "--sm-client-id=") == arg) + { + const char *id; + + if (client_id) + meta_fatal ("Can't specify client ID twice\n"); + + id = strchr (arg, '='); + ++id; + + client_id = g_strdup (id); + } + else if (prev_arg && + strcmp (prev_arg, "--sm-client-id") == 0) + { + if (client_id) + meta_fatal ("Can't specify client ID twice\n"); + + client_id = g_strdup (arg); + } + else if (strcmp (arg, "--sm-client-id") == 0) + ; /* wait for next arg */ + else + usage (); + + prev_arg = arg; + + ++i; + } + + meta_main_loop = g_main_loop_new (NULL, FALSE); + + if (display_name == NULL && + g_getenv ("METACITY_DISPLAY")) { meta_verbose ("Using METACITY_DISPLAY %s\n", g_getenv ("METACITY_DISPLAY")); display_name = g_strconcat ("DISPLAY=", g_getenv ("METACITY_DISPLAY"), NULL); + } + + if (display_name) + { putenv (display_name); /* DO NOT FREE display_name, putenv() sucks */ } - - + /* gtk_init() below overrides this, so it can be removed */ meta_errors_init (); g_type_init (0); /* grumble */ - meta_ui_init (&argc, &argv); + + if (!disable_sm) + meta_session_init (client_id); /* client_id == NULL is fine */ + + meta_ui_init (&argc, &argv); if (!meta_display_open (NULL)) meta_exit (META_EXIT_ERROR); g_main_run (meta_main_loop); + { + GSList *displays; + GSList *tmp; + + displays = meta_displays_list (); + tmp = displays; + while (tmp != NULL) + { + meta_display_close (tmp->data); + tmp = tmp->next; + } + } + return meta_exit_code; } @@ -93,8 +196,9 @@ void meta_quit (MetaExitCode code) { meta_exit_code = code; - - g_main_quit (meta_main_loop); + + if (g_main_is_running (meta_main_loop)) + g_main_quit (meta_main_loop); } void diff --git a/src/session.c b/src/session.c index 6986d5f23..414b59881 100644 --- a/src/session.c +++ b/src/session.c @@ -1,7 +1,8 @@ /* Metacity Session Management */ /* - * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2001 Havoc Pennington (some code in here from + * libgnomeui, (C) Tom Tromey, Carsten Schaar) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -16,12 +17,461 @@ * 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. - */ + * 02111-1307, USA. */ #include "session.h" +#ifndef HAVE_SM +void +meta_session_init (const char *previous_id) +{ + meta_verbose ("Compiled without session management support\n"); +} +#else /* HAVE_SM */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "main.h" +#include "util.h" + +static void ice_io_error_handler (IceConn connection); + +static void new_ice_connection (IceConn connection, IcePointer client_data, + Bool opening, IcePointer *watch_data); + +/* This is called when data is available on an ICE connection. */ +static gboolean +process_ice_messages (GIOChannel *channel, + GIOCondition condition, + gpointer client_data) +{ + IceConn connection = (IceConn) client_data; + IceProcessMessagesStatus status; + + status = IceProcessMessages (connection, NULL, NULL); + + if (status == IceProcessMessagesIOError) + { +#if 0 + IcePointer context = IceGetConnectionContext (connection); +#endif + + /* We were disconnected */ + IceSetShutdownNegotiation (connection, False); + IceCloseConnection (connection); + } + + return TRUE; +} + +/* This is called when a new ICE connection is made. It arranges for + the ICE connection to be handled via the event loop. */ +static void +new_ice_connection (IceConn connection, IcePointer client_data, Bool opening, + IcePointer *watch_data) +{ + guint input_id; + + if (opening) + { + /* Make sure we don't pass on these file descriptors to any + * exec'ed children + */ + GIOChannel *channel; + + fcntl (IceConnectionNumber(connection),F_SETFD, + fcntl(IceConnectionNumber(connection),F_GETFD,0) | FD_CLOEXEC); + + channel = g_io_channel_unix_new (IceConnectionNumber (connection)); + + input_id = g_io_add_watch (channel, + G_IO_IN | G_IO_ERR, + process_ice_messages, + connection); + + g_io_channel_unref (channel); + + *watch_data = (IcePointer) GUINT_TO_POINTER (input_id); + } + else + { + input_id = GPOINTER_TO_UINT ((gpointer) *watch_data); + + g_source_remove (input_id); + } +} + +static IceIOErrorHandler gnome_ice_installed_handler; + +/* We call any handler installed before (or after) gnome_ice_init but + avoid calling the default libICE handler which does an exit() */ +static void +ice_io_error_handler (IceConn connection) +{ + if (gnome_ice_installed_handler) + (*gnome_ice_installed_handler) (connection); +} + +static void +ice_init (void) +{ + static gboolean ice_initted = FALSE; + + if (! ice_initted) + { + IceIOErrorHandler default_handler; + + gnome_ice_installed_handler = IceSetIOErrorHandler (NULL); + default_handler = IceSetIOErrorHandler (ice_io_error_handler); + + if (gnome_ice_installed_handler == default_handler) + gnome_ice_installed_handler = NULL; + + IceAddConnectionWatch (new_ice_connection, NULL); + + ice_initted = TRUE; + } +} + +typedef enum +{ + STATE_DISCONNECTED, + STATE_IDLE, + STATE_SAVING_PHASE_1, + STATE_WAITING_FOR_PHASE_2, + STATE_SAVING_PHASE_2, + STATE_FROZEN, + STATE_REGISTERING +} ClientState; + +static void save_phase_2_callback (SmcConn smc_conn, + SmPointer client_data); +static void interact_callback (SmcConn smc_conn, + SmPointer client_data); +static void shutdown_cancelled_callback (SmcConn smc_conn, + SmPointer client_data); +static void save_complete_callback (SmcConn smc_conn, + SmPointer client_data); +static void die_callback (SmcConn smc_conn, + SmPointer client_data); +static void save_yourself_callback (SmcConn smc_conn, + SmPointer client_data, + int save_style, + Bool shutdown, + int interact_style, + Bool fast); +static void set_clone_restart_commands (void); + +static gchar *client_id = NULL; +static gpointer session_connection = NULL; +static ClientState current_state = STATE_DISCONNECTED; + +void +meta_session_init (const char *previous_id) +{ + /* Some code here from twm */ + char buf[256]; + unsigned long mask; + SmcCallbacks callbacks; + + meta_verbose ("Initializing session with session ID '%s'\n", + previous_id ? previous_id : "(none)"); + + ice_init (); + + mask = SmcSaveYourselfProcMask | SmcDieProcMask | + SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask; + + callbacks.save_yourself.callback = save_yourself_callback; + callbacks.save_yourself.client_data = NULL; + + callbacks.die.callback = die_callback; + callbacks.die.client_data = NULL; + + callbacks.save_complete.callback = save_complete_callback; + callbacks.save_complete.client_data = NULL; + + callbacks.shutdown_cancelled.callback = shutdown_cancelled_callback; + callbacks.shutdown_cancelled.client_data = NULL; + + session_connection = + SmcOpenConnection (NULL, /* use SESSION_MANAGER env */ + NULL, /* means use existing ICE connection */ + SmProtoMajor, + SmProtoMinor, + mask, + &callbacks, + previous_id, + &client_id, + 255, buf); + + if (session_connection == NULL) + { + meta_warning ("Failed to open connection to session manager: %s\n", buf); + return; + } + else + meta_verbose ("Obtained session ID '%s'\n", client_id); + + if (previous_id && strcmp (previous_id, client_id) == 0) + current_state = STATE_IDLE; + else + current_state = STATE_REGISTERING; + + { + SmProp prop1, prop2, prop3, prop4, prop5, *props[5]; + SmPropValue prop1val, prop2val, prop3val, prop4val, prop5val; + char pid[32]; + char hint = SmRestartIfRunning; + + prop1.name = SmProgram; + prop1.type = SmARRAY8; + prop1.num_vals = 1; + prop1.vals = &prop1val; + prop1val.value = "metacity"; + prop1val.length = strlen ("metacity"); + + /* twm sets getuid() for this, but the SM spec plainly + * says pw_name, twm is on crack + */ + prop2.name = SmUserID; + prop2.type = SmARRAY8; + prop2.num_vals = 1; + prop2.vals = &prop2val; + prop2val.value = g_get_user_name (); + prop2val.length = strlen (prop2val.value); + + prop3.name = SmRestartStyleHint; + prop3.type = SmCARD8; + prop3.num_vals = 1; + prop3.vals = &prop3val; + prop3val.value = &hint; + prop3val.length = 1; + + sprintf (pid, "%d", getpid ()); + prop4.name = SmProcessID; + prop4.type = SmARRAY8; + prop4.num_vals = 1; + prop4.vals = &prop4val; + prop4val.value = pid; + prop4val.length = strlen (prop4val.value); + + /* Always start in home directory */ + prop5.name = SmCurrentDirectory; + prop5.type = SmARRAY8; + prop5.num_vals = 1; + prop5.vals = &prop5val; + prop5val.value = g_get_home_dir (); + prop5val.length = strlen (prop5val.value); + + props[0] = &prop1; + props[1] = &prop2; + props[2] = &prop3; + props[3] = &prop4; + props[4] = &prop5; + + SmcSetProperties (session_connection, 5, props); + } + + set_clone_restart_commands (); +} + +static void +disconnect (void) +{ + SmcCloseConnection (session_connection, 0, NULL); + session_connection = NULL; + current_state = STATE_DISCONNECTED; +} + +static void +save_yourself_possibly_done (gboolean shutdown, + gboolean successful) +{ + if (current_state == STATE_SAVING_PHASE_1) + { + Status status; + + status = SmcRequestSaveYourselfPhase2 (session_connection, + save_phase_2_callback, + GINT_TO_POINTER (shutdown)); + + if (status) + current_state = STATE_WAITING_FOR_PHASE_2; + } + + if (current_state == STATE_SAVING_PHASE_1 || + current_state == STATE_SAVING_PHASE_2) + { + SmcSaveYourselfDone (session_connection, + successful); + + if (shutdown) + current_state = STATE_FROZEN; + else + current_state = STATE_IDLE; + } +} +static void +save_phase_2_callback (SmcConn smc_conn, SmPointer client_data) +{ + gboolean shutdown; + + shutdown = GPOINTER_TO_INT (client_data); + + current_state = STATE_SAVING_PHASE_2; + + save_yourself_possibly_done (shutdown, TRUE); +} + +static void +save_yourself_callback (SmcConn smc_conn, + SmPointer client_data, + int save_style, + Bool shutdown, + int interact_style, + Bool fast) +{ + gboolean successful; + + successful = TRUE; + + /* The first SaveYourself after registering for the first time + * is a special case (SM specs 7.2). + * + * This SaveYourself seems to be included in the protocol to + * ask the client to specify its initial SmProperties since + * there is little point saving a copy of the initial state. + * + * A bug in xsm means that it does not send us a SaveComplete + * in response to this initial SaveYourself. Therefore, we + * must not set a grab because it would never be released. + * Indeed, even telling the app that this SaveYourself has + * arrived is hazardous as the app may take its own steps + * to freeze its WM state while waiting for the SaveComplete. + * + * Fortunately, we have already set the SmProperties during + * gnome_client_connect so there is little lost in simply + * returning immediately. + * + * Apps which really want to save their initial states can + * do so safely using gnome_client_save_yourself_request. + */ + + if (current_state == STATE_REGISTERING) + { + current_state = STATE_IDLE; + + /* Double check that this is a section 7.2 SaveYourself: */ + + if (save_style == SmSaveLocal && + interact_style == SmInteractStyleNone && + !shutdown && !fast) + { + /* The protocol requires this even if xsm ignores it. */ + SmcSaveYourselfDone (session_connection, successful); + return; + } + } + + current_state = STATE_SAVING_PHASE_1; + + set_clone_restart_commands (); + + save_yourself_possibly_done (shutdown, successful); +} +static void +die_callback (SmcConn smc_conn, SmPointer client_data) +{ + meta_verbose ("Exiting at request of session manager\n"); + disconnect (); + meta_quit (META_EXIT_SUCCESS); +} + +static void +save_complete_callback (SmcConn smc_conn, SmPointer client_data) +{ + /* nothing */ +} + +static void +shutdown_cancelled_callback (SmcConn smc_conn, SmPointer client_data) +{ + /* nothing */ +} + +static void +interact_callback (SmcConn smc_conn, SmPointer client_data) +{ + /* nothing */ +} + +static void +set_clone_restart_commands (void) +{ + char *restartv[10]; + char *clonev[10]; + int i; + SmProp prop1, prop2, *props[2]; + + /* Restart (use same client ID) */ + + prop1.name = SmRestartCommand; + prop1.type = SmLISTofARRAY8; + + i = 0; + restartv[i] = "metacity"; + ++i; + restartv[i] = "--sm-client-id"; + ++i; + restartv[i] = client_id; + ++i; + restartv[i] = NULL; + + prop1.vals = g_new (SmPropValue, i); + i = 0; + while (restartv[i]) + { + prop1.vals[i].value = restartv[i]; + prop1.vals[i].length = strlen (restartv[i]); + ++i; + } + prop1.num_vals = i; + + /* Clone (no client ID) */ + + i = 0; + clonev[i] = "metacity"; + ++i; + clonev[i] = NULL; + + prop2.name = SmCloneCommand; + prop2.type = SmLISTofARRAY8; + + prop2.vals = g_new (SmPropValue, i); + i = 0; + while (clonev[i]) + { + prop2.vals[i].value = clonev[i]; + prop2.vals[i].length = strlen (clonev[i]); + ++i; + } + prop2.num_vals = i; + + props[0] = &prop1; + props[1] = &prop2; + + SmcSetProperties (session_connection, 2, props); +} + +#endif /* HAVE_SM */