jaccesswalker.cpp revision 13237:b7f007bedafb
1/*
2 * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26#include "jaccesswalker.h"
27#include "AccessInfo.h"
28
29HWND ourHwnd;
30HWND topLevelWindow;
31int depth = -1;
32FILE *logfile;
33HMENU popupMenu;
34
35char theJaccesswalkerClassName[] = "JaccesswalkerWin";
36char theAccessInfoClassName[] = "AccessInfoWin";
37
38HWND theJaccesswalkerWindow;
39HWND theTreeControlWindow;
40HINSTANCE theInstance;
41Jaccesswalker *theJaccesswalker;
42AccessibleNode *theSelectedNode;
43AccessibleNode *thePopupNode;
44AccessibleContext theSelectedAccessibleContext;
45HWND hwndTV;    // handle of tree-view control
46
47int APIENTRY WinMain(HINSTANCE hInstance,
48                     HINSTANCE hPrevInstance,
49                     LPSTR     lpCmdLine,
50                     int       nCmdShow)
51{
52
53    if (logfile == null) {
54        logfile = fopen(JACCESSWALKER_LOG, "w"); // overwrite existing log file
55        logString(logfile, "Starting jaccesswalker.exe %s\n", getTimeAndDate());
56    }
57
58    theInstance = hInstance;
59
60    // start Jaccesswalker
61    theJaccesswalker = new Jaccesswalker(nCmdShow);
62
63    return 0;
64}
65
66Jaccesswalker::Jaccesswalker(int nCmdShow) {
67
68    HWND hwnd;
69    static char szAppName[] = "jaccesswalker";
70    static char szMenuName[] = "JACCESSWALKERMENU";
71    MSG msg;
72    WNDCLASSEX wc;
73
74    // jaccesswalker window
75    wc.cbSize = sizeof(wc);
76    wc.style = CS_HREDRAW | CS_VREDRAW;
77    wc.lpfnWndProc = WinProc;
78    wc.cbClsExtra = 0;
79    wc.cbWndExtra = 0;
80    wc.hInstance = theInstance;
81    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
82    wc.hCursor = LoadCursor(NULL, IDI_APPLICATION);
83    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
84    wc.lpszMenuName = szMenuName;
85    wc.lpszClassName = szAppName;
86    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
87
88    RegisterClassEx(&wc);
89
90    // AccessInfo Window
91    wc.cbSize = sizeof(WNDCLASSEX);
92
93    wc.hInstance = theInstance;
94    wc.lpszClassName = theAccessInfoClassName;
95    wc.lpfnWndProc = (WNDPROC)AccessInfoWindowProc;
96    wc.style = 0;
97
98    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
99    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
100    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
101
102    wc.lpszMenuName = "";
103    wc.cbClsExtra = 0;
104    wc.cbWndExtra = 0;
105
106    wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
107
108    RegisterClassEx(&wc);
109
110    // create the jaccesswalker window
111    hwnd = CreateWindow(szAppName,
112                        szAppName,
113                        WS_OVERLAPPEDWINDOW,
114                        CW_USEDEFAULT,
115                        CW_USEDEFAULT,
116                        CW_USEDEFAULT,
117                        CW_USEDEFAULT,
118                        NULL,
119                        NULL,
120                        theInstance,
121                        NULL);
122
123    ourHwnd = hwnd;
124
125    /* Initialize the common controls. */
126    INITCOMMONCONTROLSEX cc;
127    cc.dwSize = sizeof(INITCOMMONCONTROLSEX);
128    cc.dwICC = ICC_TREEVIEW_CLASSES;
129    InitCommonControlsEx(&cc);
130
131    ShowWindow(hwnd, nCmdShow);
132
133    UpdateWindow(hwnd);
134
135    BOOL result = initializeAccessBridge();
136    if (result != FALSE) {
137        while (GetMessage(&msg, NULL, 0, 0)) {
138            TranslateMessage(&msg);
139            DispatchMessage(&msg);
140        }
141        shutdownAccessBridge();
142    }
143}
144
145/*
146 * the jaccesswalker window proc
147 */
148LRESULT CALLBACK WinProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) {
149
150    int command;
151    short width, height;
152
153    switch(iMsg) {
154
155    case WM_CREATE:
156        // create the accessibility tree view
157        theTreeControlWindow = CreateATreeView(hwnd);
158
159        // load the popup menu
160        popupMenu = LoadMenu(theInstance, "PopupMenu");
161        popupMenu = GetSubMenu(popupMenu, 0);
162        break;
163
164    case WM_CLOSE:
165        EndDialog(hwnd, TRUE);
166        PostQuitMessage (0);
167        break;
168
169    case WM_SIZE:
170        width = LOWORD(lParam);
171        height = HIWORD(lParam);
172        SetWindowPos(theTreeControlWindow, NULL, 0, 0, width, height, 0);
173        return FALSE;  // let windows finish handling this
174
175    case WM_COMMAND:
176        command = LOWORD(wParam);
177        switch(command) {
178
179        case cExitMenuItem:
180            EndDialog(hwnd, TRUE);
181            PostQuitMessage (0);
182            break;
183
184        case cRefreshTreeItem:
185            // update the accessibility tree
186            theJaccesswalker->buildAccessibilityTree();
187            break;
188
189        case cAPIMenuItem:
190            // open a new window with the Accessibility API in it for the
191            // selected element in the tree
192            if (theSelectedNode != (AccessibleNode *) 0) {
193                theSelectedNode->displayAPIWindow();
194            }
195            break;
196
197        case cAPIPopupItem:
198            // open a new window with the Accessibility API in it for the
199            // element in the tree adjacent to the popup menu
200            if (thePopupNode != (AccessibleNode *) 0) {
201                thePopupNode->displayAPIWindow();
202            }
203            break;
204
205        }
206        break;
207
208    case WM_NOTIFY:  // receive tree messages
209
210        NMTREEVIEW *nmptr = (LPNMTREEVIEW) lParam;
211        switch (nmptr->hdr.code) {
212
213        case TVN_SELCHANGED:
214            // get the selected tree node
215            theSelectedNode = (AccessibleNode *) nmptr->itemNew.lParam;
216            break;
217
218        case NM_RCLICK:
219
220            // display a popup menu over the tree node
221            POINT p;
222            GetCursorPos(&p);
223            TrackPopupMenu(popupMenu, 0, p.x, p.y, 0, hwnd, NULL);
224
225            // get the tree node under the popup menu
226            TVHITTESTINFO hitinfo;
227            ScreenToClient(theTreeControlWindow, &p);
228            hitinfo.pt = p;
229            HTREEITEM node = TreeView_HitTest(theTreeControlWindow, &hitinfo);
230
231            if (node != null) {
232                TVITEMEX tvItem;
233                tvItem.hItem = node;
234                if (TreeView_GetItem(hwndTV, &tvItem) == TRUE) {
235                    thePopupNode = (AccessibleNode *)tvItem.lParam;
236                }
237            }
238            break;
239        }
240    }
241    return DefWindowProc(hwnd, iMsg, wParam, lParam);
242}
243
244/*
245 * Accessibility information window proc
246 */
247LRESULT CALLBACK AccessInfoWindowProc(HWND hWnd, UINT message,
248                                    UINT wParam, LONG lParam) {
249    short width, height;
250    HWND dlgItem;
251
252    switch (message) {
253    case WM_CREATE:
254        RECT rcClient;    // dimensions of client area
255        HWND hwndEdit;    // handle of tree-view control
256
257        // Get the dimensions of the parent window's client area,
258        // and create the edit control.
259        GetClientRect(hWnd, &rcClient);
260        hwndEdit = CreateWindow("Edit",
261                                "",
262                                WS_VISIBLE | WS_TABSTOP | WS_CHILD |
263                                ES_MULTILINE | ES_AUTOVSCROLL |
264                                ES_READONLY | WS_VSCROLL,
265                                0, 0, rcClient.right, rcClient.bottom,
266                                hWnd,
267                                (HMENU) cAccessInfoText,
268                                theInstance,
269                                NULL);
270        break;
271
272    case WM_CLOSE:
273        DestroyWindow(hWnd);
274        break;
275
276    case WM_SIZE:
277        width = LOWORD(lParam);
278        height = HIWORD(lParam);
279        dlgItem = GetDlgItem(hWnd, cAccessInfoText);
280        SetWindowPos(dlgItem, NULL, 0, 0, width, height, 0);
281        return FALSE;  // let windows finish handling this
282        break;
283
284    default:
285        return DefWindowProc(hWnd, message, wParam, lParam);
286    }
287
288    return 0;
289}
290
291/**
292 * Build a tree (and the treeview control) of all accessible Java components
293 *
294 */
295void Jaccesswalker::buildAccessibilityTree() {
296    TreeView_DeleteAllItems (theTreeControlWindow);
297    // have MS-Windows call EnumWndProc() with all of the top-level windows
298    EnumWindows((WNDENUMPROC) EnumWndProc, NULL);
299}
300
301/**
302 * Create (and display) the accessible component nodes of a parent AccessibleContext
303 *
304 */
305BOOL CALLBACK EnumWndProc(HWND hwnd, LPARAM lParam) {
306    if (IsJavaWindow(hwnd)) {
307        long vmID;
308        AccessibleContext ac;
309        if (GetAccessibleContextFromHWND(hwnd, &vmID, &ac) == TRUE) {
310            theJaccesswalker->addComponentNodes(vmID, ac, (AccessibleNode *) NULL,
311                                         hwnd, TVI_ROOT, theTreeControlWindow);
312        }
313        topLevelWindow = hwnd;
314    } else {
315        char szClass [MAX_PATH] = {0};
316        ::GetClassNameA(hwnd, szClass, sizeof(szClass) - 1);
317        if ( ( 0 == ::strcmp(szClass, "IEFrame") )
318             || ( 0 == ::strcmp(szClass, "MozillaUIWindowClass") ) ) {
319            EnumChildWindows(hwnd, (WNDENUMPROC) EnumChildProc, NULL);
320        }
321    }
322    return TRUE;
323}
324
325/*
326Detects whether or not the specified Java window is one from which no useable
327information can be obtained.
328
329This function tests for various scenarios I have seen in Java applets where the
330Java applet had no meaningful accessible information.  It does not detect all
331scenarios, just the most common ones.
332*/
333BOOL IsInaccessibleJavaWindow(const HWND hwnd)
334{
335    BOOL ret_val ( FALSE );
336    {
337        BOOL bT ( FALSE );
338        long vmIdWindow ( 0 );
339        AccessibleContext acWindow ( 0 );
340        bT = GetAccessibleContextFromHWND(hwnd, &vmIdWindow, &acWindow);
341        if ( ( bT ) && ( 0 != vmIdWindow ) && ( 0 != acWindow ) ) {
342            AccessibleContextInfo infoWindow = {0};
343            bT = GetAccessibleContextInfo(vmIdWindow, acWindow, &infoWindow);
344            if ( ( bT )
345                 && ( 0 == infoWindow.name [0] )
346                 && ( 0 == infoWindow.description [0] )
347                 && ( 0 == ::wcscmp(infoWindow.role_en_US, L"frame") ) ) {
348                if ( 0 == infoWindow.childrenCount ) {
349                    ret_val = TRUE;
350                } else if ( 1 == infoWindow.childrenCount ) {
351                    AccessibleContext acChild ( 0 );
352                    acChild =
353                        GetAccessibleChildFromContext(vmIdWindow, acWindow, 0);
354                    if ( NULL != acChild ) {
355                        AccessibleContextInfo infoChild = {0};
356                        bT = GetAccessibleContextInfo( vmIdWindow, acChild,
357                                                       &infoChild );
358                        if ( ( bT )
359                             && ( 0 == infoChild.name [0] )
360                             && ( 0 == infoChild.description [0] )
361                             && ( 0 == ::wcscmp(infoChild.role_en_US, L"panel") )
362                             && ( 1 == infoChild.childrenCount ) ) {
363                            AccessibleContext acChild1 ( 0 );
364                            acChild1 = GetAccessibleChildFromContext( vmIdWindow,
365                                                                      acChild, 0);
366                            if ( NULL != acChild1 ) {
367                                AccessibleContextInfo infoChild1 = {0};
368                                bT = GetAccessibleContextInfo( vmIdWindow,
369                                                               acChild1, &infoChild1 );
370                                if ( ( bT )
371                                     && ( 0 == infoChild1.name [0] )
372                                     && ( 0 == infoChild1.description [0] )
373                                     && ( 0 == ::wcscmp(infoChild1.role_en_US, L"frame") )
374                                     && ( 0 == infoChild1.childrenCount ) ) {
375                                    ret_val = TRUE;
376                                } else if ( ( bT )
377                                            && ( 0 == infoChild1.name [0] )
378                                            && ( 0 == infoChild1.description [0] )
379                                            && ( 0 == ::wcscmp( infoChild1.role_en_US,
380                                                                L"panel") )
381                                            && ( 1 == infoChild1.childrenCount ) ) {
382                                    AccessibleContext acChild2 ( 0 );
383                                    acChild2 = GetAccessibleChildFromContext(
384                                                    vmIdWindow, acChild1, 0 );
385                                    if ( NULL != acChild2 ) {
386                                        AccessibleContextInfo infoChild2 = {0};
387                                        bT = GetAccessibleContextInfo(
388                                                vmIdWindow, acChild2, &infoChild2 );
389                                        if ( ( bT )
390                                             && ( 0 == infoChild2.name [0] )
391                                             && ( 0 == infoChild2.description [0] )
392                                             && ( 0 == ::wcscmp( infoChild2.role_en_US,
393                                                                 L"frame") )
394                                             && ( 0 == infoChild2.childrenCount ) ) {
395                                            ret_val = TRUE;
396                                        }
397                                    }
398                                }
399                            }
400                        } else if ( ( bT )
401                                    && ( 0 == infoChild.name [0] )
402                                    && ( 0 == infoChild.description [0] )
403                                    && ( 0 == ::wcscmp( infoChild.role_en_US,
404                                                        L"canvas") )
405                                    && ( 0 == infoChild.childrenCount ) ) {
406                            ret_val = TRUE;
407                        }
408                    }
409                }
410            } else if ( ( bT )
411                        && ( 0 == infoWindow.name [0] )
412                        && ( 0 == infoWindow.description [0] )
413                        && ( 0 == ::wcscmp(infoWindow.role_en_US, L"panel") ) ) {
414                if ( 1 == infoWindow.childrenCount ) {
415                    AccessibleContext acChild ( 0 );
416                    acChild = GetAccessibleChildFromContext( vmIdWindow,
417                                                             acWindow, 0 );
418                    if ( NULL != acChild ) {
419                        AccessibleContextInfo infoChild = {0};
420                        bT = GetAccessibleContextInfo( vmIdWindow,
421                                                       acChild, &infoChild );
422                        if ( ( bT )
423                             && ( 0 == infoChild.name [0] )
424                             && ( 0 == infoChild.description [0] )
425                             && ( 0 == ::wcscmp(infoChild.role_en_US, L"frame") )
426                             && ( 0 == infoChild.childrenCount ) ) {
427                                ret_val = TRUE;
428                        } else if ( ( bT )
429                                    && ( 0 == infoChild.name [0] )
430                                    && ( 0 == infoChild.description [0] )
431                                    && ( 0 == ::wcscmp( infoChild.role_en_US,
432                                                        L"panel") )
433                                    && ( 1 == infoChild.childrenCount ) ) {
434                            AccessibleContext acChild1 ( 0 );
435                            acChild1 = GetAccessibleChildFromContext( vmIdWindow,
436                                                                      acChild, 0);
437                            if ( NULL != acChild1 ) {
438                                AccessibleContextInfo infoChild1 = {0};
439                                bT = GetAccessibleContextInfo( vmIdWindow,
440                                                               acChild1,
441                                                               &infoChild1 );
442                                if ( ( bT )
443                                     && ( 0 == infoChild1.name [0] )
444                                     && ( 0 == infoChild1.description [0] )
445                                     && ( 0 == ::wcscmp( infoChild1.role_en_US,
446                                                         L"frame") )
447                                     && ( 0 == infoChild1.childrenCount ) ) {
448                                    ret_val = TRUE;
449                                }
450                            }
451                        }
452                    }
453                }
454            }
455        } else if ( FALSE == bT ) {
456            ret_val = TRUE;
457        }
458    }
459    return ret_val;
460}
461
462BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lParam)
463{
464    if ( ( IsJavaWindow(hwnd) )
465         && ( FALSE == IsInaccessibleJavaWindow(hwnd) ) ) {
466        long vmID ( 0 );
467        AccessibleContext ac ( 0 );
468        if ( TRUE == GetAccessibleContextFromHWND(hwnd, &vmID, &ac) ) {
469            theJaccesswalker->addComponentNodes(
470                vmID, ac, (AccessibleNode *) NULL,
471                hwnd, TVI_ROOT, theTreeControlWindow);
472        }
473        topLevelWindow = hwnd;
474    } else {
475        EnumChildWindows(hwnd, (WNDENUMPROC) EnumChildProc, NULL);
476    }
477    return TRUE;
478}
479
480// CreateATreeView - creates a tree-view control.
481// Returns the handle of the new control if successful or NULL
482//     otherwise.
483// hwndParent - handle of the control's parent window
484HWND CreateATreeView(HWND hwndParent) {
485    RECT rcClient;  // dimensions of client area
486
487    // Get the dimensions of the parent window's client area, and create
488    // the tree-view control.
489    GetClientRect(hwndParent, &rcClient);
490    hwndTV = CreateWindow(WC_TREEVIEW,
491                          "",
492                          WS_VISIBLE | WS_TABSTOP | WS_CHILD |
493                          TVS_HASLINES | TVS_HASBUTTONS |
494                          TVS_LINESATROOT,
495                          0, 0, rcClient.right, rcClient.bottom,
496                          hwndParent,
497                          (HMENU) cTreeControl,
498                          theInstance,
499                          NULL);
500
501    return hwndTV;
502}
503
504/**
505 * Create (and display) the accessible component nodes of a parent AccessibleContext
506 *
507 */
508void Jaccesswalker::addComponentNodes(long vmID, AccessibleContext context,
509                                    AccessibleNode *parent, HWND hwnd,
510                                    HTREEITEM treeNodeParent, HWND treeWnd) {
511
512    AccessibleNode *newNode = new AccessibleNode( vmID, context, parent, hwnd,
513                                                  treeNodeParent );
514
515    AccessibleContextInfo info;
516    if (GetAccessibleContextInfo(vmID, context, &info) != FALSE) {
517        char s[LINE_BUFSIZE];
518
519        wsprintf(s, "%ls", info.name);
520        newNode->setAccessibleName(s);
521        wsprintf(s, "%ls", info.role);
522        newNode->setAccessibleRole(s);
523
524        wsprintf(s, "%ls [%ls]", info.name, info.role);
525
526        TVITEM tvi;
527        tvi.mask = TVIF_PARAM | TVIF_TEXT;
528        tvi.pszText = (char *) s; // Accessible name and role
529        tvi.cchTextMax = (int)strlen(s);
530        tvi.lParam = (long) newNode; // Accessibility information
531
532        TVINSERTSTRUCT tvis;
533        tvis.hParent = treeNodeParent;
534        tvis.hInsertAfter = TVI_LAST;
535        tvis.item = tvi;
536
537        HTREEITEM treeNodeItem = TreeView_InsertItem(treeWnd, &tvis);
538
539        for (int i = 0; i < info.childrenCount; i++) {
540            addComponentNodes(vmID, GetAccessibleChildFromContext(vmID, context, i),
541                              newNode, hwnd, treeNodeItem, treeWnd);
542        }
543    } else {
544        char s[LINE_BUFSIZE];
545        sprintf( s,
546            "ERROR calling GetAccessibleContextInfo; vmID = %X, context = %X",
547            vmID, context );
548
549        TVITEM tvi;
550        tvi.mask = TVIF_PARAM | TVIF_TEXT;  // text and lParam are only valid parts
551        tvi.pszText = (char *) s;
552        tvi.cchTextMax = (int)strlen(s);
553        tvi.lParam = (long) newNode;
554
555        TVINSERTSTRUCT tvis;
556        tvis.hParent = treeNodeParent;
557        tvis.hInsertAfter = TVI_LAST;  // make tree in order given
558        tvis.item = tvi;
559
560        HTREEITEM treeNodeItem = TreeView_InsertItem(treeWnd, &tvis);
561    }
562}
563
564// -----------------------------
565
566/**
567 * Create an AccessibleNode
568 *
569 */
570AccessibleNode::AccessibleNode(long JavaVMID, AccessibleContext context,
571                               AccessibleNode *parent, HWND hwnd,
572                               HTREEITEM parentTreeNodeItem) {
573    vmID = JavaVMID;
574    ac = context;
575    parentNode = parent;
576    baseHWND = hwnd;
577    treeNodeParent = parentTreeNodeItem;
578
579    // setting accessibleName and accessibleRole not done here,
580    // in order to minimize calls to the AccessBridge
581    // (since such a call is needed to enumerate children)
582}
583
584/**
585 * Destroy an AccessibleNode
586 *
587 */
588AccessibleNode::~AccessibleNode() {
589    ReleaseJavaObject(vmID, ac);
590}
591
592/**
593 * Set the accessibleName string
594 *
595 */
596void AccessibleNode::setAccessibleName(char *name) {
597    strncpy(accessibleName, name, MAX_STRING_SIZE);
598}
599
600/**
601 * Set the accessibleRole string
602 *
603 */
604void AccessibleNode::setAccessibleRole(char *role) {
605    strncpy(accessibleRole, role, SHORT_STRING_SIZE);
606}
607
608
609
610
611
612
613
614/**
615 * Create an API window to show off the info for this AccessibleContext
616 */
617BOOL AccessibleNode::displayAPIWindow() {
618
619    HWND apiWindow = CreateWindow(theAccessInfoClassName,
620                                  "Java Accessibility API view",
621                                  WS_OVERLAPPEDWINDOW,
622                                  CW_USEDEFAULT,
623                                  CW_USEDEFAULT,
624                                  600,
625                                  750,
626                                  HWND_DESKTOP,
627                                  NULL,
628                                  theInstance,
629                                  (void *) NULL);
630
631    if (!apiWindow) {
632        printError("cannot create API window");
633        return FALSE;
634    }
635
636    char buffer[HUGE_BUFSIZE];
637    buffer[0] = '\0';
638    getAccessibleInfo(vmID, ac, buffer, sizeof(buffer));
639    displayAndLog(apiWindow, cAccessInfoText, logfile, buffer);
640
641    ShowWindow(apiWindow, SW_SHOWNORMAL);
642    UpdateWindow(apiWindow);
643
644    return TRUE;
645}
646
647
648
649