VirtualBox

Changeset 4266 in vbox


Ignore:
Timestamp:
Aug 21, 2007 3:45:55 PM (17 years ago)
Author:
vboxsync
Message:

Reapplied 23773

Location:
trunk/src/VBox/Additions/WINNT/VBoxService
Files:
3 added
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/Additions/WINNT/VBoxService/Makefile.kmk

    r4264 r4266  
    3030        VBoxSeamless.cpp \
    3131        VBoxClipboard.cpp \
     32        VBoxDisplay.cpp \
    3233        helpers.cpp \
    3334        VBoxService.rc
  • trunk/src/VBox/Additions/WINNT/VBoxService/VBoxService.cpp

    r4264 r4266  
    1717#include "VBoxService.h"
    1818#include "VBoxSeamless.h"
     19#include "VBoxClipboard.h"
     20#include "VBoxDisplay.h"
    1921#include <VBoxHook.h>
    2022#include "resource.h"
     
    6870
    6971
    70 /* The shared clipboard service prototypes. */
    71 int                VBoxClipboardInit    (const VBOXSERVICEENV *pEnv, void **ppInstance, bool *pfStartThread);
    72 unsigned __stdcall VBoxClipboardThread  (void *pInstance);
    73 void               VBoxClipboardDestroy (const VBOXSERVICEENV *pEnv, void *pInstance);
    74 
    7572/* The service table. */
    7673static VBOXSERVICEINFO vboxServiceTable[] =
    7774{
     75    {
     76        "Display",
     77        VBoxDisplayInit,
     78        VBoxDisplayThread,
     79        VBoxDisplayDestroy,
     80    },
    7881    {
    7982        "Shared Clipboard",
     
    296299    }
    297300
    298     /* create display change thread */
    299     HANDLE hDisplayChangeThread;
    300     if (status == NO_ERROR)
    301     {
    302         hDisplayChangeThread = (HANDLE)_beginthread(DisplayChangeThread, 0, NULL);
    303         if ((int)hDisplayChangeThread == -1L)
    304             status = ERROR_GEN_FAILURE;
    305     }
    306 
    307     dprintf(("VBoxService: hDisplayChangeThread h %p, st %p\n", hDisplayChangeThread, status));
    308 
    309301    /* terminate service if something went wrong */
    310302    if (status != NO_ERROR)
     
    380372    dprintf(("VBoxService: waiting for display change thread...\n"));
    381373
    382     /* wait for the display change thread to terminate */
    383     WaitForSingleObject(hDisplayChangeThread, INFINITE);
    384 
    385374    vboxStopServices (&svcEnv, vboxServiceTable);
    386375
     
    412401}
    413402
    414 static bool isVBoxDisplayDriverActive (void)
    415 {
    416     bool result = false;
    417 
    418     DISPLAY_DEVICE dispDevice;
    419 
    420     FillMemory(&dispDevice, sizeof(DISPLAY_DEVICE), 0);
    421 
    422     dispDevice.cb = sizeof(DISPLAY_DEVICE);
    423 
    424     INT devNum = 0;
    425 
    426     while (EnumDisplayDevices(NULL,
    427                               devNum,
    428                               &dispDevice,
    429                               0))
    430     {
    431         DDCLOG(("DevNum:%d\nName:%s\nString:%s\nID:%s\nKey:%s\nFlags=%08X\n\n",
    432                       devNum,
    433                       &dispDevice.DeviceName[0],
    434                       &dispDevice.DeviceString[0],
    435                       &dispDevice.DeviceID[0],
    436                       &dispDevice.DeviceKey[0],
    437                       dispDevice.StateFlags));
    438 
    439         if (dispDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
    440         {
    441             DDCLOG(("Primary device.\n"));
    442 
    443             if (strcmp(&dispDevice.DeviceString[0], "VirtualBox Graphics Adapter") == 0)
    444             {
    445                 DDCLOG(("VBox display driver is active.\n"));
    446                 result = true;
    447             }
    448 
    449             break;
    450         }
    451 
    452         FillMemory(&dispDevice, sizeof(DISPLAY_DEVICE), 0);
    453 
    454         dispDevice.cb = sizeof(DISPLAY_DEVICE);
    455 
    456         devNum++;
    457     }
    458 
    459     return result;
    460 }
    461 
    462 /* ChangeDisplaySettingsEx does not exist in NT. ResizeDisplayDevice uses the function. */
    463 typedef LONG WINAPI defChangeDisplaySettingsEx(LPCTSTR lpszDeviceName, LPDEVMODE lpDevMode, HWND hwnd, DWORD dwflags, LPVOID lParam);
    464 static defChangeDisplaySettingsEx *pChangeDisplaySettingsEx = NULL;
    465 
    466 /* Returns TRUE to try again. */
    467 static BOOL ResizeDisplayDevice(ULONG Id, DWORD Width, DWORD Height, DWORD BitsPerPixel)
    468 {
    469     BOOL fModeReset = (Width == 0 && Height == 0 && BitsPerPixel == 0);
    470    
    471     DISPLAY_DEVICE DisplayDevice;
    472 
    473     ZeroMemory(&DisplayDevice, sizeof(DisplayDevice));
    474     DisplayDevice.cb = sizeof(DisplayDevice);
    475    
    476     /* Find out how many display devices the system has */
    477     DWORD NumDevices = 0;
    478     DWORD i = 0;
    479     while (EnumDisplayDevices (NULL, i, &DisplayDevice, 0))
    480     {
    481         DDCLOG(("[%d] %s\n", i, DisplayDevice.DeviceName));
    482 
    483         if (DisplayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
    484         {
    485             DDCLOG(("Found primary device. err %d\n", GetLastError ()));
    486             NumDevices++;
    487         }
    488         else if (!(DisplayDevice.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER))
    489         {
    490            
    491             DDCLOG(("Found secondary device. err %d\n", GetLastError ()));
    492             NumDevices++;
    493         }
    494        
    495         ZeroMemory(&DisplayDevice, sizeof(DisplayDevice));
    496         DisplayDevice.cb = sizeof(DisplayDevice);
    497         i++;
    498     }
    499    
    500     DDCLOG(("Found total %d devices. err %d\n", NumDevices, GetLastError ()));
    501    
    502     if (NumDevices == 0 || Id >= NumDevices)
    503     {
    504         DDCLOG(("Requested identifier %d is invalid. err %d\n", Id, GetLastError ()));
    505         return FALSE;
    506     }
    507    
    508     DISPLAY_DEVICE *paDisplayDevices = (DISPLAY_DEVICE *)alloca (sizeof (DISPLAY_DEVICE) * NumDevices);
    509     DEVMODE *paDeviceModes = (DEVMODE *)alloca (sizeof (DEVMODE) * NumDevices);
    510     RECTL *paRects = (RECTL *)alloca (sizeof (RECTL) * NumDevices);
    511    
    512     /* Fetch information about current devices and modes. */
    513     DWORD DevNum = 0;
    514     DWORD DevPrimaryNum = 0;
    515    
    516     ZeroMemory(&DisplayDevice, sizeof(DISPLAY_DEVICE));
    517     DisplayDevice.cb = sizeof(DISPLAY_DEVICE);
    518    
    519     i = 0;
    520     while (EnumDisplayDevices (NULL, i, &DisplayDevice, 0))
    521     {
    522         DDCLOG(("[%d(%d)] %s\n", i, DevNum, DisplayDevice.DeviceName));
    523        
    524         BOOL bFetchDevice = FALSE;
    525 
    526         if (DisplayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
    527         {
    528             DDCLOG(("Found primary device. err %d\n", GetLastError ()));
    529             DevPrimaryNum = DevNum;
    530             bFetchDevice = TRUE;
    531         }
    532         else if (!(DisplayDevice.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER))
    533         {
    534            
    535             DDCLOG(("Found secondary device. err %d\n", GetLastError ()));
    536             bFetchDevice = TRUE;
    537         }
    538        
    539         if (bFetchDevice)
    540         {
    541             if (DevNum >= NumDevices)
    542             {
    543                 DDCLOG(("%d >= %d\n", NumDevices, DevNum));
    544                 return FALSE;
    545             }
    546        
    547             paDisplayDevices[DevNum] = DisplayDevice;
    548            
    549             ZeroMemory(&paDeviceModes[DevNum], sizeof(DEVMODE));
    550             paDeviceModes[DevNum].dmSize = sizeof(DEVMODE);
    551             if (!EnumDisplaySettings((LPSTR)DisplayDevice.DeviceName,
    552                  ENUM_REGISTRY_SETTINGS, &paDeviceModes[DevNum]))
    553             {
    554                 DDCLOG(("EnumDisplaySettings err %d\n", GetLastError ()));
    555                 return FALSE;
    556             }
    557            
    558             DDCLOG(("%dx%d at %d,%d\n",
    559                     paDeviceModes[DevNum].dmPelsWidth,
    560                     paDeviceModes[DevNum].dmPelsHeight,
    561                     paDeviceModes[DevNum].dmPosition.x,
    562                     paDeviceModes[DevNum].dmPosition.y));
    563                    
    564             paRects[DevNum].left   = paDeviceModes[DevNum].dmPosition.x;
    565             paRects[DevNum].top    = paDeviceModes[DevNum].dmPosition.y;
    566             paRects[DevNum].right  = paDeviceModes[DevNum].dmPosition.x + paDeviceModes[DevNum].dmPelsWidth;
    567             paRects[DevNum].bottom = paDeviceModes[DevNum].dmPosition.y + paDeviceModes[DevNum].dmPelsHeight;
    568             DevNum++;
    569         }
    570        
    571         ZeroMemory(&DisplayDevice, sizeof(DISPLAY_DEVICE));
    572         DisplayDevice.cb = sizeof(DISPLAY_DEVICE);
    573         i++;
    574     }
    575    
    576     if (Width == 0)
    577     {
    578         Width = paRects[Id].right - paRects[Id].left;
    579     }
    580 
    581     if (Height == 0)
    582     {
    583         Height = paRects[Id].bottom - paRects[Id].top;
    584     }
    585 
    586     /* Check whether a mode reset or a change is requested. */
    587     if (   !fModeReset
    588         && paRects[Id].right - paRects[Id].left == Width
    589         && paRects[Id].bottom - paRects[Id].top == Height
    590         && paDeviceModes[Id].dmBitsPerPel == BitsPerPixel)
    591     {
    592         DDCLOG(("VBoxService: already at desired resolution.\n"));
    593         return FALSE;
    594     }
    595 
    596     resizeRect(paRects, NumDevices, DevPrimaryNum, Id, Width, Height);
    597 #ifdef DDCLOG
    598     for (i = 0; i < NumDevices; i++)
    599     {
    600         DDCLOG(("[%d]: %d,%d %dx%d\n",
    601                 i, paRects[i].left, paRects[i].top,
    602                 paRects[i].right - paRects[i].left,
    603                 paRects[i].bottom - paRects[i].top));
    604     }
    605 #endif /* DDCLOG */
    606    
    607     /* Without this, Windows will not ask the miniport for its
    608      * mode table but uses an internal cache instead.
    609      */
    610     DEVMODE tempDevMode;
    611     ZeroMemory (&tempDevMode, sizeof (tempDevMode));
    612     tempDevMode.dmSize = sizeof(DEVMODE);
    613     EnumDisplaySettings(NULL, 0xffffff, &tempDevMode);
    614 
    615     /* Assign the new rectangles to displays. */
    616     for (i = 0; i < NumDevices; i++)
    617     {
    618         paDeviceModes[i].dmPosition.x = paRects[i].left;
    619         paDeviceModes[i].dmPosition.y = paRects[i].top;
    620         paDeviceModes[i].dmPelsWidth  = paRects[i].right - paRects[i].left;
    621         paDeviceModes[i].dmPelsHeight = paRects[i].bottom - paRects[i].top;
    622        
    623         paDeviceModes[i].dmFields = DM_POSITION | DM_PELSHEIGHT | DM_PELSWIDTH;
    624        
    625         if (   i == Id
    626             && BitsPerPixel != 0)
    627         {
    628             paDeviceModes[i].dmFields |= DM_BITSPERPEL;
    629             paDeviceModes[i].dmBitsPerPel = BitsPerPixel;
    630         }
    631        
    632         pChangeDisplaySettingsEx((LPSTR)paDisplayDevices[i].DeviceName,
    633                  &paDeviceModes[i], NULL, CDS_NORESET | CDS_UPDATEREGISTRY, NULL);
    634         DDCLOG(("ChangeDisplaySettings position err %d\n", GetLastError ()));
    635     }
    636    
    637     /* A second call to ChangeDisplaySettings updates the monitor. */
    638     LONG status = ChangeDisplaySettings(NULL, 0);
    639     DDCLOG(("ChangeDisplaySettings update status %d\n", status));
    640     if (status == DISP_CHANGE_SUCCESSFUL || status == DISP_CHANGE_BADMODE)
    641     {
    642         /* Successfully set new video mode or our driver can not set the requested mode. Stop trying. */
    643         return FALSE;
    644     }
    645 
    646     /* Retry the request. */
    647     return TRUE;
    648 }
    649 
    650 /**
    651  * Thread function to wait for and process display change
    652  * requests
    653  */
    654 VOID DisplayChangeThread(void *dummy)
    655 {
    656     bool fTerminate = false;
    657     VBoxGuestFilterMaskInfo maskInfo;
    658     DWORD cbReturned;
    659    
    660     HMODULE hUser = GetModuleHandle("USER32");
    661 
    662     if (hUser)
    663     {
    664         pChangeDisplaySettingsEx = (defChangeDisplaySettingsEx *)GetProcAddress(hUser, "ChangeDisplaySettingsExA");
    665         DDCLOG(("VBoxService: pChangeDisplaySettingsEx = %p\n", pChangeDisplaySettingsEx));
    666     }
    667 
    668     maskInfo.u32OrMask = VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST;
    669     maskInfo.u32NotMask = 0;
    670     if (DeviceIoControl (gVBoxDriver, IOCTL_VBOXGUEST_CTL_FILTER_MASK, &maskInfo, sizeof (maskInfo), NULL, 0, &cbReturned, NULL))
    671     {
    672         DDCLOG(("VBoxService: DeviceIOControl(CtlMask - or) succeeded\n"));
    673     }
    674     else
    675     {
    676         dprintf(("VBoxService: DeviceIOControl(CtlMask) failed, DisplayChangeThread exited\n"));
    677         return;
    678     }
    679 
    680     do
    681     {
    682         /* wait for a display change event */
    683         VBoxGuestWaitEventInfo waitEvent;
    684         waitEvent.u32TimeoutIn = 100;
    685         waitEvent.u32EventMaskIn = VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST;
    686         if (DeviceIoControl(gVBoxDriver, IOCTL_VBOXGUEST_WAITEVENT, &waitEvent, sizeof(waitEvent), &waitEvent, sizeof(waitEvent), &cbReturned, NULL))
    687         {
    688             DDCLOG(("VBoxService: DeviceIOControl succeded\n"));
    689 
    690             /* are we supposed to stop? */
    691             if (WaitForSingleObject(gStopSem, 0) == WAIT_OBJECT_0)
    692                 break;
    693 
    694             DDCLOG(("VBoxService: checking event\n"));
    695 
    696             /* did we get the right event? */
    697             if (waitEvent.u32EventFlagsOut & VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST)
    698             {
    699                 DDCLOG(("VBoxService: going to get display change information.\n"));
    700 
    701                 /* We got at least one event. Read the requested resolution
    702                  * and try to set it until success. New events will not be seen
    703                  * but a new resolution will be read in this poll loop.
    704                  */
    705                 for (;;)
    706                 {
    707                     /* get the display change request */
    708                     VMMDevDisplayChangeRequest2 displayChangeRequest = {0};
    709                     displayChangeRequest.header.size        = sizeof(VMMDevDisplayChangeRequest2);
    710                     displayChangeRequest.header.version     = VMMDEV_REQUEST_HEADER_VERSION;
    711                     displayChangeRequest.header.requestType = VMMDevReq_GetDisplayChangeRequest2;
    712                     displayChangeRequest.eventAck           = VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST;
    713                     BOOL fDisplayChangeQueried = DeviceIoControl(gVBoxDriver, IOCTL_VBOXGUEST_VMMREQUEST, &displayChangeRequest, sizeof(VMMDevDisplayChangeRequest2),
    714                                                                  &displayChangeRequest, sizeof(VMMDevDisplayChangeRequest2), &cbReturned, NULL);
    715                     if (!fDisplayChangeQueried)
    716                     {
    717                         /* Try the old version of the request for old VBox hosts. */
    718                         displayChangeRequest.header.size        = sizeof(VMMDevDisplayChangeRequest);
    719                         displayChangeRequest.header.version     = VMMDEV_REQUEST_HEADER_VERSION;
    720                         displayChangeRequest.header.requestType = VMMDevReq_GetDisplayChangeRequest;
    721                         displayChangeRequest.eventAck           = VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST;
    722                         fDisplayChangeQueried = DeviceIoControl(gVBoxDriver, IOCTL_VBOXGUEST_VMMREQUEST, &displayChangeRequest, sizeof(VMMDevDisplayChangeRequest),
    723                                                                  &displayChangeRequest, sizeof(VMMDevDisplayChangeRequest), &cbReturned, NULL);
    724                         displayChangeRequest.display = 0;
    725                     }
    726 
    727                     if (fDisplayChangeQueried)
    728                     {
    729                         DDCLOG(("VBoxService: VMMDevReq_GetDisplayChangeRequest2: %dx%dx%d at %d\n", displayChangeRequest.xres, displayChangeRequest.yres, displayChangeRequest.bpp, displayChangeRequest.display));
    730 
    731                         /* Horizontal resolution must be a multiple of 8, round down. */
    732                         displayChangeRequest.xres &= 0xfff8;
    733 
    734                         /*
    735                          * Only try to change video mode if the active display driver is VBox additions.
    736                          */
    737                         if (isVBoxDisplayDriverActive ())
    738                         {
    739                             if (pChangeDisplaySettingsEx != 0)
    740                             {
    741                                 /* W2K or later. */
    742                                 if (!ResizeDisplayDevice(displayChangeRequest.display,
    743                                                          displayChangeRequest.xres,
    744                                                          displayChangeRequest.yres,
    745                                                          displayChangeRequest.bpp))
    746                                 {
    747                                     break;
    748                                 }
    749                             }
    750                             else
    751                             {
    752                                 /* Single monitor NT. */
    753                                 DEVMODE devMode;
    754                                 memset (&devMode, 0, sizeof (devMode));
    755                                 devMode.dmSize = sizeof(DEVMODE);
    756 
    757                                 /* get the current screen setup */
    758                                 if (EnumDisplaySettings(NULL, ENUM_REGISTRY_SETTINGS, &devMode))
    759                                 {
    760                                     dprintf(("VBoxService: Current mode: %dx%dx%d at %d,%d\n", devMode.dmPelsWidth, devMode.dmPelsHeight, devMode.dmBitsPerPel, devMode.dmPosition.x, devMode.dmPosition.y));
    761 
    762                                     /* Check whether a mode reset or a change is requested. */
    763                                     if (displayChangeRequest.xres || displayChangeRequest.yres || displayChangeRequest.bpp)
    764                                     {
    765                                         /* A change is requested.
    766                                          * Set values which are not to be changed to the current values.
    767                                          */
    768                                         if (!displayChangeRequest.xres)
    769                                             displayChangeRequest.xres = devMode.dmPelsWidth;
    770                                         if (!displayChangeRequest.yres)
    771                                             displayChangeRequest.yres = devMode.dmPelsHeight;
    772                                         if (!displayChangeRequest.bpp)
    773                                             displayChangeRequest.bpp = devMode.dmBitsPerPel;
    774                                     }
    775                                     else
    776                                     {
    777                                         /* All zero values means a forced mode reset. Do nothing. */
    778                                     }
    779 
    780                                     /* Verify that the mode is indeed changed. */
    781                                     if (   devMode.dmPelsWidth  == displayChangeRequest.xres
    782                                         && devMode.dmPelsHeight == displayChangeRequest.yres
    783                                         && devMode.dmBitsPerPel == displayChangeRequest.bpp)
    784                                     {
    785                                         dprintf(("VBoxService: already at desired resolution.\n"));
    786                                         break;
    787                                     }
    788 
    789                                     // without this, Windows will not ask the miniport for its
    790                                     // mode table but uses an internal cache instead
    791                                     DEVMODE tempDevMode = {0};
    792                                     tempDevMode.dmSize = sizeof(DEVMODE);
    793                                     EnumDisplaySettings(NULL, 0xffffff, &tempDevMode);
    794 
    795                                     /* adjust the values that are supposed to change */
    796                                     if (displayChangeRequest.xres)
    797                                         devMode.dmPelsWidth  = displayChangeRequest.xres;
    798                                     if (displayChangeRequest.yres)
    799                                         devMode.dmPelsHeight = displayChangeRequest.yres;
    800                                     if (displayChangeRequest.bpp)
    801                                         devMode.dmBitsPerPel = displayChangeRequest.bpp;
    802 
    803                                     DDCLOG(("VBoxService: setting the new mode %dx%dx%d\n", devMode.dmPelsWidth, devMode.dmPelsHeight, devMode.dmBitsPerPel));
    804 
    805                                     /* set the new mode */
    806                                     LONG status = ChangeDisplaySettings(&devMode, CDS_UPDATEREGISTRY);
    807                                     if (status != DISP_CHANGE_SUCCESSFUL)
    808                                     {
    809                                         dprintf(("VBoxService: error from ChangeDisplaySettings: %d\n", status));
    810 
    811                                         if (status == DISP_CHANGE_BADMODE)
    812                                         {
    813                                             /* Our driver can not set the requested mode. Stop trying. */
    814                                             break;
    815                                         }
    816                                     }
    817                                     else
    818                                     {
    819                                         /* Successfully set new video mode. */
    820                                         break;
    821                                     }
    822                                 }
    823                                 else
    824                                 {
    825                                     dprintf(("VBoxService: error from EnumDisplaySettings: %d\n", GetLastError ()));
    826                                     break;
    827                                 }
    828                             }
    829                         }
    830                         else
    831                         {
    832                             dprintf(("VBoxService: vboxDisplayDriver is not active.\n"));
    833                         }
    834 
    835                         /* Retry the change a bit later. */
    836                         /* are we supposed to stop? */
    837                         if (WaitForSingleObject(gStopSem, 1000) == WAIT_OBJECT_0)
    838                         {
    839                             fTerminate = true;
    840                             break;
    841                         }
    842                     }
    843                     else
    844                     {
    845                         dprintf(("VBoxService: error from DeviceIoControl IOCTL_VBOXGUEST_VMMREQUEST\n"));
    846                         /* sleep a bit to not eat too much CPU while retrying */
    847                         /* are we supposed to stop? */
    848                         if (WaitForSingleObject(gStopSem, 50) == WAIT_OBJECT_0)
    849                         {
    850                             fTerminate = true;
    851                             break;
    852                         }
    853                     }
    854                 }
    855             }
    856         } else
    857         {
    858             dprintf(("VBoxService: error 0 from DeviceIoControl IOCTL_VBOXGUEST_WAITEVENT\n"));
    859             /* sleep a bit to not eat too much CPU in case the above call always fails */
    860             if (WaitForSingleObject(gStopSem, 10) == WAIT_OBJECT_0)
    861             {
    862                 fTerminate = true;
    863                 break;
    864             }
    865         }
    866     } while (!fTerminate);
    867 
    868     maskInfo.u32OrMask = 0;
    869     maskInfo.u32NotMask = VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST;
    870     if (DeviceIoControl (gVBoxDriver, IOCTL_VBOXGUEST_CTL_FILTER_MASK, &maskInfo, sizeof (maskInfo), NULL, 0, &cbReturned, NULL))
    871     {
    872         DDCLOG(("VBoxService: DeviceIOControl(CtlMask - not) succeeded\n"));
    873     }
    874     else
    875     {
    876         dprintf(("VBoxService: DeviceIOControl(CtlMask) failed\n"));
    877     }
    878 
    879     dprintf(("VBoxService: finished display change request thread\n"));
    880 }
    881 
    882403/**
    883404 * Window procedure for our tool window
Note: See TracChangeset for help on using the changeset viewer.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette