Re: A gestural pager assistant



Le 03/01/2012 15:49, Jasper St. Pierre a écrit :
We've seen the Num Lock bug in other things. We're not sure what
causes it yet, but it's not our bug we swear :)

With the XGrabButton() function my code establishes a passive grab on Control+Button1. However, if NumLock is activated, when one press Control+Button1 the actual triggered event is NumLock+Control+Button1; which cause no pointer grab.
(If CapsLock is activated then it also fails).

The attached code proposes a workaround: at initialization stage I retrieves currently activated modifiers with an XQueryPointer and add wanted modifiers to it. (The new "-n" option avoids it, if wanted.)
It works.
However it is ugly since I can't see how to be warned when the user press NumLock or CapsLock during the session...

Regards
Christophe
/* Gestural Pager Assistant
 * (c) 2011 Christophe Lohr <clohr users sourceforge net> 
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/cursorfont.h>
#include <getopt.h>
#include <strings.h>
#include <string.h>

// Mostly from wmctrl - Tomas Styblo <tripie cpan org>
static int client_msg(Display * dpy, Window win, Window root, char *msg,
                      unsigned long data) {
  XEvent event;
  long mask = SubstructureRedirectMask | SubstructureNotifyMask;

  event.xclient.type = ClientMessage;
  event.xclient.serial = 0;
  event.xclient.send_event = True;
  event.xclient.message_type = XInternAtom(dpy, msg, False);
  event.xclient.window = win;
  event.xclient.format = 32;
  event.xclient.data.l[0] = data;
  event.xclient.data.l[1] = CurrentTime;
  event.xclient.data.l[2] = 0;
  event.xclient.data.l[3] = 0;
  event.xclient.data.l[4] = 0;

  if (XSendEvent(dpy, root, False, mask, &event)) {
    return EXIT_SUCCESS;
  } else {
    fprintf(stderr, "Cannot send %s event.\n", msg);
    return EXIT_FAILURE;
  }
}


#define MAX_PROPERTY_VALUE_LEN 4096
static unsigned char *get_property(Display *dpy, Window win,
          Atom xa_prop_type, char *prop_name, unsigned long *size) {
  Atom xa_prop_name;
  Atom xa_ret_type;
  int ret_format;
  unsigned long ret_nitems;
  unsigned long ret_bytes_after;
  unsigned char *ret_prop;
  unsigned long tmp_size;

  xa_prop_name = XInternAtom(dpy, prop_name, False);

  if (XGetWindowProperty(dpy, win, xa_prop_name, 0,
        MAX_PROPERTY_VALUE_LEN / 4, False, xa_prop_type, &xa_ret_type, 
        &ret_format, &ret_nitems, &ret_bytes_after, &ret_prop) != Success) {
    fprintf(stderr, "Cannot get %s property.\n", prop_name);
    return NULL;
  }

  if (xa_ret_type != xa_prop_type) {
    fprintf(stderr, "Invalid type of %s property.\n", prop_name);
    XFree(ret_prop);
    return NULL;
  }

  tmp_size = (ret_format / 8) * ret_nitems;
  /* Correct 64 Architecture implementation of 32 bit data */
  if(ret_format==32) 
    tmp_size *= sizeof(long)/4;
  
  if (size)
    *size = tmp_size;
            
  return ret_prop;
}


static Window *get_client_list(Display *dpy, unsigned long *size) {
  Window *client_list;

  if ((client_list = (Window *) get_property(dpy, DefaultRootWindow(dpy),
        XA_WINDOW, "_NET_CLIENT_LIST", size)) == NULL)
    if ((client_list = (Window *) get_property(dpy, DefaultRootWindow(dpy), 
        XA_CARDINAL, "_WIN_CLIENT_LIST", size)) == NULL) {
      fputs("Cannot get client list properties.\n", stderr);
      return NULL;
    }
  *size /= sizeof(Window);
  return client_list;
}


static unsigned long *get_cur_desktop(Display *dpy) {
  unsigned long *cur_desktop = NULL;
  
  if (!(cur_desktop = (unsigned long *)get_property(dpy, 
      DefaultRootWindow(dpy), XA_CARDINAL, "_NET_CURRENT_DESKTOP", NULL))) {
    if (!(cur_desktop = (unsigned long *)get_property(dpy, 
        DefaultRootWindow(dpy),  XA_CARDINAL, "_WIN_WORKSPACE", NULL))) {
      fputs("Cannot get current desktop properties.\n", stderr);
      return NULL;
    }
  }
  return cur_desktop;
}


