1015 lines
31 KiB
C
1015 lines
31 KiB
C
/*
|
|
This file is part of g15daemon.
|
|
|
|
g15daemon 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 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
g15daemon 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 g15daemon; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
(c) 2006-2007 Mike Lampard
|
|
|
|
$Revision: 445 $ - $Date: 2008-01-23 18:56:23 +1030 (Wed, 23 Jan 2008) $ $Author: mlampard $
|
|
|
|
This daemon listens on localhost port 15550 for client connections,
|
|
and arbitrates LCD display. Allows for multiple simultaneous clients.
|
|
Client screens can be cycled through by pressing the 'L1' key.
|
|
|
|
This is a macro recorder and playback utility for the G15 and g15daemon.
|
|
*/
|
|
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
#include <arpa/inet.h>
|
|
#include <signal.h>
|
|
#include <pwd.h>
|
|
#include <pthread.h>
|
|
#include <sys/time.h>
|
|
#include <config.h>
|
|
#include <X11/Xlib.h>
|
|
#include <stdarg.h>
|
|
#ifdef HAVE_X11_EXTENSIONS_XTEST_H
|
|
#include <X11/extensions/XTest.h>
|
|
#endif
|
|
#include <X11/XF86keysym.h>
|
|
|
|
#include <g15daemon_client.h>
|
|
#include <libg15.h>
|
|
#include <libg15render.h>
|
|
#include "config.h"
|
|
|
|
#define XK_MISCELLANY
|
|
#define XK_LATIN1
|
|
#define XK_LATIN2
|
|
#include <X11/keysymdef.h>
|
|
|
|
int g15screen_fd;
|
|
int config_fd = 0;
|
|
g15canvas *canvas;
|
|
|
|
static Display *dpy;
|
|
static Window root_win;
|
|
|
|
pthread_mutex_t x11mutex;
|
|
pthread_mutex_t config_mutex;
|
|
|
|
int leaving = 0;
|
|
int display_timeout=500;
|
|
int have_xtest = False;
|
|
int debug = 0;
|
|
|
|
unsigned char recstring[1024];
|
|
|
|
static unsigned int mled_state = G15_LED_M1;
|
|
static int mkey_state = 0;
|
|
static int recording = 0;
|
|
|
|
typedef struct keypress_s {
|
|
unsigned long keycode;
|
|
unsigned long time_ms;
|
|
unsigned char pressed;
|
|
unsigned long modifiers;
|
|
unsigned int mouse_x;
|
|
unsigned int mouse_y;
|
|
unsigned int buttons;
|
|
}keypress_t;
|
|
|
|
|
|
#define MAX_KEYSTEPS 1024
|
|
|
|
typedef struct keysequence_s {
|
|
keypress_t recorded_keypress[MAX_KEYSTEPS];
|
|
unsigned int record_steps;
|
|
} keysequence_t;
|
|
|
|
typedef struct gkeys_s{
|
|
unsigned int recorded;
|
|
keysequence_t keysequence;
|
|
}gkeys_t;
|
|
|
|
typedef struct mstates_s {
|
|
gkeys_t gkeys[18];
|
|
}mstates_t;
|
|
|
|
mstates_t *mstates[3];
|
|
|
|
struct current_recording {
|
|
keypress_t recorded_keypress[MAX_KEYSTEPS];
|
|
}current_recording;
|
|
|
|
unsigned int rec_index=0;
|
|
|
|
const char *gkeystring[] = { "G1","G2","G3","G4","G5","G6","G7","G8","G9","G10","G11","G12","G13","G14","G15","G16","G17","G18","Unknown" };
|
|
|
|
const int gkeycodes[] = { 177,152,190,208,129,130,231,209,210,136,220,143,246,251,137,138,133,183 };
|
|
|
|
const int mmedia_codes[] = {164, 162, 144, 153, 174, 176};
|
|
|
|
const long mmedia_defaults[] = {
|
|
XF86XK_AudioStop,
|
|
XF86XK_AudioPlay,
|
|
XF86XK_AudioPrev,
|
|
XF86XK_AudioNext,
|
|
XF86XK_AudioLowerVolume,
|
|
XF86XK_AudioRaiseVolume
|
|
};
|
|
/* because this is an X11 client, we can work around the kernel limitations on key numbers */
|
|
const long gkeydefaults[] = {
|
|
/* M1 palette */
|
|
XF86XK_Launch4,
|
|
XF86XK_Launch5,
|
|
XF86XK_Launch6,
|
|
XF86XK_Launch7,
|
|
XF86XK_Launch8,
|
|
XF86XK_Launch9,
|
|
XF86XK_LaunchA,
|
|
XF86XK_LaunchB,
|
|
XF86XK_LaunchC,
|
|
XF86XK_LaunchD,
|
|
XF86XK_LaunchE,
|
|
XF86XK_LaunchF,
|
|
XF86XK_iTouch,
|
|
XF86XK_Calculater,
|
|
XF86XK_Support,
|
|
XF86XK_Word,
|
|
XF86XK_Messenger,
|
|
XF86XK_WebCam,
|
|
/* M2 palette */
|
|
XK_F13,
|
|
XK_F14,
|
|
XK_F15,
|
|
XK_F16,
|
|
XK_F17,
|
|
XK_F18,
|
|
XK_F19,
|
|
XK_F20,
|
|
XK_F21,
|
|
XK_F22,
|
|
XK_F23,
|
|
XK_F24,
|
|
XK_F25,
|
|
XK_F26,
|
|
XK_F27,
|
|
XK_F28,
|
|
XK_F29,
|
|
XK_F30,
|
|
/* M3 palette */
|
|
XK_Tcedilla,
|
|
XK_racute,
|
|
XK_abreve,
|
|
XK_lacute,
|
|
XK_cacute,
|
|
XK_ccaron,
|
|
XK_eogonek,
|
|
XK_ecaron,
|
|
XK_dcaron,
|
|
XK_dstroke,
|
|
XK_nacute,
|
|
XK_ncaron,
|
|
XK_odoubleacute,
|
|
XK_udoubleacute,
|
|
XK_rcaron,
|
|
XK_uring,
|
|
XK_scaron,
|
|
XK_abovedot
|
|
};
|
|
|
|
int map_gkey(keystate){
|
|
int retval = -1;
|
|
switch(keystate){
|
|
case G15_KEY_G1: retval = 0; break;
|
|
case G15_KEY_G2: retval = 1; break;
|
|
case G15_KEY_G3: retval = 2; break;
|
|
case G15_KEY_G4: retval = 3; break;
|
|
case G15_KEY_G5: retval = 4; break;
|
|
case G15_KEY_G6: retval = 5; break;
|
|
case G15_KEY_G7: retval = 6; break;
|
|
case G15_KEY_G8: retval = 7; break;
|
|
case G15_KEY_G9: retval = 8; break;
|
|
case G15_KEY_G10: retval = 9; break;
|
|
case G15_KEY_G11: retval = 10; break;
|
|
case G15_KEY_G12: retval = 11; break;
|
|
case G15_KEY_G13: retval = 12; break;
|
|
case G15_KEY_G14: retval = 13; break;
|
|
case G15_KEY_G15: retval = 14; break;
|
|
case G15_KEY_G16: retval = 15; break;
|
|
case G15_KEY_G17: retval = 16; break;
|
|
case G15_KEY_G18: retval = 17; break;
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
/* debugging wrapper */
|
|
static int g15macro_log (const char *fmt, ...) {
|
|
|
|
if (debug) {
|
|
printf("G15Macro: ");
|
|
va_list argp;
|
|
va_start (argp, fmt);
|
|
vprintf(fmt,argp);
|
|
va_end (argp);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void fake_keyevent(int keycode,int keydown,unsigned long modifiers){
|
|
if(have_xtest && !recording) {
|
|
#ifdef HAVE_X11_EXTENSIONS_XTEST_H
|
|
pthread_mutex_lock(&x11mutex);
|
|
XTestFakeKeyEvent(dpy, keycode,keydown, CurrentTime);
|
|
XSync(dpy,False);
|
|
pthread_mutex_unlock(&x11mutex);
|
|
usleep(1500);
|
|
#endif
|
|
} else {
|
|
XKeyEvent event;
|
|
Window current_focus;
|
|
int dummy = 0;
|
|
int key = 0;
|
|
|
|
pthread_mutex_lock(&x11mutex);
|
|
XGetInputFocus(dpy,¤t_focus, &dummy);
|
|
key = XKeycodeToKeysym(dpy,keycode,0);
|
|
if(keydown)
|
|
event.type=KeyPress;
|
|
else
|
|
event.type=KeyRelease;
|
|
|
|
event.keycode = keycode;
|
|
event.serial = 0;
|
|
event.send_event = False;
|
|
event.display = dpy;
|
|
event.x = event.y = event.x_root = event.y_root = 0;
|
|
event.time = CurrentTime;
|
|
event.same_screen = True;
|
|
event.subwindow = None;
|
|
event.window = current_focus;
|
|
event.root = root_win;
|
|
event.state = modifiers;
|
|
XSendEvent(dpy,current_focus,False,0,(XEvent*)&event);
|
|
XSync(dpy,False);
|
|
pthread_mutex_unlock(&x11mutex);
|
|
}
|
|
}
|
|
|
|
void record_cleanup(){
|
|
g15_send_cmd (g15screen_fd,G15DAEMON_MKEYLEDS,mled_state);
|
|
memset(recstring,0,strlen((char*)recstring));
|
|
rec_index = 0;
|
|
recording = 0;
|
|
pthread_mutex_lock(&x11mutex);
|
|
XUngrabKeyboard(dpy,CurrentTime);
|
|
XFlush(dpy);
|
|
pthread_mutex_unlock(&x11mutex);
|
|
}
|
|
|
|
void record_cancel(){
|
|
memset(canvas->buffer,0,G15_BUFFER_LEN);
|
|
g15r_renderString (canvas, (unsigned char *)"Recording", 0, G15_TEXT_LARGE, 80-((strlen("Recording")/2)*8), 4);
|
|
g15r_renderString (canvas, (unsigned char *)"Canceled", 0, G15_TEXT_LARGE, 80-((strlen("Canceled")/2)*8), 18);
|
|
g15_send(g15screen_fd,(char *)canvas->buffer,G15_BUFFER_LEN);
|
|
record_cleanup();
|
|
}
|
|
|
|
void record_complete(unsigned long keystate)
|
|
{
|
|
char tmpstr[1024];
|
|
int gkey = map_gkey(keystate);
|
|
|
|
pthread_mutex_lock(&config_mutex);
|
|
|
|
if(!rec_index) // nothing recorded - delete prior recording
|
|
memset(mstates[mkey_state]->gkeys[gkey].keysequence.recorded_keypress,0,sizeof(keysequence_t));
|
|
else
|
|
memcpy(mstates[mkey_state]->gkeys[gkey].keysequence.recorded_keypress, ¤t_recording, sizeof(keysequence_t));
|
|
|
|
mstates[mkey_state]->gkeys[gkey].keysequence.record_steps=rec_index;
|
|
pthread_mutex_unlock(&config_mutex);
|
|
|
|
memset(canvas->buffer,0,G15_BUFFER_LEN);
|
|
if(rec_index){
|
|
strcpy(tmpstr,"For key ");
|
|
strcat(tmpstr,gkeystring[map_gkey(keystate)]);
|
|
g15macro_log("Recording Complete %s\n",tmpstr);
|
|
g15r_renderString (canvas, (unsigned char *)"Recording", 0, G15_TEXT_LARGE, 80-((strlen("Recording")/2)*8), 4);
|
|
g15r_renderString (canvas, (unsigned char *)"Complete", 0, G15_TEXT_LARGE, 80-((strlen("Complete")/2)*8), 18);
|
|
|
|
}else{
|
|
strcpy(tmpstr,"From Key ");
|
|
strcat(tmpstr,gkeystring[map_gkey(keystate)]);
|
|
g15macro_log("Macro deleted %s\n",tmpstr);
|
|
g15r_renderString (canvas, (unsigned char *)"Macro", 0, G15_TEXT_LARGE, 80-((strlen("Macro")/2)*8), 4);
|
|
g15r_renderString (canvas, (unsigned char *)"Deleted", 0, G15_TEXT_LARGE, 80-((strlen("Deleted")/2)*8), 18);
|
|
}
|
|
g15r_renderString (canvas, (unsigned char *)tmpstr, 0, G15_TEXT_LARGE, 80-((strlen(tmpstr)/2)*8), 32);
|
|
|
|
g15_send(g15screen_fd,(char *)canvas->buffer,G15_BUFFER_LEN);
|
|
|
|
record_cleanup();
|
|
}
|
|
|
|
int calc_mkey_offset() {
|
|
int mkey_offset=0;
|
|
switch(mkey_state){
|
|
case 0:
|
|
mkey_offset = 0;
|
|
break;
|
|
case 1:
|
|
mkey_offset = 18;
|
|
break;
|
|
case 2:
|
|
mkey_offset = 36;
|
|
break;
|
|
default:
|
|
mkey_offset=0;
|
|
}
|
|
return mkey_offset;
|
|
}
|
|
|
|
void macro_playback(unsigned long keystate)
|
|
{
|
|
int i = 0;
|
|
KeySym key;
|
|
int keyevent;
|
|
int gkey = map_gkey(keystate);
|
|
if(gkey<0)
|
|
return;
|
|
|
|
/* if no macro has been recorded for this key, send the g15daemon default keycode */
|
|
if(mstates[mkey_state]->gkeys[gkey].keysequence.record_steps==0){
|
|
int mkey_offset=0;
|
|
|
|
mkey_offset = calc_mkey_offset();
|
|
|
|
pthread_mutex_lock(&x11mutex);
|
|
keyevent=XKeysymToKeycode(dpy, gkeydefaults[gkey+mkey_offset]);
|
|
pthread_mutex_unlock(&x11mutex);
|
|
|
|
fake_keyevent(keyevent,1,None);
|
|
fake_keyevent(keyevent,0,None);
|
|
g15macro_log("Key: \t%s\n",XKeysymToString(gkeydefaults[gkey+mkey_offset]));
|
|
return;
|
|
}
|
|
g15macro_log("Macro Playback: for key %s\n",gkeystring[gkey]);
|
|
pthread_mutex_lock(&config_mutex);
|
|
for(i=0;i<mstates[mkey_state]->gkeys[gkey].keysequence.record_steps;i++){
|
|
|
|
fake_keyevent(mstates[mkey_state]->gkeys[gkey].keysequence.recorded_keypress[i].keycode,
|
|
mstates[mkey_state]->gkeys[gkey].keysequence.recorded_keypress[i].pressed,
|
|
mstates[mkey_state]->gkeys[gkey].keysequence.recorded_keypress[i].modifiers);
|
|
|
|
pthread_mutex_lock(&x11mutex);
|
|
key = XKeycodeToKeysym(dpy,mstates[mkey_state]->gkeys[gkey].keysequence.recorded_keypress[i].keycode,0);
|
|
pthread_mutex_unlock(&x11mutex);
|
|
g15macro_log("\t%s %s\n",XKeysymToString(key),mstates[mkey_state]->gkeys[gkey].keysequence.recorded_keypress[i].pressed?"Down":"Up");
|
|
|
|
switch (key) {
|
|
case XK_Control_L:
|
|
case XK_Control_R:
|
|
case XK_Meta_L:
|
|
case XK_Meta_R:
|
|
case XK_Alt_L:
|
|
case XK_Alt_R:
|
|
case XK_Super_L:
|
|
case XK_Super_R:
|
|
case XK_Hyper_L:
|
|
case XK_Hyper_R:
|
|
usleep(mstates[mkey_state]->gkeys[gkey].keysequence.recorded_keypress[i].time_ms*1000);
|
|
break;
|
|
default:
|
|
usleep(1000);
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&config_mutex);
|
|
g15macro_log("Macro Playback Complete\n");
|
|
}
|
|
|
|
/* WARNING: uses global mkey state */
|
|
void dump_config(FILE *configfile)
|
|
{
|
|
int i=0,gkey=0;
|
|
KeySym key;
|
|
pthread_mutex_lock(&config_mutex);
|
|
int orig_mkeystate=mkey_state;
|
|
for(mkey_state=0;mkey_state<3;mkey_state++){
|
|
fprintf(configfile,"\n\nCodes for MKey %i\n",mkey_state+1);
|
|
for(gkey=0;gkey<18;gkey++){
|
|
fprintf(configfile,"Key %s:",gkeystring[gkey]);
|
|
/* if no macro has been recorded for this key, dump the g15daemon default keycode */
|
|
if(mstates[mkey_state]->gkeys[gkey].keysequence.record_steps==0){
|
|
int mkey_offset=0;
|
|
mkey_offset = calc_mkey_offset();
|
|
fprintf(configfile,"\t%s\n",XKeysymToString(gkeydefaults[gkey+mkey_offset]));
|
|
}else{
|
|
fprintf(configfile,"\n");
|
|
for(i=0;i<mstates[mkey_state]->gkeys[gkey].keysequence.record_steps;i++){
|
|
key = XKeycodeToKeysym(dpy,mstates[mkey_state]->gkeys[gkey].keysequence.recorded_keypress[i].keycode,0);
|
|
fprintf(configfile,"\t%s %s %u\n",XKeysymToString(key),mstates[mkey_state]->gkeys[gkey].keysequence.recorded_keypress[i].pressed?"Down":"Up",(unsigned int)mstates[mkey_state]->gkeys[gkey].keysequence.recorded_keypress[i].modifiers);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
mkey_state=orig_mkeystate;
|
|
pthread_mutex_unlock(&config_mutex);
|
|
}
|
|
|
|
void save_macros(char *filename){
|
|
FILE *configfile;
|
|
configfile=fopen(filename,"w");
|
|
|
|
dump_config(configfile);
|
|
|
|
fclose(configfile);
|
|
}
|
|
|
|
void restore_config(char *filename) {
|
|
FILE *f;
|
|
char tmpstring[1024];
|
|
unsigned int key=0;
|
|
unsigned int mkey=0;
|
|
unsigned int i=0;
|
|
unsigned int keycode;
|
|
f=fopen(filename,"r");
|
|
printf("restoring codes\n");
|
|
pthread_mutex_lock(&config_mutex);
|
|
do{
|
|
memset(tmpstring,0,1024);
|
|
fgets(tmpstring,1024,f);
|
|
|
|
if(tmpstring[0]=='C'){
|
|
sscanf(tmpstring,"Codes for MKey %i\n",&mkey);
|
|
mkey--;
|
|
i=0;
|
|
}
|
|
if(tmpstring[0]=='K'){
|
|
sscanf(tmpstring,"Key G%i:",&key);
|
|
key--;
|
|
i=0;
|
|
}
|
|
if(tmpstring[0]=='\t'){
|
|
char codestr[64];
|
|
char pressed[20];
|
|
unsigned int modifiers = 0;
|
|
sscanf(tmpstring,"\t%s %s %i\n",(char*)&codestr,(char*)&pressed,&modifiers);
|
|
keycode = XKeysymToKeycode(dpy,XStringToKeysym(codestr));
|
|
mstates[mkey]->gkeys[key].keysequence.recorded_keypress[i].keycode = keycode;
|
|
mstates[mkey]->gkeys[key].keysequence.recorded_keypress[i].pressed = strncmp(pressed,"Up",2)?1:0;
|
|
mstates[mkey]->gkeys[key].keysequence.recorded_keypress[i].modifiers = modifiers;
|
|
mstates[mkey]->gkeys[key].keysequence.record_steps=++i;
|
|
}
|
|
} while(!feof(f));
|
|
pthread_mutex_unlock(&config_mutex);
|
|
fclose(f);
|
|
}
|
|
|
|
void change_keymap(int offset){
|
|
int i=0,j=0;
|
|
pthread_mutex_lock(&x11mutex);
|
|
for(i=offset;i<offset+18;i++,j++)
|
|
{
|
|
KeySym newmap[1];
|
|
newmap[0]=gkeydefaults[i];
|
|
XChangeKeyboardMapping (dpy, gkeycodes[j], 1, newmap, 1);
|
|
}
|
|
XFlush(dpy);
|
|
pthread_mutex_unlock(&x11mutex);
|
|
|
|
}
|
|
|
|
/* ensure that the multimedia keys are configured */
|
|
void configure_mmediakeys(){
|
|
|
|
KeySym newmap[1];
|
|
int i=0;
|
|
pthread_mutex_lock(&x11mutex);
|
|
for(i=0;i<6;i++){
|
|
newmap[0]=mmedia_defaults[i];
|
|
XChangeKeyboardMapping (dpy, mmedia_codes[i], 1, newmap, 1);
|
|
}
|
|
XFlush(dpy);
|
|
pthread_mutex_unlock(&x11mutex);
|
|
|
|
}
|
|
|
|
void handle_mkey_switch(unsigned int mkey) {
|
|
int mkey_offset = 0;
|
|
switch(mkey) {
|
|
case G15_KEY_M1:
|
|
mled_state=G15_LED_M1;
|
|
mkey_state=0;
|
|
break;
|
|
case G15_KEY_M2:
|
|
mled_state=G15_LED_M2;
|
|
mkey_state=1;
|
|
break;
|
|
case G15_KEY_M3:
|
|
mled_state=G15_LED_M3;
|
|
mkey_state=2;
|
|
break;
|
|
}
|
|
mkey_offset = calc_mkey_offset();
|
|
if(recording)
|
|
record_cancel();
|
|
g15_send_cmd (g15screen_fd,G15DAEMON_MKEYLEDS,mled_state);
|
|
change_keymap(mkey_offset);
|
|
}
|
|
|
|
void *Lkeys_thread() {
|
|
unsigned long keystate = 0;
|
|
struct pollfd fds;
|
|
char ver[5];
|
|
int foo = 0;
|
|
strncpy(ver,G15DAEMON_VERSION,3);
|
|
float g15v;
|
|
sscanf(ver,"%f",&g15v);
|
|
|
|
g15macro_log("Using version %.2f as keypress protocol\n",g15v);
|
|
|
|
while(!leaving){
|
|
|
|
/* g15daemon series 1.2 need key request packets */
|
|
if((g15v*10)<=18) {
|
|
keystate = g15_send_cmd (g15screen_fd, G15DAEMON_GET_KEYSTATE, foo);
|
|
} else {
|
|
fds.fd = g15screen_fd;
|
|
fds.events = POLLIN;
|
|
fds.revents=0;
|
|
keystate=0;
|
|
if ((poll(&fds, 1, 1000)) > 0) {
|
|
read (g15screen_fd, &keystate, sizeof (keystate));
|
|
}
|
|
}
|
|
|
|
if (keystate!=0)
|
|
{
|
|
g15macro_log("Received Keystate == %lu\n",keystate);
|
|
|
|
switch (keystate)
|
|
{
|
|
case G15_KEY_L5:{
|
|
int fg_check = g15_send_cmd (g15screen_fd, G15DAEMON_IS_FOREGROUND, foo);
|
|
if(fg_check)
|
|
leaving = 1;
|
|
break;
|
|
}
|
|
case G15_KEY_MR: {
|
|
if(!recording) {
|
|
if(0==g15_send_cmd (g15screen_fd, G15DAEMON_IS_FOREGROUND, foo)){
|
|
usleep(1000);
|
|
g15_send_cmd (g15screen_fd, G15DAEMON_SWITCH_PRIORITIES, foo);
|
|
g15macro_log("Switching to LCD foreground\n");
|
|
}
|
|
usleep(1000);
|
|
g15_send_cmd (g15screen_fd,G15DAEMON_MKEYLEDS, G15_LED_MR | mled_state);
|
|
g15r_initCanvas (canvas);
|
|
g15r_renderString (canvas, (unsigned char *)"Recording", 0, G15_TEXT_LARGE, 80-((strlen("Recording")/2)*8), 1);
|
|
g15_send(g15screen_fd,(char *)canvas->buffer,G15_BUFFER_LEN);
|
|
g15macro_log("Recording Enabled\n");
|
|
recording = 1;
|
|
pthread_mutex_lock(&x11mutex);
|
|
XGrabKeyboard(dpy, root_win, True, GrabModeAsync, GrabModeAsync, CurrentTime);
|
|
pthread_mutex_unlock(&x11mutex);
|
|
memset(¤t_recording,0,sizeof(current_recording));
|
|
} else {
|
|
record_cancel();
|
|
}
|
|
break;
|
|
}
|
|
case G15_KEY_M1:
|
|
handle_mkey_switch(G15_KEY_M1);
|
|
break;
|
|
case G15_KEY_M2:
|
|
handle_mkey_switch(G15_KEY_M2);
|
|
break;
|
|
case G15_KEY_M3:
|
|
handle_mkey_switch(G15_KEY_M3);
|
|
break;
|
|
default:
|
|
if(keystate >=G15_KEY_G1 && keystate <=G15_KEY_G18){
|
|
if(recording==1){
|
|
record_complete(keystate);
|
|
} else {
|
|
macro_playback(keystate);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
keystate = 0;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
unsigned int g15daemon_gettime_ms(){
|
|
struct timeval tv;
|
|
gettimeofday(&tv,NULL);
|
|
return (tv.tv_sec*1000+tv.tv_usec/1000);
|
|
}
|
|
|
|
void xkey_handler(XEvent *event) {
|
|
|
|
static unsigned long lasttime;
|
|
unsigned char keytext[256];
|
|
unsigned int keycode = event->xkey.keycode;
|
|
int press = True;
|
|
|
|
if(event->type==KeyRelease){ // we only do keyreleases for some keys
|
|
pthread_mutex_lock(&x11mutex);
|
|
KeySym key = XKeycodeToKeysym(dpy, keycode, 0);
|
|
pthread_mutex_unlock(&x11mutex);
|
|
switch (key) {
|
|
case XK_Shift_L:
|
|
case XK_Shift_R:
|
|
case XK_Control_L:
|
|
case XK_Control_R:
|
|
case XK_Caps_Lock:
|
|
case XK_Shift_Lock:
|
|
case XK_Meta_L:
|
|
case XK_Meta_R:
|
|
case XK_Alt_L:
|
|
case XK_Alt_R:
|
|
case XK_Super_L:
|
|
case XK_Super_R:
|
|
case XK_Hyper_L:
|
|
case XK_Hyper_R:
|
|
default:
|
|
press = False;
|
|
}
|
|
}
|
|
if(recording){
|
|
current_recording.recorded_keypress[rec_index].keycode = keycode;
|
|
current_recording.recorded_keypress[rec_index].pressed = press;
|
|
current_recording.recorded_keypress[rec_index].modifiers = event->xkey.state;
|
|
if(rec_index==0)
|
|
current_recording.recorded_keypress[rec_index].time_ms=0;
|
|
else
|
|
current_recording.recorded_keypress[rec_index].time_ms=g15daemon_gettime_ms() - lasttime;
|
|
if(rec_index < MAX_KEYSTEPS) {
|
|
rec_index++;
|
|
/* now the default stuff */
|
|
pthread_mutex_lock(&x11mutex);
|
|
XUngrabKeyboard(dpy,CurrentTime);
|
|
pthread_mutex_unlock(&x11mutex);
|
|
|
|
fake_keyevent(keycode,press,event->xkey.state);
|
|
|
|
pthread_mutex_lock(&x11mutex);
|
|
XGrabKeyboard(dpy, root_win, True, GrabModeAsync, GrabModeAsync, CurrentTime);
|
|
XFlush(dpy);
|
|
strcpy((char*)keytext,XKeysymToString(XKeycodeToKeysym(dpy, keycode, 0)));
|
|
pthread_mutex_unlock(&x11mutex);
|
|
if(0==strcmp((char*)keytext,"space"))
|
|
strcpy((char*)keytext," ");
|
|
if(0==strcmp((char*)keytext,"period"))
|
|
strcpy((char*)keytext,".");
|
|
if(press==True){
|
|
strcat((char*)recstring,(char*)keytext);
|
|
g15macro_log("Adding %s to Macro\n",keytext);
|
|
g15r_renderString (canvas, (unsigned char *)recstring, 0, G15_TEXT_MED, 80-((strlen((char*)recstring)/2)*5), 22);
|
|
g15_send(g15screen_fd,(char *)canvas->buffer,G15_BUFFER_LEN);
|
|
}
|
|
} else {
|
|
pthread_mutex_lock(&x11mutex);
|
|
XUngrabKeyboard(dpy,CurrentTime);
|
|
pthread_mutex_unlock(&x11mutex);
|
|
recording = 0;
|
|
rec_index = 0;
|
|
}
|
|
|
|
}else
|
|
rec_index=0;
|
|
|
|
lasttime = g15daemon_gettime_ms();
|
|
}
|
|
|
|
static void* xevent_thread()
|
|
{
|
|
XEvent event;
|
|
long event_mask = KeyPressMask|KeyReleaseMask|FocusChangeMask|SubstructureNotifyMask;
|
|
int retval=0;
|
|
pthread_mutex_lock(&x11mutex);
|
|
XSelectInput(dpy, root_win, event_mask);
|
|
pthread_mutex_unlock(&x11mutex);
|
|
while(!leaving){
|
|
pthread_mutex_lock(&x11mutex);
|
|
memset(&event,0,sizeof(XEvent));
|
|
retval = XCheckMaskEvent(dpy, event_mask, &event);
|
|
pthread_mutex_unlock(&x11mutex);
|
|
if(retval == True){
|
|
switch(event.type) {
|
|
case KeyPress:
|
|
xkey_handler(&event);
|
|
break;
|
|
case KeyRelease:
|
|
xkey_handler(&event);
|
|
break;
|
|
case FocusIn:
|
|
case FocusOut:
|
|
case EnterNotify:
|
|
case LeaveNotify:
|
|
case MapNotify:
|
|
case UnmapNotify:
|
|
case MapRequest:
|
|
case ConfigureNotify:
|
|
case CreateNotify:
|
|
case DestroyNotify:
|
|
break;
|
|
case ReparentNotify: {
|
|
if(recording) {
|
|
pthread_mutex_lock(&x11mutex);
|
|
XGrabKeyboard(dpy, root_win, True, GrabModeAsync, GrabModeAsync, CurrentTime);
|
|
XFlush(dpy);
|
|
pthread_mutex_unlock(&x11mutex);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
g15macro_log("Unhandled event (%i) received\n",event.type);
|
|
}
|
|
}else
|
|
usleep(25000);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int myx_error_handler(Display *dpy, XErrorEvent *err){
|
|
return 0;
|
|
}
|
|
|
|
void g15macro_sighandler(int sig) {
|
|
switch(sig){
|
|
case SIGINT:
|
|
case SIGTERM:
|
|
case SIGQUIT:
|
|
case SIGPIPE:
|
|
leaving = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void helptext() {
|
|
printf("G15Macro %s\n",PACKAGE_VERSION);
|
|
printf("\n--user (-u) \"username\" run as user \"username\"\n");
|
|
printf("--dump (-d) dump current configuration to stdout\n");
|
|
printf("--debug (-g) print debugging information\n");
|
|
printf("--version (-v) print version and exit\n");
|
|
printf("--keysonly (-k) configure multimedia and extra keys then exit\n");
|
|
printf("--help (-h) this help text\n\n");
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
pthread_t Xkeys;
|
|
pthread_t Lkeys;
|
|
#ifdef USE_XTEST
|
|
int xtest_major_version = 0;
|
|
int xtest_minor_version = 0;
|
|
#endif
|
|
struct sigaction new_action;
|
|
int dummy=0,i=0;
|
|
unsigned char user[256];
|
|
struct passwd *username;
|
|
char configpath[1024];
|
|
char splashpath[1024];
|
|
unsigned int dump = 0;
|
|
unsigned int keysonly = 0;
|
|
FILE *config;
|
|
unsigned int convert = 0;
|
|
strncpy(configpath,getenv("HOME"),1024);
|
|
|
|
memset(user,0,256);
|
|
for(i=0;i<argc;i++){
|
|
if (!strncmp(argv[i], "-u",2) || !strncmp(argv[i], "--user",6)) {
|
|
if(argv[i+1]!=NULL){
|
|
strncpy((char*)user,argv[i+1],128);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
if (!strncmp(argv[i], "-d",2) || !strncmp(argv[i], "--dump",6)) {
|
|
dump = 1;
|
|
}
|
|
|
|
if (!strncmp(argv[i], "-h",2) || !strncmp(argv[i], "--help",6)) {
|
|
helptext();
|
|
exit(0);
|
|
}
|
|
|
|
if (!strncmp(argv[i], "-k",2) || !strncmp(argv[i], "--keysonly",10)) {
|
|
keysonly = 1;
|
|
}
|
|
|
|
if (!strncmp(argv[i], "-g",2) || !strncmp(argv[i], "--debug",7)) {
|
|
printf("Debugging Enabled\n");
|
|
debug = 1;
|
|
}
|
|
|
|
if (!strncmp(argv[i], "-v",2) || !strncmp(argv[i], "--version",9)) {
|
|
printf("G15Macro version %s\n\n",PACKAGE_VERSION);
|
|
exit(0);
|
|
}
|
|
|
|
}
|
|
|
|
if(strlen((char*)user)){
|
|
username = getpwnam((char*)user);
|
|
if (username==NULL) {
|
|
username = getpwuid(geteuid());
|
|
printf("BEWARE: running as effective uid %i\n",username->pw_uid);
|
|
}
|
|
else {
|
|
if(0==setuid(username->pw_uid)) {
|
|
setgid(username->pw_gid);
|
|
strncpy(configpath,username->pw_dir,1024);
|
|
printf("running as user %s\n",username->pw_name);
|
|
}
|
|
else
|
|
printf("Unable to run as user \"%s\" - you dont have permissions for that.\nRunning as \"%s\"\n",username->pw_name,getenv("USER"));
|
|
}
|
|
}
|
|
|
|
do {
|
|
dpy = XOpenDisplay(getenv("DISPLAY"));
|
|
if (!dpy) {
|
|
printf("Unable to open display %s - retrying\n",getenv("DISPLAY"));
|
|
sleep(2);
|
|
}
|
|
}while(!dpy);
|
|
|
|
/* completely ignore errors and carry on */
|
|
XSetErrorHandler(myx_error_handler);
|
|
configure_mmediakeys();
|
|
change_keymap(0);
|
|
XFlush(dpy);
|
|
|
|
if(keysonly>0)
|
|
goto close_and_exit;
|
|
|
|
/* old binary config format */
|
|
strncat(configpath,"/.g15macro",1024-strlen(configpath));
|
|
strncat(configpath,"/g15macro-data",1024-strlen(configpath));
|
|
config_fd = open(configpath,O_RDONLY|O_SYNC);
|
|
|
|
mstates[0] = malloc(sizeof(mstates_t));
|
|
mstates[1] = (mstates_t*)malloc(sizeof(mstates_t));
|
|
mstates[2] = (mstates_t*)malloc(sizeof(mstates_t));
|
|
|
|
if(config_fd>0) {
|
|
printf("Converting old data\n");
|
|
read(config_fd,mstates[0],sizeof(mstates_t));
|
|
read(config_fd,mstates[1],sizeof(mstates_t));
|
|
read(config_fd,mstates[2],sizeof(mstates_t));
|
|
close(config_fd);
|
|
strncpy(configpath,getenv("HOME"),1024);
|
|
strncat(configpath,"/.g15macro",1024-strlen(configpath));
|
|
char configbak[1024];
|
|
strcpy(configbak,configpath);
|
|
strncat(configpath,"/g15macro-data",1024-strlen(configpath));
|
|
strncat(configbak,"/g15macro-data.old",1024-strlen(configpath));
|
|
rename(configpath,configbak);
|
|
convert = 1;
|
|
}else {
|
|
memset(mstates[0],0,sizeof(mstates));
|
|
memset(mstates[1],0,sizeof(mstates));
|
|
memset(mstates[2],0,sizeof(mstates));
|
|
}
|
|
/* new format */
|
|
strncpy(configpath,getenv("HOME"),1024);
|
|
strncat(configpath,"/.g15macro",1024-strlen(configpath));
|
|
mkdir(configpath,0777);
|
|
strncat(configpath,"/g15macro.conf",1024-strlen(configpath));
|
|
config=fopen(configpath,"a");
|
|
fclose(config);
|
|
|
|
do {
|
|
if((g15screen_fd = new_g15_screen(G15_G15RBUF))<0){
|
|
printf("Sorry, cant connect to the G15daemon - retrying\n");
|
|
sleep(2);
|
|
}
|
|
}while(g15screen_fd<0);
|
|
|
|
if(!convert)
|
|
restore_config(configpath);
|
|
|
|
if(dump){
|
|
printf("G15Macro Dumping Codes...");
|
|
dump_config(stderr);
|
|
exit(0);
|
|
}
|
|
|
|
g15_send_cmd (g15screen_fd,G15DAEMON_KEY_HANDLER, dummy);
|
|
usleep(1000);
|
|
g15_send_cmd (g15screen_fd,G15DAEMON_MKEYLEDS,mled_state);
|
|
usleep(1000);
|
|
canvas = (g15canvas *) malloc (sizeof (g15canvas));
|
|
|
|
if (canvas != NULL) {
|
|
g15r_initCanvas(canvas);
|
|
} else {
|
|
printf("Unable to initialise the libg15render canvas\nExiting\n");
|
|
return 1;
|
|
}
|
|
|
|
root_win = DefaultRootWindow(dpy);
|
|
if (!root_win) {
|
|
printf("Cant find root window\n");
|
|
return 1;
|
|
}
|
|
|
|
have_xtest = False;
|
|
#ifdef HAVE_XTEST
|
|
#ifdef USE_XTEST
|
|
have_xtest = XTestQueryExtension(dpy, &dummy, &dummy, &xtest_major_version, &xtest_minor_version);
|
|
if(have_xtest == False || xtest_major_version < 2 || (xtest_major_version <= 2 && xtest_minor_version < 2))
|
|
{
|
|
printf("Warning: XTEST extension not supported by Xserver. This is not fatal.\nReverting to XSendEvent for keypress emulation\n");
|
|
}
|
|
#else
|
|
printf("XTest disabled by configure option. Using XSendEvent instead.\n");
|
|
#endif
|
|
#else
|
|
printf("XTest disabled by configure: no devel package was found. Using XSendEvent instead.\n");
|
|
#endif
|
|
|
|
|
|
new_action.sa_handler = g15macro_sighandler;
|
|
new_action.sa_flags = 0;
|
|
sigaction(SIGINT, &new_action, NULL);
|
|
sigaction(SIGQUIT, &new_action, NULL);
|
|
sigaction(SIGTERM, &new_action, NULL);
|
|
sigaction(SIGPIPE, &new_action, NULL);
|
|
|
|
snprintf((char*)splashpath,1024,"%s/%s",DATADIR,"g15macro/splash/g15macro.wbmp");
|
|
g15r_loadWbmpSplash(canvas, splashpath);
|
|
g15_send(g15screen_fd,(char *)canvas->buffer,G15_BUFFER_LEN);
|
|
#ifdef G15DAEMON_NEVER_SELECT
|
|
g15_send_cmd (g15screen_fd, G15DAEMON_NEVER_SELECT, dummy);
|
|
#endif
|
|
|
|
usleep(1000);
|
|
pthread_mutex_init(&x11mutex,NULL);
|
|
pthread_mutex_init(&config_mutex,NULL);
|
|
pthread_attr_t attr;
|
|
pthread_attr_init(&attr);
|
|
int thread_policy=SCHED_FIFO;
|
|
pthread_attr_setschedpolicy(&attr,thread_policy);
|
|
pthread_attr_setstacksize(&attr,32*1024); /* set stack to 32k - dont need 8Mb !! */
|
|
|
|
pthread_create(&Xkeys, &attr, xevent_thread, NULL);
|
|
pthread_create(&Lkeys, &attr, Lkeys_thread, NULL);
|
|
do{
|
|
if(display_timeout<-1)
|
|
display_timeout=-1;
|
|
else
|
|
display_timeout--;
|
|
|
|
if(recording)
|
|
display_timeout=500;
|
|
|
|
if(display_timeout<=0){
|
|
int fg_check = g15_send_cmd (g15screen_fd, G15DAEMON_IS_FOREGROUND, dummy);
|
|
if (fg_check==1) { // foreground
|
|
do {
|
|
g15_send_cmd (g15screen_fd, G15DAEMON_SWITCH_PRIORITIES, dummy);
|
|
} while(g15_send_cmd (g15screen_fd, G15DAEMON_IS_FOREGROUND, dummy)==1);
|
|
}
|
|
|
|
usleep(500*1000);
|
|
}
|
|
}while(!usleep(1000) && !leaving);
|
|
|
|
if(recording){
|
|
recording = 0;
|
|
XUngrabKeyboard(dpy,CurrentTime);
|
|
}
|
|
|
|
save_macros(configpath);
|
|
g15_send_cmd (g15screen_fd,G15DAEMON_MKEYLEDS,0);
|
|
|
|
pthread_join(Xkeys,NULL);
|
|
pthread_join(Lkeys,NULL);
|
|
pthread_mutex_destroy(&x11mutex);
|
|
pthread_mutex_destroy(&config_mutex);
|
|
/* revert the keymap to g15daemon default on exit */
|
|
change_keymap(0);
|
|
close(g15screen_fd);
|
|
close_and_exit:
|
|
/* XCloseDisplay(dpy); */
|
|
return 0;
|
|
}
|