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