Subject: Display geometry change hook

From: David De La Harpe Golden <[email-masked]>
Date: Thu, 25 Aug 2011 06:16:58 +0100

Severity: wishlist

Raised by Edward O'Connor in emacs-devel thread [1], filing in 
bugtracker so it doesn't get lost over the feature freeze period.

Emacs doesn't appear to currently provide a hook that is reliably called 
when the _display_ geometry changes on graphical window systems. e.g. 
screen resolution changed by user, monitor hotplugging.

Emacs frame/window geometry might sometimes be considered to remain the 
same by the window system terms despite changing overall display 
geometry, so e.g. window-configuration-change-hook is not always run 
when the display geometry changes.

Attached is an initial x11-only stab at an implementation that calls 
window-configuration-change-hook when display geometry changes, though a 
separate hook would be another design option.

For ns, Edward also mentioned:

"On GNUStep and Mac OS X, applications can listen for 
NSApplicationDidChangeScreenParametersNotification for this sort of thing"

Dunno about w32 as yet, but presumably it has something similar.

[1] http://lists.gnu.org/archive/html/emacs-devel/2011-08/msg00544.html
From: "Edward O'Connor" <...>
Date: Fri, 12 Aug 2011 10:51:25 -0700
Message-ID: 
<CAME3nGLnjLXixmSY45BWFX6qtCp2_XBR4x6a281hO6Mu=[email-masked]>
Subject: is there a hook run when display geometry changes?

