Tk Source Code

Check-in [9067abb1]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Additional refinements for at-spi integration
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | tka11y
Files: files | file ages | folders
SHA3-256: 9067abb148de15d522828da46d3fa190bd2bd370da8972bd5c28076cd65eb621
User & Date: kevin_walzer 2025-07-27 02:14:04.276
Context
2025-07-27
02:14
Additional refinements for at-spi integration Leaf check-in: 9067abb1 user: kevin_walzer tags: tka11y
01:45
Remove unneeded calls check-in: fac81696 user: kevin_walzer tags: tka11y
Changes
Unified Diff Ignore Whitespace Patch
Changes to unix/tkUnixAccessibility.c.
84
85
86
87
88
89
90


91
92
93
94
95
96
97
/* Script-level commands and helper functions. */
static int EmitSelectionChanged(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]);
static int EmitFocusChanged(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]);
static int IsScreenReaderRunning(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]);
void TkAtkAccessible_RegisterEventHandlers(Tk_Window tkwin, void *tkAccessible);
static void TkAtkAccessible_DestroyHandler(ClientData clientData, XEvent *eventPtr);
static void TkAtkAccessible_NameHandler(ClientData clientData, XEvent *eventPtr);


int TkAtkAccessibleObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]);
int TkAtkAccessibility_Init(Tcl_Interp *interp);

/*
 * Struct to map Tk roles into Atk roles.
 */








>
>







84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/* Script-level commands and helper functions. */
static int EmitSelectionChanged(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]);
static int EmitFocusChanged(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]);
static int IsScreenReaderRunning(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]);
void TkAtkAccessible_RegisterEventHandlers(Tk_Window tkwin, void *tkAccessible);
static void TkAtkAccessible_DestroyHandler(ClientData clientData, XEvent *eventPtr);
static void TkAtkAccessible_NameHandler(ClientData clientData, XEvent *eventPtr);
static void TkAtkAccessible_MapHandler(ClientData clientData, XEvent *eventPtr);
static void TkAtkAccessible_UnmapHandler(ClientData clientData, XEvent *eventPtr);
int TkAtkAccessibleObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]);
int TkAtkAccessibility_Init(Tcl_Interp *interp);

/*
 * Struct to map Tk roles into Atk roles.
 */

