252 lines
6.5 KiB
C
252 lines
6.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 <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;
|
|
|
|
struct screen_reader {
|
|
const char *service;
|
|
const char *path;
|
|
const char *interface;
|
|
};
|
|
|
|
static const struct screen_reader screen_readers[] = {
|
|
{
|
|
"org.stormux.Cthulhu1.Service",
|
|
"/org/stormux/Cthulhu1/Service",
|
|
"org.stormux.Cthulhu1.Service",
|
|
},
|
|
{
|
|
"org.gnome.Orca1.Service",
|
|
"/org/gnome/Orca1/Service",
|
|
"org.gnome.Orca1.Service",
|
|
},
|
|
};
|
|
|
|
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 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;
|
|
}
|
|
|
|
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 flush_stars(GDBusConnection *connection, size_t count) {
|
|
char message[640];
|
|
size_t position = 0;
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
const char *separator = (i == 0 ? "" : " ");
|
|
position += snprintf(message + position, sizeof(message) - position, "%sstar", separator);
|
|
}
|
|
|
|
present_message(connection, message);
|
|
}
|
|
|
|
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) {
|
|
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);
|
|
if (connection == NULL) {
|
|
g_clear_error(&error);
|
|
}
|
|
|
|
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]);
|
|
size_t stars = 0;
|
|
for (size_t i = 0; i < count; i++) {
|
|
if (feedback[i] == ACCESSIBILITY_CHARACTER_TYPED) {
|
|
stars++;
|
|
continue;
|
|
}
|
|
|
|
if (connection != NULL && stars > 0) {
|
|
flush_stars(connection, stars);
|
|
}
|
|
stars = 0;
|
|
|
|
const char *message = feedback_message(feedback[i]);
|
|
if (connection != NULL && message != NULL) {
|
|
present_message(connection, message);
|
|
}
|
|
}
|
|
|
|
if (connection != NULL && stars > 0) {
|
|
flush_stars(connection, stars);
|
|
}
|
|
}
|
|
|
|
if (connection != NULL) {
|
|
g_object_unref(connection);
|
|
}
|
|
close(read_fd);
|
|
}
|
|
|
|
void accessibility_feedback_start(int inherited_xcb_fd) {
|
|
int pipe_fds[2];
|
|
if (pipe(pipe_fds) != 0) {
|
|
return;
|
|
}
|
|
|
|
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]);
|
|
return;
|
|
}
|
|
|
|
signal(SIGPIPE, SIG_IGN);
|
|
|
|
pid_t pid = fork();
|
|
if (pid == -1) {
|
|
close(pipe_fds[0]);
|
|
close(pipe_fds[1]);
|
|
return;
|
|
}
|
|
|
|
if (pid == 0) {
|
|
close(pipe_fds[1]);
|
|
feedback_worker(pipe_fds[0], inherited_xcb_fd);
|
|
_exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
close(pipe_fds[0]);
|
|
feedback_fd = pipe_fds[1];
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|