static unsigned long *get_desk_window(Display *dpy, Window win) {
  unsigned long *desktop = NULL;
  
  if ((desktop = (unsigned long *)get_property(dpy, win, XA_CARDINAL, 
        "_NET_WM_DESKTOP", NULL)) == NULL) {
    desktop = (unsigned long *)get_property(dpy, win, XA_CARDINAL, 
        "_WIN_WORKSPACE", NULL);
  }
  return desktop;
}


static unsigned long *get_num_desktops(Display *dpy) {
  unsigned long *num_desktops = NULL;

  if (!(num_desktops = (unsigned long *)get_property(dpy,
     DefaultRootWindow(dpy), XA_CARDINAL, "_NET_NUMBER_OF_DESKTOPS", NULL))) {
    if (!(num_desktops = (unsigned long *)get_property(dpy,
        DefaultRootWindow(dpy), XA_CARDINAL, "_WIN_WORKSPACE_COUNT", NULL))) {
      fputs("Cannot get number of desktops properties.\n", stderr);
      return NULL;
    }
  }
  return num_desktops;
}


static Window *get_active_window(Display *dpy) {
  return (Window *)get_property(dpy, DefaultRootWindow(dpy), XA_WINDOW, 
              "_NET_ACTIVE_WINDOW", NULL);
}
// End of code from wmctrl


int change_desktop(Display *dpy, int next) {
  long target;
  unsigned long *num_desktops;
  unsigned long *cur_desktop;
  int ret = EXIT_SUCCESS;

  if (!(cur_desktop = get_cur_desktop(dpy)))
    return EXIT_FAILURE;

  if (!(num_desktops = get_num_desktops(dpy))) {
    XFree(cur_desktop);
    return EXIT_FAILURE;
  }

  target = next + *cur_desktop;
  if (target < 0)
    target = 0;
  if (target >= *num_desktops)
    target = *num_desktops - 1;

  if (target != *cur_desktop) {
    ret = client_msg(dpy, DefaultRootWindow(dpy), DefaultRootWindow(dpy), 
               "_NET_CURRENT_DESKTOP", target);
    XSync(dpy, False);
  }
  XFree(num_desktops);
  XFree(cur_desktop);
  return ret;
}


int focus_window(Display * dpy, int next) {
  Window *client_list, *active;
  unsigned long client_list_size = 0;
  int i, j, active_i = 0, target, step;
  unsigned long *cur_desktop = NULL, *desktop = NULL;
  int ret = EXIT_SUCCESS;

  if (!(client_list = get_client_list(dpy, &client_list_size)))
    return EXIT_FAILURE;
  if (!client_list_size)
    return EXIT_SUCCESS; // nothing to do
    
  if ((active = get_active_window(dpy))) { 
    // retreives the active window within the clients list
    for (i = 0; i < client_list_size; i++)
      if (client_list[i] == *active) {
        active_i = i;
        break;
      }
    XFree(active);
    if (i == client_list_size) {
      // Cannot retreive active window within clients
      XFree(client_list);
      active_i = 0;
    }
  } else // no window has the focus yet, select the first one
    active_i = 0;

  if (!(cur_desktop = get_cur_desktop(dpy))) {
    XFree(client_list);
    return EXIT_FAILURE;
  }
  
  // select the "next" window of the current desktop
  step = (next > 0) ? +1 : -1;
  target = j = active_i;
  for (i = 1; (i < client_list_size) && next; i++) {
    j += step;
    target = j % (int)client_list_size;
    if (target < 0) 
      target += client_list_size;
    desktop = get_desk_window(dpy, client_list[target]);
    if (*desktop == *cur_desktop)
      next-=step;
    XFree(desktop);
  }
  XFree(cur_desktop);

  if (!next) {
    ret = client_msg(dpy, client_list[target], DefaultRootWindow(dpy), 
                     "_NET_ACTIVE_WINDOW", 2);
    XMapRaised(dpy, client_list[target]);
    XSync(dpy, False);
  }
  XFree(client_list);
  return ret;
}


int io_handler(Display *dpy) {
  // Display closed, stop.
  exit(EXIT_SUCCESS);
}


struct point {
  int x, y;
};