212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
{
    TkAtkAccessible *acc = (TkAtkAccessible *)obj;

    if (!acc) return NULL;

    if (obj == tk_root_accessible) {
        if (i >= (gint)g_list_length(toplevel_accessible_objects))
 {
            return NULL;
        }
	/* Get accessible object from toplevel list. */
        AtkObject *child = g_list_nth_data(toplevel_accessible_objects, i);
        if (child) {
            g_object_ref(child); /* Increment ref count as per ATK interface contract. */
        }
        return child;
    }







|
|
|







214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
{
    TkAtkAccessible *acc = (TkAtkAccessible *)obj;

    if (!acc) return NULL;

    if (obj == tk_root_accessible) {
        if (i >= (gint)g_list_length(toplevel_accessible_objects))
	    {
		return NULL;
	    }
	/* Get accessible object from toplevel list. */
        AtkObject *child = g_list_nth_data(toplevel_accessible_objects, i);
        if (child) {
            g_object_ref(child); /* Increment ref count as per ATK interface contract. */
        }
        return child;
    }
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693




694

695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
 */
static void RegisterChildWidgets(Tcl_Interp *interp, Tk_Window tkwin, AtkObject *parent_obj)
{
    if (!tkwin || !parent_obj) return;

    TkWindow *winPtr = (TkWindow *)tkwin;
    TkWindow *childPtr;
    int index = 0; /* Track child index for children-changed::add signal. */

    for (childPtr = winPtr->childList; childPtr != NULL; childPtr = childPtr->nextPtr) {
        Tk_Window child = (Tk_Window)childPtr;
        if (!child || !Tk_WindowId(child)) continue; 

        AtkObject *child_obj = GetAtkObjectForTkWindow(child);
        if (!child_obj) {
            child_obj = TkCreateAccessibleAtkObject(interp, child, Tk_PathName(child));
            if (!child_obj) continue; 

            RegisterAtkObjectForTkWindow(child, child_obj);
            TkAtkAccessible_RegisterEventHandlers(child, (TkAtkAccessible *)child_obj);
        }






        /* Ensure proper parent relationship. */
        AtkObject *current_parent = atk_object_get_parent(child_obj);
        if (current_parent != parent_obj) {
	    /* 
	     * If the child was previously parented to something else or unparented,
	     * emit a remove signal from the old parent first if known.
	     * For simplicity, we'll just set the parent and emit 'add' for the new parent.
	     */
            atk_object_set_parent(child_obj, parent_obj);
            g_signal_emit_by_name(parent_obj, "children-changed::add", index, child_obj);
        }

        /* Set the name for the child object. */
        const gchar *child_name = tk_get_name(child_obj);
        if (child_name) {
            tk_set_name(child_obj, child_name);
            g_free((gpointer)child_name);
        }

        /* Recursively register children. */
        RegisterChildWidgets(interp, child, child_obj);
        index++;
    }
}


/*
 * Root window setup. These are the foundation of the
 * accessibility object system in Atk. atk_get_root() is the
 * critical link to at-spi - it is called by the Atk system
 * and at-spi bridge initialization will silently fail if this
 * function is not implemented. This API is confusing because







|



|




|



|
>
>
>
>
|
>
|


<
<
<
<
<




<






<




<







675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704





705
706
707
708

709
710
711
712
713
714

715
716
717
718

719
720
721
722
723
724
725
 */
static void RegisterChildWidgets(Tcl_Interp *interp, Tk_Window tkwin, AtkObject *parent_obj)
{
    if (!tkwin || !parent_obj) return;

    TkWindow *winPtr = (TkWindow *)tkwin;
    TkWindow *childPtr;
    int index = 0;

    for (childPtr = winPtr->childList; childPtr != NULL; childPtr = childPtr->nextPtr) {
        Tk_Window child = (Tk_Window)childPtr;
        if (!child) continue;

        AtkObject *child_obj = GetAtkObjectForTkWindow(child);
        if (!child_obj) {
            child_obj = TkCreateAccessibleAtkObject(interp, child, Tk_PathName(child));
            if (!child_obj) continue;

            RegisterAtkObjectForTkWindow(child, child_obj);
            TkAtkAccessible_RegisterEventHandlers(child, (TkAtkAccessible *)child_obj);

            /* Set a default role if none found. */
            AtkRole role = GetAtkRoleForWidget(child);
            if (role == ATK_ROLE_UNKNOWN) {
                atk_object_set_role(child_obj, ATK_ROLE_PANEL);
            }
        }

        AtkObject *current_parent = atk_object_get_parent(child_obj);
        if (current_parent != parent_obj) {





            atk_object_set_parent(child_obj, parent_obj);
            g_signal_emit_by_name(parent_obj, "children-changed::add", index, child_obj);
        }


        const gchar *child_name = tk_get_name(child_obj);
        if (child_name) {
            tk_set_name(child_obj, child_name);
            g_free((gpointer)child_name);
        }


        RegisterChildWidgets(interp, child, child_obj);
        index++;
    }
}


/*
 * Root window setup. These are the foundation of the
 * accessibility object system in Atk. atk_get_root() is the
 * critical link to at-spi - it is called by the Atk system
 * and at-spi bridge initialization will silently fail if this
 * function is not implemented. This API is confusing because
1111
1112
1113
1114
1115
1116
1117































































1118
1119
1120
1121
1122
1123
1124
	if (name) {
	    tk_set_name(atk_obj, name);
	    g_free((gpointer)name); /* Free the string returned by tk_get_name. */
	}
    }
}

































































