A gestural pager assistant



Hi,

Gnome shell describes itself as a slides from the traditional desktop metaphor. The consequence is more tasks bar, no more desktop pager... The usual graphical artifacts that used to be displayed continuously and that used to guide users through windows managements are now replaced by plenty of shortcut keys to learn.

Unfortunately, most of the shortcut keys seem to have been preconfigured with a right-handed mind. A right-handed user has its right hand on the mouse, and its left hand on the keyboard. Not so surprisingly, most interesting shortcut keys are on the left part of the keyboard (e.g. Alt-Left, Tab, Escape). A left-handed user (10% to 12% of the population) has its left hand on the mouse, not on the keyboard...

This two above mentioned points may be very disturbing.  So, what's to do?
Either go back to the past (e.g. gnome-panel) as suggested by plenty of blogs around the web that comment gnome-shell, or continue through innovation!

Motivated by this second strategy, I propose to extend gestural functionality in gnome-shell.


Attached code is (just) a proof of concept, named "gestural pager assistant" (or gpa in short).
Compile it with: gcc -Wall -g -O2 -o gpa gpa.c -lX11

Press <Control>+<Button1>, then move your pointer up and down to go to next or previous desktop, or move your pointer left and right to navigate through windows of the current desktop.

Okay, it's very basic. But please, try it! I hope this may convince you to introduce more gesture into gnome-shell.

Best Wishes for the New Year
Regards Christophe

PS: Let me please suggest other points to enhance user experience:
- allowing arrow keys on the "dash" bar and into "Activities" menus
- moving the pointer on the to right corner to get the pager bar
(Today one have to move to pointer to the top left corner to get the dash bar and the pager bar, then make the mouse crossing the screen to select a desktop on the pager bar which is on the other side of the screen)

/* 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;

  XSetIOErrorHandler(io_handler);
  
  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.xmotion.x_root;
        }
        if (vertical) {
          if (revert)
            focus_window(dpy, vertical);
          else
            change_desktop(dpy, -vertical);
          start.y = ev.xmotion.y_root;
        }
    }
  }
}
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" \
  " --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 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'},
      {"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 '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);
  }
  
  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]