=== modified file 'configure.in'
--- configure.in	2011-08-04 17:04:39 +0000
+++ configure.in	2011-08-13 04:36:34 +0000
@@ -160,6 +160,8 @@
 OPTION_DEFAULT_ON([xml2],[don't compile with XML parsing support])
 OPTION_DEFAULT_ON([imagemagick],[don't compile with ImageMagick image support])
 
+OPTION_DEFAULT_ON([xrandr],[don't compile with XRandR support])
+
 OPTION_DEFAULT_ON([xft],[don't use XFT for anti aliased fonts])
 OPTION_DEFAULT_ON([libotf],[don't use libotf for OpenType font support])
 OPTION_DEFAULT_ON([m17n-flt],[don't use m17n-flt for text shaping])
@@ -2590,6 +2592,21 @@
 fi
 AC_SUBST(LIBXSM)
 
+### Use xrandr (-lXrandr) if available
+HAVE_XRANDR=no
+XRANDR_LIBS=
+XRANDR_CFLAGS=
+if test "${HAVE_X11}" = "yes"; then
+  if test "${with_xrandr}" != "no"; then
+    PKG_CHECK_MODULES(XRANDR, xrandr > 1.0, HAVE_XRANDR=yes, HAVE_XRANDR=no)
+    if test "${HAVE_XRANDR}" = "yes"; then
+      AC_DEFINE(HAVE_XRANDR, 1, [Define to 1 if you have the Xrandr library (-lXrandr).])
+    fi
+  fi
+fi
+AC_SUBST(XRANDR_LIBS)
+AC_SUBST(XRANDR_CFLAGS)
+
 ### Use libxml (-lxml2) if available
 if test "${with_xml2}" != "no"; then
   ### I'm not sure what the version number should be, so I just guessed.
@@ -3700,6 +3717,8 @@
 echo "  Does Emacs use -lotf?                                   ${HAVE_LIBOTF}"
 echo "  Does Emacs use -lxft?                                   ${HAVE_XFT}"
 
+echo "  Does Emacs use -lXrandr?                                ${HAVE_XRANDR}"
+
 echo "  Does Emacs use toolkit scroll bars?                     ${USE_TOOLKIT_SCROLL_BARS}"
 echo

=== modified file 'lisp/frame.el'
--- lisp/frame.el	2011-07-16 13:02:51 +0000
+++ lisp/frame.el	2011-08-13 04:04:16 +0000
@@ -1585,6 +1585,18 @@
 (define-obsolete-variable-alias 'blink-cursor 'blink-cursor-mode "22.1")
 
 
+;; Display geometry changes
+
+(defun handle-screen-change-notify-event (event)
+  "Handle screen-change-notify-event on the display in EVENT.
+   At present, just runs any window-configuration-changed-hook
+   for all frames on the display."
+  ; FIXME: do we want a completely separate hook instead?
+  (interactive "e")
+  (let ((display-name (nth 1 event)))
+    (mapcar #'run-window-configuration-change-hook
+            (frames-on-display-list display-name))))
+
 ;;;; Key bindings
 
 (define-key ctl-x-5-map "2" 'make-frame-command)
@@ -1592,6 +1604,9 @@
 (define-key ctl-x-5-map "0" 'delete-frame)
 (define-key ctl-x-5-map "o" 'other-frame)
 
+(define-key special-event-map [screen-change-notify-event]
+  'handle-screen-change-notify-event)
+
 (provide 'frame)
 
 ;;; frame.el ends here

=== modified file 'src/Makefile.in'
--- src/Makefile.in	2011-08-04 17:04:39 +0000
+++ src/Makefile.in	2011-08-13 05:16:52 +0000
@@ -170,6 +170,9 @@
 
 LIBXSM=@LIBXSM@
 
+XRANDR_CFLAGS = @XRANDR_CFLAGS@
+XRANDR_LIBS = @XRANDR_LIBS@
+
 LIBXTR6=@LIBXTR6@
 
 ## $(LIBXMU) -lXt $(LIBXTR6) -lXext if USE_X_TOOLKIT, else $(LIBXSM).
@@ -313,6 +316,7 @@
   $(SETTINGS_CFLAGS) $(FREETYPE_CFLAGS) $(FONTCONFIG_CFLAGS) \
   $(LIBOTF_CFLAGS) $(M17N_FLT_CFLAGS) $(DEPFLAGS) $(PROFILING_CFLAGS) \
   $(LIBGNUTLS_CFLAGS) \
+  $(XRANDR_CFLAGS) \
   $(C_WARNINGS_SWITCH) $(CFLAGS)
 ALL_OBJC_CFLAGS=$(ALL_CFLAGS) $(GNU_OBJC_CFLAGS)
 
@@ -388,7 +392,8 @@
    $(LIBXML2_LIBS) $(LIBGPM) $(LIBRESOLV) $(LIBS_SYSTEM) \
    $(LIBS_TERMCAP) $(GETLOADAVG_LIBS) $(SETTINGS_LIBS) $(LIBSELINUX_LIBS) \
    $(FREETYPE_LIBS) $(FONTCONFIG_LIBS) $(LIBOTF_LIBS) $(M17N_FLT_LIBS) \
-   $(LIBGNUTLS_LIBS) $(LIB_PTHREAD) $(LIB_PTHREAD_SIGMASK) \
+   $(LIBGNUTLS_LIBS) $(XRANDR_LIBS) \
+   $(LIB_PTHREAD) $(LIB_PTHREAD_SIGMASK) \
    $(LIB_GCC) $(LIB_MATH) $(LIB_STANDARD) $(LIB_GCC)
 
 all: emacs$(EXEEXT) $(OTHER_FILES)

=== modified file 'src/keyboard.c'
--- src/keyboard.c	2011-08-04 17:04:39 +0000
+++ src/keyboard.c	2011-08-13 03:41:32 +0000
@@ -331,6 +331,8 @@
 #endif
 static Lisp_Object Qconfig_changed_event;
 
+static Lisp_Object Qscreen_change_notify_event;
+
 /* Lisp_Object Qmouse_movement; - also an event header */
 
 /* Properties of event headers.  */
@@ -4033,6 +4035,11 @@
 	  obj = make_lispy_event (event);
 	  kbd_fetch_ptr = event + 1;
 	}
+      else if (event->kind == SCREEN_CHANGE_NOTIFY_EVENT)
+	{
+	  obj = make_lispy_event (event);
+	  kbd_fetch_ptr = event + 1;
+	}
       else
 	{
 	  /* If this event is on a different frame, return a switch-frame this
@@ -5987,6 +5994,9 @@
 			       Qnil));
        }
 #endif /* HAVE_GPM */
+    case SCREEN_CHANGE_NOTIFY_EVENT:
+	return Fcons (Qscreen_change_notify_event,
+                      Fcons (event->frame_or_window, Qnil));
 
       /* The 'kind' field of the event is something we don't recognize.  */
     default:
@@ -11527,6 +11537,7 @@
   DEFSYM (Qsave_session, "save-session");
   DEFSYM (Qconfig_changed_event, "config-changed-event");
   DEFSYM (Qmenu_enable, "menu-enable");
+  DEFSYM (Qscreen_change_notify_event, "screen-change-notify-event");
 
 #if defined (WINDOWSNT)
   DEFSYM (Qlanguage_change, "language-change");
@@ -12288,6 +12299,10 @@
 
   initial_define_lispy_key (Vspecial_event_map, "config-changed-event",
 			    "ignore");
+
+  initial_define_lispy_key (Vspecial_event_map, "screen-change-notify-event",
+			    "ignore");
+
 }
 
 /* Mark the pointers in the kboard objects.

=== modified file 'src/termhooks.h'
--- src/termhooks.h	2011-06-06 19:43:39 +0000
+++ src/termhooks.h	2011-08-13 03:18:24 +0000
@@ -206,6 +206,13 @@
   , NS_NONKEY_EVENT
 #endif
 
+  /* Generated when a bitmapped display's (upon which emacs has
+     frame(s) open) geometry changes dynamically e.g. by monitor
+     hotplugging or resolution change.
+     On X, that which is handled by the X Resize and Rotate
+     Extension.  */
+  , SCREEN_CHANGE_NOTIFY_EVENT
+
 };
 
 /* If a struct input_event has a kind which is SELECTION_REQUEST_EVENT

=== modified file 'src/window.c'
--- src/window.c	2011-08-05 11:04:44 +0000
+++ src/window.c	2011-08-13 04:04:18 +0000
@@ -6542,7 +6542,11 @@
 	       doc: /* Functions to call when window configuration changes.
 The buffer-local part is run once per window, with the relevant window
 selected; while the global part is run only once for the modified frame,
-with the relevant frame selected.  */);
+with the relevant frame selected.
+
+If emacs can detect them, changes to the overall geometry of a display
+upon which a frame is open will by default also run this hook via
+the function `handle-screen-change-notify-event`.  */);
   Vwindow_configuration_change_hook = Qnil;
 
   DEFVAR_LISP ("recenter-redisplay", Vrecenter_redisplay,

=== modified file 'src/xterm.c'
--- src/xterm.c	2011-08-04 11:06:22 +0000
+++ src/xterm.c	2011-08-13 05:00:47 +0000
@@ -85,6 +85,10 @@
 #include <X11/Shell.h>
 #endif
 
+#ifdef HAVE_XRANDR
+#include <X11/extensions/Xrandr.h>
+#endif
+
 #ifdef HAVE_SYS_TIME_H
 #include <sys/time.h>
 #endif
@@ -359,7 +363,6 @@
 static void x_wm_set_icon_pixmap (struct frame *, ptrdiff_t);
 static void x_initialize (void);
 
-
 /* Flush display of frame F, or of all frames if F is null.  */
 
 static void
@@ -6993,6 +6996,26 @@
       break;
 
     default:
+#ifdef HAVE_XRANDR
+    /* xrandr extension may or may not be present on a display =>
+       can't check for it as a constant C "case" arg, hence "if" here  */
+    if (dpyinfo->xrandr_present &&
+        (event.type == (dpyinfo->xrandr_event_base + RRScreenChangeNotify)))
+      {
+        /* FIXME: is XRRUpdateConfiguration actually called by the toolkit/gtk+?
+           Even if it is, can/should we call it ourselves here?  */
+#if !(defined USE_X_TOOLKIT || defined USE_GTK)
+        BLOCK_INPUT;
+        XRRUpdateConfiguration(&event);
+        UNBLOCK_INPUT;
+#endif
+        /* FIXME: do we really need a separate event kind? It seems
+           conceptually similar to a CONFIG_CHANGED_EVENT */
+        inev.ie.kind = SCREEN_CHANGE_NOTIFY_EVENT;
+        inev.ie.frame_or_window = XCAR(dpyinfo->name_list_element);
+      }
+    goto OTHER;
+#endif /* HAVE_XRANDR */
     OTHER:
 #ifdef USE_X_TOOLKIT
     BLOCK_INPUT;
@@ -8169,6 +8192,25 @@
 
 #endif /* not HAVE_X11R6_XIM */
 
+
+/***********************************************************************
+			   X Resize and Rotate
+ ***********************************************************************/
+
+#ifdef HAVE_XRANDR
+
+static void
+xrandr_initialize (struct x_display_info *dpyinfo)
+{
+  dpyinfo->xrandr_present = XRRQueryExtension (dpyinfo->display,
+                                               &(dpyinfo->xrandr_event_base),
+                                               &(dpyinfo->xrandr_error_base));
+  if (dpyinfo->xrandr_present)
+    XRRSelectInput (dpyinfo->display, dpyinfo->root_window,
+                    RRScreenChangeNotifyMask);
+}
+
+#endif /* HAVE_XRANDR */
 
 
 /* Calculate the absolute position in frame F
@@ -10315,6 +10357,10 @@
   xim_initialize (dpyinfo, resource_name);
 #endif
 
+#ifdef HAVE_XRANDR
+  xrandr_initialize (dpyinfo);
+#endif
+
   xsettings_initialize (dpyinfo);
 
   /* This is only needed for distinguishing keyboard and process input.  */

=== modified file 'src/xterm.h'
--- src/xterm.h	2011-07-07 02:24:56 +0000
+++ src/xterm.h	2011-08-13 02:41:25 +0000
@@ -354,6 +354,14 @@
 
   /* SM */
   Atom Xatom_SM_CLIENT_ID;
+
+#ifdef HAVE_XRANDR
+  /* X Resize and Rotate */
+  Bool xrandr_present;
+  int xrandr_event_base;
+  int xrandr_error_base;
+#endif
+
 };
 
 #ifdef HAVE_X_I18N