blob: 9d5ca1d230310de27c5e12a34b44e3e7d037b8a6 [file] [log] [blame] [edit]
/*
* 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-2023 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 <dave@mielke.cc>.
*/
#include "prologue.h"
#include "embed.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <locale.h>
#ifdef HAVE_ATSPI_GET_A11Y_BUS
#include <atspi/atspi.h>
#endif /* HAVE_ATSPI_GET_A11Y_BUS */
#ifdef __MINGW32__
#include "win_pthread.h"
#else /* __MINGW32__ */
#include <semaphore.h>
#endif /* __MINGW32__ */
#include <dbus/dbus.h>
#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 <X11/keysym.h>
#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_handle.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<pos+num;y++)
free(curRows[y]);
memmove(curRows +pos,curRows +pos+num,(curNumRows-(pos+num))*sizeof(*curRows));
memmove(curRowLengths+pos,curRowLengths+pos+num,(curNumRows-(pos+num))*sizeof(*curRowLengths));
curNumRows -= num;
curRows = realloc(curRows,curNumRows*sizeof(*curRows));
curRowLengths = realloc(curRowLengths,curNumRows*sizeof(*curRowLengths));
}
static int
processParameters_AtSpi2Screen (char **parameters) {
releaseScreen = 1;
{
const char *parameter = parameters[PARM_RELEASE];
if (*parameter) {
if (!validateYesNo(&releaseScreen, parameter)) {
logMessage(LOG_WARNING, "invalid release screen setting: %s", parameter);
}
}
}
{
const char *parameter = parameters[PARM_TYPE];
for (unsigned int index=0; index<TYPE_COUNT; index+=1) {
typeFlags[index] = 0;
}
if (*parameter) {
if (!isAbbreviation("default", parameter)) {
int count;
char **types = splitString(parameter, '+', &count);
if (types) {
static const char *const choices[] = {
[TYPE_ALL] = "all",
[TYPE_TERMINAL] = "terminal",
[TYPE_TEXT] = "text",
[TYPE_COUNT] = NULL
};
for (unsigned int index=0; index<count; index+=1) {
const char *type = types[index];
unsigned int choice;
if (!validateChoice(&choice, type, choices)) {
logMessage(LOG_WARNING, "%s: %s", "invalid widget type", type);
} else if ((choice == TYPE_ALL) && (index > 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<curNumRows; y++) {
if ((newoffset = offset + curRowLengths[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<yy; y++) {
offset += curRowLengths[y];
}
if (xx >= 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<curNumRows;i++)
free(curRows[i]);
free(curRows);
}
curRows = NULL;
free(curRowLengths);
curRowLengths = NULL;
curNumCols = curNumRows = 0;
}
#define ROLE_TERMINAL "terminal"
#define ROLE_TEXT "text"
static int
isRole (const char *role) {
if (!curRole) return 0;
return strcmp(curRole, role) == 0;
}
static int
isTerminal (void) {
return isRole(ROLE_TERMINAL);
}
static int
isText (void) {
return isRole(ROLE_TEXT);
}
/* Get the role of an AT-SPI2 object */
static char *getRole(const char *sender, const char *path) {
const char *text;
char *res = NULL;
DBusMessage *msg, *reply;
DBusMessageIter iter;
msg = new_method_call(sender, path, SPI2_DBUS_INTERFACE_ACCESSIBLE, "GetRoleName");
if (!msg)
return NULL;
reply = send_with_reply_and_block(bus, msg, 1000, "getting role");
if (!reply)
return NULL;
dbus_message_iter_init(reply, &iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
logMessage(LOG_CATEGORY(SCREEN_DRIVER),
"GetRoleName didn't return a string but '%c'", dbus_message_iter_get_arg_type(&iter));
goto out;
}
dbus_message_iter_get_basic(&iter, &text);
res = strdup(text);
out:
dbus_message_unref(reply);
return res;
}
/* Get the get interfaces of an AT-SPI2 object */
static int getHasTextInterface(const char *sender, const char *path) {
DBusMessage *msg, *reply;
DBusMessageIter iter;
DBusMessageIter iter_array;
int ret = 0;
msg = new_method_call(sender, path, SPI2_DBUS_INTERFACE_ACCESSIBLE, "GetInterfaces");
if (!msg)
return 0;
reply = send_with_reply_and_block(bus, msg, 1000, "getting interfaces");
if (!reply)
return 0;
dbus_message_iter_init(reply, &iter);
dbus_message_iter_recurse (&iter, &iter_array);
while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
{
const char *iface;
dbus_message_iter_get_basic (&iter_array, &iface);
if (!strcmp (iface, "org.a11y.atspi.Text"))
{
ret = 1;
goto out;
}
dbus_message_iter_next (&iter_array);
}
out:
dbus_message_unref(reply);
return ret;
}
static char *getName(const char *sender, const char *path) {
const char *name;
char *res = NULL;
DBusMessage *msg, *reply = NULL;
const char *interface = SPI2_DBUS_INTERFACE_ACCESSIBLE;
const char *property = "Name";
DBusMessageIter iter, iter_variant;
msg = new_method_call(sender, path, DBUS_INTERFACE_PROPERTIES, "Get");
if (!msg)
return NULL;
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, "getting name");
if (!reply)
return NULL;
dbus_message_iter_init(reply, &iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
logMessage(LOG_CATEGORY(SCREEN_DRIVER),
"getName didn't return a variant but '%c'", dbus_message_iter_get_arg_type(&iter));
goto out;
}
dbus_message_iter_recurse(&iter, &iter_variant);
if (dbus_message_iter_get_arg_type(&iter_variant) != DBUS_TYPE_STRING) {
logMessage(LOG_CATEGORY(SCREEN_DRIVER),
"getName didn't return a variant but '%c'", dbus_message_iter_get_arg_type(&iter_variant));
goto out;
}
dbus_message_iter_get_basic(&iter_variant, &name);
res = strdup(name);
out:
dbus_message_unref(reply);
return res;
}
/* Get the text of an AT-SPI2 object */
static char *getText(const char *sender, const char *path) {
const char *text;
char *res = NULL;
DBusMessage *msg, *reply;
dbus_int32_t begin = 0;
dbus_int32_t end = -1;
DBusMessageIter iter;
msg = new_method_call(sender, path, SPI2_DBUS_INTERFACE_TEXT, "GetText");
if (!msg)
return NULL;
dbus_message_append_args(msg, DBUS_TYPE_INT32, &begin, DBUS_TYPE_INT32, &end, DBUS_TYPE_INVALID);
reply = send_with_reply_and_block(bus, msg, 1000, "getting text");
if (!reply)
return NULL;
dbus_message_iter_init(reply, &iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
logMessage(LOG_CATEGORY(SCREEN_DRIVER),
"GetText didn't return a string but '%c'", dbus_message_iter_get_arg_type(&iter));
goto out;
}
dbus_message_iter_get_basic(&iter, &text);
res = strdup(text);
out:
dbus_message_unref(reply);
return res;
}
/* Get the caret of an AT-SPI2 object */
static dbus_int32_t getCaret(const char *sender, const char *path) {
dbus_int32_t res = -1;
DBusMessage *msg, *reply = NULL;
const char *interface = SPI2_DBUS_INTERFACE_TEXT;
const char *property = "CaretOffset";
DBusMessageIter iter, iter_variant;
msg = new_method_call(sender, path, DBUS_INTERFACE_PROPERTIES, "Get");
if (!msg)
return -1;
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, "getting caret");
if (!reply)
return -1;
dbus_message_iter_init(reply, &iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
logMessage(LOG_CATEGORY(SCREEN_DRIVER),
"getCaret didn't return a variant but '%c'", dbus_message_iter_get_arg_type(&iter));
goto out;
}
dbus_message_iter_recurse(&iter, &iter_variant);
if (dbus_message_iter_get_arg_type(&iter_variant) != DBUS_TYPE_INT32) {
logMessage(LOG_CATEGORY(SCREEN_DRIVER),
"getCaret didn't return an int32 but '%c'", dbus_message_iter_get_arg_type(&iter_variant));
goto out;
}
dbus_message_iter_get_basic(&iter_variant, &res);
logMessage(LOG_CATEGORY(SCREEN_DRIVER),
"Got caret %d", res);
out:
dbus_message_unref(reply);
return res;
}
/* Switched to a new terminal, restart from scratch */
static void restartTerm(const char *sender, const char *path) {
char *text = getText(sender, path);
if (!text) {
text = getName(sender, path);
if (!text) return;
}
char *c,*d;
const char *e;
long i,len;
curSender = strdup(sender);
curPath = strdup(path);
logMessage(LOG_CATEGORY(SCREEN_DRIVER),
"new term %s:%s with text %s", curSender, curPath, text);
if (curRows) {
for (i=0;i<curNumRows;i++)
free(curRows[i]);
free(curRows);
}
curNumRows = 0;
free(curRowLengths);
c = text;
while (*c) {
curNumRows++;
if (!(c = strchr(c,'\n')))
break;
c++;
}
logMessage(LOG_CATEGORY(SCREEN_DRIVER),
"%ld rows",curNumRows);
curRows = malloc(curNumRows * sizeof(*curRows));
curRowLengths = malloc(curNumRows * sizeof(*curRowLengths));
i = 0;
curNumCols = 0;
for (c = text; *c; c = d+1) {
d = strchr(c,'\n');
if (d)
*d = 0;
e = c;
curRowLengths[i] = (len = my_mbsrtowcs(NULL,&e,0,NULL)) + (d != NULL);
if (len > 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<<ATSPI_STATE_ACTIVE)) != 0 || checkActiveParent(sender, path);
free(states);
} else {
res = 0;
}
out:
dbus_message_unref(reply);
return res;
}
/* Check whether this object is the focused object (which is way faster than
* browsing all objects of the desktop) */
static int reinitTerm(const char *sender, const char *path) {
dbus_uint32_t *states = getState(sender, path);
int active = 0;
if (!states)
return 0;
/* Whether this widget is active */
active = (states[0] & (1<<ATSPI_STATE_ACTIVE)) != 0;
if (states[0] & (1<<ATSPI_STATE_FOCUSED)) {
free(states);
logMessage(LOG_CATEGORY(SCREEN_DRIVER),
"%s %s is focused!", sender, path);
/* This widget is focused */
if (active) {
/* And it is active, we are done. */
tryRestartTerm(sender, path);
return 1;
} else {
/* Check that a parent is active. */
return checkActiveParent(sender, path);
}
}
free(states);
return 0;
}
/* Try to find an active object among children of the given object */
/* We need to take care of bogus applications which have children loops, so we
* need to compare newly found children with the list of ancestors */
struct pathList {
const char *sender;
const char *path;
struct pathList *prev;
int loop;
};
static int findTerm(const char *sender, const char *path, int active, int depth, struct pathList *list);
static int recurseFindTerm(const char *sender, const char *path, int active, int depth, struct pathList *list) {
DBusMessage *msg, *reply;
DBusMessageIter iter, iter_array, iter_struct;
int res = 0;
msg = new_method_call(sender, path, SPI2_DBUS_INTERFACE_ACCESSIBLE, "GetChildren");
if (!msg)
return 0;
reply = send_with_reply_and_block(bus, msg, 1000, "getting active object");
if (!reply)
return 0;
if (strcmp (dbus_message_get_signature (reply), "a(so)") != 0)
{
logMessage(LOG_CATEGORY(SCREEN_DRIVER),
"unexpected signature %s while getting active object", dbus_message_get_signature(reply));
goto out;
}
dbus_message_iter_init(reply, &iter);
dbus_message_iter_recurse (&iter, &iter_array);
while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
{
const char *childsender, *childpath;
struct pathList *cur;
dbus_message_iter_recurse (&iter_array, &iter_struct);
dbus_message_iter_get_basic (&iter_struct, &childsender);
dbus_message_iter_next (&iter_struct);
dbus_message_iter_get_basic (&iter_struct, &childpath);
/* Make sure that the child is not the same as an ancestor, to avoid
* recursing indefinitely. */
for (cur = list; cur; cur = cur->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<<ATSPI_STATE_ACTIVE))
/* This application is active */
active = 1;
if (states[0] & (1<<ATSPI_STATE_FOCUSED) && active)
{
/* And this widget is focused */
logMessage(LOG_CATEGORY(SCREEN_DRIVER),
"%s %s is focused!", sender, path);
free(states);
tryRestartTerm(sender, path);
return 1;
}
free(states);
return recurseFindTerm(sender, path, active, depth+1, list);
}
/* Find out currently focused terminal, starting from registry */
static void initTerm(void) {
recurseFindTerm(SPI2_DBUS_INTERFACE_REG, SPI2_DBUS_PATH_ROOT, 0, 0, NULL);
}
/* Handle incoming events */
static void AtSpi2HandleEvent(const char *interface, DBusMessage *message)
{
DBusMessageIter iter, iter_variant;
const char *detail;
dbus_int32_t detail1, detail2;
const char *member = dbus_message_get_member(message);
const char *sender = dbus_message_get_sender(message);
const char *path = dbus_message_get_path(message);
int StateChanged_focused;
dbus_message_iter_init(message, &iter);
if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRUCT) {
/* skip struct */
dbus_message_iter_next(&iter);
}
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
logMessage(LOG_CATEGORY(SCREEN_DRIVER),
"message detail not a string but '%c'", dbus_message_iter_get_arg_type(&iter));
return;
}
dbus_message_iter_get_basic(&iter, &detail);
dbus_message_iter_next(&iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) {
logMessage(LOG_CATEGORY(SCREEN_DRIVER),
"message detail1 not an int32 but '%c'", dbus_message_iter_get_arg_type(&iter));
return;
}
dbus_message_iter_get_basic(&iter, &detail1);
dbus_message_iter_next(&iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) {
logMessage(LOG_CATEGORY(SCREEN_DRIVER),
"message detail2 not an int32 but '%c'", dbus_message_iter_get_arg_type(&iter));
return;
}
dbus_message_iter_get_basic(&iter, &detail2);
dbus_message_iter_next(&iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
logMessage(LOG_CATEGORY(SCREEN_DRIVER),
"message detail2 not a variant but '%c'", dbus_message_iter_get_arg_type(&iter));
return;
}
dbus_message_iter_recurse(&iter, &iter_variant);
StateChanged_focused =
!strcmp(interface, "Object")
&& !strcmp(member, "StateChanged")
&& !strcmp(detail, "focused");
if (StateChanged_focused && !detail1) {
if (curSender && !strcmp(sender, curSender) && !strcmp(path, curPath))
finiTerm();
} else if (!strcmp(interface,"Focus") || (StateChanged_focused && detail1)) {
tryRestartTerm(sender, path);
} else if (!strcmp(interface, "Object") && !strcmp(member, "TextCaretMoved")) {
if (!curSender || strcmp(sender, curSender) || strcmp(path, curPath)) return;
logMessage(LOG_CATEGORY(SCREEN_DRIVER),
"caret move to %d", detail1);
caretPosition(detail1);
} else if (!strcmp(interface, "Object") && !strcmp(member, "TextChanged") && !strcmp(detail, "delete")) {
long x,y,toDelete = detail2;
const char *deleted;
long length = 0, toCopy;
long downTo; /* line that will provide what will follow x */
if (!curSender || strcmp(sender, curSender) || strcmp(path, curPath)) return;
logMessage(LOG_CATEGORY(SCREEN_DRIVER),
"delete %d from %d",detail2,detail1);
if (detail1 < 0) {
logMessage(LOG_ERR,"deleting %ld %d before beginning of text!", toDelete, detail1);
toDelete -= -detail1;
detail1 = 0;
}
if (toDelete <= 0) {
return;
}
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;
}
dbus_message_iter_get_basic(&iter_variant, &deleted);
logMessage(LOG_CATEGORY(SCREEN_DRIVER),
"'%s'",deleted);
downTo = y;
if (downTo < curNumRows)
length = curRowLengths[downTo];
while (x+toDelete >= 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 int closeX = 1;
static void
onScreenDriverFailure (const char *cause) {
logMessage(LOG_ERR, "screen driver failure: %s", cause);
closeX = 1;
brlttyInterrupt(WAIT_STOP);
}
#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;
if (closeX) {
asyncCancelRequest(a2XWatch);
a2XWatch = NULL;
return 1;
}
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);
}
}
}
static int ErrorHandler(Display *dpy, XErrorEvent *ev) {
char buffer[128];
XGetErrorText(dpy, ev->error_code, buffer, sizeof(buffer));
logMessage(LOG_ERR, "X Error %d, %s", ev->type, buffer);
logMessage(LOG_ERR, "resource %#010lx, req %u:%u",ev->resourceid,ev->request_code,ev->minor_code);
onScreenDriverFailure("X error");
return 0;
}
static int IOErrorHandler(Display *dpy) {
onScreenDriverFailure("X I/O error");
return 0;
}
#ifdef HAVE_XSETIOERROREXITHANDLER
static void IOErrorExitHandler(Display *dpy, void *data) {
onScreenDriverFailure("X I/O Error Exit");
return;
}
#endif
#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)) {
logMessage(LOG_ERR, "can't add watch %s %s", watch->message, watch->event);
return 0;
}
}
return 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")) {
onScreenDriverFailure("DBus disconnected");
} 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;
}
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)) {
logMessage(LOG_ERR, "can't add atspi2 filter");
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
closeX = 0;
dpy = XOpenDisplay(NULL);
if (dpy) {
XSetErrorHandler(ErrorHandler);
XSetIOErrorHandler(IOErrorHandler);
#ifdef HAVE_XSETIOERROREXITHANDLER
XSetIOErrorExitHandler(dpy, IOErrorExitHandler, NULL);
#endif
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");
brlttyEnableInterrupt();
return 1;
noWatches:
noConnection:
dbus_connection_unref(bus);
noBus:
onScreenDriverFailure("driver couldn't start");
return 0;
}
static void
destruct_AtSpi2Screen (void) {
brlttyDisableInterrupt();
#ifdef HAVE_PKG_X11
if (dpy) {
if (coreSelUpdatedListener) {
unregisterReportListener(coreSelUpdatedListener);
coreSelUpdatedListener = NULL;
}
if (a2XWatch) {
asyncCancelRequest(a2XWatch);
a2XWatch = NULL;
}
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; y<box->height; y+=1) {
if (curRowLengths[box->top+y]) {
for (unsigned int x=0; x<box->width; 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;
mapScreenKey(&key);
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;
}