/* * vim:ts=4:sw=4:expandtab * * See LICENSE for licensing information * */ #include #include #include #include #include #include #include #include #include #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; } }