blob: 27f0eeffa22d989c238cebaa4e57907b3025a08c [file] [log] [blame] [edit]
/*
* XBrlAPI - A background process tinkering with X for proper BrlAPI behavior
*
* Copyright (C) 2003-2023 by Samuel Thibault <Samuel.Thibault@ens-lyon.org>
*
* XBrlAPI comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
/* Compile with:
* gcc -O3 -Wall xbrlapi.c -L/usr/X11R6/lib -lbrlapi -lX11 -o xbrlapi
*/
#include "prologue.h"
#include <stdio.h>
#include <stdarg.h>
#include <signal.h>
#include <string.h>
#ifdef HAVE_LANGINFO_H
#include <langinfo.h>
#endif /* HAVE_LANGINFO_H */
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#else /* HAVE_SYS_SELECT_H */
#include <sys/time.h>
#endif /* HAVE_SYS_SELECT_H */
#ifdef HAVE_ICONV_H
#include <iconv.h>
#endif /* HAVE_ICONV_H */
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/XKBlib.h>
#include <X11/keysym.h>
#undef CAN_SIMULATE_KEY_PRESSES
#if defined(HAVE_X11_EXTENSIONS_XTEST_H) && defined(HAVE_X11_EXTENSIONS_XKB_H)
#include <X11/extensions/XTest.h>
#define CAN_SIMULATE_KEY_PRESSES
#else /* HAVE_X11_EXTENSIONS_XTEST_H && HAVE_X11_EXTENSIONS_XKB_H */
#warning key press simulation not supported by this build - check that libxtst has been installed
#endif /* HAVE_X11_EXTENSIONS_XTEST_H && HAVE_X11_EXTENSIONS_XKB_H */
#include "xsel.h"
#define BRLAPI_NO_DEPRECATED
#include "brlapi.h"
#include "cmdline.h"
#define debugf(fmt, ...) do { if (verbose) fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
/******************************************************************************
* option handling
*/
static char *auth;
static char *host;
static char *xDisplay;
static int no_daemon;
static int quiet;
static int verbose;
static int xkb_major_opcode;
static int brlapi_fd;
static void *clipboardData;
BEGIN_OPTION_TABLE(programOptions)
{ .word = "brlapi",
.letter = 'b',
.argument = strtext("[host][:port]"),
.setting.string = &host,
.description = strtext("BrlAPI host and/or port to connect to")
},
{ .word = "auth",
.letter = 'a',
.argument = strtext("scheme+..."),
.setting.string = &auth,
.description = strtext("BrlAPI authorization/authentication schemes")
},
{ .word = "display",
.letter = 'd',
.argument = strtext("display"),
.setting.string = &xDisplay,
.description = strtext("X display to connect to")
},
{ .word = "quiet",
.letter = 'q',
.setting.flag = &quiet,
.description = strtext("Do not write any text to the braille device")
},
{ .word = "verbose",
.letter = 'v',
.setting.flag = &verbose,
.description = strtext("Write debugging output to stdout")
},
{ .word = "no-daemon",
.letter = 'n',
.setting.flag = &no_daemon,
.description = strtext("Remain a foreground process")
},
END_OPTION_TABLE(programOptions)
/******************************************************************************
* error handling
*/
static void api_cleanExit(void) {
if (brlapi_fd>=0)
{
close(brlapi_fd);
brlapi_fd=-1;
}
}
/* dumps errors which are fatal to brlapi only */
static void fatal_brlapi_errno(const char *msg, const char *fmt, ...) {
brlapi_perror(msg);
if (fmt) {
va_list va;
va_start(va,fmt);
vfprintf(stderr,fmt,va);
va_end(va);
}
api_cleanExit();
}
static void exception_handler(int error, brlapi_packetType_t type, const void *packet, size_t size) {
char str[0X100];
brlapi_strexception(str,0X100, error, type, packet, size);
fprintf(stderr, "xbrlapi: BrlAPI exception: %s\nDisconnecting from brlapi\n", str);
api_cleanExit();
}
/* dumps errors which are fatal to the whole xbrlapi */
static void fatal_errno(const char *msg, const char *fmt, ...) {
perror(msg);
if (fmt) {
va_list va;
va_start(va,fmt);
vfprintf(stderr,fmt,va);
va_end(va);
}
exit(PROG_EXIT_FATAL);
}
static void fatal(const char *fmt, ...) {
if (fmt) {
va_list va;
va_start(va,fmt);
vfprintf(stderr,fmt,va);
va_end(va);
}
exit(PROG_EXIT_FATAL);
}
/******************************************************************************
* brlapi handling
*/
#ifndef MIN
#define MIN(a, b) (((a) < (b))? (a): (b))
#endif /* MIN */
static void clipboardContentChanged(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, void *priv, const void *data, size_t len);
static int tobrltty_init(char *auth, char *host) {
brlapi_connectionSettings_t settings;
unsigned int x,y;
settings.host=host;
settings.auth=auth;
static int had_succeeded;
brlapi_param_clientPriority_t priority;
brlapi_param_retainDots_t dots;
if ((brlapi_fd = brlapi_openConnection(&settings,&settings))<0)
{
if (!had_succeeded)
{
/* This is the first attempt to connect to BRLTTY, and it failed.
* Return the error immediately to the user, to provide feedback to users
* running xbrlapi by hand, but not fill logs, eat battery, spam
* 127.0.0.1 with reconnection attempts.
*/
fatal_brlapi_errno("openConnection",gettext("cannot connect to braille devices daemon brltty at %s\n"),settings.host);
exit(PROG_EXIT_FATAL);
}
return 0;
}
/* We achieved connecting to BRLTTY. If BRLTTY dies later on, we will
* silently try to reconnect to it. */
had_succeeded = 1;
if (brlapi_getDisplaySize(&x,&y)<0)
{
fatal_brlapi_errno("getDisplaySize",NULL);
return 0;
}
if (x == 0)
{
/* Braille device not initialized yet */
api_cleanExit();
return 0;
}
brlapi_setExceptionHandler(exception_handler);
/* Our output is really not very interesting */
priority = 10;
brlapi_setParameter(BRLAPI_PARAM_CLIENT_PRIORITY, 0, BRLAPI_PARAMF_LOCAL, &priority, sizeof(priority));
/* We prefer to get translated keypresses */
dots = 0;
brlapi_setParameter(BRLAPI_PARAM_RETAIN_DOTS, 0, BRLAPI_PARAMF_LOCAL, &dots, sizeof(dots));
/* X already has some clipboard content */
if (clipboardData)
brlapi_setParameter(BRLAPI_PARAM_CLIPBOARD_CONTENT, 0, BRLAPI_PARAMF_GLOBAL, clipboardData, strlen(clipboardData));
/* We want to monitor clipboard changes */
brlapi_watchParameter(BRLAPI_PARAM_CLIPBOARD_CONTENT, 0, BRLAPI_PARAMF_GLOBAL, clipboardContentChanged, NULL, NULL, 0);
return 1;
}
static int getXVTnb(void);
static void getVT(void) {
char *path = getenv("WINDOWPATH");
char *vtnr = getenv("XDG_VTNR");
int vtno = -1;
if (!path && !vtnr)
/* Workaround for old xinit/xdm/gdm/kdm */
vtno = getXVTnb();
if (path || vtnr || vtno == -1) {
if (brlapi_enterTtyModeWithPath(NULL,0,NULL)<0)
{
fatal_brlapi_errno("geTtyPath",gettext("cannot get tty\n"));
return;
}
} else {
if (brlapi_enterTtyMode(vtno,NULL)<0)
{
fatal_brlapi_errno("enterTtyMode",gettext("cannot get tty %d\n"),vtno);
return;
}
}
if (brlapi_ignoreAllKeys()<0)
{
fatal_brlapi_errno("ignoreAllKeys",gettext("cannot ignore keys\n"));
return;
}
#ifdef CAN_SIMULATE_KEY_PRESSES
/* All X keysyms with any modifier */
brlapi_keyCode_t cmd = BRLAPI_KEY_TYPE_SYM;
if (brlapi_acceptKeys(brlapi_rangeType_type, &cmd, 1))
{
fatal_brlapi_errno("acceptKeys",NULL);
return;
}
cmd = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_SHIFT;
if (brlapi_acceptKeys(brlapi_rangeType_key, &cmd, 1))
{
fatal_brlapi_errno("acceptKeys",NULL);
return;
}
cmd = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_UPPER;
if (brlapi_acceptKeys(brlapi_rangeType_key, &cmd, 1))
{
fatal_brlapi_errno("acceptKeys",NULL);
return;
}
cmd = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_CONTROL;
if (brlapi_acceptKeys(brlapi_rangeType_key, &cmd, 1))
{
fatal_brlapi_errno("acceptKeys",NULL);
return;
}
cmd = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_META;
if (brlapi_acceptKeys(brlapi_rangeType_key, &cmd, 1))
{
fatal_brlapi_errno("acceptKeys",NULL);
return;
}
#endif /* CAN_SIMULATE_KEY_PRESSES */
}
static char *last_name;
static void api_setLastName(void) {
if (!last_name) return;
if (brlapi_writeText(0,last_name)<0) {
brlapi_perror("writeText");
fprintf(stderr,gettext("xbrlapi: cannot write window name %s\n"),last_name);
}
}
static void api_setName(const char *wm_name) {
if (brlapi_fd<0) return;
debugf("%s got focus\n",wm_name);
if (last_name) {
if (!strcmp(wm_name,last_name)) return;
free(last_name);
}
if (!(last_name=strdup(wm_name))) fatal_errno("strdup(wm_name)",NULL);
api_setLastName();
}
static int last_win;
static void api_setLastFocus(void)
{
if (brlapi_setFocus(last_win)<0)
fatal_brlapi_errno("setFocus",gettext("cannot set focus to %#010x\n"),last_win);
}
static void api_setFocus(int win) {
if (brlapi_fd<0) return;
debugf("%#010x (%d) got focus\n",win,win);
last_win = win;
api_setLastFocus();
}
/******************************************************************************
* X handling
*/
static const char *Xdisplay;
static Display *dpy;
static Window curWindow;
static Atom netWmNameAtom, utf8StringAtom;
static XSelData xselData;
static volatile sig_atomic_t grabFailed;
#ifdef HAVE_ICONV_H
iconv_t utf8Conv = (iconv_t)(-1);
#endif /* HAVE_ICONV_H */
#define WINHASHBITS 12
static struct window {
Window win;
Window root;
char *wm_name;
struct window *next;
} *windows[(1<<WINHASHBITS)];
#define WINHASH(win) windows[(win)>>(32-WINHASHBITS)^(win&((1<<WINHASHBITS)-1))]
static void add_window(Window win, Window root, char *wm_name) {
struct window *cur;
if (!(cur=malloc(sizeof(struct window))))
fatal_errno("malloc(struct window)",NULL);
cur->win=win;
cur->wm_name=wm_name;
cur->root=root;
cur->next=WINHASH(win);
WINHASH(win)=cur;
}
static struct window *window_of_Window(Window win) {
struct window *cur;
for (cur=WINHASH(win); cur && cur->win!=win; cur=cur->next);
return cur;
}
static int isRootWindow (Window win) {
if (win == PointerRoot) return 1;
{
int count = ScreenCount(dpy);
for (int index=0; index<count; index+=1) {
if (RootWindow(dpy, index) == win) {
return 1;
}
}
}
return 0;
}
static int del_window(Window win) {
struct window **pred;
struct window *cur;
for (pred=&WINHASH(win); cur = *pred, cur && cur->win!=win; pred=&cur->next);
if (cur) {
*pred=cur->next;
free(cur->wm_name);
free(cur);
return 0;
} else return -1;
}
static int ErrorHandler(Display *dpy, XErrorEvent *ev) {
char buffer[128];
if (ev->error_code==BadWindow) {
grabFailed=1;
return 0;
}
#ifdef CAN_SIMULATE_KEY_PRESSES
if (ev->request_code == xkb_major_opcode && ev->minor_code == X_kbSetMap) {
/* Server refused our Xkb remapping request, probably the buggy version 21, ignore error */
fprintf(stderr,gettext("xbrlapi: server refused our mapping request, could not synthesize key\n"));
return 0;
}
#endif
if (XGetErrorText(dpy, ev->error_code, buffer, sizeof(buffer)))
fatal("XGetErrorText");
fprintf(stderr,gettext("xbrlapi: X Error %d, %s on display %s\n"), ev->type, buffer, XDisplayName(Xdisplay));
fprintf(stderr,gettext("xbrlapi: resource %#010lx, req %u:%u\n"),ev->resourceid,ev->request_code,ev->minor_code);
exit(PROG_EXIT_FATAL);
}
static int getXVTnb(void) {
Window root;
Atom property;
Atom actual_type;
int actual_format;
unsigned long nitems;
unsigned long bytes_after;
unsigned char *buf;
int vt = -1;
root=DefaultRootWindow(dpy);
if ((property=XInternAtom(dpy,"XFree86_VT",False))==None) {
fprintf(stderr,gettext("xbrlapi: no XFree86_VT atom\n"));
return -1;
}
if (XGetWindowProperty(dpy,root,property,0,1,False,AnyPropertyType,
&actual_type, &actual_format, &nitems, &bytes_after, &buf)) {
fprintf(stderr,gettext("xbrlapi: cannot get root window XFree86_VT property\n"));
return -1;
}
if (nitems<1) {
fprintf(stderr, gettext("xbrlapi: no items for VT number\n"));
goto out;
}
if (nitems>1)
fprintf(stderr,gettext("xbrlapi: more than one item for VT number\n"));
switch (actual_type) {
case XA_CARDINAL:
case XA_INTEGER:
case XA_WINDOW:
switch (actual_format) {
case 8: vt = (*(uint8_t *)buf); break;
case 16: vt = (*(uint16_t *)buf); break;
case 32: vt = (*(uint32_t *)buf); break;
default: fprintf(stderr, gettext("xbrlapi: bad format for VT number\n")); goto out;
}
break;
default: fprintf(stderr, gettext("xbrlapi: bad type for VT number\n")); goto out;
}
out:
if (!XFree(buf)) fatal("XFree(VTnobuf)");
return vt;
}
static int grabWindow(Window win,int level) {
#ifdef DEBUG
char spaces[level+1];
#endif /* DEBUG */
grabFailed=0;
if (!XSelectInput(dpy,win,PropertyChangeMask|FocusChangeMask|SubstructureNotifyMask) || grabFailed)
return 0;
#ifdef DEBUG
memset(spaces,' ',level);
spaces[level]='\0';
debugf("%sgrabbed %#010lx\n",spaces,win);
#endif /* DEBUG */
return 1;
}
static char *getWindowTitle(Window win) {
int wm_name_size=32;
Atom actual_type;
int actual_format;
unsigned long nitems,bytes_after;
unsigned char *wm_name=NULL;
char *ret;
do {
if (XGetWindowProperty(dpy,win,netWmNameAtom,0,wm_name_size,False,
/*XA_STRING*/AnyPropertyType,&actual_type,&actual_format,&nitems,&bytes_after,
&wm_name)) {
wm_name = NULL;
break; /* window disappeared or not available */
}
wm_name_size+=bytes_after;
if (!bytes_after) break;
if (!XFree(wm_name)) fatal("tempo_XFree(wm_name)");
} while (1);
if (!wm_name) do {
if (XGetWindowProperty(dpy,win,XA_WM_NAME,0,wm_name_size,False,
/*XA_STRING*/AnyPropertyType,&actual_type,&actual_format,&nitems,&bytes_after,
&wm_name))
return NULL; /* window disappeared */
if (wm_name_size >= nitems + 1) break;
wm_name_size += bytes_after + 1;
if (!XFree(wm_name)) fatal("tempo_XFree(wm_name)");
} while (1);
if (actual_type==None) {
XFree(wm_name);
return NULL;
}
wm_name[nitems++] = 0;
ret = strdup((char *) wm_name);
XFree(wm_name);
debugf("type %ld name %s len %ld\n",actual_type,ret,nitems);
#ifdef HAVE_ICONV_H
{
if (actual_type == utf8StringAtom && utf8Conv != (iconv_t)(-1)) {
char *ret2;
size_t input_size, output_size;
char *input, *output;
input_size = nitems;
input = ret;
output_size = nitems * MB_CUR_MAX;
output = ret2 = malloc(output_size);
if (iconv(utf8Conv, &input, &input_size, &output, &output_size) == -1) {
free(ret2);
} else {
free(ret);
ret = realloc(ret2, nitems * MB_CUR_MAX - output_size);
debugf("-> %s\n",ret);
}
}
}
#endif /* HAVE_ICONV_H */
return ret;
}
static int grabWindows(Window win,int level) {
Window root,parent,*children;
unsigned int nchildren,i;
int res=1;
if (!grabWindow(win,level)) return 1; /* window disappeared */
if (!XQueryTree(dpy,win,&root,&parent,&children,&nchildren)) return 0;
add_window(win,root,getWindowTitle(win));
if (!children) return 1;
for (i=0;i<nchildren;i++)
if (children[i] && !grabWindows(children[i],level+1)) {
res=0;
break;
}
if (!XFree(children)) fatal("XFree(children)");
return res;
}
static void setName(const struct window *window) {
if (!window->wm_name) {
if (window->win!=window->root)
api_setName("window without name");
} else api_setName(window->wm_name);
}
static void setFocus(Window win) {
curWindow=win;
api_setFocus((uint32_t)win);
if (!quiet) {
struct window *window = window_of_Window(win);
if (window) {
setName(window);
} else {
fprintf(stderr, gettext("xbrlapi: didn't grab window %#010lx but got focus\n"), win);
api_setName(isRootWindow(win)? "root window": "unnamed window");
}
}
}
#ifdef CAN_SIMULATE_KEY_PRESSES
static int tryModifiers(KeyCode keycode, unsigned int *modifiers, unsigned int modifiers_try, KeySym keysym) {
KeySym keysymRet;
unsigned int modifiersRet;
if (!XkbLookupKeySym(dpy, keycode, modifiers_try, &modifiersRet, &keysymRet))
return 0;
if (keysymRet != keysym)
return 0;
*modifiers |= modifiers_try;
return 1;
}
static void ignoreServerKeys(void) {
brlapi_range_t range = {
.first = BRLAPI_KEY_FLG(ControlMask|Mod1Mask),
.last = BRLAPI_KEY_FLG(ControlMask|Mod1Mask)|~BRLAPI_KEY_FLAGS_MASK,
};
if (brlapi_ignoreKeyRanges(&range, 1))
{
fatal_brlapi_errno("ignoreKeyRanges",NULL);
return;
}
}
#endif /* CAN_SIMULATE_KEY_PRESSES */
static void clipboardContentChanged(brlapi_param_t parameter, brlapi_param_subparam_t subparam, brlapi_param_flags_t flags, void *priv, const void *data, size_t len) {
free(clipboardData);
clipboardData = strndup(data, len);
debugf("new clipboard content from BrlAPI: '%s'\n", (const char *) clipboardData);
if (dpy)
XSelSet(dpy, &xselData);
}
static void XClipboardContentChanged(const char *data, unsigned long size) {
free(clipboardData);
if (data) {
clipboardData = strndup(data, size);
brlapi_setParameter(BRLAPI_PARAM_CLIPBOARD_CONTENT, 0, BRLAPI_PARAMF_GLOBAL, clipboardData, size);
debugf("new clipboard content from X: '%s'\n", (const char *) clipboardData);
} else
clipboardData = NULL;
}
static void toX_f(const char *display) {
Window root;
XEvent ev;
int i;
int X_fd;
fd_set readfds;
int maxfd;
#ifdef CAN_SIMULATE_KEY_PRESSES
int res;
brlapi_keyCode_t code;
unsigned int keysym, keycode, modifiers, next_modifiers = 0;
Bool haveXTest;
int eventBase, errorBase, majorVersion, minorVersion;
XkbDescPtr xkb = NULL;
XkbMapChangesRec changes = { .changed = XkbKeyTypesMask|XkbKeySymsMask };
int oneGroupType[XkbNumKbdGroups] = { XkbOneLevelIndex };
Status status;
int last_remap_keycode = -1, remap_keycode;
#endif /* CAN_SIMULATE_KEY_PRESSES */
Xdisplay = display;
if (!Xdisplay) Xdisplay=getenv("DISPLAY");
if (!(dpy=XOpenDisplay(Xdisplay))) fatal(gettext("cannot connect to display %s\n"),Xdisplay);
if (!XSetErrorHandler(ErrorHandler)) fatal(gettext("strange old error handler\n"));
#ifdef CAN_SIMULATE_KEY_PRESSES
haveXTest = XTestQueryExtension(dpy, &eventBase, &errorBase, &majorVersion, &minorVersion);
{
int foo;
int major = XkbMajorVersion, minor = XkbMinorVersion;
if (!XkbLibraryVersion(&major, &minor))
fatal(gettext("Incompatible XKB library\n"));
if (!XkbQueryExtension(dpy, &foo, &foo, &foo, &major, &minor))
fatal(gettext("Incompatible XKB server support\n"));
if (!XQueryExtension(dpy, "XKEYBOARD", &xkb_major_opcode, &foo, &foo))
fatal(gettext("Could not get XKB major opcode\n"));
}
#endif /* CAN_SIMULATE_KEY_PRESSES */
XSelInit(dpy, &xselData);
if (clipboardData)
XSelSet(dpy, &xselData);
X_fd = XConnectionNumber(dpy);
if (brlapi_fd>=0)
{
getVT();
#ifdef CAN_SIMULATE_KEY_PRESSES
ignoreServerKeys();
#endif /* CAN_SIMULATE_KEY_PRESSES */
}
netWmNameAtom = XInternAtom(dpy,"_NET_WM_NAME",False);
utf8StringAtom = XInternAtom(dpy,"UTF8_STRING",False);
#if defined(HAVE_NL_LANGINFO) && defined(HAVE_ICONV_H)
{
char *localCharset = nl_langinfo(CODESET);
if (strcmp(localCharset, "UTF-8")) {
char buf[strlen(localCharset) + 10 + 1];
snprintf(buf, sizeof(buf), "%s//TRANSLIT", localCharset);
if ((utf8Conv = iconv_open(buf, "UTF-8")) == (iconv_t)(-1))
utf8Conv = iconv_open(localCharset, "UTF-8");
}
}
#endif /* defined(HAVE_NL_LANGINFO) && defined(HAVE_ICONV_H) */
for (i=0;i<ScreenCount(dpy);i++) {
root=RootWindow(dpy,i);
if (!grabWindows(root,0)) fatal(gettext("cannot grab windows on screen %d\n"),i);
}
{
Window win;
int revert_to;
if (!XGetInputFocus(dpy,&win,&revert_to))
fatal(gettext("failed to get first focus\n"));
setFocus(win);
}
while(1) {
struct timeval timeout={.tv_sec=1,.tv_usec=0};
XFlush(dpy);
FD_ZERO(&readfds);
if (brlapi_fd>=0)
FD_SET(brlapi_fd, &readfds);
FD_SET(X_fd, &readfds);
maxfd = brlapi_fd>=0 && X_fd<brlapi_fd ? brlapi_fd+1 : X_fd+1;
/* Try to reconnect to brlapi every second while disconnected */
if (select(maxfd,&readfds,NULL,NULL,brlapi_fd<=0?&timeout:NULL)<0)
fatal_errno("select",NULL);
if (FD_ISSET(X_fd,&readfds))
while (XPending(dpy)) {
if ((i=XNextEvent(dpy,&ev)))
fatal("XNextEvent: %d\n",i);
if (!XSelProcess(dpy, &xselData, &ev, clipboardData, XClipboardContentChanged))
switch (ev.type) {
/* focus events */
case FocusIn:
switch (ev.xfocus.detail) {
case NotifyAncestor:
case NotifyInferior:
case NotifyNonlinear:
case NotifyPointerRoot:
case NotifyDetailNone:
setFocus(ev.xfocus.window); break;
} break;
case FocusOut:
/* ignore
switch (ev.xfocus.detail) {
case NotifyAncestor:
case NotifyInferior:
case NotifyNonlinear:
case NotifyPointerRoot:
case NotifyDetailNone:
printf("win %#010lx lost focus\n",ev.xfocus.window);
break;
}
*/
break;
/* create & destroy events */
case CreateNotify: {
/* there's a race condition here : a window may get the focus or change
* its title between it is created and we achieve XSelectInput on it */
Window win = ev.xcreatewindow.window;
struct window *window;
if (!grabWindow(win,0)) break; /* window already disappeared ! */
debugf("win %#010lx created\n",win);
if (!(window = window_of_Window(ev.xcreatewindow.parent))) {
fprintf(stderr,gettext("xbrlapi: didn't grab parent of %#010lx\n"),win);
add_window(win,None,getWindowTitle(win));
} else add_window(win,window->root,getWindowTitle(win));
} break;
case DestroyNotify:
debugf("win %#010lx destroyed\n",ev.xdestroywindow.window);
if (del_window(ev.xdestroywindow.window))
debugf("destroy: didn't grab window %#010lx\n",ev.xdestroywindow.window);
break;
/* Property change: WM_NAME ? */
case PropertyNotify:
if (ev.xproperty.atom==XA_WM_NAME ||
(netWmNameAtom != None && ev.xproperty.atom == netWmNameAtom)) {
Window win = ev.xproperty.window;
debugf("WM_NAME property of %#010lx changed\n",win);
struct window *window;
if (!(window=window_of_Window(win))) {
fprintf(stderr,gettext("xbrlapi: didn't grab window %#010lx\n"),win);
add_window(win,None,getWindowTitle(win));
} else {
if (window->wm_name)
if (!XFree(window->wm_name)) fatal(gettext("XFree(wm_name) for change"));
if ((window->wm_name=getWindowTitle(win))) {
if (!quiet && win==curWindow)
api_setName(window->wm_name);
} else fprintf(stderr,gettext("xbrlapi: window %#010lx changed to NULL name\n"),win);
}
}
break;
case MappingNotify:
XRefreshKeyboardMapping(&ev.xmapping);
break;
/* ignored events */
case UnmapNotify:
case MapNotify:
case MapRequest:
case ReparentNotify:
case ConfigureNotify:
case GravityNotify:
case ConfigureRequest:
case CirculateNotify:
case CirculateRequest:
case ClientMessage:
break;
/* "shouldn't happen" events */
default: fprintf(stderr,gettext("xbrlapi: unhandled event type: %d\n"),ev.type); break;
}
}
if (brlapi_fd>=0) {
#ifdef CAN_SIMULATE_KEY_PRESSES
if (haveXTest && FD_ISSET(brlapi_fd,&readfds)) {
while (((res = brlapi_readKey(0, &code))==1)) {
switch (code & BRLAPI_KEY_TYPE_MASK) {
case BRLAPI_KEY_TYPE_CMD:
switch (code & BRLAPI_KEY_CODE_MASK) {
{
unsigned int modifier;
case BRLAPI_KEY_CMD_SHIFT:
modifier = ShiftMask;
goto doModifier;
case BRLAPI_KEY_CMD_UPPER:
modifier = LockMask;
goto doModifier;
case BRLAPI_KEY_CMD_CONTROL:
modifier = ControlMask;
goto doModifier;
case BRLAPI_KEY_CMD_META:
modifier = Mod1Mask;
goto doModifier;
doModifier:
switch (code & BRLAPI_KEY_FLG_TOGGLE_MASK) {
case 0:
next_modifiers ^= modifier;
break;
case BRLAPI_KEY_FLG_TOGGLE_ON:
next_modifiers |= modifier;
break;
case BRLAPI_KEY_FLG_TOGGLE_OFF:
next_modifiers &= ~modifier;
break;
default:
case BRLAPI_KEY_FLG_TOGGLE_ON | BRLAPI_KEY_FLG_TOGGLE_OFF:
break;
}
break;
}
default:
fprintf(stderr, "xbrlapi: %s: %" BRLAPI_PRIxKEYCODE "\n",
gettext("unexpected cmd"), code);
break;
}
break;
case BRLAPI_KEY_TYPE_SYM:
modifiers = ((code & BRLAPI_KEY_FLAGS_MASK) >> BRLAPI_KEY_FLAGS_SHIFT) & 0xFF;
keysym = code & BRLAPI_KEY_CODE_MASK;
keycode = XKeysymToKeycode(dpy,keysym);
remap_keycode = -1;
if (keycode == NoSymbol) {
debugf(gettext("xbrlapi: Couldn't translate keysym %08X to keycode.\n"),keysym);
goto needRemap;
}
{
static const unsigned int tryTable[] = {
0,
ShiftMask,
Mod2Mask,
Mod3Mask,
Mod4Mask,
Mod5Mask,
ShiftMask|Mod2Mask,
ShiftMask|Mod3Mask,
ShiftMask|Mod4Mask,
ShiftMask|Mod5Mask,
0
};
const unsigned int *try = tryTable;
do {
if (tryModifiers(keycode, &modifiers, *try, keysym)) goto foundModifiers;
} while (*++try);
debugf(gettext("xbrlapi: Couldn't find modifiers to apply to %d for getting keysym %08X\n"),keycode,keysym);
}
needRemap:
{
/* Try tofind an unassigned keycode to remap it temporarily to the requested keysym. */
xkb = XkbGetMap(dpy,XkbKeyTypesMask|XkbKeySymsMask,XkbUseCoreKbd);
/* Start from big keycodes, usually unassigned. */
for (i = xkb->max_key_code;
i >= xkb->min_key_code && (XkbKeyNumGroups(xkb,i) != 0 || i == last_remap_keycode);
i--)
;
if (i < xkb->min_key_code) {
fprintf(stderr,gettext("xbrlapi: Couldn't find a keycode to remap for simulating unbound keysym %08X\n"),keysym);
goto abortRemap;
}
remap_keycode = keycode = i;
next_modifiers = modifiers = 0;
/* Remap this keycode. */
changes.first_key_sym = keycode;
changes.num_key_syms = 1;
if ((status = XkbChangeTypesOfKey(xkb,keycode,1,XkbGroup1Mask,oneGroupType,&changes))) {
debugf("Error while changing client keymap: %d\n", status);
goto abortRemap;
}
XkbKeySymEntry(xkb,keycode,0,0) = keysym;
if (!XkbChangeMap(dpy,xkb,&changes)) {
debugf("Error while changing server keymap\n");
goto abortRemap;
}
XkbFreeKeyboard(xkb,0,True);
debugf("Remapped keycode %d to keysym %08X\n",keycode,keysym);
}
foundModifiers:
debugf("key %08X: (%d,%x,%x)\n", keysym, keycode, next_modifiers, modifiers);
modifiers |= next_modifiers;
next_modifiers = 0;
if (modifiers)
XkbLockModifiers(dpy, XkbUseCoreKbd, modifiers, modifiers);
XTestFakeKeyEvent(dpy,keycode,True,1);
XTestFakeKeyEvent(dpy,keycode,False,1);
if (modifiers)
XkbLockModifiers(dpy, XkbUseCoreKbd, modifiers, 0);
/* Remove previous keycode mapping */
if (last_remap_keycode != -1) {
/* Note: since X11 is asynchronous, we should not immediately
* unmap the just-mapped keycode, otherwise when the client
* eventually gets to read the new Xkb state from the server,
* the key might have been synthesized and the keycode unmapped
* already. We just hope the user does not type too fast for the
* application to catch up. */
xkb = XkbGetMap(dpy,XkbKeyTypesMask|XkbKeySymsMask,XkbUseCoreKbd);
changes.first_key_sym = last_remap_keycode;
changes.num_key_syms = 1;
if ((status = XkbChangeTypesOfKey(xkb,last_remap_keycode,0,XkbGroup1Mask,NULL,&changes))) {
debugf("Oops, error while restoring client keymap: %d\n", status);
} else {
XkbChangeMap(dpy,xkb,&changes);
debugf("restored last keycode %d\n", last_remap_keycode);
}
XkbFreeKeyboard(xkb,0,True);
}
XFlush(dpy);
last_remap_keycode = remap_keycode;
break;
abortRemap:
XkbFreeKeyboard(xkb, 0, True);
xkb = NULL;
break;
default:
fprintf(stderr, "xbrlapi: %s: %" BRLAPI_PRIxKEYCODE "\n",
gettext("unexpected block type"), code);
next_modifiers = 0;
break;
}
}
if (res<0)
fatal_brlapi_errno("brlapi_readKey",NULL);
}
#endif /* CAN_SIMULATE_KEY_PRESSES */
} else {
/* Try to reconnect */
if (tobrltty_init(auth,host))
{
getVT();
#ifdef CAN_SIMULATE_KEY_PRESSES
ignoreServerKeys();
#endif /* CAN_SIMULATE_KEY_PRESSES */
api_setLastName();
api_setLastFocus();
}
}
}
}
/******************************************************************************
* main
*/
static void term_handler(int foo) {
api_cleanExit();
exit(PROG_EXIT_SUCCESS);
}
int
main (int argc, char *argv[]) {
{
const CommandLineDescriptor descriptor = {
.options = &programOptions,
.applicationName = "xbrlapi",
.usage = {
.purpose = strtext("Augment an X session by supporting input typed on the braille device, showing the title of the focused window on the braille display, and switching braille focus to it."),
}
};
PROCESS_OPTIONS(descriptor, argc, argv);
}
signal(SIGTERM,term_handler);
signal(SIGINT,term_handler);
#ifdef SIGHUP
signal(SIGHUP,term_handler);
#endif /* SIGHUP */
#ifdef SIGQUIT
signal(SIGQUIT,term_handler);
#endif /* SIGQUIT */
#ifdef SIGPIPE
signal(SIGPIPE,term_handler);
#endif /* SIGPIPE */
tobrltty_init(auth,host);
if (!no_daemon) {
pid_t child = fork();
if (child == -1)
fatal_errno("failed to fork", NULL);
if (child)
exit(PROG_EXIT_SUCCESS);
if (setsid() == -1)
fatal_errno("failed to create background session", NULL);
}
toX_f(xDisplay);
return PROG_EXIT_SUCCESS;
}