364 lines
9.5 KiB
C
364 lines
9.5 KiB
C
/*
|
|
* vim:ts=4:sw=4:expandtab
|
|
*
|
|
* See LICENSE for licensing information
|
|
*
|
|
*/
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <gio/gio.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "accessibility_feedback.h"
|
|
|
|
static int feedback_fd = -1;
|
|
|
|
#define I38LOCK_SECURE_INPUT_BUS_NAME "org.stormux.I38Lock1"
|
|
#define DBUS_REQUEST_NAME_FLAG_DO_NOT_QUEUE 4
|
|
#define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1
|
|
#define DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER 4
|
|
#define ACCESSIBILITY_READY_OK '1'
|
|
#define ACCESSIBILITY_READY_FAILED '0'
|
|
#define ACCESSIBILITY_READY_TIMEOUT_MS 2000
|
|
|
|
struct screen_reader {
|
|
const char *service;
|
|
const char *path;
|
|
const char *interface;
|
|
const char *interrupt_path;
|
|
const char *interrupt_interface;
|
|
};
|
|
|
|
static const struct screen_reader screen_readers[] = {
|
|
{
|
|
"org.stormux.Cthulhu1.Service",
|
|
"/org/stormux/Cthulhu1/Service",
|
|
"org.stormux.Cthulhu1.Service",
|
|
"/org/stormux/Cthulhu1/Service/SpeechManager",
|
|
"org.stormux.Cthulhu1.SpeechManager",
|
|
},
|
|
{
|
|
"org.gnome.Orca1.Service",
|
|
"/org/gnome/Orca1/Service",
|
|
"org.gnome.Orca1.Service",
|
|
NULL,
|
|
NULL,
|
|
},
|
|
};
|
|
|
|
static const char *feedback_message(accessibility_feedback_t feedback) {
|
|
switch (feedback) {
|
|
case ACCESSIBILITY_SCREEN_LOCKED:
|
|
return "Screen locked. Enter password.";
|
|
case ACCESSIBILITY_ENTER_PASSWORD:
|
|
return "Enter password.";
|
|
case ACCESSIBILITY_CHARACTER_TYPED:
|
|
return "star";
|
|
case ACCESSIBILITY_CHARACTER_ERASED:
|
|
return "backspace star";
|
|
case ACCESSIBILITY_NOTHING_TO_ERASE:
|
|
return "backspace blank";
|
|
case ACCESSIBILITY_PASSWORD_CLEARED:
|
|
return "Password cleared.";
|
|
case ACCESSIBILITY_VERIFYING:
|
|
return "Verifying.";
|
|
case ACCESSIBILITY_AUTHENTICATED:
|
|
return "Authenticated.";
|
|
case ACCESSIBILITY_INCORRECT_PASSWORD:
|
|
return "Incorrect password. Enter password.";
|
|
case ACCESSIBILITY_CAPS_LOCK_ON:
|
|
return "Caps lock on.";
|
|
case ACCESSIBILITY_CAPS_LOCK_OFF:
|
|
return "Caps lock off.";
|
|
case ACCESSIBILITY_NUM_LOCK_ON:
|
|
return "Num lock on.";
|
|
case ACCESSIBILITY_NUM_LOCK_OFF:
|
|
return "Num lock off.";
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static bool service_has_owner(GDBusConnection *connection, const char *service) {
|
|
GError *error = NULL;
|
|
GVariant *result = g_dbus_connection_call_sync(
|
|
connection,
|
|
"org.freedesktop.DBus",
|
|
"/org/freedesktop/DBus",
|
|
"org.freedesktop.DBus",
|
|
"NameHasOwner",
|
|
g_variant_new("(s)", service),
|
|
G_VARIANT_TYPE("(b)"),
|
|
G_DBUS_CALL_FLAGS_NONE,
|
|
250,
|
|
NULL,
|
|
&error);
|
|
|
|
if (result == NULL) {
|
|
g_clear_error(&error);
|
|
return false;
|
|
}
|
|
|
|
gboolean has_owner = false;
|
|
g_variant_get(result, "(b)", &has_owner);
|
|
g_variant_unref(result);
|
|
return has_owner;
|
|
}
|
|
|
|
static bool own_secure_input_name(GDBusConnection *connection) {
|
|
GError *error = NULL;
|
|
GVariant *result = g_dbus_connection_call_sync(
|
|
connection,
|
|
"org.freedesktop.DBus",
|
|
"/org/freedesktop/DBus",
|
|
"org.freedesktop.DBus",
|
|
"RequestName",
|
|
g_variant_new(
|
|
"(su)",
|
|
I38LOCK_SECURE_INPUT_BUS_NAME,
|
|
(guint32)DBUS_REQUEST_NAME_FLAG_DO_NOT_QUEUE),
|
|
G_VARIANT_TYPE("(u)"),
|
|
G_DBUS_CALL_FLAGS_NONE,
|
|
500,
|
|
NULL,
|
|
&error);
|
|
|
|
if (result == NULL) {
|
|
g_clear_error(&error);
|
|
return false;
|
|
}
|
|
|
|
guint32 reply = 0;
|
|
g_variant_get(result, "(u)", &reply);
|
|
g_variant_unref(result);
|
|
return reply == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER ||
|
|
reply == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER;
|
|
}
|
|
|
|
static void interrupt_speech(GDBusConnection *connection, const struct screen_reader *reader) {
|
|
if (reader->interrupt_path == NULL || reader->interrupt_interface == NULL) {
|
|
return;
|
|
}
|
|
|
|
GError *error = NULL;
|
|
GVariant *result = g_dbus_connection_call_sync(
|
|
connection,
|
|
reader->service,
|
|
reader->interrupt_path,
|
|
reader->interrupt_interface,
|
|
"InterruptSpeech",
|
|
g_variant_new("(b)", TRUE),
|
|
G_VARIANT_TYPE("(b)"),
|
|
G_DBUS_CALL_FLAGS_NONE,
|
|
250,
|
|
NULL,
|
|
&error);
|
|
|
|
if (result != NULL) {
|
|
g_variant_unref(result);
|
|
}
|
|
g_clear_error(&error);
|
|
}
|
|
|
|
static void report_worker_ready(int ready_fd, bool is_ready) {
|
|
char status = is_ready ? ACCESSIBILITY_READY_OK : ACCESSIBILITY_READY_FAILED;
|
|
ssize_t result;
|
|
do {
|
|
result = write(ready_fd, &status, sizeof(status));
|
|
} while (result == -1 && errno == EINTR);
|
|
|
|
close(ready_fd);
|
|
}
|
|
|
|
static bool wait_for_worker_ready(int ready_fd) {
|
|
struct pollfd poll_fd = {
|
|
.fd = ready_fd,
|
|
.events = POLLIN,
|
|
};
|
|
|
|
int poll_result;
|
|
do {
|
|
poll_result = poll(&poll_fd, 1, ACCESSIBILITY_READY_TIMEOUT_MS);
|
|
} while (poll_result == -1 && errno == EINTR);
|
|
|
|
if (poll_result <= 0) {
|
|
close(ready_fd);
|
|
return false;
|
|
}
|
|
|
|
char status = ACCESSIBILITY_READY_FAILED;
|
|
ssize_t read_result;
|
|
do {
|
|
read_result = read(ready_fd, &status, sizeof(status));
|
|
} while (read_result == -1 && errno == EINTR);
|
|
|
|
close(ready_fd);
|
|
return read_result == sizeof(status) && status == ACCESSIBILITY_READY_OK;
|
|
}
|
|
|
|
static void present_message(GDBusConnection *connection, const char *message) {
|
|
for (size_t i = 0; i < sizeof(screen_readers) / sizeof(screen_readers[0]); i++) {
|
|
const struct screen_reader *reader = &screen_readers[i];
|
|
if (!service_has_owner(connection, reader->service)) {
|
|
continue;
|
|
}
|
|
|
|
interrupt_speech(connection, reader);
|
|
|
|
GError *error = NULL;
|
|
GVariant *result = g_dbus_connection_call_sync(
|
|
connection,
|
|
reader->service,
|
|
reader->path,
|
|
reader->interface,
|
|
"PresentMessage",
|
|
g_variant_new("(s)", message),
|
|
G_VARIANT_TYPE("(b)"),
|
|
G_DBUS_CALL_FLAGS_NONE,
|
|
500,
|
|
NULL,
|
|
&error);
|
|
|
|
if (result != NULL) {
|
|
g_variant_unref(result);
|
|
}
|
|
g_clear_error(&error);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void close_sleep_lock_fd(void) {
|
|
const char *sleep_lock_fd = getenv("XSS_SLEEP_LOCK_FD");
|
|
if (sleep_lock_fd == NULL || *sleep_lock_fd == '\0') {
|
|
return;
|
|
}
|
|
|
|
char *endptr;
|
|
long int fd = strtol(sleep_lock_fd, &endptr, 10);
|
|
if (*endptr == '\0') {
|
|
close(fd);
|
|
}
|
|
}
|
|
|
|
static void feedback_worker(int read_fd, int inherited_xcb_fd, int ready_fd) {
|
|
if (inherited_xcb_fd >= 0) {
|
|
close(inherited_xcb_fd);
|
|
}
|
|
close_sleep_lock_fd();
|
|
|
|
GError *error = NULL;
|
|
GDBusConnection *connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
|
|
bool secure_input_ready = false;
|
|
if (connection == NULL) {
|
|
g_clear_error(&error);
|
|
} else {
|
|
secure_input_ready = own_secure_input_name(connection);
|
|
}
|
|
|
|
report_worker_ready(ready_fd, secure_input_ready);
|
|
if (!secure_input_ready) {
|
|
if (connection != NULL) {
|
|
g_object_unref(connection);
|
|
}
|
|
close(read_fd);
|
|
return;
|
|
}
|
|
|
|
accessibility_feedback_t feedback[128];
|
|
ssize_t bytes_read;
|
|
while ((bytes_read = read(read_fd, feedback, sizeof(feedback))) != 0) {
|
|
if (bytes_read < 0) {
|
|
if (errno == EINTR) {
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
size_t count = (size_t)bytes_read / sizeof(feedback[0]);
|
|
for (size_t i = 0; i < count; i++) {
|
|
const char *message = feedback_message(feedback[i]);
|
|
if (connection != NULL && message != NULL) {
|
|
present_message(connection, message);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (connection != NULL) {
|
|
g_object_unref(connection);
|
|
}
|
|
close(read_fd);
|
|
}
|
|
|
|
bool accessibility_feedback_start(int inherited_xcb_fd) {
|
|
int pipe_fds[2];
|
|
if (pipe(pipe_fds) != 0) {
|
|
return false;
|
|
}
|
|
|
|
int ready_fds[2];
|
|
if (pipe(ready_fds) != 0) {
|
|
close(pipe_fds[0]);
|
|
close(pipe_fds[1]);
|
|
return false;
|
|
}
|
|
|
|
int flags = fcntl(pipe_fds[1], F_GETFL);
|
|
if (flags == -1 || fcntl(pipe_fds[1], F_SETFL, flags | O_NONBLOCK) == -1) {
|
|
close(pipe_fds[0]);
|
|
close(pipe_fds[1]);
|
|
close(ready_fds[0]);
|
|
close(ready_fds[1]);
|
|
return false;
|
|
}
|
|
|
|
signal(SIGPIPE, SIG_IGN);
|
|
|
|
pid_t pid = fork();
|
|
if (pid == -1) {
|
|
close(pipe_fds[0]);
|
|
close(pipe_fds[1]);
|
|
close(ready_fds[0]);
|
|
close(ready_fds[1]);
|
|
return false;
|
|
}
|
|
|
|
if (pid == 0) {
|
|
close(pipe_fds[1]);
|
|
close(ready_fds[0]);
|
|
feedback_worker(pipe_fds[0], inherited_xcb_fd, ready_fds[1]);
|
|
_exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
close(pipe_fds[0]);
|
|
close(ready_fds[1]);
|
|
if (!wait_for_worker_ready(ready_fds[0])) {
|
|
close(pipe_fds[1]);
|
|
return false;
|
|
}
|
|
|
|
feedback_fd = pipe_fds[1];
|
|
return true;
|
|
}
|
|
|
|
void accessibility_feedback_notify(accessibility_feedback_t feedback) {
|
|
if (feedback_fd == -1) {
|
|
return;
|
|
}
|
|
|
|
ssize_t result;
|
|
do {
|
|
result = write(feedback_fd, &feedback, sizeof(feedback));
|
|
} while (result == -1 && errno == EINTR);
|
|
|
|
if (result == -1 && errno == EPIPE) {
|
|
close(feedback_fd);
|
|
feedback_fd = -1;
|
|
}
|
|
}
|