xscsize.c, etc.: Report geometry of individual monitors.
authorMark Wooding <mdw@distorted.org.uk>
Fri, 22 Apr 2016 17:53:01 +0000 (18:53 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Fri, 22 Apr 2016 17:59:58 +0000 (18:59 +0100)
This is enabled by the new `-m' option.

Makefile.am
configure.ac
debian/control
xscsize.1
xscsize.c

index 8b99a3a..676c52b 100644 (file)
@@ -50,6 +50,7 @@ LDADD                  = -lX11 $(X_LIBS)
 bin_PROGRAMS           += xscsize
 dist_man_MANS          += xscsize.1
 xscsize_SOURCES                 = xscsize.c
+xscsize_LDADD           = $(LDADD) $(XRANDR_LIBS)
 
 ## xatom.
 bin_PROGRAMS           += xatom
index 7a635b3..ce290ec 100644 (file)
@@ -40,6 +40,22 @@ AC_PROG_CC
 AX_CFLAGS_WARN_ALL
 AC_PATH_XTRA
 
+mdw_LIBS=$LIBS LIBS= mdw_have_xrandr_p=nil
+AC_SEARCH_LIBS([XRRGetScreenInfo], [Xrandr], [mdw_have_xrandr_p=t])
+case $mdw_have_xrandr_p in
+  t)
+    AC_CHECK_HEADERS([X11/extensions/Xrandr.h], [], [mdw_have_xrandr_p=nil])
+    ;;
+esac
+case $mdw_have_xrandr_p in
+  t)
+    XRANDR_LIBS=$LIBS
+    AC_DEFINE([HAVE_XRANDR], [1], [Define if libXrandr is installed.])
+    ;;
+esac
+LIBS=$mdw_LIBS
+AC_SUBST([XRANDR_LIBS])
+
 PKG_CHECK_MODULES([mLib], [mLib >= 2.0.4])
 CFLAGS="$CFLAGS $mLib_CFLAGS"
 LIBS="$LIBS $mLib_LIBS"
index 49e6b1e..8003ed0 100644 (file)
@@ -2,7 +2,7 @@ Source: xtoys
 Section: x11
 Priority: extra
 Build-Depends: debhelper (>= 9), pkg-config,
-       libx11-dev, mlib-dev (>= 2.0.4)
+       libx11-dev, libxrandr-dev, mlib-dev (>= 2.0.4)
 Build-Depends-Indep: python-central, python (>= 2.4),
        python-gtk2
 Maintainer: Mark Wooding <mdw@distorted.org.uk>
index 0ba808c..c06ea8e 100644 (file)
--- a/xscsize.1
+++ b/xscsize.1
@@ -4,7 +4,7 @@
 xscsize \- return size of an X display to a shell script
 .SH SYNOPSIS
 .B xscsize
-.RB [ \-bcx ]
+.RB [ \-bcmx ]
 .RB [ \-d
 .IR display ]
 .SH DESCRIPTION
@@ -18,6 +18,13 @@ and
 .B XHEIGHT
 variables.
 .PP
+With the
+.B \-m
+option (see below),
+.B xscsize
+instead reports the geometry of the individual monitors (CRTCs) of a
+(possibly) multihead display.
+.PP
 Command line options can be used to force output in either Bourne or C
 shell syntax.  In the absence of any explicit instructions,
 .B xscsize
@@ -45,16 +52,98 @@ POSIX, Korn, Z and Bourne Again shells).
 Output the assignments in C shell syntax (usable by C and Terminal C
 shells).
 .TP 5
+.B \-m, \-\-multiscreen
+Output geometry information about the individual monitors of a
+(possibly) multihead display, rather than the overall size of the root
+window.  This sets
+.B XNSCR
+to the number of monitors, and
+.BI XSCR n _X \fR,
+.BI XSCR n _Y \fR,
+.BI XSCR n _WIDTH \fR,
+and
+.BI XSCR n _HEIGHT \fR
+to the
+.IR x -
+and
+.IR y -coordinates
+of the pixel shown in the top-left of monitor
+.I n
+(starting from zero), and the width and height of monitor
+.I n
+in pixels.  The monitors will be sorted in order of increasing
+.IR y -coordinate
+(i.e., top to bottom), with monitors with equal
+.IR y -coordinates
+sorted in order of increasing
+.IR x -coordinate
+(i.e., left to right).  Usually, then, the first monitor will be at
+(0, 0).
+.IP
+This requires that support libraries for the X RANDR extension were
+available when
+.B xscsize
+was compiled, and that the display actually implements at least version
+1.2 of the RANDR extension.  If not,
+.B xscsize
+will still accept the
+.B \-m
+option, and produce appropriately formatted output, but will assume that
+there is exactly one monitor, and that it shows the entire root window.
+.TP 5
 .B \-x, --export
 Output a variable export command, so that the screen size is inherited
 by child processes.  The default is to just set local shell variables.
 .SH ENVIRONMENT
 .TP
 .B XWIDTH
-Set to the width of the display in pixels.
+Set to the width of the display in pixels (not in
+.B \-m
+mode).
 .TP
 .B XHEIGHT
-Set to the height of the display in pixels.
+Set to the height of the display in pixels (not in
+.B \-m
+mode).
+.TP
+.B XNSCR
+The number of monitors attached to the display (only in
+.B \-m
+mode).
+.TP
+.BI XSCR n _X
+The
+.I x -coordinate
+of the top-left pixel shown on monitor number
+.I n
+counting from zero (only in
+.B \-m
+mode).
+.TP
+.BI XSCR n _Y
+The
+.I y -coordinate
+of the top-left pixel shown on monitor number
+.I n
+counting from zero (only in
+.B \-m
+mode).
+.TP
+.BI XSCR n _WIDTH
+The width, in pixels, of the portion of the root window shown on monitor
+number
+.I n
+counting from zero (only in
+.B \-m
+mode).
+.TP
+.BI XSCR n _HEIGHT
+The height, in pixels, of the portion of the root window shown on monitor
+number
+.I n
+counting from zero (only in
+.B \-m
+mode).
 .TP
 .B SHELL
 Used to decide the nature of the calling shell.  If absent, a Bourne
index 46d58f7..99d729f 100644 (file)
--- a/xscsize.c
+++ b/xscsize.c
 #include <string.h>
 
 #include <X11/Xlib.h>
+#ifdef HAVE_XRANDR
+#  include <X11/extensions/Xrandr.h>
+#endif
 
+#include <mLib/alloc.h>
+#include <mLib/dstr.h>
 #include <mLib/mdwopt.h>
 #include <mLib/quis.h>
 
+/*----- Data structures ---------------------------------------------------*/
+
+struct screen {
+  int x, y;
+  unsigned wd, ht;
+};
+
 /*----- Static variables --------------------------------------------------*/
 
 static unsigned int flags = 0;
@@ -74,8 +86,10 @@ Options:\n\
        fp);
 }
 
-static void print_var(const char *name, unsigned long value)
+static void print_var(const char *name, int index, unsigned long value)
 {
+  dstr d = DSTR_INIT;
+
   if (index >= 0) {
     dstr_putf(&d, "XSCR%d_%s", index, name);
     name = d.buf;
@@ -91,6 +105,14 @@ static void print_var(const char *name, unsigned long value)
   dstr_destroy(&d);
 }
 
+static int compare_screen(const void *a, const void *b)
+{
+  const struct screen *s = a, *t = b;
+  if (s->y != t->y) return (s->y < t->y ? -1 : +1);
+  else if (s->x != t->x) return (s->x < t->x ? -1 : +1);
+  else return (0);
+}
+
 int main(int argc, char *argv[])
 {
   Display *dpy;
@@ -99,8 +121,18 @@ int main(int argc, char *argv[])
   unsigned f = 0;
   unsigned long wd, ht;
   int sc;
+#ifdef HAVE_XRANDR
+  Window root;
+  int rrev, rrerr, rrmaj, rrmin;
+  XRRScreenResources *res;
+  XRRCrtcInfo *crtc;
+  struct screen *scr;
+  size_t nscr, j;
+  int i;
+#endif
 
 #define f_bogus 1u
+#define f_multi 2u
 
   /* --- Parse command line options --- */
 
@@ -114,11 +146,12 @@ int main(int argc, char *argv[])
       { "display",     OPTF_ARGREQ,    0,      'd' },
       { "bourne-shell",        0,              0,      'b' },
       { "c-shell",     0,              0,      'c' },
+      { "multiscreen", 0,              0,      'm' },
       { "export",      0,              0,      'x' },
       {        0,              0,              0,      0 }
     };
 
-    int i = getopt_long(argc, argv, "huv" "d:bcx", opt, 0);
+    int i = getopt_long(argc, argv, "huv" "d:bcmx", opt, 0);
     if (i < 0) break;
     switch (i) {
       case 'h': help(stdout); exit(0); break;
@@ -127,6 +160,7 @@ int main(int argc, char *argv[])
       case 'd': display = optarg; break;
       case 'b': flags |= F_SH; break;
       case 'c': flags |= F_CSH; break;
+      case 'm': f |= f_multi; break;
       case 'x': flags |= F_EXPORT; break;
       default: f |= f_bogus; break;
     }
@@ -153,22 +187,84 @@ int main(int argc, char *argv[])
     exit(EXIT_FAILURE);
   }
 
-  /* --- Get the important information --- */
+  /* --- Open the display --- */
 
   dpy = XOpenDisplay(display);
   if (!dpy) {
     fprintf(stderr, "xscsize: couldn't open display\n");
     exit(EXIT_FAILURE);
   }
+
+  /* --- Fetch the root window size --- *
+   *
+   * We might need this whatever happens, so go with the flow.
+   */
+
   sc = DefaultScreen(dpy);
   wd = DisplayWidth(dpy, sc);
   ht = DisplayHeight(dpy, sc);
-  XCloseDisplay(dpy);
 
-  /* --- Do the output thing --- */
+  /* --- Calculate and produce the necessary output --- *
+   *
+   * If we're meant to report on individual screens then try to collect
+   * information about them using the RANDR extension.  If that doesn't
+   * exist, or the version is too ancient, or its otherwise not going to
+   * work, then pretend there's just one screen that's the size of the root
+   * window.
+   */
+
+  if (f & f_multi) {
+#ifdef HAVE_XRANDR
+    if (XRRQueryExtension(dpy, &rrev, &rrerr) &&
+       XRRQueryVersion(dpy, &rrmaj, &rrmin) &&
+       (rrmaj > 1 || (rrmaj == 1 && rrmin >= 2))) {
+      root = RootWindow(dpy, sc);
+      res = XRRGetScreenResources(dpy, root);
+      scr = xmalloc(res->ncrtc*sizeof(*scr)); j = 0;
+      for (i = 0; i < res->ncrtc; i++) {
+       crtc = XRRGetCrtcInfo(dpy, res, res->crtcs[i]);
+       if (crtc->mode) {
+         scr[j].x = crtc->x; scr[j].wd = crtc->width;
+         scr[j].y = crtc->y; scr[j].ht = crtc->height;
+         j++;
+       }
+       XRRFreeCrtcInfo(crtc);
+      }
+      nscr = j;
+      XRRFreeScreenResources(res);
+    } else
+#endif
+    {
+      /* --- The RANDR extension isn't available --- */
+
+      nscr = 1;
+      scr = xmalloc(sizeof(*scr));
+      scr->x = 0; scr->wd = wd;
+      scr->y = 0; scr->ht = ht;
+    }
+
+    /* --- Sort and report the screens --- *
+     *
+     * The chances are good that the screens reported by RANDR aren't in any
+     * especially useful order.  Sort them into (my) reading order.
+     */
 
-  print_var("XWIDTH", wd);
-  print_var("XHEIGHT", ht);
+    qsort(scr, nscr, sizeof(*scr), compare_screen);
+    print_var("XNSCR", -1, nscr);
+    for (j = 0; j < nscr; j++) {
+      print_var("X", j, scr[j].x);
+      print_var("Y", j, scr[j].y);
+      print_var("WIDTH", j, scr[j].wd);
+      print_var("HEIGHT", j, scr[j].ht);
+    }
+  } else {
+    print_var("XWIDTH", -1, wd);
+    print_var("XHEIGHT", -1, ht);
+  }
+
+  /* --- We're done with the display now --- */
+
+  XCloseDisplay(dpy);
 
   /* --- Done --- */