master
Eric Messick 2013-05-07 11:06:51 -07:00
rodzic c2521c6c45
commit d399f2edd7
10 zmienionych plików z 1457 dodań i 0 usunięć

Wyświetl plik

@ -0,0 +1,6 @@
# Allows global read access to any ShuttlePRO device
# install this file in /etc/udev/rules.d
ATTRS{name}=="Contour Design ShuttlePRO v2" MODE="0644"

28
Makefile 100644
Wyświetl plik

@ -0,0 +1,28 @@
# Copyright 2013 Eric Messick (FixedImagePhoto.com/Contact)
#CFLAGS=-g -W -Wall
CFLAGS=-O3 -W -Wall
INSTALL_DIR=/usr/local/bin
OBJ=\
readconfig.o \
shuttlepro.o
all: shuttlepro
install: all
install shuttle shuttlepro ${INSTALL_DIR}
shuttlepro: ${OBJ}
gcc ${CFLAGS} ${OBJ} -o shuttlepro -L /usr/X11R6/lib -lX11 -lXtst
clean:
rm -f shuttlepro keys.h $(OBJ)
keys.h: keys.sed /usr/include/X11/keysymdef.h
sed -f keys.sed < /usr/include/X11/keysymdef.h > keys.h
readconfig.o: shuttle.h keys.h
shuttlepro.o: shuttle.h

45
README 100644
Wyświetl plik

@ -0,0 +1,45 @@
Copyright 2013 Eric Messick (FixedImagePhoto.com/Contact)
This is a user program for interpreting key, shuttle, and jog events
from a Contour Design ShuttlePRO v2. It translates these events into
X keystrokes, mouse button presses, or scroll wheel events. It was
developed and tested on kubuntu 12.10.
ShuttlePRO events can generate sequences of multiple keystrokes,
including the pressing and releasing of modifier keys. The binding
can be selected based on the title of the window which is focused.
Build instructions:
# apt-get install build-essential libx11-dev libxtst-dev
$ make
Install instructions:
# cp 99-ShuttlePRO.rules /etc/udev/rules.d
# make install
This will install the binary "shuttlepro" and a script "shuttle" in
/usr/local/bin. The udev .rules file allows global read access to any
ShuttlePRO v2 devices plugged into the system. The script passes the
name of the shuttle device to the binary, so you can configure it
there if it is different on your system.
Configuration instructions:
Copy the example.shuttlerc file to $HOME/.shuttlerc and edit it
there. While you are configuring this file, you probably want to run
the "shuttle" script from a terminal to see the program output. Make
sure /usr/local/bin is in your $PATH.
The program re-reads the .shuttlerc file whenever it notices that the
file has been changed, but it only checks when a shuttle event is sent
with a different window focused from the previous shuttle event. When
you save a new version of the .shuttlerc file, it is a good idea to
deliver shuttle events to two different windows to insure that the new
copy of your file is loaded.
See the example.shuttlerc file for information about the file. You
may also want to look at the comment at the top of readconfig.c.

18
TODO 100644
Wyświetl plik

@ -0,0 +1,18 @@
Should be wrapped up in an installation package.
Should start automatically on login.
But note that debugging output is useful when configuring.
Should restart on hotplug.
This would help with crashes caused by static discharges.
If it starts on reboot, needs to notice current user to find config
file.
Should probably use uinput instead of XTest.
Might want to allow shuttle positions to allow repeated outputs at a
specified time interval.
A GUI for setting keystroke bindings would be nice.

119
example.shuttlerc 100644
Wyświetl plik