void main_loop(Display *dpy, struct point amplitude, int revert, 
               unsigned int button, unsigned int modifiers) {
  struct point start = { 0, 0 };
  XEvent ev;

  XGrabButton(dpy, button, modifiers, DefaultRootWindow(dpy), True,
              ButtonMotionMask | PointerMotionMask,
              GrabModeAsync, GrabModeAsync, None,
              XCreateFontCursor(dpy, XC_fleur));

  while (1) {
    int vertical, horizontal;
    XNextEvent(dpy, &ev);
    switch (ev.type) {
      case ButtonPress:
        start.x = ev.xbutton.x_root;
        start.y = ev.xbutton.y_root;
        break;
      case MotionNotify:
        horizontal = (ev.xmotion.x_root - start.x) / amplitude.x;
        vertical = (ev.xmotion.y_root - start.y) / amplitude.y;
        if (horizontal) {
          if (revert)
            change_desktop(dpy, horizontal);
          else
            focus_window(dpy, horizontal);
          start.x = ev.xbutton.x_root;
        }
        if (vertical) {
          if (revert)
            focus_window(dpy, vertical);
          else
            change_desktop(dpy, -vertical);
          start.y = ev.xbutton.y_root;
        }
      break;
    }
  }
}
void main_loop() __attribute__((noreturn));

#define USAGE   \
  "Usage: %s [OPTIONS]\n" \
  "Gestural pager assistant.\n" \
  "Press <modifiers>+<button>, then move your mouse up/down to navigate\n" \
  "to next/previous desktop, or left/right to navigate through windows\n" \
  "of the current desktop.\n\n" \
  "Options:\n" \
  " --amplitude_x,-x <int>     Vertical/horizontal amplitude of a gesture;\n" \
  " --amplitude_y,-y <int>     unit is (mm) millimeters, (cm) centimeters, \n" \
  "     or (px) pixels; a negative value inverses navigation direction.\n" \
  " --button,-b <unsigned>     Grab button number.\n" \
  "     0:AnyButton  1:Button1  2:Button2  3:Button3  etc.\n" \
  " --modifiers,-m <unsigned>  Grab modifiers mask. Please compute yourself\n" \
  "     a bitwise inclusive OR of:\n" \
  "     0:None  1:ShiftMask(1<<0)  2:LockMask(1<<1)  4:ControlMask(1<<2)\n" \
  "     8:Mod1Mask(1<<3)  16:Mod2Mask(1<<4)  32:Mod3Mask(1<<5) \n" \
  "     64:Mod4Mask(1<<6)  128:Mod5Mask(1<<7)  32768:AnyModifier(1<<15)\n" \
  " --no_add_mod,-n            Do not add currently activated modifiers if any\n" \
  "     such as num lock (Mod2Mask), caps lock (LockMask), etc.\n" \
  " --display,-d <string>      X11 display to connect to.\n" \
  " --revert,-r                Revert gestures actions.\n" \
  "     Up/down gestures navigates through windows, left/right through desks.\n" \
  " --help,-h                  Print this help and exit.\n\n" \
  "Default is: %s -m 1 -b 1 -x 3cm -y 3cm\n" \
  "i.e.: Press Control+Button1, then have gesture amplitude of 3cm.\n"


