Files
i38lock/accessibility_feedback.c
2026-06-02 17:29:55 -04:00

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;
}
}