Revision: 4484 http://geany.svn.sourceforge.net/geany/?rev=4484&view=rev Author: ntrel Date: 2009-12-08 16:36:42 +0000 (Tue, 08 Dec 2009)
Log Message: ----------- Restart Geany and restore some state when logging in (patch by Eugene Arshinov, thanks).
Modified Paths: -------------- branches/sm/ChangeLog branches/sm/src/main.c
Modified: branches/sm/ChangeLog =================================================================== --- branches/sm/ChangeLog 2009-12-08 16:26:11 UTC (rev 4483) +++ branches/sm/ChangeLog 2009-12-08 16:36:42 UTC (rev 4484) @@ -7,6 +7,9 @@ src/document.h, src/main.c, src/main.h: Refactor quitting code into main_save() and main_finalize() (patch by Eugene Arshinov, thanks). + * src/main.c: + Restart Geany and restore some state when logging in (patch by + Eugene Arshinov, thanks).
2009-12-08 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
Modified: branches/sm/src/main.c =================================================================== --- branches/sm/src/main.c 2009-12-08 16:26:11 UTC (rev 4483) +++ branches/sm/src/main.c 2009-12-08 16:36:42 UTC (rev 4484) @@ -83,6 +83,11 @@ # include "vte.h" #endif
+#ifdef HAVE_LIBSM +# include <X11/SM/SMlib.h> +# include <X11/ICE/ICElib.h> +#endif + #ifndef N_ # define N_(String) (String) #endif @@ -115,6 +120,7 @@ static gboolean no_plugins = FALSE; #endif static gboolean dummy = FALSE; +static gchar * libsm_client_id = NULL;
/* in alphabetical order of short options */ static GOptionEntry entries[] = @@ -143,10 +149,311 @@ { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose_mode, N_("Be verbose"), NULL }, { "version", 'V', 0, G_OPTION_ARG_NONE, &show_version, N_("Show version and exit"), NULL }, { "dummy", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &dummy, NULL, NULL }, /* for +NNN line number arguments */ + { "libsm-client-id", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &libsm_client_id, NULL, NULL }, { NULL, 0, 0, 0, NULL, NULL, NULL } };
+#ifdef HAVE_LIBSM + /* + * As libSM is not available on Windows, + * it is safe enough to use POSIX-specific things here. + * + * Note that we have to support ICE (Inter-Client Exchange Protocol) + * in order to support XSMP (X Session Management Protocol). So both + * are initialized here. + * + * Usage: sm_init() is called from the main() function. + */ + + /* --- ICE --- */ + +static gboolean ice_process_messages(GIOChannel * source, GIOCondition condition, gpointer data) +{ + IceConn icecon = (IceConn)data; + IceProcessMessages(icecon, NULL, NULL); + return TRUE; +} + +static void ice_handle_connection(IceConn icecon, IcePointer client_data, + Bool opening, IcePointer *watch_data) +{ + guint input_id; + + if (opening) + { + /* + * Install a GLib IO Channel to process ICE messages coming from this connection + * in our GTK event loop. This is how session manager communicates to us. + */ + GIOChannel * channel = g_io_channel_unix_new(IceConnectionNumber(icecon)); + input_id = g_io_add_watch(channel, G_IO_IN | G_IO_HUP | G_IO_ERR, + ice_process_messages, icecon); + 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); + } +} + + /* --- libSM: data --- */ + +SmPropValue sm_program_val; +SmPropValue sm_client_id_arg_val; + + /* --- libSM: utility functions --- */ + +static void sm_set_command_props(SmcConn smcon) +{ + /* + * According to libSM documentation, client-ID should be saved as part of the + * SmRestartCommand so that the client will retain the same ID after it is + * restarted. We use '--libsm-client-id' command-line option for that. + */ + + /* + * FIXME: We have to specify '--no-session' command-line argument in commands. + * + * Reason: + * + * Currently all Geany instances try to save session. Consider the + * following use case. + * + * User creates two instances of geany, a "main" one (for example, using + * the main menu of his DE) and a "non-main" one typing 'geany --new-instance' + * in a terminal emulator. When this user logouts, session manager sends + * termination messages to Geany instances in unpredictable order. Geany + * session will be saved by the instance that was last to handle the + * message. Suppose that was the "non-main" Geany instance. + * + * When the user logins again, session manager restores Geany instances, + * again in unpredictable order. If we do not supply '--no-session' argument, + * the "main" instance will catch the session stored by the "non-main" + * one, which is not desired behaviour. + * + * Drawbacks of the '--no-session' solution: + * + * The "main" instance won't save Geany session as required. + * + * Possible fixes: + * + * * Disable saving of Geany session for "non-main" Geany instances. + * Sounds sensible and applicable. + * + * * Save session even when '--no-session' option is specified (i.e., + * consider this options only when reading Geany session). Sounds + * non-applicable as the described behaviour does not match + * even the option's name. + * + * * Create a separate option to be used when reading Geany session + * is needed and writing Geany session is forbidden. Sounds awkward. + */ + + const gint page_count = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook)); + gint page; + + /* + * Allocate space for `page_count+4' elements: + * * tree elements for program name, client-ID and '--no-session' option; + * * possibly one element for '--new-instance' option; + * * max `page_count' elements for file paths. + * Store the number of actually used elements in `arr_real_len'. + */ + GArray * arr = g_array_sized_new(FALSE, FALSE, sizeof(SmPropValue), page_count+4); + gint arr_real_len = 0; + SmPropValue * val; + SmProp * prop; + + ((SmPropValue *)arr->data)[0] = sm_program_val; + ((SmPropValue *)arr->data)[1] = sm_client_id_arg_val; + ((SmPropValue *)arr->data)[2].length = 2; /* length of "-s" */ + ((SmPropValue *)arr->data)[2].value = "-s"; + arr_real_len = 3; + +#ifdef HAVE_SOCKET + if (cl_options.new_instance) + { + val = ((SmPropValue *)arr->data) + arr_real_len++; + val->length = 2; /* length of "-i" */ + val->value = "-i"; + } +#endif + /* TODO: handle other command-line options */ + + for (page = 0; page < page_count; page++) + { + GeanyDocument * doc = document_get_from_page(page); + if (doc->real_path) + { + val = ((SmPropValue *)arr->data) + arr_real_len++; + val->length = strlen(doc->real_path); + val->value = doc->real_path; + } + } + + SmProp restart_command_prop = { + SmRestartCommand, + SmLISTofARRAY8, + arr_real_len, + (SmPropValue *)arr->data}; + prop = &restart_command_prop; + SmcSetProperties(smcon, 1, &prop); + + /* + * We should not specify client-ID in SmCloneCommand, + * so "remove" the corresponding element from `arr'. + */ + ((SmPropValue *)arr->data)[1] = sm_program_val; + SmProp clone_command_prop = { + SmCloneCommand, + SmLISTofARRAY8, + arr_real_len-1, + ((SmPropValue *)arr->data)+1}; + prop = &clone_command_prop; + SmcSetProperties(smcon, 1, &prop); + + g_array_free(arr, TRUE); +} + + /* --- libSM: callbacks --- */ + +static void sm_interact_callback(SmcConn smcon, SmPointer client_data) +{ + gboolean interactive = (gboolean)client_data; + gboolean cancelled = !main_save(interactive); + + sm_set_command_props(smcon); + SmcInteractDone(smcon, cancelled); +} + +static void sm_die_callback(SmcConn smcon, SmPointer client_data) +{ + SmcCloseConnection(smcon, 0, NULL); + main_finalize(); +} + +static void sm_save_complete_callback(SmcConn smcon, SmPointer client_data) +{ +} + +static void sm_shutdown_cancelled_callback(SmcConn smcon, SmPointer client_data) +{ + SmcSaveYourselfDone(smcon, TRUE); +} + +static void sm_save_yourself_callback (SmcConn smcon, SmPointer client_data, + int save_style, gboolean shutdown, int interact_style, gboolean fast) +{ + + if ((save_style == SmSaveGlobal || save_style == SmSaveBoth) && document_any_unsaved()) + { + if (!SmcInteractRequest(smcon, SmDialogNormal, sm_interact_callback, + (gpointer)(interact_style == SmInteractStyleAny))) + SmcSaveYourselfDone(smcon, FALSE); + + return; + } + + /* + * TODO: libSM documentation says that when the system is being shutting down + * (`shutdown' argument is True), we should disable user interaction here + * until we get "Die" or "Shutdown" message. + */ + + sm_set_command_props(smcon); + SmcSaveYourselfDone(smcon, TRUE); +} + + /* --- libSM: sm_init() --- */ + +static void sm_init(char * argv0) +{ + if (!g_getenv("SESSION_MANAGER")) + return; + + IceAddConnectionWatch(ice_handle_connection, NULL); + + SmcCallbacks callbacks = { + {sm_save_yourself_callback, NULL}, + {sm_die_callback, NULL}, + {sm_save_complete_callback, NULL}, + {sm_shutdown_cancelled_callback, NULL}}; + + gchar * client_id; + gchar err[256] = ""; + SmcConn smcon = (gpointer)SmcOpenConnection(NULL, NULL, + SmProtoMajor, SmProtoMinor, + SmcSaveYourselfProcMask | + SmcDieProcMask | + SmcSaveCompleteProcMask | + SmcShutdownCancelledProcMask, + &callbacks, + libsm_client_id, &client_id, + 256, err); + + if (!smcon) + { + g_warning("While connecting to session manager:\n%s.", err); + return; + } + + + const gchar * username = g_get_user_name(); + gchar * curdir = g_get_current_dir(); + + SmPropValue userid_val = { + (username ? strlen(username) : 0), + username ? (char *)username : ""}; + SmPropValue curdir_val = { + strlen(curdir), + curdir}; + + SmProp program_prop = {SmProgram, SmARRAY8, 1, &sm_program_val}; + SmProp userid_prop = {SmUserID, SmARRAY8, 1, &userid_val}; + SmProp curdir_prop = {SmCurrentDirectory, SmARRAY8, 1, &curdir_val}; + + SmProp * proplist[3] = { + &program_prop, + &userid_prop, + &curdir_prop + }; + SmcSetProperties(smcon, 3, proplist); + + + /* + * Required SmCloneCommand and SmRestartCommand properties are set later + * as their values may change in runtime. See also sm_set_command_props(). + */ + + gchar * client_id_arg = g_strconcat("--libsm-client-id=", client_id, NULL); /* never freed */ + + /* + * If this instance is run with path (e.g., "./geany", "src/geany", + * "/usr/local/bin/geany"), specify absolute path in restart command. + * Relative paths may not work even if we set SmCurrentDirectory prop. + * + * Otherwise leave argv[0] as is (geany will be found + * using PATH environment variable). + */ + gchar * executable_path = (strchr(argv0, G_DIR_SEPARATOR) == NULL) ? argv0 : + g_build_filename(curdir, argv0, NULL); /* never freed */ + + sm_program_val.length = strlen(executable_path); + sm_program_val.value = executable_path; + sm_client_id_arg_val.length = strlen(client_id_arg); + sm_client_id_arg_val.value = client_id_arg; + + + free(client_id); + g_free(curdir); +} +#endif + + static void setup_window_position(void) { /* interprets the saved window geometry */ @@ -948,6 +1255,10 @@ } #endif
+#ifdef HAVE_LIBSM + sm_init(argv[0]); +#endif + geany_debug("Geany %s, GTK+ %u.%u.%u, GLib %u.%u.%u", main_get_version_string(), gtk_major_version, gtk_minor_version, gtk_micro_version,
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.