int main(int argc, char **argv) {
  char *display_name = NULL;
  Display *dpy;
  Screen *screen;
  struct point size, size_mm;
  struct point amplitude = {0, 0}, amp_mm = {30, 30};
  unsigned int button = Button1;
  unsigned int modifiers = ControlMask;
  int no_add_mod = 0;
  int revert = 0;
  int c, unexpected = 0;  
  char *endptr;
  long val;

  while (1) {
    int option_index = 0;
    static struct option long_options[] = {
      {"amplitude_x",   required_argument, 0, 'x'},
      {"amplitude_y",   required_argument, 0, 'y'},
      {"button",        required_argument, 0, 'b'},
      {"display",       required_argument, 0, 'd'},
      {"modifiers",     required_argument, 0, 'm'},
      {"no_add_mod",	no_argument,       0, 'n'},
      {"revert",        no_argument,       0, 'r'},
      {"help",          no_argument,       0, 'h'},
      {0, 0, 0, 0} };
      
    c = getopt_long(argc, argv, "x:y:b:m:d:rh", long_options, &option_index);
    if (c == -1)
      break;
      
    switch (c) {
    case 'x':
      errno = 0;
      val = strtol(optarg, &endptr, 0);
      if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
            || (errno != 0 && val == 0)) {
        perror("strtol on `amplitude_x'");
        unexpected = 1;
      } else {
        if ( (val < INT_MIN) || (val > INT_MAX) ) {
          fprintf(stderr, "Amplitude_x %ld out of bounds.\n", val);
          unexpected = 1;
        }
        if ( endptr == optarg ) {
          fprintf(stderr, "Amplitude_x `%s' is not number.\n", optarg);
          unexpected = 1;
        }
        if ( strcasecmp(endptr, "px") == 0 )
          amplitude.x = val;
        else if ( strcasecmp(endptr, "mm") == 0 )
          amp_mm.x = val;
        else if ( strcasecmp(endptr, "cm") == 0 )
          amp_mm.x = 10 * val;
        else {
          fprintf(stderr, "Unexpected unit `%s' for amplitude_x.\n", endptr);
          unexpected = 1;
        }
      }
      break;
    case 'y':
      errno = 0;
      val = strtol(optarg, &endptr, 0);
      if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
            || (errno != 0 && val == 0)) {
        perror("strtol on `amplitude_y'");
        unexpected = 1;
      } else {
        if ( (val < INT_MIN) || (val > INT_MAX) ) {
          fprintf(stderr, "Amplitude_y %ld out of bounds.\n", val);
          unexpected = 1;
        }
        if ( endptr == optarg ) {
          fprintf(stderr, "Amplitude_y `%s' is not number.\n", optarg);
          unexpected = 1;
        }
        if ( strcasecmp(endptr, "px") == 0 )
          amplitude.y = val;
        else if ( strcasecmp(endptr, "mm") == 0 )
          amp_mm.y = val;
        else if ( strcasecmp(endptr, "cm") == 0 )
          amp_mm.y = 10 * val;
        else {
          fprintf(stderr, "Unexpected unit `%s' for amplitude_y.\n", endptr);
          unexpected = 1;
        }
      }
      break;
    case 'b':
      val = atol(optarg);
      if ( (val < 0) || (val > UINT_MAX) ) {
        fprintf(stderr, "Invalid button number %ld.\n", val);
        unexpected = 1;
      } else
        button = val;
      break;
    case 'm':
      errno = 0;
      val = strtol(optarg, &endptr, 0);
      if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
            || (errno != 0 && val == 0)) {
        perror("strtol on `modifiers mask'");
        unexpected = 1;
      } else {
        if ( (val < 0) || (val > UINT_MAX) ) {
          fprintf(stderr, "Modifier mask %ld out of bounds.\n", val);
          unexpected = 1;
        }
        if ( (endptr == optarg) || (*endptr != '\0') ) {
          fprintf(stderr, "Modifier mask `%s' is not number.\n", optarg);
          unexpected = 1;
        }
        modifiers = val;
      }
      break;
    case 'n':
      no_add_mod = 1;
      break;
    case 'd':
      display_name = optarg;
      break;
    case 'r':
      revert = 1;
      break;
    case 'h':
      printf(USAGE, argv[0], argv[0]);
      exit(EXIT_SUCCESS);
      break;
    default:
      unexpected = 1;
    }
  }
  while (optind < argc) {
    fprintf(stderr, "Unexpected argument `%s'\n", argv[optind++]);
    unexpected = 1;
  }
  if (unexpected) {
    fprintf(stderr, "Try: %s --help\n", argv[0]);
    exit(EXIT_FAILURE);
  }
    
  if (! (dpy = XOpenDisplay(display_name)) ) {
    fprintf(stderr, "Cannot open display %s\n", XDisplayName(display_name));
    exit(EXIT_FAILURE);
  }

  XSetIOErrorHandler(io_handler);

  if (!no_add_mod) {
    Window root_return, child_return;
    int root_x_return, root_y_return, win_x_return, win_y_return;
    unsigned int mask_return;
          
    XQueryPointer(dpy, DefaultRootWindow(dpy),
                  &root_return, &child_return,
                  &root_x_return, &root_y_return,
                  &win_x_return, &win_y_return, &mask_return);
    modifiers |= mask_return;
  }
  
  screen = XDefaultScreenOfDisplay(dpy);
  size.x = XWidthOfScreen(screen);
  size.y = XHeightOfScreen(screen);
  size_mm.x = XWidthMMOfScreen(screen);
  size_mm.y = XHeightMMOfScreen(screen);
  if (!amplitude.x)
    amplitude.x = amp_mm.x * size.x / size_mm.x;
  if (!amplitude.y)
    amplitude.y = amp_mm.y * size.y / size_mm.y;

  main_loop(dpy, amplitude, revert, button, modifiers);
}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]