/*
 *----------------------------------------------------------------------
 *
 * TkAtkAccessibleObjCmd --
 *
 *	Main command for adding and managing accessibility objects to Tk







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
	if (name) {
	    tk_set_name(atk_obj, name);
	    g_free((gpointer)name); /* Free the string returned by tk_get_name. */
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkAtkAccessible_MapHandler --
 *
 * Notify ATK system when Tk window is mapped. 
 *
 * Results:
 *	Window visibility is registered.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void TkAtkAccessible_MapHandler(ClientData clientData, XEvent *eventPtr)
{
    if (eventPtr->type != MapNotify) return;

    TkAtkAccessible *tkAccessible = (TkAtkAccessible *)clientData;
    if (!tkAccessible || !tkAccessible->tkwin || !tkAccessible->interp) return;

    AtkObject *atk_obj = (AtkObject*)tkAccessible;
    RegisterChildWidgets(tkAccessible->interp, tkAccessible->tkwin, atk_obj);
    AtkStateSet *state_set = atk_state_set_new();
    atk_state_set_add_state(state_set, ATK_STATE_VISIBLE);
    atk_state_set_add_state(state_set, ATK_STATE_SHOWING);
    g_signal_emit_by_name(atk_obj, "state-change", "visible", TRUE);
    g_signal_emit_by_name(atk_obj, "state-change", "showing", TRUE);
    g_object_unref(state_set);
}

/*
 *----------------------------------------------------------------------
 *
 * TkAtkAccessible_UnmapHandler --
 *
 * Notify ATK system when Tk window is unmapped. 
 *
 * Results:
 *	Window visibility is removed.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */
 
static void TkAtkAccessible_UnmapHandler(ClientData clientData, XEvent *eventPtr)
{
    if (eventPtr->type != UnmapNotify) return;

    TkAtkAccessible *tkAccessible = (TkAtkAccessible *)clientData;
    if (!tkAccessible || !tkAccessible->tkwin) return;

    AtkObject *atk_obj = (AtkObject*)tkAccessible;
    AtkStateSet *state_set = atk_state_set_new();
    atk_state_set_remove_state(state_set, ATK_STATE_SHOWING);
    g_signal_emit_by_name(atk_obj, "state-change", "showing", FALSE);
    g_object_unref(state_set);
}


/*
 *----------------------------------------------------------------------
 *
 * TkAtkAccessibleObjCmd --
 *
 *	Main command for adding and managing accessibility objects to Tk
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210











1211

1212
1213









1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233

1234
1235
1236
1237
1238
1239
1240
1241
1242

1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
 */
#ifdef USE_ATK
int TkAtkAccessibility_Init(Tcl_Interp *interp)
{
    /* Create and configure root object. */
    tk_root_accessible = tk_util_get_root();
    if (tk_root_accessible) {
	const gchar *name = tk_get_name(tk_root_accessible);
	if (name) {
	    tk_set_name(tk_root_accessible, name); /* Set the name and notify. */
	    g_free((gpointer)name); /* Free the string returned by tk_get_name. */











	}

    }










    /* Initialize AT-SPI bridge. */
    if (atk_bridge_adaptor_init(NULL, NULL) != 0) {
	g_warning("Failed to initialize AT-SPI bridge\n");
	return TCL_ERROR;
    }

    /* Initialize mapping table. */
    InitAtkTkMapping();

    /* Register main window with root. */
    Tk_Window mainWin = Tk_MainWindow(interp);
    if (mainWin) {
	/* 
	 * Create accessible object for main window, which will also register it
	 * and set its parent to the root.
	 */
	TkAtkAccessible *main_acc = (TkAtkAccessible*) TkCreateAccessibleAtkObject(interp, mainWin, Tk_PathName(mainWin));
	if (main_acc) {
	    /* Ensure the main window is treated as a toplevel for ATK hierarchy. */
	    RegisterToplevelWindow(interp, mainWin, (AtkObject*)main_acc);

	}
    }

    /* 
     * Process pending GLib events to establish AT-SPI connection. 
     * Limiting iterations to prevent blocking.
     */
    int iterations = 0;
    while (g_main_context_pending(NULL) && iterations < 100) {

	iterations++;
    }

    /* Install event loop integration. */
    InstallGtkEventLoop();

    /* Register Tcl commands. */
    Tcl_CreateObjCommand(interp, "::tk::accessible::add_acc_object",
			 TkAtkAccessibleObjCmd, NULL, NULL);
    Tcl_CreateObjCommand(interp, "::tk::accessible::emit_selection_change",
			 EmitSelectionChanged, NULL, NULL);
    Tcl_CreateObjCommand(interp, "::tk::accessible::emit_focus_change",
			 EmitFocusChanged, NULL, NULL);
    Tcl_CreateObjCommand(interp, "::tk::accessible::check_screenreader",
			 IsScreenReaderRunning, NULL, NULL);

    /* 
     * Force initial hierarchy update. 
     */
    g_signal_emit_by_name((AtkObject*)tk_root_accessible, "children-changed", 0, NULL);

    return TCL_OK;
}

