From 4e3f92301d062dcfa29128e7df5058e6e21dcb07 Mon Sep 17 00:00:00 2001 From: YAMAMOTO Mitsuharu Date: Tue, 7 May 2013 10:12:22 +0900 Subject: [PATCH] Add multi-monitor support on X11. --- ChangeLog | 5 + configure.ac | 50 +++++ etc/ChangeLog | 4 + etc/NEWS | 6 + lisp/ChangeLog | 5 + lisp/frame.el | 61 ++++++ src/ChangeLog | 25 +++ src/Makefile.in | 10 +- src/xfns.c | 575 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/xterm.c | 2 + src/xterm.h | 3 +- 11 files changed, 743 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index 1d17e1a86c6..4636d43eee7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2013-05-07 Jan Djärv + + * configure.ac (HAVE_XRANDR, HAVE_XINERAMA): Define if available. + (XRANDR_LIBS, XINERAMA_LIBS): New AC_SUBSTs. + 2013-05-06 Paul Eggert Merge from gnulib, incorporating: diff --git a/configure.ac b/configure.ac index 6c59dcaa7e3..e3a10275df4 100644 --- a/configure.ac +++ b/configure.ac @@ -2814,6 +2814,56 @@ if test "${HAVE_X11}" = "yes"; then fi AC_SUBST(LIBXSM) +### Use XRandr (-lXrandr) if available +HAVE_XRANDR=no +if test "${HAVE_X11}" = "yes"; then + XRANDR_REQUIRED=1.2.2 + XRANDR_MODULES="xrandr >= $XRANDR_REQUIRED" + PKG_CHECK_MODULES(XRANDR, $XRANDR_MODULES, HAVE_XRANDR=yes, HAVE_XRANDR=no) + if test $HAVE_XRANDR = no; then + # Test old way in case pkg-config doesn't have it (older machines). + AC_CHECK_HEADER(X11/extensions/Xrandr.h, + [AC_CHECK_LIB(Xrandr, XRRQueryExtension, HAVE_XRANDR=yes)]) + if test $HAVE_XRANDR = yes; then + XRANDR_LIBS=-lXrandr + AC_SUBST(XRANDR_LIBS) + fi + fi + if test $HAVE_XRANDR = yes; then + SAVE_CFLAGS="$CFLAGS" + SAVE_LIBS="$LIBS" + CFLAGS="$XRANDR_CFLAGS $CFLAGS" + LIBS="$XRANDR_LIBS $LIBS" + AC_CHECK_FUNCS(XRRGetOutputPrimary XRRGetScreenResourcesCurrent) + CFLAGS="$SAVE_CFLAGS" + LIBS="$SAVE_LIBS" + + AC_DEFINE(HAVE_XRANDR, 1, [Define to 1 if you have the XRandr extension.]) + fi +fi + +### Use Xinerama (-lXinerama) if available +HAVE_XINERAMA=no +if test "${HAVE_X11}" = "yes"; then + XINERAMA_REQUIRED=1.0.2 + XINERAMA_MODULES="xinerama >= $XINERAMA_REQUIRED" + PKG_CHECK_MODULES(XINERAMA, $XINERAMA_MODULES, HAVE_XINERAMA=yes, + HAVE_XINERAMA=no) + if test $HAVE_XINERAMA = no; then + # Test old way in case pkg-config doesn't have it (older machines). + AC_CHECK_HEADER(X11/extensions/Xinerama.h, + [AC_CHECK_LIB(Xinerama, XineramaQueryExtension, HAVE_XINERAMA=yes)]) + if test $HAVE_XINERAMA = yes; then + XINERAMA_LIBS=-lXinerama + AC_SUBST(XINERAMA_LIBS) + fi + fi + if test $HAVE_XINERAMA = yes; then + AC_DEFINE(HAVE_XINERAMA, 1, [Define to 1 if you have the Xinerama extension.]) + fi +fi + + ### Use libxml (-lxml2) if available HAVE_LIBXML2=no if test "${with_xml2}" != "no"; then diff --git a/etc/ChangeLog b/etc/ChangeLog index 9cbd67c209f..4e9ba154cfe 100644 --- a/etc/ChangeLog +++ b/etc/ChangeLog @@ -1,3 +1,7 @@ +2013-05-07 YAMAMOTO Mitsuharu + + * NEWS: Mention multi-monitor support. + 2013-05-05 Paul Eggert `write-region-inhibit-fsync' defaults to noninteractive (Bug#14273). diff --git a/etc/NEWS b/etc/NEWS index d56c02960fc..f278317b08d 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -79,6 +79,12 @@ of the buffer is visible). ** In compiled Lisp files, the header no longer includes a timestamp. +** Multi-monitor support has been added. + +*** New functions `display-monitor-attributes-list' and +`frame-monitor-attributes' can be used to obtain information about +each physical monitor on multi-monitor setups. + * Editing Changes in Emacs 24.4 diff --git a/lisp/ChangeLog b/lisp/ChangeLog index c4dbc803fcc..676e4286395 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,8 @@ +2013-05-07 YAMAMOTO Mitsuharu + + * frame.el (display-monitor-attributes-list) + (frame-monitor-attributes): New functions. + 2013-05-06 Leo Liu * progmodes/octave.el (octave-syntax-propertize-function): Change diff --git a/lisp/frame.el b/lisp/frame.el index 454b229d59e..94a4842fc4e 100644 --- a/lisp/frame.el +++ b/lisp/frame.el @@ -1256,6 +1256,23 @@ bars (top, bottom, or nil)." (unless (memq vert '(left right nil)) (setq vert default-frame-scroll-bars)) (cons vert hor))) + +(defun frame-monitor-attributes (&optional frame) + "Return the attributes of the physical monitor dominating FRAME. +If FRAME is omitted, describe the currently selected frame. + +A frame is dominated by a physical monitor when either the +largest area of the frame resides in the monitor, or the monitor +is the closest to the frame if the frame does not intersect any +physical monitors. + +See `display-monitor-attributes-list' for the list of attribute +keys and their meanings." + (or frame (setq frame (selected-frame))) + (cl-loop for attributes in (display-monitor-attributes-list frame) + for frames = (cdr (assq 'frames attributes)) + if (memq frame frames) return attributes)) + ;;;; Frame/display capabilities. (defun selected-terminal () @@ -1476,6 +1493,50 @@ The value is one of the symbols `static-gray', `gray-scale', (t 'static-gray)))) +(declare-function x-display-monitor-attributes-list "xfns.c" + (&optional terminal)) + +(defun display-monitor-attributes-list (&optional display) + "Return a list of physical monitor attributes on DISPLAY. +Each element of the list represents the attributes of each +physical monitor. The first element corresponds to the primary +monitor. + +Attributes for a physical monitor is represented as an alist of +attribute keys and values as follows: + + geometry -- Position and size in pixels in the form of + (X Y WIDTH HEIGHT) + workarea -- Position and size of the workarea in pixels in the + form of (X Y WIDTH HEIGHT) + mm-size -- Width and height in millimeters in the form of + (WIDTH HEIGHT) + frames -- List of frames dominated by the physical monitor + name (*) -- Name of the physical monitor as a string + +where X, Y, WIDTH, and HEIGHT are integers. Keys labeled +with (*) are optional. + +A frame is dominated by a physical monitor when either the +largest area of the frame resides in the monitor, or the monitor +is the closest to the frame if the frame does not intersect any +physical monitors. Every non-tip frame (including invisible one) +in a graphical display is dominated by exactly one physical +monitor at a time, though it can span multiple (or no) physical +monitors." + (let ((frame-type (framep-on-display display))) + (cond + ((eq frame-type 'x) + (x-display-monitor-attributes-list display)) + (t + (let ((geometry (list 0 0 (display-pixel-width display) + (display-pixel-height display)))) + `(((geometry . ,geometry) + (workarea . ,geometry) + (mm-size . (,(display-mm-width display) + ,(display-mm-height display))) + (frames . ,(frames-on-display-list display))))))))) + ;;;; Frame geometry values diff --git a/src/ChangeLog b/src/ChangeLog index 5ac0f885d8e..0d06f4e291d 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,28 @@ +2013-05-07 YAMAMOTO Mitsuharu + Jan Djärv + + * Makefile.in (XRANDR_LIBS, XRANDR_CFLAGS, XINERAMA_LIBS) + (XINERAMA_CFLAGS): New macros. + (ALL_CFLAGS, LIBES): Use them. + + * xfns.c: Include if HAVE_XRANDR, and + include if HAVE_XINERAMA. + (Qgeometry, Qworkarea, Qmm_size, Qframes, Qsource): New variables. + (syms_of_xfns): DEFSYM them. + (struct MonitorInfo): New struct. + (x_get_net_workarea, free_monitors, x_get_monitor_for_frame) + (x_make_monitor_attribute_list, x_get_monitor_attributes_fallback) + (x_get_monitor_attributes_xrandr, x_get_monitor_attributes) + (x_get_monitor_attributes_xinerama): New functions. + (Fx_display_monitor_attributes_list): New primitive. + (syms_of_xfns): Defsubr it. + + * xterm.h (x_display_info): Add Xatom_net_workarea and + Xatom_net_current_desktop. + + * xterm.c (x_term_init): Initialize dpyinfo->Xatom_net_workarea + and dpyinfo->Xatom_net_current_desktop. + 2013-05-06 Eli Zaretskii * xdisp.c (pos_visible_p): Use the special code for finding the diff --git a/src/Makefile.in b/src/Makefile.in index cef58c55e68..1222b5a5f98 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -232,6 +232,12 @@ IMAGEMAGICK_CFLAGS= @IMAGEMAGICK_CFLAGS@ LIBXML2_LIBS = @LIBXML2_LIBS@ LIBXML2_CFLAGS = @LIBXML2_CFLAGS@ +XRANDR_LIBS = @XRANDR_LIBS@ +XRANDR_CFLAGS = @XRANDR_CFLAGS@ + +XINERAMA_LIBS = @XINERAMA_LIBS@ +XINERAMA_CFLAGS = @XINERAMA_CFLAGS@ + ## widget.o if USE_X_TOOLKIT, otherwise empty. WIDGET_OBJ=@WIDGET_OBJ@ @@ -315,7 +321,7 @@ ALL_CFLAGS=-Demacs $(MYCPPFLAGS) -I. -I$(srcdir) \ -I$(lib) -I$(srcdir)/../lib \ $(C_SWITCH_MACHINE) $(C_SWITCH_SYSTEM) $(C_SWITCH_X_SITE) \ $(GNUSTEP_CFLAGS) $(CFLAGS_SOUND) $(RSVG_CFLAGS) $(IMAGEMAGICK_CFLAGS) \ - $(LIBXML2_CFLAGS) $(DBUS_CFLAGS) \ + $(LIBXML2_CFLAGS) $(DBUS_CFLAGS) $(XRANDR_CFLAGS) $(XINERAMA_CFLAGS) \ $(SETTINGS_CFLAGS) $(FREETYPE_CFLAGS) $(FONTCONFIG_CFLAGS) \ $(LIBOTF_CFLAGS) $(M17N_FLT_CFLAGS) $(DEPFLAGS) \ $(LIBGNUTLS_CFLAGS) \ @@ -393,7 +399,7 @@ LIBES = $(LIBS) $(W32_LIBS) $(LIBS_GNUSTEP) $(LIBX_BASE) $(LIBIMAGE) \ $(LIBX_OTHER) $(LIBSOUND) \ $(RSVG_LIBS) $(IMAGEMAGICK_LIBS) $(LIB_CLOCK_GETTIME) \ $(LIB_EACCESS) $(LIB_FDATASYNC) $(LIB_TIMER_TIME) $(DBUS_LIBS) \ - $(LIB_EXECINFO) \ + $(LIB_EXECINFO) $(XRANDR_LIBS) $(XINERAMA_LIBS) \ $(LIBXML2_LIBS) $(LIBGPM) $(LIBRESOLV) $(LIBS_SYSTEM) \ $(LIBS_TERMCAP) $(GETLOADAVG_LIBS) $(SETTINGS_LIBS) $(LIBSELINUX_LIBS) \ $(FREETYPE_LIBS) $(FONTCONFIG_LIBS) $(LIBOTF_LIBS) $(M17N_FLT_LIBS) \ diff --git a/src/xfns.c b/src/xfns.c index f4c24cb09a0..d3e3479be2d 100644 --- a/src/xfns.c +++ b/src/xfns.c @@ -59,6 +59,13 @@ along with GNU Emacs. If not, see . */ #include "xsettings.h" +#ifdef HAVE_XRANDR +#include +#endif +#ifdef HAVE_XINERAMA +#include +#endif + #ifdef USE_GTK #include "gtkutil.h" #endif @@ -126,6 +133,7 @@ extern LWLIB_ID widget_id_tick; static Lisp_Object Qsuppress_icon; static Lisp_Object Qundefined_color; static Lisp_Object Qcompound_text, Qcancel_timer; +static Lisp_Object Qgeometry, Qworkarea, Qmm_size, Qframes, Qsource; Lisp_Object Qfont_param; #ifdef GLYPH_DEBUG @@ -3791,6 +3799,567 @@ If omitted or nil, that stands for the selected frame's display. */) else return Qnil; } + +/* Store the geometry of the workarea on display DPYINFO into *RECT. + Return false if and only if the workarea information cannot be + obtained via the _NET_WORKAREA root window property. */ + +static bool +x_get_net_workarea (struct x_display_info *dpyinfo, XRectangle *rect) +{ + Display *dpy = dpyinfo->display; + long offset, max_len; + Atom target_type, actual_type; + unsigned long actual_size, bytes_remaining; + int rc, actual_format; + unsigned char *tmp_data = NULL; + bool result = false; + + x_catch_errors (dpy); + offset = 0; + max_len = 1; + target_type = XA_CARDINAL; + rc = XGetWindowProperty (dpy, dpyinfo->root_window, + dpyinfo->Xatom_net_current_desktop, + offset, max_len, False, target_type, + &actual_type, &actual_format, &actual_size, + &bytes_remaining, &tmp_data); + if (rc == Success && actual_type == target_type && !x_had_errors_p (dpy) + && actual_format == 32 && actual_size == max_len) + { + long current_desktop = ((long *) tmp_data)[0]; + + XFree (tmp_data); + tmp_data = NULL; + + offset = 4 * current_desktop; + max_len = 4; + rc = XGetWindowProperty (dpy, dpyinfo->root_window, + dpyinfo->Xatom_net_workarea, + offset, max_len, False, target_type, + &actual_type, &actual_format, &actual_size, + &bytes_remaining, &tmp_data); + if (rc == Success && actual_type == target_type && !x_had_errors_p (dpy) + && actual_format == 32 && actual_size == max_len) + { + long *values = (long *) tmp_data; + + rect->x = values[0]; + rect->y = values[1]; + rect->width = values[2]; + rect->height = values[3]; + + XFree (tmp_data); + tmp_data = NULL; + + result = true; + } + } + if (tmp_data) + XFree (tmp_data); + x_uncatch_errors (); + + return result; +} + +struct MonitorInfo { + XRectangle geom, work; + int mm_width, mm_height; + char *name; +}; + +static void +free_monitors (struct MonitorInfo *monitors, int n_monitors) +{ + int i; + for (i = 0; i < n_monitors; ++i) + xfree (monitors[i].name); + xfree (monitors); +} + + +/* Return monitor number where F is "most" or closest to. */ +static int +x_get_monitor_for_frame (struct frame *f, + struct MonitorInfo *monitors, + int n_monitors) +{ + XRectangle frect; + int area = 0, dist = -1; + int best_area = -1, best_dist = -1; + int i; + + if (n_monitors == 1) return 0; + frect.x = f->left_pos; + frect.y = f->top_pos; + frect.width = FRAME_PIXEL_WIDTH (f); + frect.height = FRAME_PIXEL_HEIGHT (f); + + for (i = 0; i < n_monitors; ++i) + { + struct MonitorInfo *mi = &monitors[i]; + XRectangle res; + int a = 0; + + if (mi->geom.width == 0) continue; + + if (x_intersect_rectangles (&mi->geom, &frect, &res)) + { + a = res.width * res.height; + if (a > area) { + area = a; + best_area = i; + } + } + + if (a == 0 && area == 0) + { + int dx, dy, d; + if (frect.x + frect.width < mi->geom.x) + dx = mi->geom.x - frect.x + frect.width; + else if (frect.x > mi->geom.x + mi->geom.width) + dx = frect.x - mi->geom.x + mi->geom.width; + else + dx = 0; + if (frect.y + frect.height < mi->geom.y) + dy = mi->geom.y - frect.y + frect.height; + else if (frect.y > mi->geom.y + mi->geom.height) + dy = frect.y - mi->geom.y + mi->geom.height; + else + dy = 0; + + d = dx*dx + dy*dy; + if (dist == -1 || dist > d) + { + dist = d; + best_dist = i; + } + } + } + + return best_area != -1 ? best_area : (best_dist != -1 ? best_dist : 0); +} + +static Lisp_Object +x_make_monitor_attribute_list (struct MonitorInfo *monitors, + int n_monitors, + int primary_monitor, + struct x_display_info *dpyinfo, + const char *source) +{ + Lisp_Object monitor_frames = Fmake_vector (make_number (n_monitors), Qnil); + Lisp_Object frame, rest, attributes_list = Qnil; + Lisp_Object primary_monitor_attributes = Qnil; + int i; + + FOR_EACH_FRAME (rest, frame) + { + struct frame *f = XFRAME (frame); + + if (FRAME_X_P (f) && FRAME_X_DISPLAY_INFO (f) == dpyinfo + && !EQ (frame, tip_frame)) + { + i = x_get_monitor_for_frame (f, monitors, n_monitors); + ASET (monitor_frames, i, Fcons (frame, AREF (monitor_frames, i))); + } + } + + for (i = 0; i < n_monitors; ++i) + { + Lisp_Object geometry, workarea, attributes = Qnil; + struct MonitorInfo *mi = &monitors[i]; + + if (mi->geom.width == 0) continue; + + workarea = list4i (mi->work.x, mi->work.y, + mi->work.width, mi->work.height); + geometry = list4i (mi->geom.x, mi->geom.y, + mi->geom.width, mi->geom.height); + attributes = Fcons (Fcons (Qsource, + make_string (source, strlen (source))), + attributes); + attributes = Fcons (Fcons (Qframes, AREF (monitor_frames, i)), + attributes); + attributes = Fcons (Fcons (Qmm_size, + list2i (mi->mm_width, mi->mm_height)), + attributes); + attributes = Fcons (Fcons (Qworkarea, workarea), attributes); + attributes = Fcons (Fcons (Qgeometry, geometry), attributes); + if (mi->name) + attributes = Fcons (Fcons (Qname, make_string (mi->name, + strlen (mi->name))), + attributes); + + if (i == primary_monitor) + primary_monitor_attributes = attributes; + else + attributes_list = Fcons (attributes, attributes_list); + } + + if (!NILP (primary_monitor_attributes)) + attributes_list = Fcons (primary_monitor_attributes, attributes_list); + return attributes_list; +} + +static Lisp_Object +x_get_monitor_attributes_fallback (struct x_display_info *dpyinfo) +{ + struct MonitorInfo monitor; + int width_mm, height_mm; + XRectangle workarea_r; + + /* Fallback: treat (possibly) multiple physical monitors as if they + formed a single monitor as a whole. This should provide a + consistent result at least on single monitor environments. */ + monitor.geom.x = monitor.geom.y = 0; + monitor.geom.width = x_display_pixel_width (dpyinfo); + monitor.geom.height = x_display_pixel_height (dpyinfo); + monitor.mm_width = WidthMMOfScreen (dpyinfo->screen); + monitor.mm_height = HeightMMOfScreen (dpyinfo->screen); + monitor.name = xstrdup ("combined screen"); + + if (x_get_net_workarea (dpyinfo, &workarea_r)) + monitor.work = workarea_r; + else + monitor.work = monitor.geom; + return x_make_monitor_attribute_list (&monitor, 1, 0, dpyinfo, "fallback"); +} + + +#ifdef HAVE_XINERAMA +static Lisp_Object +x_get_monitor_attributes_xinerama (struct x_display_info *dpyinfo) +{ + int n_monitors, i; + Lisp_Object attributes_list = Qnil; + Display *dpy = dpyinfo->display; + XineramaScreenInfo *info = XineramaQueryScreens (dpy, &n_monitors); + struct MonitorInfo *monitors; + float mm_width_per_pixel, mm_height_per_pixel; + + if (! info || n_monitors == 0) + { + if (info) + XFree (info); + return attributes_list; + } + + mm_width_per_pixel = ((float) WidthMMOfScreen (dpyinfo->screen) + / x_display_pixel_width (dpyinfo)); + mm_height_per_pixel = ((float) HeightMMOfScreen (dpyinfo->screen) + / x_display_pixel_height (dpyinfo)); + monitors = (struct MonitorInfo *) xzalloc (n_monitors * sizeof (*monitors)); + for (i = 0; i < n_monitors; ++i) + { + struct MonitorInfo *mi = &monitors[i]; + XRectangle workarea_r; + + mi->geom.x = info[i].x_org; + mi->geom.y = info[i].y_org; + mi->geom.width = info[i].width; + mi->geom.height = info[i].height; + mi->mm_width = mi->geom.width * mm_width_per_pixel + 0.5; + mi->mm_height = mi->geom.height * mm_height_per_pixel + 0.5; + mi->name = 0; + + /* Xinerama usually have primary monitor first, just use that. */ + if (i == 0 && x_get_net_workarea (dpyinfo, &workarea_r)) + { + mi->work = workarea_r; + if (! x_intersect_rectangles (&mi->geom, &mi->work, &mi->work)) + mi->work = mi->geom; + } + else + mi->work = mi->geom; + } + XFree (info); + + attributes_list = x_make_monitor_attribute_list (monitors, + n_monitors, + 0, + dpyinfo, + "Xinerama"); + free_monitors (monitors, n_monitors); + return attributes_list; +} +#endif /* HAVE_XINERAMA */ + + +#ifdef HAVE_XRANDR +static Lisp_Object +x_get_monitor_attributes_xrandr (struct x_display_info *dpyinfo) +{ + Lisp_Object attributes_list = Qnil; + XRRScreenResources *resources; + Display *dpy = dpyinfo->display; + int i, n_monitors, primary = -1; + RROutput pxid = None; + struct MonitorInfo *monitors; + +#ifdef HAVE_XRRGETSCREENRESOURCESCURRENT + resources = XRRGetScreenResourcesCurrent (dpy, dpyinfo->root_window); +#else + resources = XRRGetScreenResources (dpy, dpyinfo->root_window); +#endif + if (! resources || resources->noutput == 0) + { + if (resources) + XRRFreeScreenResources (resources); + return Qnil; + } + n_monitors = resources->noutput; + monitors = (struct MonitorInfo *) xzalloc (n_monitors * sizeof (*monitors)); + +#ifdef HAVE_XRRGETOUTPUTPRIMARY + pxid = XRRGetOutputPrimary (dpy, dpyinfo->root_window); +#endif + + for (i = 0; i < n_monitors; ++i) + { + XRROutputInfo *info = XRRGetOutputInfo (dpy, resources, + resources->outputs[i]); + Connection conn = info ? info->connection : RR_Disconnected; + RRCrtc id = info ? info->crtc : None; + + if (strcmp (info->name, "default") == 0) + { + /* Non XRandr 1.2 driver, does not give useful data. */ + XRRFreeOutputInfo (info); + XRRFreeScreenResources (resources); + free_monitors (monitors, n_monitors); + return Qnil; + } + + if (conn != RR_Disconnected && id != None) + { + XRRCrtcInfo *crtc = XRRGetCrtcInfo (dpy, resources, id); + struct MonitorInfo *mi = &monitors[i]; + XRectangle workarea_r; + + if (! crtc) + { + XRRFreeOutputInfo (info); + continue; + } + + mi->geom.x = crtc->x; + mi->geom.y = crtc->y; + mi->geom.width = crtc->width; + mi->geom.height = crtc->height; + mi->mm_width = info->mm_width; + mi->mm_height = info->mm_height; + mi->name = xstrdup (info->name); + + if (pxid != None && pxid == resources->outputs[i]) + primary = i; + else if (primary == -1 && strcmp (info->name, "LVDS") == 0) + primary = i; + + if (i == primary && x_get_net_workarea (dpyinfo, &workarea_r)) + { + mi->work= workarea_r; + if (! x_intersect_rectangles (&mi->geom, &mi->work, &mi->work)) + mi->work = mi->geom; + } + else + mi->work = mi->geom; + + XRRFreeCrtcInfo (crtc); + } + XRRFreeOutputInfo (info); + } + XRRFreeScreenResources (resources); + + attributes_list = x_make_monitor_attribute_list (monitors, + n_monitors, + primary, + dpyinfo, + "XRandr"); + free_monitors (monitors, n_monitors); + return attributes_list; +} +#endif /* HAVE_XRANDR */ + +static Lisp_Object +x_get_monitor_attributes (struct x_display_info *dpyinfo) +{ + Lisp_Object attributes_list = Qnil; + Display *dpy = dpyinfo->display; + +#ifdef HAVE_XRANDR + int xrr_event_base, xrr_error_base; + bool xrr_ok = false; + xrr_ok = XRRQueryExtension (dpy, &xrr_event_base, &xrr_error_base); + if (xrr_ok) + { + int xrr_major, xrr_minor; + XRRQueryVersion (dpy, &xrr_major, &xrr_minor); + xrr_ok = (xrr_major == 1 && xrr_minor >= 2) || xrr_major > 1; + } + + if (xrr_ok) + attributes_list = x_get_monitor_attributes_xrandr (dpyinfo); +#endif /* HAVE_XRANDR */ + +#ifdef HAVE_XINERAMA + if (NILP (attributes_list)) + { + int xin_event_base, xin_error_base; + bool xin_ok = false; + xin_ok = XineramaQueryExtension (dpy, &xin_event_base, &xin_error_base); + if (xin_ok && XineramaIsActive (dpy)) + attributes_list = x_get_monitor_attributes_xinerama (dpyinfo); + } +#endif /* HAVE_XINERAMA */ + + if (NILP (attributes_list)) + attributes_list = x_get_monitor_attributes_fallback (dpyinfo); + + return attributes_list; +} + +DEFUN ("x-display-monitor-attributes-list", Fx_display_monitor_attributes_list, + Sx_display_monitor_attributes_list, + 0, 1, 0, + doc: /* Return a list of physical monitor attributes on the X display TERMINAL. + +The optional argument TERMINAL specifies which display to ask about. +TERMINAL should be a terminal object, a frame or a display name (a string). +If omitted or nil, that stands for the selected frame's display. + +In addition to the standard attribute keys listed in +`display-monitor-attributes-list', the following keys are contained in +the attributes: + + source -- String describing the source from which multi-monitor + information is obtained, one of \"Gdk\", \"XRandr\", + \"Xinerama\", or \"fallback\" + +Internal use only, use `display-monitor-attributes-list' instead. */) + (Lisp_Object terminal) +{ + struct x_display_info *dpyinfo = check_x_display_info (terminal); + Lisp_Object attributes_list = Qnil; + +#ifdef USE_GTK + float mm_width_per_pixel, mm_height_per_pixel; + GdkDisplay *gdpy; + GdkScreen *gscreen; + gint primary_monitor = 0, n_monitors, i; + Lisp_Object primary_monitor_attributes = Qnil; + Lisp_Object monitor_frames, rest, frame; + static const char *source = "Gdk"; + + block_input (); + mm_width_per_pixel = ((float) WidthMMOfScreen (dpyinfo->screen) + / x_display_pixel_width (dpyinfo)); + mm_height_per_pixel = ((float) HeightMMOfScreen (dpyinfo->screen) + / x_display_pixel_height (dpyinfo)); + gdpy = gdk_x11_lookup_xdisplay (dpyinfo->display); + gscreen = gdk_display_get_default_screen (gdpy); +#if GTK_MAJOR_VERSION > 2 || GTK_MINOR_VERSION >= 20 + primary_monitor = gdk_screen_get_primary_monitor (gscreen); +#endif + n_monitors = gdk_screen_get_n_monitors (gscreen); + monitor_frames = Fmake_vector (make_number (n_monitors), Qnil); + FOR_EACH_FRAME (rest, frame) + { + struct frame *f = XFRAME (frame); + + if (FRAME_X_P (f) && FRAME_X_DISPLAY_INFO (f) == dpyinfo + && !EQ (frame, tip_frame)) + { + GdkWindow *gwin = gtk_widget_get_window (FRAME_GTK_WIDGET (f)); + + i = gdk_screen_get_monitor_at_window (gscreen, gwin); + ASET (monitor_frames, i, Fcons (frame, AREF (monitor_frames, i))); + } + } + + i = n_monitors; + while (i-- > 0) + { + Lisp_Object geometry, workarea, attributes = Qnil; + gint width_mm = -1, height_mm = -1; + GdkRectangle rec; + + attributes = Fcons (Fcons (Qsource, + make_string (source, strlen (source))), + attributes); + attributes = Fcons (Fcons (Qframes, AREF (monitor_frames, i)), + attributes); + + gdk_screen_get_monitor_geometry (gscreen, i, &rec); + geometry = list4i (rec.x, rec.y, rec.width, rec.height); + +#if GTK_MAJOR_VERSION > 2 || GTK_MINOR_VERSION >= 14 + width_mm = gdk_screen_get_monitor_width_mm (gscreen, i); + height_mm = gdk_screen_get_monitor_height_mm (gscreen, i); +#endif + if (width_mm < 0) + width_mm = rec.width * mm_width_per_pixel + 0.5; + if (height_mm < 0) + height_mm = rec.height * mm_height_per_pixel + 0.5; + attributes = Fcons (Fcons (Qmm_size, + list2i (width_mm, height_mm)), + attributes); + +#if GTK_MAJOR_VERSION > 3 || (GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 4) + gdk_screen_get_monitor_workarea (gscreen, i, &rec); + workarea = list4i (rec.x, rec.y, rec.width, rec.height); +#else + /* Emulate the behavior of GTK+ 3.4. */ + { + XRectangle workarea_r; + + workarea = Qnil; + if (i == primary_monitor && x_get_net_workarea (dpyinfo, &workarea_r)) + { + GdkRectangle work; + + work.x = workarea_r.x; + work.y = workarea_r.y; + work.width = workarea_r.width; + work.height = workarea_r.height; + if (gdk_rectangle_intersect (&rec, &work, &work)) + workarea = list4i (work.x, work.y, work.width, work.height); + } + if (NILP (workarea)) + workarea = geometry; + } +#endif + attributes = Fcons (Fcons (Qworkarea, workarea), attributes); + + attributes = Fcons (Fcons (Qgeometry, geometry), attributes); +#if GTK_MAJOR_VERSION > 2 || (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 14) + { + char *name = gdk_screen_get_monitor_plug_name (gscreen, i); + if (name) + attributes = Fcons (Fcons (Qname, make_string (name, strlen (name))), + attributes); + } +#endif + + if (i == primary_monitor) + primary_monitor_attributes = attributes; + else + attributes_list = Fcons (attributes, attributes_list); + } + + if (!NILP (primary_monitor_attributes)) + attributes_list = Fcons (primary_monitor_attributes, attributes_list); + unblock_input (); +#else /* not USE_GTK */ + + block_input (); + attributes_list = x_get_monitor_attributes (dpyinfo); + unblock_input (); + +#endif /* not USE_GTK */ + + return attributes_list; +} + int x_pixel_width (register struct frame *f) @@ -5701,6 +6270,11 @@ syms_of_xfns (void) DEFSYM (Qundefined_color, "undefined-color"); DEFSYM (Qcompound_text, "compound-text"); DEFSYM (Qcancel_timer, "cancel-timer"); + DEFSYM (Qgeometry, "geometry"); + DEFSYM (Qworkarea, "workarea"); + DEFSYM (Qmm_size, "mm-size"); + DEFSYM (Qframes, "frames"); + DEFSYM (Qsource, "source"); DEFSYM (Qfont_param, "font-parameter"); /* This is the end of symbol initialization. */ @@ -5864,6 +6438,7 @@ When using Gtk+ tooltips, the tooltip face is not used. */); defsubr (&Sx_display_visual_class); defsubr (&Sx_display_backing_store); defsubr (&Sx_display_save_under); + defsubr (&Sx_display_monitor_attributes_list); defsubr (&Sx_wm_set_size_hint); defsubr (&Sx_create_frame); defsubr (&Sx_open_connection); diff --git a/src/xterm.c b/src/xterm.c index e4a681031ef..93473986ca5 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -10251,6 +10251,8 @@ x_term_init (Lisp_Object display_name, char *xrm_option, char *resource_name) { "_NET_WM_WINDOW_OPACITY", &dpyinfo->Xatom_net_wm_window_opacity }, { "_NET_ACTIVE_WINDOW", &dpyinfo->Xatom_net_active_window }, { "_NET_FRAME_EXTENTS", &dpyinfo->Xatom_net_frame_extents }, + { "_NET_CURRENT_DESKTOP", &dpyinfo->Xatom_net_current_desktop }, + { "_NET_WORKAREA", &dpyinfo->Xatom_net_workarea }, /* Session management */ { "SM_CLIENT_ID", &dpyinfo->Xatom_SM_CLIENT_ID }, { "_XSETTINGS_SETTINGS", &dpyinfo->Xatom_xsettings_prop }, diff --git a/src/xterm.h b/src/xterm.h index 16effc5c9ea..dc060fbbcff 100644 --- a/src/xterm.h +++ b/src/xterm.h @@ -346,7 +346,8 @@ struct x_display_info Atom Xatom_net_wm_state, Xatom_net_wm_state_fullscreen, Xatom_net_wm_state_maximized_horz, Xatom_net_wm_state_maximized_vert, Xatom_net_wm_state_sticky, Xatom_net_wm_state_hidden, - Xatom_net_frame_extents; + Xatom_net_frame_extents, + Xatom_net_current_desktop, Xatom_net_workarea; /* XSettings atoms and windows. */ Atom Xatom_xsettings_sel, Xatom_xsettings_prop, Xatom_xsettings_mgr; -- 2.11.4.GIT