1 /*
2 * Copyright (c) 2005, 2016, 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
29 HWND ourHwnd;
30 HWND topLevelWindow;
31 int depth = -1;
32 FILE *logfile;
33 HMENU popupMenu;
34
35 char theJaccesswalkerClassName[] = "JaccesswalkerWin";
36 char theAccessInfoClassName[] = "AccessInfoWin";
37
38 HWND theJaccesswalkerWindow;
39 HWND theTreeControlWindow;
40 HINSTANCE theInstance;
41 Jaccesswalker *theJaccesswalker;
42 AccessibleNode *theSelectedNode;
43 AccessibleNode *thePopupNode;
44 AccessibleContext theSelectedAccessibleContext;
45 HWND hwndTV; // handle of tree-view control
46
WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)47 int 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
Jaccesswalker(int nCmdShow)66 Jaccesswalker::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 */
WinProc(HWND hwnd,UINT iMsg,WPARAM wParam,LPARAM lParam)148 LRESULT 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 */
AccessInfoWindowProc(HWND hWnd,UINT message,UINT wParam,LONG lParam)247 LRESULT 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 */
buildAccessibilityTree()295 void 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 */
EnumWndProc(HWND hwnd,LPARAM lParam)305 BOOL 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 /*
326 Detects whether or not the specified Java window is one from which no useable
327 information can be obtained.
328
329 This function tests for various scenarios I have seen in Java applets where the
330 Java applet had no meaningful accessible information. It does not detect all
331 scenarios, just the most common ones.
332 */
IsInaccessibleJavaWindow(const HWND hwnd)333 BOOL 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
EnumChildProc(HWND hwnd,LPARAM lParam)462 BOOL 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
CreateATreeView(HWND hwndParent)484 HWND 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 */
addComponentNodes(long vmID,AccessibleContext context,AccessibleNode * parent,HWND hwnd,HTREEITEM treeNodeParent,HWND treeWnd)508 void 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 = (LPARAM) 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 = %p",
547 vmID, (void*)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 = (LPARAM) 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 */
AccessibleNode(long JavaVMID,AccessibleContext context,AccessibleNode * parent,HWND hwnd,HTREEITEM parentTreeNodeItem)570 AccessibleNode::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 */
~AccessibleNode()588 AccessibleNode::~AccessibleNode() {
589 ReleaseJavaObject(vmID, ac);
590 }
591
592 /**
593 * Set the accessibleName string
594 *
595 */
setAccessibleName(char * name)596 void AccessibleNode::setAccessibleName(char *name) {
597 strncpy(accessibleName, name, MAX_STRING_SIZE);
598 }
599
600 /**
601 * Set the accessibleRole string
602 *
603 */
setAccessibleRole(char * role)604 void 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 */
displayAPIWindow()617 BOOL 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