@ -0,0 +1,119 @@
# Copyright 2013 Eric Messick (FixedImagePhoto.com/Contact)
#
# Lines in this file starting with # are comments.
#
#
# This file is divided into paragraphs, each specifying the bindings to
# be used when the keyboard focus is on a specific window. The
# paragraph is introduced with a line starting with [. That line
# contains the paragraph name (which is only used for debugging output
# to help you in editing this file) followed by ], followed by a regular
# expression. When the title bar of the focused window matches the
# regular expression (see regex(7)), the bindings in the paragraph will
# be in effect. The program tries these regular expressions in order,
# and the first match is used.
# If there is no regex on the line, like the [Default] line near the
# bottom, the paragraph acts as a default. Any window title which does
# not match any regex will use the default bindings. Any keys which are
# not specified in the paragraph which does match will use the default
# bindings for those keys.
# While you are working on regular expressions to match your window
# names, is is useful to see the window names and paragraph names which
# the program finds as you generate ShuttlePRO events. Run the shuttle
# program in a terminal window and remove the comment character from the
# following line:
#DEBUG_REGEX
# Within a paragraph, key bindings are introduced with the name of the
# key or event being defined. Keys are named K1 through K15. Positions
# of the shuttle wheel are named S-7 through S-1 for counter-clockwise
# positions, S0 for the rest position in the center, and S1 through S7
# for the clockwise positions. The jog wheel emits two events named JL
# and JR, for counter-clockwise and clockwise rotations respectively.
# The keys on the Contour Shuttle Pro v2 are arranged like this:
#
# K1 K2 K3 K4
# K5 K6 K7 K8 K9
#
# K14 Jog K15
#
# K10 K11
# K12 K13
# After the name of the key being bound, the remainder of the line is
# the sequence of X KeySyms which will be generated when that event is
# received. Look up the KeySyms in /usr/include/X11/keysymdef.h. In
# addition to the KeySym names found there, you can also use XK_Button_1
# for the left mouse button, XK_Button_2 for the middle mouse button,
# XK_Button_3 for the right mouse button, XK_Scroll_Up and
# XK_Scroll_Down for mouse scroll wheel events. For sequences of one or
# more printable characters, you can just enclose them in double quotes.
# Each KeySym you specify will be pressed and released before the next
# KeySym is pressed. If you wish a key to be held down, you can add a
# /D to the end of the KeySym. For example: XK_Shift_L/D,
# XK_Control_L/D or XK_Alt_L/D. Such keys will be held down until you
# specify they should be released with a /U on the same KeySym name.
# They will all be released at the end of the binding anyway, so you
# usually won't have to use /U.
# Key bindings, whose names start with a K, allow for some extra
# options. Since they generate separate events when pressed and
# released, you can control that as well. Each non-modifier key is
# pressed and released in sequence except for the last which is not
# released until the shuttle key is released. If you want to press more
# keys during the release sequence, you can put them after the special
# word "RELEASE". Modifier keys specified with /D are released at the
# end of the press sequence, and re-pressed if there are any keys to be
# pressed after RELEASE. If you don't want the modifier keys to be
# released (you want to use a ShuttlePRO key as Shift, for example) you
# can follow it with a /H instead of /D.
# If you want to see exactly how this file is parsed and converted into
# KeySym strokes, run the shuttle program in a terminal window and
# remove the comment character from the following line:
#DEBUG_STROKES
# As one of the main reasons to use a ShuttlePRO is video editing, I've
# included a sample set of bindings for Cinelerra as an example.
[Cinelerra Resources] ^Cinelerra: Resources$
# use [Default], avoiding main Cinelerra rule
[Cinelerra Load] ^Cinelerra: Load$
# use [Default], avoiding main Cinelerra rule
[Cinelerra] ^Cinelerra: [^[:space:]]*$
K5 XK_KP_0 # Stop
K9 XK_KP_3 # Play
K12 XK_Home # Beginning
K13 XK_End # End
K14 "[" # Toggle in
K15 "]" # Toggle out
S-3 XK_KP_Add # Fast reverse
S-2 XK_KP_6 # Play reverse
S-1 XK_KP_5 # Slow reverse
S0 XK_KP_0 # Stop
S1 XK_KP_2 # Slow forward
S2 XK_KP_3 # Play forward
S3 XK_KP_Enter # Fast forward
JL XK_KP_4 # Frame reverse
JR XK_KP_1 # Frame forward
[Default]
K6 XK_Button_1
K7 XK_Button_2
K8 XK_Button_3
JL XK_Scroll_Up
JR XK_Scroll_Down

5
keys.sed 100644
Wyświetl plik

@ -0,0 +1,5 @@
/^\#ifdef/p
/^\#endif/p
/^\#define/!d
s/^\#define //
s/^\([^[:space:]]*\).*$/{ "\1", \1 }, /

809
readconfig.c 100644
Wyświetl plik

