commit 5ec169278f060e8d7662b20fa34f8aa77ae7ffd8 Author: Kimapr Date: Mon Apr 7 21:14:52 2025 +0500 We know where the text caret is diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..2527a9b --- /dev/null +++ b/meson.build @@ -0,0 +1,24 @@ +project('at-emoji', 'c', + version: '0.0.1', + meson_version: '>= 1.0.0', +) + +cc = meson.get_compiler('c') + +ate_dependencies = [ + dependency('atspi-2', version: '>= 2.50.0'), + dependency('glib-2.0', version: '>=2.44'), + dependency('gtk+-3.0', version: '>=3.24.43'), + cc.find_library('m', required: false) +] + +ate_sources = [ + 'src/main.c', +] + +executable('at-emoji', + sources: ate_sources, + dependencies: ate_dependencies, + install: true +) + diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..87fa319 --- /dev/null +++ b/src/main.c @@ -0,0 +1,234 @@ +#include +#include +#include + +#include +#include + +AtspiAccessible *find_active_window() +{ + AtspiAccessible *active = NULL; + + for (gint x = 0; x < atspi_get_desktop_count(); x++) { + + AtspiAccessible *desktop = atspi_get_desktop(x); + + for (gint i = 0; i < atspi_accessible_get_child_count(desktop, NULL); i++) { + + AtspiAccessible *app = atspi_accessible_get_child_at_index(desktop, i, NULL); + + g_print("\t%s: %d windows\n", atspi_accessible_get_name(app, NULL), + atspi_accessible_get_child_count(app, NULL)); + + for (gint j = 0; j < atspi_accessible_get_child_count(app, NULL); j++) { + + AtspiAccessible *wnd = atspi_accessible_get_child_at_index(app, j, NULL); + + AtspiStateSet *wst = atspi_accessible_get_state_set(wnd); + + if (atspi_state_set_contains(wst, ATSPI_STATE_ACTIVE)) { + g_print("Active window: %s\n", atspi_accessible_get_name(app, NULL), 0); + active = wnd; + g_object_unref(wst); + g_object_unref(app); + g_object_unref(desktop); + return active; + } + + g_object_unref(wst); + g_object_unref(wnd); + } + + g_object_unref(app); + } + + g_object_unref(desktop); + } + + return active; +} + +AtspiAccessible *find_focused_text_BAD(AtspiAccessible *active) +{ + + AtspiStateSet *wst = atspi_accessible_get_state_set(active); + + if (!(atspi_state_set_contains(wst, ATSPI_STATE_SHOWING) && + atspi_state_set_contains(wst, ATSPI_STATE_VISIBLE))) { + g_object_unref(wst); + return NULL; + } + + g_object_unref(wst); + + for (gint j = 0; j < atspi_accessible_get_child_count(active, NULL); j++) { + + AtspiAccessible *chld = atspi_accessible_get_child_at_index(active, j, NULL); + + AtspiText *txt = atspi_accessible_get_text_iface(chld); + + if (txt) { + g_object_unref(txt); + return chld; + } + + AtspiAccessible *x = find_focused_text_BAD(chld); + + if (x) { + g_object_unref(chld); + return x; + } + + g_object_unref(chld); + } + + return NULL; +} + +AtspiAccessible *find_focused_text(AtspiAccessible *active) +{ + AtspiCollection *things = atspi_accessible_get_collection_iface(active); + + if (!things) { // Qt is bad + g_print("No Collection implemented! BAD search engaged\n"); + return find_focused_text_BAD(active); + } + + AtspiAccessible *text = NULL; + g_print("(application.length)=(%d)\n", atspi_accessible_get_child_count(active, NULL)); + + if (things) { + AtspiStateType appd_state = ATSPI_STATE_FOCUSED; + GArray *match_states = g_array_new(TRUE, TRUE, sizeof(appd_state)); + g_array_append_val(match_states, appd_state); + + gchar *appd_iface = "Text"; + GArray *match_ifaces = g_array_new(TRUE, TRUE, sizeof(appd_iface)); + g_array_append_val(match_ifaces, appd_iface); + + AtspiStateSet *match_stateset = atspi_state_set_new(match_states); + g_array_unref(match_states); + + AtspiMatchRule *match_rule = atspi_match_rule_new( + match_stateset, ATSPI_Collection_MATCH_ALL, NULL, ATSPI_Collection_MATCH_ANY, NULL, + ATSPI_Collection_MATCH_ANY, match_ifaces, ATSPI_Collection_MATCH_ALL, FALSE); + g_array_unref(match_ifaces); + + GArray *matches = atspi_collection_get_matches(things, match_rule, + ATSPI_Collection_SORT_ORDER_CANONICAL, 0, TRUE, NULL); + g_object_unref(match_rule); + + g_print("(#matches)=(%d)\n", matches->len); + + if (!matches->len) { + g_array_unref(matches); + return text; + } + + text = g_array_index(matches, AtspiAccessible *, 0); + g_array_unref(matches); + } + + return text; +} + +AtspiPoint get_text_caret_pos(AtspiAccessible *text) +{ + AtspiText *tiface = atspi_accessible_get_text_iface(text); + + gint caret_pos = atspi_text_get_caret_offset(tiface, NULL); + + AtspiRect *trect = + atspi_text_get_range_extents(tiface, caret_pos, caret_pos + 1, ATSPI_COORD_TYPE_SCREEN, NULL); + + gint x = trect->x, y = trect->y; + g_free(trect); + + if (x == 0 && y == 0) { // Qt is bad + g_print("Text returned bogus caret position! Trying inward extent.\n"); + AtspiRect *trect = + atspi_text_get_range_extents(tiface, caret_pos - 1, caret_pos, ATSPI_COORD_TYPE_SCREEN, NULL); + x = trect->x + trect->width; + y = trect->y; + + gchar *txt = atspi_text_get_text(tiface, caret_pos - 1, caret_pos, NULL); + + if (g_strcmp0(txt, "\n") == 0) { + AtspiRect *trect1 = + atspi_text_get_range_extents(tiface, 0, caret_pos - 1, ATSPI_COORD_TYPE_SCREEN, NULL); + x = trect1->x; + y = trect->y + trect->height; + + g_free(trect1); + } + + g_free(txt); + g_free(trect); + } + + g_object_unref(tiface); + + if (x == 0 && y == 0) { // Qt is bad + g_print("Text returned bogus caret position (again)! Trying component position.\n"); + AtspiComponent *cface = atspi_accessible_get_component_iface(text); + + AtspiPoint *point = atspi_component_get_position(cface, ATSPI_COORD_TYPE_SCREEN, NULL); + x = point->x; + y = point->y; + + g_free(point); + g_object_unref(cface); + } + + return (AtspiPoint){x, y}; +} + +static void activate(GtkApplication *app, gpointer user_data) +{ + GtkWidget *window; + + window = gtk_window_new(GTK_WINDOW_POPUP); + gtk_window_set_title(GTK_WINDOW(window), "Window"); + gtk_window_set_default_size(GTK_WINDOW(window), 200, 200); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_NONE); + gint w, h; + gtk_window_get_size(GTK_WINDOW(window), &w, &h); + printf("%i, %i\n", w, h); + g_print("scanning apps...\n"); + + AtspiAccessible *active = find_active_window(); + + if (!active) { + g_print("No active window! skedaddling outta here\n"); + return; + } + + AtspiAccessible *text = find_focused_text(active); + + if (!text) { + g_print("No focused text! skedaddling outta here\n"); + return; + } + + AtspiPoint trect = get_text_caret_pos(text); + + g_print("(x,y)=(%d,%d)\n", trect.x, trect.y); + gtk_window_move(GTK_WINDOW(window), trect.x - w / 2, trect.y - h); + + g_object_unref(active); + + gtk_widget_show_all(window); +} + +int main(int argc, char **argv) +{ + atspi_init(); + + GtkApplication *app = gtk_application_new("net.kimapr.AtEmoji", G_APPLICATION_FLAGS_NONE); + g_application_hold(G_APPLICATION(app)); + g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); + int status = g_application_run(G_APPLICATION(app), argc, argv); + g_object_unref(app); + + return status; +}