kopia lustrzana https://github.com/nanosyzygy/ShuttlePRO
Initial commit
rodzic
c2521c6c45
commit
d399f2edd7
|
@ -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"
|
|
@ -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
|
|
@ -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.
|
|
@ -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.
|
||||
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
/^\#ifdef/p
|
||||
/^\#endif/p
|
||||
/^\#define/!d
|
||||
s/^\#define //
|
||||
s/^\([^[:space:]]*\).*$/{ "\1", \1 }, /
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
exec shuttlepro /dev/input/by-id/usb-Contour_Design_ShuttlePRO_v2-event-if00
|
|
@ -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);
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Ładowanie…
Reference in New Issue