@ -0,0 +1,809 @@
/*
Copyright 2013 Eric Messick (FixedImagePhoto.com/Contact)
Read and process the configuration file ~/.shuttlepro
Lines starting with # are comments.
Sequence of sections defining translation classes, each section is:
[name] regex
K<1..15> output
S<-7..7> output
J<LR> output
When focus is on a window whose title matches regex, the following
translation class is in effect. An empty regex for the last class
will always match, allowing default translations. Any output
sequences not bound in a matched section will be loaded from the
default section if they are bound there.
Each "[name] regex" line introduces the list of key and shuttle
translations for the named translation class. The name is only used
for debugging output, and needn't be unique. The following lines
with K, S, and J labels indicate what output should be produced for
the given keypress, shuttle position, or jog direction.
output is a sequence of one or more key codes with optional up/down
indicators, or strings of printable characters enclosed in double
quotes, separated by whitespace. Sequences bound to keys may have
separate press and release sequences, separated by the word RELEASE.
Examples:
K1 "qwer"
K2 XK_Right
K3 XK_Alt_L/D XK_Right
K4 "V" XK_Left XK_Page_Up "v"
K5 XK_Alt_L/D "v" XK_Alt_L/U "x" RELEASE "q"
Any keycode can be followed by an optional /D, /U, or /H, indicating
that the key is just going down (without being released), going up,
or going down and being held until the shuttlepro key is released.
So, in general, modifier key codes will be followed by /D, and
precede the keycodes they are intended to modify. If a sequence
requires different sets of modifiers for different keycodes, /U can
be used to release a modifier that was previously pressed with /D.
At the end of shuttle and jog sequences, all down keys will be
released.
Keypresses translate to separate press and release sequences.
At the end of the press sequence for key sequences, all down keys
marked by /D will be released, and the last key not marked by /D,
/U, or /H will remain pressed. The release sequence will begin by
releasing the last held key. If keys are to be pressed as part of
the release sequence, then any keys marked with /D will be repressed
before continuing the sequence. Keycodes marked with /H remain held
between the press and release sequences.
*/
#include "shuttle.h"
int debug_regex = 0;
int debug_strokes = 0;
char *
allocate(size_t len)
{
char *ret = (char *)malloc(len);
if (ret == NULL) {
fprintf(stderr, "Out of memory!\n");
exit(1);
}
return ret;
}
char *
alloc_strcat(char *a, char *b)
{
size_t len = 0;
char *result;
if (a != NULL) {
len += strlen(a);
}
if (b != NULL) {
len += strlen(b);
}
result = allocate(len+1);
result[0] = '\0';
if (a != NULL) {
strcpy(result, a);
}
if (b != NULL) {
strcat(result, b);
}
return result;
}
static char *read_line_buffer = NULL;
static int read_line_buffer_length = 0;
#define BUF_GROWTH_STEP 1024
// read a line of text from the given file into a managed buffer.
// returns a partial line at EOF if the file does not end with \n.
// exits with error message on read error.
char *
read_line(FILE *f, char *name)
{
int pos = 0;
char *new_buffer;
int new_buffer_length;
if (read_line_buffer == NULL) {
read_line_buffer_length = BUF_GROWTH_STEP;
read_line_buffer = allocate(read_line_buffer_length);
read_line_buffer[0] = '\0';
}
while (1) {
read_line_buffer[read_line_buffer_length-1] = '\377';
if (fgets(read_line_buffer+pos, read_line_buffer_length-pos, f) == NULL) {
if (feof(f)) {
if (pos > 0) {
// partial line at EOF
return read_line_buffer;
} else {
return NULL;
}
}
perror(name);
exit(1);
}
if (read_line_buffer[read_line_buffer_length-1] != '\0') {
return read_line_buffer;
}
if (read_line_buffer[read_line_buffer_length-2] == '\n') {
return read_line_buffer;
}
new_buffer_length = read_line_buffer_length + BUF_GROWTH_STEP;
new_buffer = allocate(new_buffer_length);
memcpy(new_buffer, read_line_buffer, read_line_buffer_length);
free(read_line_buffer);
pos = read_line_buffer_length-1;
read_line_buffer = new_buffer;
read_line_buffer_length = new_buffer_length;
}
}
static translation *first_translation_section = NULL;
static translation *last_translation_section = NULL;
translation *default_translation;
translation *
new_translation_section(char *name, char *regex)
{
translation *ret = (translation *)allocate(sizeof(translation));
int err;
int i;
if (debug_strokes) {
printf("------------------------\n[%s] %s\n\n", name, regex);
}
ret->next = NULL;
ret->name = alloc_strcat(name, NULL);
if (regex == NULL || *regex == '\0') {
ret->is_default = 1;
default_translation = ret;
} else {
ret->is_default = 0;
err = regcomp(&ret->regex, regex, REG_NOSUB);
if (err != 0) {
regerror(err, &ret->regex, read_line_buffer, read_line_buffer_length);
fprintf(stderr, "error compiling regex for [%s]: %s\n", name, read_line_buffer);
regfree(&ret->regex);
free(ret->name);
free(ret);
return NULL;
}
}
for (i=0; i<NUM_KEYS; i++) {
ret->key_down[i] = NULL;
ret->key_up[i] = NULL;
}
for (i=0; i<NUM_SHUTTLES; i++) {
ret->shuttle[i] = NULL;
}
for (i=0; i<NUM_JOGS; i++) {
ret->jog[i] = NULL;
}
if (first_translation_section == NULL) {
first_translation_section = ret;
last_translation_section = ret;
} else {
last_translation_section->next = ret;
last_translation_section = ret;
}
return ret;
}
void
free_strokes(stroke *s)
{
stroke *next;
while (s != NULL) {
next = s->next;
free(s);
s = next;
}
}
void
free_translation_section(translation *tr)
{
int i;
if (tr != NULL) {
free(tr->name);
if (!tr->is_default) {
regfree(&tr->regex);
}
for (i=0; i<NUM_KEYS; i++) {
free_strokes(tr->key_down[i]);
free_strokes(tr->key_up[i]);
}
for (i=0; i<NUM_SHUTTLES; i++) {
free_strokes(tr->shuttle[i]);
}
for (i=0; i<NUM_JOGS; i++) {
free_strokes(tr->jog[i]);
}
free(tr);
}
}
void
free_all_translations(void)
{
translation *tr = first_translation_section;
translation *next;
while (tr != NULL) {
next = tr->next;
free_translation_section(tr);
tr = next;
}
first_translation_section = NULL;
last_translation_section = NULL;
}
static char *config_file_name = NULL;
static time_t config_file_modification_time;
static char *token_src = NULL;
// similar to strtok, but it tells us what delimiter was found at the
// end of the token, handles double quoted strings specially, and
// hardcodes the delimiter set.
char *
token(char *src, char *delim_found)
{
char *delims = " \t\n/\"";
char *d;
char *token_start;
if (src == NULL) {
src = token_src;
}
if (src == NULL) {
*delim_found = '\0';
return NULL;
}
token_start = src;
while (*src) {
d = delims;
while (*d && *src != *d) {
d++;
}
if (*d) {
if (src == token_start) {
src++;
token_start = src;
if (*d == '"') {
while (*src && *src != '"' && *src != '\n') {
src++;
}
} else {
continue;
}
}
*delim_found = *d;
if (*src) {
*src = '\0';
token_src = src+1;
} else {
token_src = NULL;
}
return token_start;
}
src++;
}
token_src = NULL;
*delim_found = '\0';
if (src == token_start) {
return NULL;
}
return token_start;
}
typedef struct _keysymmapping {
char *str;
KeySym sym;
} keysymmapping;
static keysymmapping key_sym_mapping[] = {
#include "keys.h"
{ "XK_Button_1", XK_Button_1 },
{ "XK_Button_2", XK_Button_2 },
{ "XK_Button_3", XK_Button_3 },
{ "XK_Scroll_Up", XK_Scroll_Up },
{ "XK_Scroll_Down", XK_Scroll_Down },
{ NULL, 0 }
};
KeySym
string_to_KeySym(char *str)
{
size_t len = strlen(str) + 1;
int i = 0;
while (key_sym_mapping[i].str != NULL) {
if (!strncmp(str, key_sym_mapping[i].str, len)) {
return key_sym_mapping[i].sym;
}
i++;
}
return 0;
}
char *
KeySym_to_string(KeySym ks)
{
int i = 0;
while (key_sym_mapping[i].sym != 0) {
if (key_sym_mapping[i].sym == ks) {
return key_sym_mapping[i].str;
}
i++;
}
return NULL;
}
void
print_stroke(stroke *s)
{
char *str;
if (s != NULL) {
str = KeySym_to_string(s->keysym);
if (str == NULL) {
printf("0x%x", (int)s->keysym);
str = "???";
}
printf("%s/%c ", str, s->press ? 'D' : 'U');
}
}
void
print_stroke_sequence(char *name, char *up_or_down, stroke *s)
{
printf("%s[%s]: ", name, up_or_down);
while (s) {
print_stroke(s);
s = s->next;
}
printf("\n");
}
stroke **first_stroke;
stroke *last_stroke;
stroke **press_first_stroke;
stroke **release_first_stroke;
int is_keystroke;
char *current_translation;
char *key_name;
int first_release_stroke; // is this the first stroke of a release?
KeySym regular_key_down;
#define NUM_MODIFIERS 64
stroke modifiers_down[NUM_MODIFIERS];
int modifier_count;
void
append_stroke(KeySym sym, int press)
{
stroke *s = (stroke *)allocate(sizeof(stroke));
s->next = NULL;
s->keysym = sym;
s->press = press;
if (*first_stroke) {
last_stroke->next = s;
} else {
*first_stroke = s;
}
last_stroke = s;
}
// s->press values in modifiers_down:
// PRESS -> down
// HOLD -> held
// PRESS_RELEASE -> released, but to be re-pressed if necessary
// RELEASE -> up
void
mark_as_down(KeySym sym, int hold)
{
int i;
for (i=0; i<modifier_count; i++) {
if (modifiers_down[i].keysym == sym) {
modifiers_down[i].press = hold ? HOLD : PRESS;
return;
}
}
if (modifier_count > NUM_MODIFIERS) {
fprintf(stderr, "too many modifiers down in [%s]%s\n", current_translation, key_name);
return;
}
modifiers_down[modifier_count].keysym = sym;
modifiers_down[modifier_count].press = hold ? HOLD : PRESS;
modifier_count++;
}
void
mark_as_up(KeySym sym)
{
int i;
for (i=0; i<modifier_count; i++) {
if (modifiers_down[i].keysym == sym) {
modifiers_down[i].press = RELEASE;
return;
}
}
}
void
release_modifiers(int allkeys)
{
int i;
for (i=0; i<modifier_count; i++) {
if (modifiers_down[i].press == PRESS) {
append_stroke(modifiers_down[i].keysym, 0);
modifiers_down[i].press = PRESS_RELEASE;
} else if (allkeys && modifiers_down[i].press == HOLD) {
append_stroke(modifiers_down[i].keysym, 0);
modifiers_down[i].press = RELEASE;
}
}
}
void
re_press_temp_modifiers(void)
{
int i;
for (i=0; i<modifier_count; i++) {
if (modifiers_down[i].press == PRESS_RELEASE) {
append_stroke(modifiers_down[i].keysym, 1);
modifiers_down[i].press = PRESS;
}
}
}
int
start_translation(translation *tr, char *which_key)
{
char c;
int k;
int n;
//printf("start_translation(%s)\n", which_key);
if (tr == NULL) {
fprintf(stderr, "need to start translation section before defining key: %s\n", which_key);
return 1;
}
current_translation = tr->name;
key_name = which_key;
is_keystroke = 0;
first_release_stroke = 0;
regular_key_down = 0;
modifier_count = 0;
// JL, JR
if (tolower(which_key[0]) == 'j' &&
(tolower(which_key[1]) == 'l' || tolower(which_key[1]) == 'r') &&
which_key[2] == '\0') {
k = tolower(which_key[1]) == 'l' ? 0 : 1;
first_stroke = &(tr->jog[k]);
} else {
n = 0;
sscanf(which_key, "%c%d%n", &c, &k, &n);
if (n != (int)strlen(which_key)) {
fprintf(stderr, "bad key name: [%s]%s\n", current_translation, which_key);
return 1;
}
switch (c) {
case 'k':
case 'K':
// K1 .. K15
k = k - 1;
if (k < 0 || k >= NUM_KEYS) {
fprintf(stderr, "bad key name: [%s]%s\n", current_translation, which_key);
return 1;
}
first_stroke = &(tr->key_down[k]);
release_first_stroke = &(tr->key_up[k]);
is_keystroke = 1;
break;
case 's':
case 'S':
// S-7 .. S7
if (k < -7 || k > 7) {
fprintf(stderr, "bad key name: [%s]%s\n", current_translation, which_key);
return 1;
}
first_stroke = &(tr->shuttle[k+7]);
break;
default:
fprintf(stderr, "bad key name: [%s]%s\n", current_translation, which_key);
return 1;
}
}
if (*first_stroke != NULL) {
fprintf(stderr, "can't redefine key: [%s]%s\n", current_translation, which_key);
return 1;
}
press_first_stroke = first_stroke;
return 0;
}
void
add_keysym(KeySym sym, int press_release)
{
//printf("add_keysym(0x%x, %d)\n", (int)sym, press_release);
switch (press_release) {
case PRESS:
append_stroke(sym, 1);
mark_as_down(sym, 0);
break;
case RELEASE:
append_stroke(sym, 0);
mark_as_up(sym);
break;
case HOLD:
append_stroke(sym, 1);
mark_as_down(sym, 1);
break;
case PRESS_RELEASE:
default:
if (first_release_stroke) {
re_press_temp_modifiers();
}
if (regular_key_down != 0) {
append_stroke(regular_key_down, 0);
}
append_stroke(sym, 1);
regular_key_down = sym;
first_release_stroke = 0;
break;
}
}
void
add_release(int all_keys)
{
//printf("add_release(%d)\n", all_keys);
release_modifiers(all_keys);
if (!all_keys) {
first_stroke = release_first_stroke;
}
if (regular_key_down != 0) {
append_stroke(regular_key_down, 0);
}
regular_key_down = 0;
first_release_stroke = 1;
}
void
add_keystroke(char *keySymName, int press_release)
{
KeySym sym;
if (is_keystroke && !strncmp(keySymName, "RELEASE", 8)) {
add_release(0);
return;
}
sym = string_to_KeySym(keySymName);
if (sym != 0) {
add_keysym(sym, press_release);
} else {
fprintf(stderr, "unrecognized KeySym: %s\n", keySymName);
}
}
void
add_string(char *str)
{
while (str && *str) {
if (*str >= ' ' && *str <= '~') {
add_keysym((KeySym)(*str), PRESS_RELEASE);
}
str++;
}
}
void
finish_translation(void)
{
//printf("finish_translation()\n");
if (is_keystroke) {
add_release(0);
}
add_release(1);
if (debug_strokes) {
if (is_keystroke) {
print_stroke_sequence(key_name, "D", *press_first_stroke);
print_stroke_sequence(key_name, "U", *release_first_stroke);
} else {
print_stroke_sequence(key_name, "", *first_stroke);
}
printf("\n");
}
}
void
read_config_file(void)
{
struct stat buf;
char *home;
char *line;
char *s;
char *name;
char *regex;
char *tok;
char *which_key;
char *updown;
char delim;
translation *tr = NULL;
FILE *f;
if (config_file_name == NULL) {
config_file_name = getenv("SHUTTLE_CONFIG_FILE");
if (config_file_name == NULL) {
home = getenv("HOME");
config_file_name = alloc_strcat(home, "/.shuttlerc");
} else {
config_file_name = alloc_strcat(config_file_name, NULL);
}
config_file_modification_time = 0;
}
if (stat(config_file_name, &buf) < 0) {
perror(config_file_name);
return;
}
if (buf.st_mtime == 0) {
buf.st_mtime = 1;
}
if (buf.st_mtime > config_file_modification_time) {
config_file_modification_time = buf.st_mtime;
f = fopen(config_file_name, "r");
if (f == NULL) {
perror(config_file_name);
return;
}
free_all_translations();
debug_regex = 0;
debug_strokes = 0;
while ((line=read_line(f, config_file_name)) != NULL) {
//printf("line: %s", line);
s = line;
while (*s && isspace(*s)) {
s++;
}
if (*s == '#') {
continue;
}
if (*s == '[') {
// [name] regex\n
name = ++s;
while (*s && *s != ']') {
s++;
}
regex = NULL;
if (*s) {
*s = '\0';
s++;
while (*s && isspace(*s)) {
s++;
}
regex = s;
while (*s) {
s++;
}
s--;
while (s > regex && isspace(*s)) {
s--;
}
s[1] = '\0';
}
tr = new_translation_section(name, regex);
continue;
}
tok = token(s, &delim);
if (tok == NULL) {
continue;
}
if (!strcmp(tok, "DEBUG_REGEX")) {
debug_regex = 1;
continue;
}
if (!strcmp(tok, "DEBUG_STROKES")) {
debug_strokes = 1;
continue;
}
which_key = tok;
if (start_translation(tr, which_key)) {
continue;
}
tok = token(NULL, &delim);
while (tok != NULL) {
if (delim != '"' && tok[0] == '#') {
break; // skip rest as comment
}
//printf("token: [%s] delim [%d]\n", tok, delim);
switch (delim) {
case ' ':
case '\t':
case '\n':
add_keystroke(tok, PRESS_RELEASE);
break;
case '"':
add_string(tok);
break;
default: // should be slash
updown = token(NULL, &delim);
if (updown != NULL) {
switch (updown[0]) {
case 'U':
add_keystroke(tok, RELEASE);
break;
case 'D':
add_keystroke(tok, PRESS);
break;
case 'H':
add_keystroke(tok, HOLD);
break;
default:
fprintf(stderr, "invalid up/down modifier [%s]%s: %s\n", name, which_key, updown);
add_keystroke(tok, PRESS);
break;
}
}
}
tok = token(NULL, &delim);
}
finish_translation();
}
fclose(f);
}
}
translation *
get_translation(char *win_title)
{
translation *tr;
read_config_file();
tr = first_translation_section;
while (tr != NULL) {
if (tr->is_default) {
return tr;
}
if (regexec(&tr->regex, win_title, 0, NULL, 0) == 0) {
return tr;
}
tr = tr->next;
}
return NULL;
}

