/* * BRLTTY - A background process providing access to the console screen (when in * text mode) for a blind person using a refreshable braille display. * * Copyright (C) 1995-2021 by The BRLTTY Developers. * * BRLTTY 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 . */ #include "prologue.h" #include #include #include #include #include #include #ifdef HAVE_ATSPI_GET_A11Y_BUS #include #endif /* HAVE_ATSPI_GET_A11Y_BUS */ #ifdef __MINGW32__ #include "win_pthread.h" #else /* __MINGW32__ */ #include #endif /* __MINGW32__ */ #include #define SPI2_DBUS_INTERFACE "org.a11y.atspi" #define SPI2_DBUS_INTERFACE_REG SPI2_DBUS_INTERFACE".Registry" #define SPI2_DBUS_PATH_REG "/org/a11y/atspi/registry" #define SPI2_DBUS_PATH_ROOT "/org/a11y/atspi/accessible/root" #define SPI2_DBUS_PATH_DEC SPI2_DBUS_PATH_REG "/deviceeventcontroller" #define SPI2_DBUS_INTERFACE_DEC SPI2_DBUS_INTERFACE".DeviceEventController" #define SPI2_DBUS_INTERFACE_DEL SPI2_DBUS_INTERFACE".DeviceEventListener" #define SPI2_DBUS_INTERFACE_EVENT SPI2_DBUS_INTERFACE".Event" #define SPI2_DBUS_INTERFACE_TREE SPI2_DBUS_INTERFACE".Tree" #define SPI2_DBUS_INTERFACE_TEXT SPI2_DBUS_INTERFACE".Text" #define SPI2_DBUS_INTERFACE_ACCESSIBLE SPI2_DBUS_INTERFACE".Accessible" #define ATSPI_STATE_ACTIVE 1 #define ATSPI_STATE_FOCUSED 12 #ifdef HAVE_X11_KEYSYM_H #include #endif /* HAVE_X11_KEYSYM_H */ #ifdef HAVE_PKG_X11 #include "xsel.h" #include "clipboard.h" #endif /* HAVE_PKG_X11 */ #include "log.h" #include "report.h" #include "parse.h" #include "thread.h" #include "brl_cmds.h" #include "async_io.h" #include "async_alarm.h" #include "async_event.h" typedef enum { PARM_RELEASE, PARM_TYPE } ScreenParameters; #define SCRPARMS "release", "type" #include "scr_driver.h" typedef enum { TYPE_ALL, TYPE_TERMINAL, TYPE_TEXT, TYPE_COUNT } TypeValue; static unsigned int releaseScreen; static unsigned char typeFlags[TYPE_COUNT]; static char *curSender; static char *curPath; static char *curRole; static ScreenContentQuality curQuality; static long curNumRows, curNumCols; static wchar_t **curRows; static long *curRowLengths; static long curCaret,curPosX,curPosY; static DBusConnection *bus = NULL; static int updated; #ifdef HAVE_PKG_X11 static Display *dpy; static XSelData xselData; static char *clipboardContent; #endif /* HAVE_PKG_X11 */ /* having our own implementation is much more independant on locales */ typedef struct { int remaining; wint_t current; } my_mbstate_t; static my_mbstate_t internal; static size_t my_mbrtowc(wchar_t *pwc, const char *s, size_t n, my_mbstate_t *ps) { const unsigned char *c = (const unsigned char *) s; int read = 0; if (!c) { if (ps->remaining) { errno = EILSEQ; return (size_t)(-1); } return 0; } if (n && !ps->remaining) { /* initial state */ if (!(*c&0x80)) { /* most frequent case: ascii */ if (pwc) *pwc = *c; if (!*c) return 0; return 1; } else if (!(*c&0x40)) { /* utf-8 char continuation, shouldn't happen with remaining == 0 ! */ goto error; } else { /* new utf-8 char, get remaining chars */ read = 1; if (!(*c&0x20)) { ps->remaining = 1; ps->current = *c&((1<<5)-1); } else if (!(*c&0x10)) { ps->remaining = 2; ps->current = *c&((1<<4)-1); } else if (!(*c&0x08)) { ps->remaining = 3; ps->current = *c&((1<<3)-1); } else if (!(*c&0x04)) { ps->remaining = 4; ps->current = *c&((1<<2)-1); } else if (!(*c&0x02)) { ps->remaining = 5; ps->current = *c&((1<<1)-1); } else /* 0xff and 0xfe are not allowed */ goto error; c++; } } /* looking for continuation chars */ while (n-read) { if ((*c&0xc0) != 0X80) /* not continuation char, error ! */ goto error; /* utf-8 char continuation */ ps->current = (ps->current<<6) | (*c&((1<<6)-1)); read++; if (!(--ps->remaining)) { if (pwc) *pwc = ps->current; if (!ps->current) /* shouldn't coded this way, but well... */ return 0; return read; } c++; } return (size_t)(-2); error: errno = EILSEQ; return (size_t)(-1); } static size_t my_mbsrtowcs(wchar_t *dest, const char **src, size_t len, my_mbstate_t *ps) { int put = 0; size_t skip; wchar_t buf,*bufp; if (!ps) ps = &internal; if (dest) bufp = dest; else bufp = &buf; while (len-put || !dest) { skip = my_mbrtowc(bufp, *src, 6, ps); switch (skip) { case (size_t)(-2): errno = EILSEQ; /* shouldn't happen ! */ /* fall through */ case (size_t)(-1): return (size_t)(-1); case 0: *src = NULL; return put; } *src += skip; if (dest) bufp++; put++; } return put; } static size_t my_mbrlen(const char *s, size_t n, my_mbstate_t *ps) { return my_mbrtowc(NULL, s, n, ps?ps:&internal); } static size_t my_mbslen(const char *s, size_t n) { my_mbstate_t ps; size_t ret=0; size_t eaten; memset(&ps,0,sizeof(ps)); while(n) { if ((ssize_t)(eaten = my_mbrlen(s,n,&ps))<0) return eaten; if (!(eaten)) return ret; s+=eaten; n-=eaten; ret++; } return ret; } static void addRows(long pos, long num) { curNumRows += num; curRows = realloc(curRows,curNumRows*sizeof(*curRows)); curRowLengths = realloc(curRowLengths,curNumRows*sizeof(*curRowLengths)); memmove(curRows +pos+num,curRows +pos,(curNumRows-(pos+num))*sizeof(*curRows)); memmove(curRowLengths+pos+num,curRowLengths+pos,(curNumRows-(pos+num))*sizeof(*curRowLengths)); } static void delRows(long pos, long num) { long y; for (y=pos;y 0)) { logMessage(LOG_WARNING, "widget type is mutually exclusive: %s", type); } else if (typeFlags[choice] || typeFlags[TYPE_ALL]) { logMessage(LOG_WARNING, "widget type specified more than once: %s", type); } else { typeFlags[choice] = 1; } } deallocateStrings(types); } } } } return 1; } /* Creates a method call message */ static DBusMessage * new_method_call(const char *sender, const char *path, const char *interface, const char *method) { DBusError error; DBusMessage *msg; dbus_error_init(&error); msg = dbus_message_new_method_call(sender, path, interface, method); if (dbus_error_is_set(&error)) { logMessage(LOG_CATEGORY(SCREEN_DRIVER), "error while making %s message: %s %s", method, error.name, error.message); dbus_error_free(&error); return NULL; } if (!msg) { logMessage(LOG_CATEGORY(SCREEN_DRIVER), "no memory while making %s message", method); return NULL; } return msg; } /* Sends a method call message, and returns the reply, if any. This unrefs the message. */ static DBusMessage * send_with_reply_and_block(DBusConnection *bus, DBusMessage *msg, int timeout_ms, const char *doing) { DBusError error; DBusMessage *reply; dbus_error_init(&error); reply = dbus_connection_send_with_reply_and_block(bus, msg, timeout_ms, &error); dbus_message_unref(msg); if (dbus_error_is_set(&error)) { logMessage(LOG_CATEGORY(SCREEN_DRIVER), "error while %s: %s %s", doing, error.name, error.message); dbus_error_free(&error); return NULL; } if (!reply) { logMessage(LOG_CATEGORY(SCREEN_DRIVER), "timeout while %s", doing); return NULL; } if (dbus_message_get_type (reply) == DBUS_MESSAGE_TYPE_ERROR) { logMessage(LOG_CATEGORY(SCREEN_DRIVER), "error while %s", doing); dbus_message_unref(reply); return NULL; } return reply; } static void findPosition(long position, long *px, long *py) { long offset=0, newoffset, x, y; /* XXX: I don't know what they do with necessary combining accents */ for (y=0; y position) break; offset = newoffset; } if (y==curNumRows) { if (!curNumRows) { y = 0; x = 0; } else { /* this _can_ happen, when deleting while caret is at the end of the * terminal: caret position is only updated afterwards... In the * meanwhile, keep caret at the end of last line. */ y = curNumRows-1; x = curRowLengths[y]; } } else x = position-offset; *px = x; *py = y; } static long findCoordinates(long xx, long yy) { long offset=0, y; /* XXX: I don't know what they do with necessary combining accents */ if (yy >= curNumRows) { return -1; } for (y=0; y= curRowLengths[y]) xx = curRowLengths[y]-1; return offset + xx; } static void caretPosition(long caret) { if (caret < 0) { caret = 0; } findPosition(caret,&curPosX,&curPosY); curCaret = caret; } static void finiTerm(void) { unsigned i; logMessage(LOG_CATEGORY(SCREEN_DRIVER), "end of term %s:%s",curSender,curPath); free(curSender); curSender = NULL; free(curPath); curPath = NULL; free(curRole); curRole = NULL; curPosX = curPosY = 0; if (curRows) { for (i=0;i curNumCols) curNumCols = len; else if (len < 0) { if (len==-2) logMessage(LOG_ERR,"unterminated sequence %s",c); else if (len==-1) logSystemError("mbrlen"); curRowLengths[i] = (len = 0) + (d != NULL); } curRows[i] = malloc((len + (d!=NULL)) * sizeof(*curRows[i])); e = c; my_mbsrtowcs(curRows[i],&e,len,NULL); if (d) curRows[i][len]='\n'; else break; i++; } logMessage(LOG_CATEGORY(SCREEN_DRIVER), "%ld cols",curNumCols); caretPosition(getCaret(sender, path)); free(text); } /* Switched to a new object, check whether we want to read it, and if so, restart with it */ static void tryRestartTerm(const char *sender, const char *path) { if (curPath) finiTerm(); restartTerm(sender, path); curRole = getRole(sender, path); logMessage(LOG_CATEGORY(SCREEN_DRIVER), "state changed focus to role %s", curRole); curQuality = getHasTextInterface(sender, path)? SCQ_POOR: SCQ_NONE; unsigned char requested = typeFlags[TYPE_ALL]; if (!requested) { if (isTerminal()) { curQuality = SCQ_GOOD; requested = typeFlags[TYPE_TERMINAL]; } else if (isText()) { curQuality = SCQ_FAIR; requested = typeFlags[TYPE_TEXT]; } } if (requested) curQuality = SCQ_GOOD; } /* Get the state of an object */ static dbus_uint32_t *getState(const char *sender, const char *path) { DBusMessage *msg, *reply; DBusMessageIter iter, iter_array; dbus_uint32_t *states, *ret = NULL; int count; msg = new_method_call(sender, path, SPI2_DBUS_INTERFACE_ACCESSIBLE, "GetState"); if (!msg) return NULL; reply = send_with_reply_and_block(bus, msg, 1000, "getting state"); if (!reply) return NULL; if (strcmp (dbus_message_get_signature (reply), "au") != 0) { logMessage(LOG_CATEGORY(SCREEN_DRIVER), "unexpected signature %s while getting active state", dbus_message_get_signature(reply)); goto out; } dbus_message_iter_init (reply, &iter); dbus_message_iter_recurse (&iter, &iter_array); dbus_message_iter_get_fixed_array (&iter_array, &states, &count); if (count != 2) { logMessage(LOG_CATEGORY(SCREEN_DRIVER), "unexpected signature %s while getting active state", dbus_message_get_signature(reply)); goto out; } ret = malloc(sizeof(*ret) * count); memcpy(ret, states, sizeof(*ret) * count); out: dbus_message_unref(reply); return ret; } /* Check whether an ancestor of this object is active */ static int checkActiveParent(const char *sender, const char *path) { DBusMessage *msg, *reply; DBusMessageIter iter, iter_variant, iter_struct; int res = 0; const char *interface = SPI2_DBUS_INTERFACE_ACCESSIBLE; const char *property = "Parent"; dbus_uint32_t *states; msg = new_method_call(sender, path, DBUS_INTERFACE_PROPERTIES, "Get"); if (!msg) return 0; dbus_message_append_args(msg, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID); reply = send_with_reply_and_block(bus, msg, 1000, "checking active object"); if (!reply) return 0; if (strcmp (dbus_message_get_signature (reply), "v") != 0) { logMessage(LOG_CATEGORY(SCREEN_DRIVER), "unexpected signature %s while checking active object", dbus_message_get_signature(reply)); goto out; } dbus_message_iter_init (reply, &iter); dbus_message_iter_recurse (&iter, &iter_variant); dbus_message_iter_recurse (&iter_variant, &iter_struct); dbus_message_iter_get_basic (&iter_struct, &sender); dbus_message_iter_next (&iter_struct); dbus_message_iter_get_basic (&iter_struct, &path); states = getState(sender, path); if (states) { res = (states[0] & (1<prev) if (!strcmp(childsender, cur->sender) && !strcmp(childpath, cur->path)) { /* Loop detected, avoid continuing looking at this part of the tree which is not actually a tree! */ cur->loop = 1; break; } if (! cur) { struct pathList me = { .sender = sender, .path = path, .prev = list, }; if (findTerm(childsender, childpath, active, depth, &me)) { res = 1; goto out; } if (me.loop) { /* There is a loop up to us. Avoid continuing looking here which may * entail an exponential number of lookups. */ break; } } dbus_message_iter_next (&iter_array); } out: dbus_message_unref(reply); return res; } /* Test whether this object is active, and if not recurse in its children */ static int findTerm(const char *sender, const char *path, int active, int depth, struct pathList *list) { dbus_uint32_t *states = getState(sender, path); if (!states) return 0; if (states[0] & (1<= length) { downTo++; if (downTo <= curNumRows - 1) length += curRowLengths[downTo]; else { /* imaginary extra line doesn't provide more length, and shouldn't need to ! */ if (x+toDelete > length) { logMessage(LOG_ERR,"deleting %ld %ld past end of text !", toDelete, x+toDelete - length); /* discarding */ if (x > length) x = length; toDelete = length - x; } break; /* deleting up to end */ } } if (toDelete <= 0) return; if (length-toDelete>0) { /* still something on line y */ if (y!=downTo) { curRowLengths[y] = length-toDelete; curRows[y]=realloc(curRows[y],curRowLengths[y]*sizeof(*curRows[y])); } if ((toCopy = length-toDelete-x)) memmove(curRows[y]+x,curRows[downTo]+curRowLengths[downTo]-toCopy,toCopy*sizeof(*curRows[downTo])); if (y==downTo) { curRowLengths[y] = length-toDelete; curRows[y]=realloc(curRows[y],curRowLengths[y]*sizeof(*curRows[y])); } } else { /* kills this line as well ! */ y--; } if (downTo>=curNumRows) /* imaginary extra lines don't need to be deleted */ downTo=curNumRows-1; if (downTo>y) { delRows(y+1,downTo-y); } caretPosition(curCaret); } else if (!strcmp(interface, "Object") && !strcmp(member, "TextChanged") && !strcmp(detail, "insert")) { long len=detail2,semilen,x,y; const char *added; const char *adding,*c; if (!curSender || strcmp(sender, curSender) || strcmp(path, curPath)) return; logMessage(LOG_CATEGORY(SCREEN_DRIVER), "insert %d from %d",detail2,detail1); findPosition(detail1,&x,&y); if (dbus_message_iter_get_arg_type(&iter_variant) != DBUS_TYPE_STRING) { logMessage(LOG_CATEGORY(SCREEN_DRIVER), "ergl, not string but '%c'", dbus_message_iter_get_arg_type(&iter_variant)); return; } if (detail1 < 0) { logMessage(LOG_ERR,"adding %ld %d before beginning of text!", len, detail1); detail1 = 0; } dbus_message_iter_get_basic(&iter_variant, &added); logMessage(LOG_CATEGORY(SCREEN_DRIVER), "'%s'",added); adding = c = added; if (y < curNumRows && x > curRowLengths[y]) { logMessage(LOG_ERR,"adding %ld %ld past end of text!", len, x - curRowLengths[y]); x = curRowLengths[y]; } if (x && (c = strchr(adding,'\n'))) { /* splitting line */ addRows(y,1); semilen=my_mbslen(adding,c+1-adding); curRowLengths[y]=x+semilen; if (x+semilen-1>curNumCols) curNumCols=x+semilen-1; /* copy beginning */ curRows[y]=malloc(curRowLengths[y]*sizeof(*curRows[y])); memcpy(curRows[y],curRows[y+1],x*sizeof(*curRows[y])); /* add */ my_mbsrtowcs(curRows[y]+x,&adding,semilen,NULL); len-=semilen; adding=c+1; /* shift end */ curRowLengths[y+1]-=x; memmove(curRows[y+1],curRows[y+1]+x,curRowLengths[y+1]*sizeof(*curRows[y+1])); x=0; y++; } while ((c = strchr(adding,'\n'))) { /* adding lines */ addRows(y,1); semilen=my_mbslen(adding,c+1-adding); curRowLengths[y]=semilen; if (semilen-1>curNumCols) curNumCols=semilen-1; curRows[y]=malloc(semilen*sizeof(*curRows[y])); my_mbsrtowcs(curRows[y],&adding,semilen,NULL); len-=semilen; adding=c+1; y++; } if (len) { /* still length to add on the line following it */ if (y==curNumRows) { /* It won't insert ending \n yet */ addRows(y,1); curRows[y]=NULL; curRowLengths[y]=0; } curRowLengths[y] += len; curRows[y]=realloc(curRows[y],curRowLengths[y]*sizeof(*curRows[y])); memmove(curRows[y]+x+len,curRows[y]+x,(curRowLengths[y]-(x+len))*sizeof(*curRows[y])); my_mbsrtowcs(curRows[y]+x,&adding,len,NULL); if (curRowLengths[y]-(curRows[y][curRowLengths[y]-1]=='\n')>curNumCols) curNumCols=curRowLengths[y]-(curRows[y][curRowLengths[y]-1]=='\n'); } caretPosition(curCaret); } else { return; } updated = 1; } static DBusHandlerResult AtSpi2Filter(DBusConnection *connection, DBusMessage *message, void *user_data) { int type = dbus_message_get_type(message); const char *interface = dbus_message_get_interface(message); const char *member = dbus_message_get_member(message); if (type == DBUS_MESSAGE_TYPE_SIGNAL) { if (!strncmp(interface, SPI2_DBUS_INTERFACE_EVENT".", strlen(SPI2_DBUS_INTERFACE_EVENT"."))) { AtSpi2HandleEvent(interface + strlen(SPI2_DBUS_INTERFACE_EVENT"."), message); } else if (!strcmp(interface, DBUS_INTERFACE_LOCAL) && !strcmp(member, "Disconnected")) { logMessage(LOG_CATEGORY(SCREEN_DRIVER), "DBus disconnected signal, shutting down"); #if defined(SIGTERM) raise(SIGTERM); #elif defined(SIGINT) raise(SIGINT); #else /* termination signal */ #warning no defined termination signal #endif /* termination signal */ } else { logMessage(LOG_CATEGORY(SCREEN_DRIVER), "unknown signal: Intf:%s Msg:%s", interface, member); } } else { logMessage(LOG_CATEGORY(SCREEN_DRIVER), "unknown message: Type:%d Intf:%s Msg:%s", type, interface, member); } return DBUS_HANDLER_RESULT_HANDLED; } #ifdef HAVE_PKG_X11 /* Integration of X11 events with brltty monitors */ static AsyncHandle a2XWatch; static ReportListenerInstance *coreSelUpdatedListener; static int settingClipboard; /* Called when X selection got updated, update the BRLTTY clipboard content */ void a2XSelUpdated(const char *data, unsigned long size) { if (!data) return; char content[size + 1]; memcpy(content, data, size); content[size] = 0; logMessage(LOG_CATEGORY(SCREEN_DRIVER), "X Selection got '%s'", content); settingClipboard = 1; setMainClipboardContent(content); settingClipboard = 0; } #ifdef HAVE_PTHREAD_ATFORK /* Drop X connection without shutting down its resources. Needed at fork. */ void a2DropX(void) { close(XConnectionNumber(dpy)); dpy = NULL; } #endif /* HAVE_PTHREAD_ATFORK */ /* Called when X events are available, process them */ ASYNC_MONITOR_CALLBACK(a2ProcessX) { XEvent ev; while (XPending(dpy)) { XNextEvent(dpy, &ev); XSelProcess(dpy, &xselData, &ev, clipboardContent, a2XSelUpdated); } return 1; } /* Called when BRLTTY selection got updated, update the X clipboard content */ REPORT_LISTENER(a2CoreSelUpdated) { const ApiParameterUpdatedReport *report = parameters->reportData; if (report->parameter != BRLAPI_PARAM_CLIPBOARD_CONTENT) return; if (settingClipboard) return; char *newContent = getMainClipboardContent(); if (newContent) { if (!clipboardContent || (strcmp(clipboardContent, newContent) != 0)) { free(clipboardContent); logMessage(LOG_CATEGORY(SCREEN_DRIVER), "core Selection got '%s'", newContent); clipboardContent = newContent; XSelSet(dpy, &xselData); } else { free(newContent); } } } #endif /* HAVE_PKG_X11 */ /* Integration of DBus watches with brltty monitors */ struct a2Watch { AsyncHandle input_monitor; AsyncHandle output_monitor; DBusWatch *watch; }; int a2ProcessWatch(const AsyncMonitorCallbackParameters *parameters, int flags) { struct a2Watch *a2Watch = parameters->data; DBusWatch *watch = a2Watch->watch; /* Read/Write on socket */ dbus_watch_handle(watch, parameters->error?DBUS_WATCH_ERROR:flags); /* And process messages */ while (dbus_connection_dispatch(bus) != DBUS_DISPATCH_COMPLETE) ; if (updated) { updated = 0; mainScreenUpdated(); } return dbus_watch_get_enabled(watch); } ASYNC_MONITOR_CALLBACK(a2ProcessInput) { if (a2ProcessWatch(parameters, DBUS_WATCH_READABLE)) return 1; struct a2Watch *a2Watch = parameters->data; asyncDiscardHandle(a2Watch->input_monitor); a2Watch->input_monitor = NULL; return 0; } ASYNC_MONITOR_CALLBACK(a2ProcessOutput) { if (a2ProcessWatch(parameters, DBUS_WATCH_WRITABLE)) return 1; struct a2Watch *a2Watch = parameters->data; asyncDiscardHandle(a2Watch->output_monitor); a2Watch->output_monitor = NULL; return 0; } dbus_bool_t a2AddWatch(DBusWatch *watch, void *data) { struct a2Watch *a2Watch = calloc(1, sizeof(*a2Watch)); a2Watch->watch = watch; int flags = dbus_watch_get_flags(watch); if (dbus_watch_get_enabled(watch)) { if (flags & DBUS_WATCH_READABLE) asyncMonitorFileInput(&a2Watch->input_monitor, dbus_watch_get_unix_fd(watch), a2ProcessInput, a2Watch); if (flags & DBUS_WATCH_WRITABLE) asyncMonitorFileOutput(&a2Watch->output_monitor, dbus_watch_get_unix_fd(watch), a2ProcessOutput, a2Watch); } dbus_watch_set_data(watch, a2Watch, NULL); return TRUE; } void a2RemoveWatch(DBusWatch *watch, void *data) { struct a2Watch *a2Watch = dbus_watch_get_data(watch); dbus_watch_set_data(watch, NULL, NULL); if (a2Watch->input_monitor) asyncCancelRequest(a2Watch->input_monitor); if (a2Watch->output_monitor) asyncCancelRequest(a2Watch->output_monitor); free(a2Watch); } void a2WatchToggled(DBusWatch *watch, void *data) { if (dbus_watch_get_enabled(watch)) { if (!dbus_watch_get_data(watch)) a2AddWatch(watch, data); } else { if (dbus_watch_get_data(watch)) a2RemoveWatch(watch, data); } } /* Integration of DBus timeouts with brltty monitors */ struct a2Timeout { AsyncHandle monitor; DBusTimeout *timeout; }; ASYNC_ALARM_CALLBACK(a2ProcessTimeout) { struct a2Timeout *a2Timeout = parameters->data; DBusTimeout *timeout = a2Timeout->timeout; /* Process timeout */ dbus_timeout_handle(timeout); /* And process messages */ while (dbus_connection_dispatch(bus) != DBUS_DISPATCH_COMPLETE) ; if (updated) { updated = 0; mainScreenUpdated(); } asyncDiscardHandle(a2Timeout->monitor); a2Timeout->monitor = NULL; if (dbus_timeout_get_enabled(timeout)) /* Still enabled, requeue it */ asyncNewRelativeAlarm(&a2Timeout->monitor, dbus_timeout_get_interval(timeout), a2ProcessTimeout, a2Timeout); } dbus_bool_t a2AddTimeout(DBusTimeout *timeout, void *data) { struct a2Timeout *a2Timeout = calloc(1, sizeof(*a2Timeout)); a2Timeout->timeout = timeout; if (dbus_timeout_get_enabled(timeout)) asyncNewRelativeAlarm(&a2Timeout->monitor, dbus_timeout_get_interval(timeout), a2ProcessTimeout, a2Timeout); dbus_timeout_set_data(timeout, a2Timeout, NULL); return TRUE; } void a2RemoveTimeout(DBusTimeout *timeout, void *data) { struct a2Timeout *a2Timeout = dbus_timeout_get_data(timeout); dbus_timeout_set_data(timeout, NULL, NULL); if (a2Timeout->monitor) asyncCancelRequest(a2Timeout->monitor); free(a2Timeout); } void a2TimeoutToggled(DBusTimeout *timeout, void *data) { if (dbus_timeout_get_enabled(timeout)) { if (!dbus_timeout_get_data(timeout)) a2AddTimeout(timeout, data); } else { if (dbus_timeout_get_data(timeout)) a2RemoveTimeout(timeout, data); } } /* Driver construction / destruction */ static int addWatch(const char *message, const char *event) { DBusError error; DBusMessage *msg, *reply; dbus_error_init(&error); dbus_bus_add_match(bus, message, &error); if (dbus_error_is_set(&error)) { logMessage(LOG_ERR, "error while adding watch %s: %s %s", message, error.name, error.message); dbus_error_free(&error); return 0; } if (!event) return 1; /* Register as event listener. */ msg = new_method_call(SPI2_DBUS_INTERFACE_REG, SPI2_DBUS_PATH_REG, SPI2_DBUS_INTERFACE_REG, "RegisterEvent"); if (!msg) return 0; dbus_message_append_args(msg, DBUS_TYPE_STRING, &event, DBUS_TYPE_INVALID); reply = send_with_reply_and_block(bus, msg, 1000, "registering listener"); if (!reply) return 0; dbus_message_unref(reply); return 1; } static int addWatches (void) { typedef struct { const char *message; const char *event; } WatchEntry; static const WatchEntry watchTable[] = { { .message = "type='method_call',interface='"SPI2_DBUS_INTERFACE_TREE"'", .event = NULL }, { .message = "type='signal',interface='"SPI2_DBUS_INTERFACE_EVENT".Focus'", .event = "focus" }, { .message = "type='signal',interface='"SPI2_DBUS_INTERFACE_EVENT".Object'", .event = "object" }, { .message = "type='signal',interface='"SPI2_DBUS_INTERFACE_EVENT".Object',member='ChildrenChanged'", .event = "object:childrenchanged" }, { .message = "type='signal',interface='"SPI2_DBUS_INTERFACE_EVENT".Object',member='TextChanged'", .event = "object:textchanged" }, { .message = "type='signal',interface='"SPI2_DBUS_INTERFACE_EVENT".Object',member='TextCaretMoved'", .event = "object:textcaretmoved" }, { .message = "type='signal',interface='"SPI2_DBUS_INTERFACE_EVENT".Object',member='StateChanged'", .event = "object:statechanged" }, { .message = NULL } }; for (const WatchEntry *watch=watchTable; watch->message; watch+=1) { if (!addWatch(watch->message, watch->event)) return 0; } return 1; } static int construct_AtSpi2Screen (void) { DBusError error; dbus_error_init(&error); #ifdef HAVE_ATSPI_GET_A11Y_BUS bus = atspi_get_a11y_bus(); if (!bus) #endif /* HAVE_ATSPI_GET_A11Y_BUS */ { bus = dbus_bus_get(DBUS_BUS_SESSION, &error); if (dbus_error_is_set(&error)) { logMessage(LOG_ERR, "can't get dbus session bus: %s %s", error.name, error.message); dbus_error_free(&error); goto noBus; } } if (!bus) { logMessage(LOG_ERR, "can't get dbus session bus"); goto noBus; } if (!dbus_connection_add_filter(bus, AtSpi2Filter, NULL, NULL)) goto noConnection; if (!addWatches()) goto noWatches; if (!curPath) { initTerm(); } else if (!reinitTerm(curSender, curPath)) { logMessage(LOG_CATEGORY(SCREEN_DRIVER), "caching failed, restarting from scratch"); initTerm(); } dbus_connection_set_watch_functions(bus, a2AddWatch, a2RemoveWatch, a2WatchToggled, NULL, NULL); dbus_connection_set_timeout_functions(bus, a2AddTimeout, a2RemoveTimeout, a2TimeoutToggled, NULL, NULL); #ifdef HAVE_PKG_X11 dpy = XOpenDisplay(NULL); if (dpy) { XSelInit(dpy, &xselData); XFlush(dpy); #ifdef HAVE_PTHREAD_ATFORK pthread_atfork(NULL, NULL, a2DropX); #endif /* HAVE_PTHREAD_ATFORK */ asyncMonitorFileInput(&a2XWatch, XConnectionNumber(dpy), a2ProcessX, NULL); coreSelUpdatedListener = registerReportListener(REPORT_API_PARAMETER_UPDATED, a2CoreSelUpdated , NULL); } #endif /* HAVE_PKG_X11 */ logMessage(LOG_CATEGORY(SCREEN_DRIVER), "SPI2 initialized"); return 1; noWatches: noConnection: dbus_connection_unref(bus); noBus: return 0; } static void destruct_AtSpi2Screen (void) { #ifdef HAVE_PKG_X11 if (dpy) { unregisterReportListener(coreSelUpdatedListener); coreSelUpdatedListener = NULL; asyncCancelRequest(a2XWatch); XCloseDisplay(dpy); dpy = NULL; free(clipboardContent); clipboardContent = NULL; } #endif /* HAVE_PKG_X11 */ dbus_connection_remove_filter(bus, AtSpi2Filter, NULL); dbus_connection_close(bus); dbus_connection_unref(bus); logMessage(LOG_CATEGORY(SCREEN_DRIVER), "SPI2 stopped"); finiTerm(); } static int currentVirtualTerminal_AtSpi2Screen (void) { return (curPath || !releaseScreen)? 0: SCR_NO_VT; } static const char msgNotAtSpi [] = "not an AT-SPI2 text widget"; static int poll_AtSpi2Screen (void) { return 0; } ASYNC_EVENT_CALLBACK(AtSpi2ScreenUpdated) { mainScreenUpdated(); } static int refresh_AtSpi2Screen (void) { return 1; } static void describe_AtSpi2Screen (ScreenDescription *description) { if (curPath) { description->cols = curPosX>=curNumCols?curPosX+1:curNumCols; description->rows = curNumRows?curNumRows:1; description->posx = curPosX; description->posy = curPosY; description->quality = curQuality; } else { const char *message = msgNotAtSpi; if (releaseScreen) description->unreadable = message; description->rows = 1; description->cols = strlen(message); description->posx = 0; description->posy = 0; description->quality = SCQ_NONE; } description->number = currentVirtualTerminal_AtSpi2Screen(); } static int readCharacters_AtSpi2Screen (const ScreenBox *box, ScreenCharacter *buffer) { clearScreenCharacters(buffer, (box->height * box->width)); if (!curPath) { setScreenMessage(box, buffer, msgNotAtSpi); return 1; } if (!curNumCols || !curNumRows) return 0; short cols = (curPosX >= curNumCols)? (curPosX + 1): curNumCols; if (!validateScreenBox(box, cols, curNumRows)) return 0; for (unsigned int y=0; yheight; y+=1) { if (curRowLengths[box->top+y]) { for (unsigned int x=0; xwidth; x+=1) { if (box->left+x < curRowLengths[box->top+y] - (curRows[box->top+y][curRowLengths[box->top+y]-1]==WC_C('\n'))) { buffer[y*box->width+x].text = curRows[box->top+y][box->left+x]; } } } } return 1; } enum key_type_e { PRESS, RELEASE, PRESSRELEASE, SYM }; static int AtSpi2GenerateKeyboardEvent (dbus_uint32_t keysym, enum key_type_e key_type) { DBusMessage *msg, *reply; const char *s = ""; msg = new_method_call(SPI2_DBUS_INTERFACE_REG, SPI2_DBUS_PATH_DEC, SPI2_DBUS_INTERFACE_DEC, "GenerateKeyboardEvent"); if (!msg) return 0; dbus_message_append_args(msg, DBUS_TYPE_INT32, &keysym, DBUS_TYPE_STRING, &s, DBUS_TYPE_UINT32, &key_type, DBUS_TYPE_INVALID); reply = send_with_reply_and_block(bus, msg, 1000, "generating keyboard event"); if (!reply) return 0; return 1; } static int insertKey_AtSpi2Screen (ScreenKey key) { long keysym; int modMeta=0, modControl=0; setScreenKeyModifiers(&key, SCR_KEY_CONTROL); if (isSpecialKey(key)) { switch (key & SCR_KEY_CHAR_MASK) { #ifdef HAVE_X11_KEYSYM_H case SCR_KEY_ENTER: keysym = XK_KP_Enter; break; case SCR_KEY_TAB: keysym = XK_Tab; break; case SCR_KEY_BACKSPACE: keysym = XK_BackSpace; break; case SCR_KEY_ESCAPE: keysym = XK_Escape; break; case SCR_KEY_CURSOR_LEFT: keysym = XK_Left; break; case SCR_KEY_CURSOR_RIGHT: keysym = XK_Right; break; case SCR_KEY_CURSOR_UP: keysym = XK_Up; break; case SCR_KEY_CURSOR_DOWN: keysym = XK_Down; break; case SCR_KEY_PAGE_UP: keysym = XK_Page_Up; break; case SCR_KEY_PAGE_DOWN: keysym = XK_Page_Down; break; case SCR_KEY_HOME: keysym = XK_Home; break; case SCR_KEY_END: keysym = XK_End; break; case SCR_KEY_INSERT: keysym = XK_Insert; break; case SCR_KEY_DELETE: keysym = XK_Delete; break; case SCR_KEY_FUNCTION + 0: keysym = XK_F1; break; case SCR_KEY_FUNCTION + 1: keysym = XK_F2; break; case SCR_KEY_FUNCTION + 2: keysym = XK_F3; break; case SCR_KEY_FUNCTION + 3: keysym = XK_F4; break; case SCR_KEY_FUNCTION + 4: keysym = XK_F5; break; case SCR_KEY_FUNCTION + 5: keysym = XK_F6; break; case SCR_KEY_FUNCTION + 6: keysym = XK_F7; break; case SCR_KEY_FUNCTION + 7: keysym = XK_F8; break; case SCR_KEY_FUNCTION + 8: keysym = XK_F9; break; case SCR_KEY_FUNCTION + 9: keysym = XK_F10; break; case SCR_KEY_FUNCTION + 10: keysym = XK_F11; break; case SCR_KEY_FUNCTION + 11: keysym = XK_F12; break; case SCR_KEY_FUNCTION + 12: keysym = XK_F13; break; case SCR_KEY_FUNCTION + 13: keysym = XK_F14; break; case SCR_KEY_FUNCTION + 14: keysym = XK_F15; break; case SCR_KEY_FUNCTION + 15: keysym = XK_F16; break; case SCR_KEY_FUNCTION + 16: keysym = XK_F17; break; case SCR_KEY_FUNCTION + 17: keysym = XK_F18; break; case SCR_KEY_FUNCTION + 18: keysym = XK_F19; break; case SCR_KEY_FUNCTION + 19: keysym = XK_F20; break; case SCR_KEY_FUNCTION + 20: keysym = XK_F21; break; case SCR_KEY_FUNCTION + 21: keysym = XK_F22; break; case SCR_KEY_FUNCTION + 22: keysym = XK_F23; break; case SCR_KEY_FUNCTION + 23: keysym = XK_F24; break; case SCR_KEY_FUNCTION + 24: keysym = XK_F25; break; case SCR_KEY_FUNCTION + 25: keysym = XK_F26; break; case SCR_KEY_FUNCTION + 26: keysym = XK_F27; break; case SCR_KEY_FUNCTION + 27: keysym = XK_F28; break; case SCR_KEY_FUNCTION + 28: keysym = XK_F29; break; case SCR_KEY_FUNCTION + 29: keysym = XK_F30; break; case SCR_KEY_FUNCTION + 30: keysym = XK_F31; break; case SCR_KEY_FUNCTION + 31: keysym = XK_F32; break; case SCR_KEY_FUNCTION + 32: keysym = XK_F33; break; case SCR_KEY_FUNCTION + 33: keysym = XK_F34; break; case SCR_KEY_FUNCTION + 34: keysym = XK_F35; break; #else /* HAVE_X11_KEYSYM_H */ #warning insertion of non-character key presses not supported by this build - check that X11 protocol headers have been installed #endif /* HAVE_X11_KEYSYM_H */ default: logMessage(LOG_WARNING, "key not insertable: %04X", key); return 0; } } else { wchar_t wc; if (key & SCR_KEY_ALT_LEFT) { key &= ~SCR_KEY_ALT_LEFT; modMeta = 1; } if (key & SCR_KEY_CONTROL) { key &= ~SCR_KEY_CONTROL; modControl = 1; } wc = key & SCR_KEY_CHAR_MASK; if (wc < 0x100) keysym = wc; /* latin1 character */ else keysym = 0x1000000 | wc; } logMessage(LOG_CATEGORY(SCREEN_DRIVER), "inserting key: %04X -> %s%s%ld", key, (modMeta? "meta ": ""), (modControl? "control ": ""), keysym); { int ok = 0; #ifdef HAVE_X11_KEYSYM_H if (!modMeta || AtSpi2GenerateKeyboardEvent(XK_Meta_L,PRESS)) { if (!modControl || AtSpi2GenerateKeyboardEvent(XK_Control_L,PRESS)) { #endif /* HAVE_X11_KEYSYM_H */ if (AtSpi2GenerateKeyboardEvent(keysym,SYM)) { ok = 1; } else { logMessage(LOG_WARNING, "key insertion failed."); } #ifdef HAVE_X11_KEYSYM_H if (modControl && !AtSpi2GenerateKeyboardEvent(XK_Control_L,RELEASE)) { logMessage(LOG_WARNING, "control release failed."); ok = 0; } } else { logMessage(LOG_WARNING, "control press failed."); } if (modMeta && !AtSpi2GenerateKeyboardEvent(XK_Meta_L,RELEASE)) { logMessage(LOG_WARNING, "meta release failed."); ok = 0; } } else { logMessage(LOG_WARNING, "meta press failed."); } #endif /* HAVE_X11_KEYSYM_H */ return ok; } } static int setSelection_AtSpi2Screen (int beginOffset, int endOffset) { dbus_bool_t result; DBusMessage *msg, *reply; DBusMessageIter iter; dbus_int32_t num = 0; dbus_int32_t begin = beginOffset; dbus_int32_t end = endOffset; msg = new_method_call(curSender, curPath, SPI2_DBUS_INTERFACE_TEXT, "SetSelection"); if (!msg) return 0; dbus_message_append_args(msg, DBUS_TYPE_INT32, &num, DBUS_TYPE_INT32, &begin, DBUS_TYPE_INT32, &end, DBUS_TYPE_INVALID); reply = send_with_reply_and_block(bus, msg, 1000, "setting selection"); if (!reply) return 0; dbus_message_iter_init(reply, &iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN) { logMessage(LOG_CATEGORY(SCREEN_DRIVER), "SetSelection didn't return a boolean but '%c'", dbus_message_iter_get_arg_type(&iter)); result = 0; goto out; } dbus_message_iter_get_basic(&iter, &result); out: dbus_message_unref(reply); return result; } static int highlightRegion_AtSpi2Screen (int left, int right, int top, int bottom) { int begin, end; /* It is safe to play with selections only with terminals */ if (!isTerminal()) return 0; if (top != bottom) /* AtSpi selection only supports linear selection */ return 0; begin = findCoordinates(left, top); if (begin == -1) return 0; end = findCoordinates(right, bottom); if (end == -1) return 0; return setSelection_AtSpi2Screen(begin, end+1); } static int unhighlightRegion_AtSpi2Screen (void) { /* It is safe to play with selections only with terminals */ if (!isTerminal()) return 0; return setSelection_AtSpi2Screen(0, 0); } static void scr_initialize (MainScreen *main) { initializeRealScreen(main); main->base.poll = poll_AtSpi2Screen; main->base.refresh = refresh_AtSpi2Screen; main->base.describe = describe_AtSpi2Screen; main->base.readCharacters = readCharacters_AtSpi2Screen; main->base.insertKey = insertKey_AtSpi2Screen; main->base.highlightRegion = highlightRegion_AtSpi2Screen; main->base.unhighlightRegion = unhighlightRegion_AtSpi2Screen; main->base.currentVirtualTerminal = currentVirtualTerminal_AtSpi2Screen; main->processParameters = processParameters_AtSpi2Screen; main->construct = construct_AtSpi2Screen; main->destruct = destruct_AtSpi2Screen; }