We know where the text caret is

This commit is contained in:
Kimapr 2025-04-07 21:14:52 +05:00
commit 5ec169278f
2 changed files with 258 additions and 0 deletions

24
meson.build Normal file
View file

@ -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
)

234
src/main.c Normal file
View file

@ -0,0 +1,234 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atspi/atspi.h>
#include <gtk/gtk.h>
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;
}