3
shuttle 100755
Wyświetl plik

@ -0,0 +1,3 @@
#!/bin/bash
exec shuttlepro /dev/input/by-id/usb-Contour_Design_ShuttlePRO_v2-event-if00

94
shuttle.h 100644
Wyświetl plik

@ -0,0 +1,94 @@
// Copyright 2013 Eric Messick (FixedImagePhoto.com/Contact)
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <linux/input.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <regex.h>
#include <X11/Xlib.h>
#include <X11/extensions/XTest.h>
#include <X11/keysym.h>
#include <X11/Xatom.h>
// delay in ms before processing each XTest event
// CurrentTime means no delay
#define DELAY CurrentTime
// protocol for events from the shuttlepro HUD device
//
// ev.type values:
#define EVENT_TYPE_DONE 0
#define EVENT_TYPE_KEY 1
#define EVENT_TYPE_JOGSHUTTLE 2
#define EVENT_TYPE_ACTIVE_KEY 4
// ev.code when ev.type == KEY
#define EVENT_CODE_KEY1 256
// KEY2 257, etc...
// ev.value when ev.type == KEY
// 1 -> PRESS; 0 -> RELEASE
// ev.code when ev.type == JOGSHUTTLE
#define EVENT_CODE_JOG 7
#define EVENT_CODE_SHUTTLE 8
// ev.value when ev.code == JOG
// 8 bit value changing by one for each jog step
// ev.value when ev.code == SHUTTLE
// -7 .. 7 encoding shuttle position
// we define these as extra KeySyms to represent mouse events
#define XK_Button_0 0x2000000 // just an offset, not a real button
#define XK_Button_1 0x2000001
#define XK_Button_2 0x2000002
#define XK_Button_3 0x2000003
#define XK_Scroll_Up 0x2000004
#define XK_Scroll_Down 0x2000005
#define PRESS 1
#define RELEASE 2
#define PRESS_RELEASE 3
#define HOLD 4
#define NUM_KEYS 15
#define NUM_SHUTTLES 15
#define NUM_JOGS 2
typedef struct _stroke {
struct _stroke *next;
KeySym keysym;
int press; // zero -> release, non-zero -> press
} stroke;
#define KJS_KEY_DOWN 1
#define KJS_KEY_UP 2
#define KJS_SHUTTLE 3
#define KJS_JOG 4
typedef struct _translation {
struct _translation *next;
char *name;
int is_default;
regex_t regex;
stroke *key_down[NUM_KEYS];
stroke *key_up[NUM_KEYS];
stroke *shuttle[NUM_SHUTTLES];
stroke *jog[NUM_JOGS];
} translation;
extern translation *get_translation(char *win_title);

