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