From 951791f1d11a13db026339e8c015f356df65825e Mon Sep 17 00:00:00 2001 From: Philip Allison Date: Sun, 4 May 2008 03:31:28 +0100 Subject: [PATCH] Add session management support cteddy can now act as a session management client. This behaviour is disabled by "-nosm", which is also implied by "-nobg". The functionality can be disabled in its entirety by telling configure to "--disable-sm". Signed-off-by: Philip Allison --- configure.ac | 16 ++++ src/Makefile.am | 4 +- src/cteddy.cxx | 276 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 292 insertions(+), 4 deletions(-) diff --git a/configure.ac b/configure.ac index fc10f9d..46a05a7 100644 --- a/configure.ac +++ b/configure.ac @@ -42,6 +42,22 @@ dnl # cteddy uses GTK+ and Cairo PKG_PROG_PKG_CONFIG PKG_CHECK_MODULES([CTEDDY], [gtk+-2.0 >= 2.10 cairo]) +dnl # Optional X11 session management support +AC_ARG_ENABLE(sm, AS_HELP_STRING([--disable-sm], [Disable X11 session management support]), + [ + if test "x$enableval" = "xyes"; then + enable_sm="yes" + else + enable_sm="no" + fi + ], + enable_sm="yes" +) +if test "x$enable_sm" = "xyes"; then + PKG_CHECK_MODULES([SM], [sm]) + AC_DEFINE([__USE_SM], 1, [Define if X11 session management support is enabled]) +fi + dnl # All done. Spit out the result. AC_CONFIG_FILES([ Makefile diff --git a/src/Makefile.am b/src/Makefile.am index b0536be..ea4ad40 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -18,6 +18,6 @@ bin_PROGRAMS = cteddy cteddy_SOURCES = cteddy.cxx -cteddy_CXXFLAGS = $(CTEDDY_CFLAGS) $(AM_CXXFLAGS) -cteddy_LDADD = $(CTEDDY_LIBS) +cteddy_CXXFLAGS = $(CTEDDY_CFLAGS) $(SM_CFLAGS) $(AM_CXXFLAGS) +cteddy_LDADD = $(CTEDDY_LIBS) $(SM_LIBS) cteddy_CPPFLAGS = -DPKGDATADIR='"$(pkgdatadir)"' $(AM_CPPFLAGS) diff --git a/src/cteddy.cxx b/src/cteddy.cxx index 82bb02f..6203609 100644 --- a/src/cteddy.cxx +++ b/src/cteddy.cxx @@ -20,6 +20,11 @@ // Includes // +// Project headers +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + // Language headers #include #include @@ -29,12 +34,18 @@ // Library headers #include #include +#ifdef __USE_SM +# include +#endif // System headers #include #include #include #include +#ifdef __USE_SM +# include +#endif // @@ -51,9 +62,23 @@ GError* gerr = NULL; GdkPixbuf* pixbuf; gint width, height; -// Whether or not the key & mouse binding for quit are disabled +// Whether or not the key binding for quit is disabled bool noquit = false; +#ifdef __USE_SM +// Options structure - needed so that sm_save_yourself can know about the +// values of various program options which are usually only in main()'s scope +typedef struct smopts +{ + bool sticky; + bool noquit; + bool ontop; + char* image; + char* progname; + char* smcid; +}; +#endif + // // Implementation @@ -131,7 +156,7 @@ bool image_exists(std::string& image, const std::string& find_image) { // Set of image extensions to try appending when searching for an image - static char* extensions[16]; + static const char* extensions[16]; extensions[0] = ".png"; extensions[1] = ".gif"; extensions[2] = ".jpg"; extensions[3] = ".jpeg"; extensions[4] = ".pnm"; extensions[5] = ".xpm"; extensions[6] = ".tif"; extensions[7] = ".tiff"; @@ -154,6 +179,165 @@ bool image_exists(std::string& image, const std::string& find_image) return false; } +#ifdef __USE_SM +// Session management "SaveYourself" callback +void sm_save_yourself(SmcConn conn, SmPointer data, int type, Bool shutdown, int istyle, Bool fast) +{ + // Set the required properties for session management + smopts* opts = static_cast(data); + + // Program name + SmProp progname; + progname.name = SmProgram; + progname.type = SmARRAY8; + progname.num_vals = 1; + SmPropValue progname_value; + progname_value.length = strlen(opts->progname); + progname_value.value = opts->progname; + progname.vals = &progname_value; + + // User ID + SmProp uid; + uid.name = SmUserID; + uid.type = SmARRAY8; + uid.num_vals = 1; + SmPropValue uid_value; + const char* c_uidstr = g_get_user_name(); + char* uidstr = new char[strlen(c_uidstr) + 1]; + strcpy(uidstr, c_uidstr); + uid_value.length = strlen(c_uidstr); + uid_value.value = uidstr; + uid.vals = &uid_value; + + // Current working directory + SmProp cwd; + cwd.name = SmCurrentDirectory; + cwd.type = SmARRAY8; + cwd.num_vals = 1; + SmPropValue cwd_value; + const char* c_cwdstr = g_get_current_dir(); + char* cwdstr = new char[strlen(c_cwdstr) + 1]; + strcpy(cwdstr, c_cwdstr); + cwd_value.length = strlen(c_cwdstr); + cwd_value.value = cwdstr; + cwd.vals = &cwd_value; + + // Clone & restart commands + SmPropValue command[6]; + int numvals = 0; + command[numvals++] = progname_value; + SmProp restartcommand, clonecommand; + char sv[] = "-stick"; + char fv[] = "-float"; + char qv[] = "-noquit"; + + if (opts->sticky) + { + SmPropValue stickprop_value; + stickprop_value.value = sv; + stickprop_value.length = 6; + command[numvals++] = stickprop_value; + } + + if (opts->ontop) + { + SmPropValue floatprop_value; + floatprop_value.value = fv; + floatprop_value.length = 6; + command[numvals++] = floatprop_value; + } + + if (opts->noquit) + { + SmPropValue noquitprop_value; + noquitprop_value.value = qv; + noquitprop_value.length = 7; + command[numvals++] = noquitprop_value; + } + + SmPropValue image_value; + char* imagestr = new char[strlen(opts->image) + 3]; + imagestr[0] = '-'; imagestr[1] = 'F'; + strcpy(imagestr + 2, opts->image); + image_value.value = imagestr; + image_value.length = strlen(imagestr); + command[numvals++] = image_value; + + SmPropValue smcid_value; + std::string c_smcidarg("-smcid"); + c_smcidarg.append(opts->smcid); + char* smcidarg = new char[c_smcidarg.length() + 1]; + strcpy(smcidarg, c_smcidarg.c_str()); + smcid_value.value = smcidarg; + smcid_value.length = c_smcidarg.length(); + command[numvals++] = smcid_value; + + restartcommand.num_vals = numvals; + clonecommand.num_vals = numvals - 1; + restartcommand.vals = command; + clonecommand.vals = command; + + restartcommand.name = SmRestartCommand; + restartcommand.type = SmLISTofARRAY8; + clonecommand.name = SmCloneCommand; + clonecommand.type = SmLISTofARRAY8; + + SmProp* props[5]; + props[0] = &progname; + props[1] = &uid; + props[2] = &cwd; + props[3] = &restartcommand; + props[4] = &clonecommand; + SmcSetProperties(conn, 5, props); + + // Delete any non-const things we had to explicitly allocate + delete[] uidstr; + delete[] cwdstr; + delete[] smcidarg; + delete[] imagestr; + + // Send SaveYourselfDone. + SmcSaveYourselfDone(conn, True); +} + +// Session management "Save complete" callback +void sm_save_complete(SmcConn conn, SmPointer data) +{ +} + +// Session management "Die" callback +void sm_die(SmcConn conn, SmPointer data) +{ + // TODO - Have GTK quit event handler disconnect from SM if in use + SmcCloseConnection(conn, 0, NULL); + gtk_main_quit(); +} + +// Pump ICE messages when data comes in +gboolean sm_pump_ice(GIOChannel* chan, GIOCondition cond, gpointer data) +{ + IceProcessMessages(static_cast(data), NULL, NULL); + return true; +} + +// Called whenever an ICE connection is opened or closed +void ice_connection_watch(IceConn conn, IcePointer data, Bool opening, IcePointer* wdata) +{ + if (opening) + { + // When we get a new ICE connection, install a IO channel which will pump + // ICE messages when data is waiting + int icefd = IceConnectionNumber(conn); + GIOChannel* ioc = g_io_channel_unix_new(icefd); + g_io_add_watch(ioc, G_IO_IN, sm_pump_ice, conn); + *wdata = ioc; + } else { + // When an ICE connection closes, close the IO channel + g_io_channel_unref(static_cast(*wdata)); + } +} +#endif + // Entry point int main (int argc, char* argv[]) { @@ -168,10 +352,31 @@ int main (int argc, char* argv[]) // Whether or not to fork into the background bool background = true; +#ifdef __USE_SM + // Whether or not to use session management + bool sm = true; + + // Old session client ID, if being restored from a saved session + char* oldsmcid = NULL; + + // Set of callbacks + smopts opts; + SmcCallbacks smcallbacks; + smcallbacks.save_yourself.callback = sm_save_yourself; + smcallbacks.save_yourself.client_data = &opts; + smcallbacks.die.callback = sm_die; + smcallbacks.die.client_data = NULL; + smcallbacks.save_complete.callback = sm_save_complete; + smcallbacks.save_complete.client_data = NULL; +#endif + // Mimic the image loading behaviour of xteddy, to an extent. { // Start from the name of the executable itself std::string image(argv[0]); +#ifdef __USE_SM + char* smimage = argv[0]; +#endif // Take just the last component of the path std::string::size_type s = image.find_last_of('/'); @@ -184,7 +389,12 @@ int main (int argc, char* argv[]) { // Allow image specification with -F if (strncmp(argv[i], "-F", 2) == 0) + { image = argv[i] + 2; +#ifdef __USE_SM + smimage = argv[i] + 2; +#endif + } // Allow disablement of "q" keybinding else if (strncmp(argv[i], "-noquit", 7) == 0) noquit = true; @@ -195,9 +405,65 @@ int main (int argc, char* argv[]) else if (strncmp(argv[i], "-stick", 6) == 0) sticky = true; // Allow disablement of backgrounding + // Implies -nosm (see below) else if (strncmp(argv[i], "-nobg", 5) == 0) background = false; +#ifdef __USE_SM + // Allow disablement of session management + else if (strncmp(argv[i], "-nosm", 5) == 0) + sm = false; + // Read in old session client ID + else if (strncmp(argv[i], "-smcid", 6) == 0) + oldsmcid = argv[i] + 6; +#endif + else + std::cerr << "Unrecognised option: " << argv[i] << std::endl; } + +#ifdef __USE_SM + // If we're being restarted by the session manager, don't bother + // backgrounding, as we aren't attached to a terminal anyway + if (sm && oldsmcid != NULL) + background = false; + + // On the other hand, don't use session management if not forking + // into the background, if being started directly by the user + // rather than restarted as part of session. + // XXX This is how -nobg imples -nosm. + else if (!background && oldsmcid == NULL) + sm = false; + + if (sm) + { + // Open & monitor a GIOChannel each time there is a new ICE connection + IceAddConnectionWatch(ice_connection_watch, NULL); + + // Fill in SM opts so sm_save_yourself can set properties + opts.sticky = sticky; + opts.ontop = ontop; + opts.noquit = noquit; + opts.progname = argv[0]; + opts.image = smimage; + + // Try to connect to the session manager, displaying an error if we can't + char smerr[512]; + memset(smerr, '\0', 512); + if (SmcOpenConnection(NULL, NULL, SmProtoMajor, SmProtoMinor, + SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask, + &smcallbacks, oldsmcid, &(opts.smcid), 512, smerr) == NULL) + { + if (strlen(smerr) == 0) + std::cerr << "Cannot connect to session manager; unknown error." << std::endl; + else + std::cerr << "Cannot connect to session manager: " << smerr << std::endl; + + // Continue anyway, just without SM (for convenience of anyone without a session manager) + sm = false; + } + } + + // TODO - Use GTK dialogues to display errors, not stderr, as we may not be attached to a terminal +#endif // If the given path contains slashes, assume it is absolute or // relative to the current working directory and use it verbatim. @@ -301,6 +567,12 @@ int main (int argc, char* argv[]) // Have to do this before we can use window->window as a GdkWindow* gtk_widget_realize(window); +#ifdef __USE_SM + // Set the window's SM client ID + if (sm) + gdk_set_sm_client_id(opts.smcid); +#endif + { // Load in the heart-shaped cursor image and use it for our window's cursor GdkPixbuf* heart = gdk_pixbuf_new_from_file(PKGDATADIR "/heart.png", &gerr); -- 2.11.4.GIT