330
shuttlepro.c 100644
Wyświetl plik

@ -0,0 +1,330 @@
/*
Contour ShuttlePro v2 interface
Copyright 2013 Eric Messick (FixedImagePhoto.com/Contact)
Based on a version (c) 2006 Trammell Hudson <hudson@osresearch.net>
which was in turn
Based heavily on code by Arendt David <admin@prnet.org>
*/
#include "shuttle.h"
typedef struct input_event EV;
extern int debug_regex;
extern translation *default_translation;
unsigned short jogvalue = 0xffff;
int shuttlevalue = 0xffff;
struct timeval last_shuttle;
int need_synthetic_shuttle;
Display *display;
void
initdisplay(void)
{
int event, error, major, minor;
display = XOpenDisplay(0);
if (!display) {
fprintf(stderr, "unable to open X display\n");
exit(1);
}
if (!XTestQueryExtension(display, &event, &error, &major, &minor)) {
fprintf(stderr, "Xtest extensions not supported\n");
XCloseDisplay(display);
exit(1);
}
}
void
send_button(unsigned int button, int press)
{
XTestFakeButtonEvent(display, button, press ? True : False, DELAY);
}
void
send_key(KeySym key, int press)
{
KeyCode keycode;
if (key >= XK_Button_1 && key <= XK_Scroll_Down) {
send_button((unsigned int)key - XK_Button_0, press);
return;
}
keycode = XKeysymToKeycode(display, key);
XTestFakeKeyEvent(display, keycode, press ? True : False, DELAY);
}
stroke *
fetch_stroke(translation *tr, int kjs, int index)
{
if (tr != NULL) {
switch (kjs) {
case KJS_SHUTTLE:
return tr->shuttle[index];
case KJS_JOG:
return tr->jog[index];
case KJS_KEY_UP:
return tr->key_up[index];
case KJS_KEY_DOWN:
default:
return tr->key_down[index];
}
}
return NULL;
}
void
send_stroke_sequence(translation *tr, int kjs, int index)
{
stroke *s;
s = fetch_stroke(tr, kjs, index);
if (s == NULL) {
s = fetch_stroke(default_translation, kjs, index);
}
while (s) {
send_key(s->keysym, s->press);
s = s->next;
}
XFlush(display);
}
void
key(unsigned short code, unsigned int value, translation *tr)
{
code -= EVENT_CODE_KEY1;
if (code <= NUM_KEYS) {
send_stroke_sequence(tr, value ? KJS_KEY_DOWN : KJS_KEY_UP, code);
} else {
fprintf(stderr, "key(%d, %d) out of range\n", code + EVENT_CODE_KEY1, value);
}
}
void
shuttle(int value, translation *tr)
{
if (value < -7 || value > 7) {
fprintf(stderr, "shuttle(%d) out of range\n", value);
} else {
gettimeofday(&last_shuttle, 0);
need_synthetic_shuttle = value != 0;
if( value != shuttlevalue ) {
shuttlevalue = value;
send_stroke_sequence(tr, KJS_SHUTTLE, value+7);
}
}
}
// Due to a bug (?) in the way Linux HID handles the ShuttlePro, the
// center position is not reported for the shuttle wheel. Instead,
// a jog event is generated immediately when it returns. We check to
// see if the time since the last shuttle was more than a few ms ago
// and generate a shuttle of 0 if so.
//
// Note, this fails if jogvalue happens to be 0, as we don't see that
// event either!
void
jog(unsigned int value, translation *tr)
{
int direction;
struct timeval now;
struct timeval delta;
// We should generate a synthetic event for the shuttle going
// to the home position if we have not seen one recently
if (need_synthetic_shuttle) {
gettimeofday( &now, 0 );
timersub( &now, &last_shuttle, &delta );
if (delta.tv_sec >= 1 || delta.tv_usec >= 5000) {
shuttle(0, tr);
need_synthetic_shuttle = 0;
}
}
if (jogvalue != 0xffff) {
value = value & 0xff;
direction = ((value - jogvalue) & 0x80) ? -1 : 1;
while (jogvalue != value) {
// driver fails to send an event when jogvalue == 0
if (jogvalue != 0) {
send_stroke_sequence(tr, KJS_JOG, direction > 0 ? 1 : 0);
}
jogvalue = (jogvalue + direction) & 0xff;
}
}
jogvalue = value;
}
void
jogshuttle(unsigned short code, unsigned int value, translation *tr)
{
switch (code) {
case EVENT_CODE_JOG:
jog(value, tr);
break;
case EVENT_CODE_SHUTTLE:
shuttle(value, tr);
break;
default:
fprintf(stderr, "jogshuttle(%d, %d) invalid code\n", code, value);
break;
}
}
char *
get_window_name(Window win)
{
Atom prop = XInternAtom(display, "WM_NAME", False);
Atom type;
int form;
unsigned long remain, len;
unsigned char *list;
if (XGetWindowProperty(display, win, prop, 0, 1024, False,
AnyPropertyType, &type, &form, &len, &remain,
&list) != Success) {
fprintf(stderr, "XGetWindowProperty failed for window 0x%x\n", (int)win);
return NULL;
}
return (char*)list;
}
char *
walk_window_tree(Window win)
{
char *window_name;
Window root = 0;
Window parent;
Window *children;
unsigned int nchildren;
while (win != root) {
window_name = get_window_name(win);
if (window_name != NULL) {
return window_name;
}
if (XQueryTree(display, win, &root, &parent, &children, &nchildren)) {
win = parent;
XFree(children);
} else {
fprintf(stderr, "XQueryTree failed for window 0x%x\n", (int)win);
return NULL;
}
}
return NULL;
}
static Window last_focused_window = 0;
static translation *last_window_translation = NULL;
translation *
get_focused_window_translation()
{
Window focus;
int revert_to;
char *window_name = NULL;
char *name;
XGetInputFocus(display, &focus, &revert_to);
if (focus != last_focused_window) {
last_focused_window = focus;
window_name = walk_window_tree(focus);
if (window_name == NULL) {
name = "-- Unlabeled Window --";
} else {
name = window_name;
}
last_window_translation = get_translation(name);
if (debug_regex) {
if (last_window_translation != NULL) {
printf("translation: %s for %s\n", last_window_translation->name, name);
} else {
printf("no translation found for %s\n", name);
}
}
if (window_name != NULL) {
XFree(window_name);
}
}
return last_window_translation;
}
void
handle_event(EV ev)
{
translation *tr = get_focused_window_translation();
//fprintf(stderr, "event: (%d, %d, 0x%x)\n", ev.type, ev.code, ev.value);
if (tr != NULL) {
switch (ev.type) {
case EVENT_TYPE_DONE:
case EVENT_TYPE_ACTIVE_KEY:
break;
case EVENT_TYPE_KEY:
key(ev.code, ev.value, tr);
break;
case EVENT_TYPE_JOGSHUTTLE:
jogshuttle(ev.code, ev.value, tr);
break;
default:
fprintf(stderr, "handle_event() invalid type code\n");
break;
}
}
}
int
main(int argc, char **argv)
{
EV ev;
int nread;
char *dev_name;
int fd;
if (argc != 2) {
fprintf(stderr, "usage: shuttlepro <device>\n" );
exit(1);
}
dev_name = argv[1];
fd = open(dev_name, O_RDONLY);
if (fd < 0) {
perror(dev_name);
exit(1);
}
// Flag it as exclusive access
if(ioctl( fd, EVIOCGRAB, 1 ) < 0) {
perror( "evgrab ioctl" );
exit(1);
}
initdisplay();
while (1) {
nread = read(fd, &ev, sizeof(ev));
if (nread == sizeof(ev)) {
handle_event(ev);
} else {
if (nread < 0) {
perror("read event");
} else {
fprintf(stderr, "short read: %d\n", nread);
}
}
}
}