#else
/* No Atk found. */
int TkAtkAccessibility_Init(Tcl_Interp *interp)







|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
|
>
|
|
>
>
>
>
>
>
>
>
>


|
|








<
<
<
<
|
|
<
|
>
|


<
|
<
<

|
>
|







|

|

|

|

<
|
<
|







1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308




1309
1310

1311
1312
1313
1314
1315

1316


1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335

1336

1337
1338
1339
1340
1341
1342
1343
1344
 */
#ifdef USE_ATK
int TkAtkAccessibility_Init(Tcl_Interp *interp)
{
    /* Create and configure root object. */
    tk_root_accessible = tk_util_get_root();
    if (tk_root_accessible) {
        const gchar *name = tk_get_name(tk_root_accessible);
        if (name) {
            tk_set_name(tk_root_accessible, name);
            g_free((gpointer)name);
        } else {
            /* Fallback to main window title. */
            Tk_Window mainWin = Tk_MainWindow(interp);
            if (mainWin) {
                Tcl_DString cmd;
                Tcl_DStringInit(&cmd);
                Tcl_DStringAppend(&cmd, "wm title ", -1);
                Tcl_DStringAppend(&cmd, Tk_PathName(mainWin), -1);
                if (Tcl_Eval(interp, Tcl_DStringValue(&cmd)) == TCL_OK) {
                    const char *title = Tcl_GetStringResult(interp);
                    tk_set_name(tk_root_accessible, title && *title ? title : "Tk Application");
                }
                Tcl_DStringFree(&cmd);
            }
        }
        /* Ensure root is marked as visible and showing. */
        AtkStateSet *state_set = atk_state_set_new();
        atk_state_set_add_state(state_set, ATK_STATE_VISIBLE);
        atk_state_set_add_state(state_set, ATK_STATE_SHOWING);
        g_signal_emit_by_name(tk_root_accessible, "state-change", "visible", TRUE);
        g_signal_emit_by_name(tk_root_accessible, "state-change", "showing", TRUE);
        g_object_unref(state_set);
    }

    /* Initialize AT-SPI bridge. */
    if (atk_bridge_adaptor_init(NULL, NULL) != 0) {
        g_warning("Failed to initialize AT-SPI bridge\n");
        return TCL_ERROR;
    }

    /* Initialize mapping table. */
    InitAtkTkMapping();

    /* Register main window with root. */
    Tk_Window mainWin = Tk_MainWindow(interp);
    if (mainWin) {




        TkAtkAccessible *main_acc = (TkAtkAccessible*) TkCreateAccessibleAtkObject(interp, mainWin, Tk_PathName(mainWin));
        if (main_acc) {

            RegisterToplevelWindow(interp, mainWin, (AtkObject*)main_acc);
            g_signal_emit_by_name(tk_root_accessible, "children-changed::add", 0, (AtkObject*)main_acc);
        }
    }


    /* Process pending GLib events with higher iteration limit */


    int iterations = 0;
    while (g_main_context_pending(NULL) && iterations < 1000) {
        g_main_context_iteration(NULL, FALSE);
        iterations++;
    }

    /* Install event loop integration. */
    InstallGtkEventLoop();

    /* Register Tcl commands. */
    Tcl_CreateObjCommand(interp, "::tk::accessible::add_acc_object",
                         TkAtkAccessibleObjCmd, NULL, NULL);
    Tcl_CreateObjCommand(interp, "::tk::accessible::emit_selection_change",
                         EmitSelectionChanged, NULL, NULL);
    Tcl_CreateObjCommand(interp, "::tk::accessible::emit_focus_change",
                         EmitFocusChanged, NULL, NULL);
    Tcl_CreateObjCommand(interp, "::tk::accessible::check_screenreader",
                         IsScreenReaderRunning, NULL, NULL);


    /* Force initial hierarchy update. */

    g_signal_emit_by_name(tk_root_accessible, "children-changed::add", 0, NULL);

    return TCL_OK;
}

#else
/* No Atk found. */
int TkAtkAccessibility_Init(Tcl_Interp *interp)