VirtualBox

source: vbox/trunk/src/VBox/Main/MouseImpl.cpp@ 26624

Last change on this file since 26624 was 26624, checked in by vboxsync, 15 years ago

Devices, Main, pdmifs.h: initial support for PS/2 touchscreen emulation, based on the Lifebook touchscreen device

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 17.6 KB
Line 
1/* $Id: MouseImpl.cpp 26624 2010-02-18 10:35:24Z vboxsync $ */
2/** @file
3 * VirtualBox COM class implementation
4 */
5
6/*
7 * Copyright (C) 2006-2008 Sun Microsystems, Inc.
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
18 * Clara, CA 95054 USA or visit http://www.sun.com if you need
19 * additional information or have any questions.
20 */
21
22#include "MouseImpl.h"
23#include "DisplayImpl.h"
24#include "VMMDev.h"
25
26#include "AutoCaller.h"
27#include "Logging.h"
28
29#include <VBox/pdmdrv.h>
30#include <iprt/asm.h>
31#include <VBox/VMMDev.h>
32
33/**
34 * Mouse driver instance data.
35 */
36typedef struct DRVMAINMOUSE
37{
38 /** Pointer to the mouse object. */
39 Mouse *pMouse;
40 /** Pointer to the driver instance structure. */
41 PPDMDRVINS pDrvIns;
42 /** Pointer to the mouse port interface of the driver/device above us. */
43 PPDMIMOUSEPORT pUpPort;
44 /** Our mouse connector interface. */
45 PDMIMOUSECONNECTOR IConnector;
46} DRVMAINMOUSE, *PDRVMAINMOUSE;
47
48/** Converts a PDMIMOUSECONNECTOR pointer to a DRVMAINMOUSE pointer. */
49#define PPDMIMOUSECONNECTOR_2_MAINMOUSE(pInterface) ( (PDRVMAINMOUSE) ((uintptr_t)pInterface - RT_OFFSETOF(DRVMAINMOUSE, IConnector)) )
50
51// constructor / destructor
52/////////////////////////////////////////////////////////////////////////////
53
54DEFINE_EMPTY_CTOR_DTOR (Mouse)
55
56HRESULT Mouse::FinalConstruct()
57{
58 mpDrv = NULL;
59 mLastAbsX = 0x8000;
60 mLastAbsY = 0x8000;
61 mLastButtons = 0;
62 return S_OK;
63}
64
65void Mouse::FinalRelease()
66{
67 uninit();
68}
69
70// public methods only for internal purposes
71/////////////////////////////////////////////////////////////////////////////
72
73/**
74 * Initializes the mouse object.
75 *
76 * @returns COM result indicator
77 * @param parent handle of our parent object
78 */
79HRESULT Mouse::init (Console *parent)
80{
81 LogFlowThisFunc(("\n"));
82
83 ComAssertRet(parent, E_INVALIDARG);
84
85 /* Enclose the state transition NotReady->InInit->Ready */
86 AutoInitSpan autoInitSpan(this);
87 AssertReturn(autoInitSpan.isOk(), E_FAIL);
88
89 unconst(mParent) = parent;
90
91#ifdef RT_OS_L4
92 /* L4 console has no own mouse cursor */
93 uHostCaps = VMMDEV_MOUSE_HOST_CANNOT_HWPOINTER;
94#else
95 uHostCaps = 0;
96#endif
97 uDevCaps = 0;
98
99 /* Confirm a successful initialization */
100 autoInitSpan.setSucceeded();
101
102 return S_OK;
103}
104
105/**
106 * Uninitializes the instance and sets the ready flag to FALSE.
107 * Called either from FinalRelease() or by the parent when it gets destroyed.
108 */
109void Mouse::uninit()
110{
111 LogFlowThisFunc(("\n"));
112
113 /* Enclose the state transition Ready->InUninit->NotReady */
114 AutoUninitSpan autoUninitSpan(this);
115 if (autoUninitSpan.uninitDone())
116 return;
117
118 if (mpDrv)
119 mpDrv->pMouse = NULL;
120 mpDrv = NULL;
121
122 unconst(mParent).setNull();
123}
124
125
126// IMouse properties
127/////////////////////////////////////////////////////////////////////////////
128
129int Mouse::getVMMDevMouseCaps(uint32_t *pfCaps)
130{
131 AssertPtrReturn(pfCaps, E_POINTER);
132 VMMDev *pVMMDev = mParent->getVMMDev();
133 ComAssertRet(pVMMDev, E_FAIL);
134 PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
135 ComAssertRet(pVMMDevPort, E_FAIL);
136
137 int rc = pVMMDevPort->pfnQueryMouseCapabilities(pVMMDevPort, pfCaps);
138 return RT_SUCCESS(rc) ? S_OK : E_FAIL;
139}
140
141int Mouse::setVMMDevMouseCaps(uint32_t fCaps)
142{
143 VMMDev *pVMMDev = mParent->getVMMDev();
144 ComAssertRet(pVMMDev, E_FAIL);
145 PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
146 ComAssertRet(pVMMDevPort, E_FAIL);
147
148 int rc = pVMMDevPort->pfnSetMouseCapabilities(pVMMDevPort, fCaps);
149 return RT_SUCCESS(rc) ? S_OK : E_FAIL;
150}
151
152/**
153 * Returns whether the current setup can accept absolute mouse
154 * events.
155 *
156 * @returns COM status code
157 * @param absoluteSupported address of result variable
158 */
159STDMETHODIMP Mouse::COMGETTER(AbsoluteSupported) (BOOL *absoluteSupported)
160{
161 if (!absoluteSupported)
162 return E_POINTER;
163
164 AutoCaller autoCaller(this);
165 if (FAILED(autoCaller.rc())) return autoCaller.rc();
166
167 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
168
169 if (uDevCaps & MOUSE_DEVCAP_ABSOLUTE)
170 *absoluteSupported = TRUE;
171 else
172 {
173 CHECK_CONSOLE_DRV (mpDrv);
174
175 uint32_t mouseCaps;
176 int rc = getVMMDevMouseCaps(&mouseCaps);
177 AssertComRCReturn(rc, rc);
178 *absoluteSupported = mouseCaps & VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE;
179 }
180
181 return S_OK;
182}
183
184/**
185 * Returns whether the guest can currently draw the mouse cursor itself.
186 *
187 * @returns COM status code
188 * @param absoluteSupported address of result variable
189 */
190STDMETHODIMP Mouse::COMGETTER(NeedsHostCursor) (BOOL *pfNeedsHostCursor)
191{
192 if (!pfNeedsHostCursor)
193 return E_POINTER;
194
195 AutoCaller autoCaller(this);
196 if (FAILED(autoCaller.rc())) return autoCaller.rc();
197
198 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
199
200 CHECK_CONSOLE_DRV (mpDrv);
201
202 uint32_t fMouseCaps;
203 int rc = getVMMDevMouseCaps(&fMouseCaps);
204 AssertComRCReturn(rc, rc);
205 *pfNeedsHostCursor = !!( fMouseCaps
206 & VMMDEV_MOUSE_GUEST_NEEDS_HOST_CURSOR);
207 return S_OK;
208}
209
210// IMouse methods
211/////////////////////////////////////////////////////////////////////////////
212
213static uint32_t mouseButtonsToPDM(LONG buttonState)
214{
215 uint32_t fButtons = 0;
216 if (buttonState & MouseButtonState_LeftButton)
217 fButtons |= PDMIMOUSEPORT_BUTTON_LEFT;
218 if (buttonState & MouseButtonState_RightButton)
219 fButtons |= PDMIMOUSEPORT_BUTTON_RIGHT;
220 if (buttonState & MouseButtonState_MiddleButton)
221 fButtons |= PDMIMOUSEPORT_BUTTON_MIDDLE;
222 if (buttonState & MouseButtonState_XButton1)
223 fButtons |= PDMIMOUSEPORT_BUTTON_X1;
224 if (buttonState & MouseButtonState_XButton2)
225 fButtons |= PDMIMOUSEPORT_BUTTON_X2;
226 return fButtons;
227}
228
229
230/**
231 * Send a relative event to the mouse device.
232 *
233 * @returns COM status code
234 */
235int Mouse::reportRelEventToMouseDev(int32_t dx, int32_t dy, int32_t dz,
236 int32_t dw, uint32_t fButtons)
237{
238 CHECK_CONSOLE_DRV (mpDrv);
239
240 if (dx || dy || dz || dw || fButtons != mLastButtons)
241 {
242 PPDMIMOUSEPORT pUpPort = mpDrv->pUpPort;
243 int vrc = pUpPort->pfnPutEvent(pUpPort, dx, dy, dz, dw, fButtons);
244
245 if (RT_FAILURE(vrc))
246 setError(VBOX_E_IPRT_ERROR,
247 tr("Could not send the mouse event to the virtual mouse (%Rrc)"),
248 vrc);
249 AssertRCReturn(vrc, VBOX_E_IPRT_ERROR);
250 }
251 return S_OK;
252}
253
254
255/**
256 * Send an absolute position event to the mouse device.
257 *
258 * @returns COM status code
259 */
260int Mouse::reportAbsEventToMouseDev(uint32_t mouseXAbs, uint32_t mouseYAbs)
261{
262 CHECK_CONSOLE_DRV (mpDrv);
263
264 if (mouseXAbs != mLastAbsX || mouseYAbs != mLastAbsY)
265 {
266 int vrc = mpDrv->pUpPort->pfnPutEventAbs(mpDrv->pUpPort, mouseXAbs,
267 mouseYAbs);
268 if (RT_FAILURE(vrc))
269 setError(VBOX_E_IPRT_ERROR,
270 tr("Could not send the mouse event to the virtual mouse (%Rrc)"),
271 vrc);
272 AssertRCReturn(vrc, VBOX_E_IPRT_ERROR);
273 }
274 return S_OK;
275}
276
277
278/**
279 * Send an absolute position event to the VMM device.
280 *
281 * @returns COM status code
282 */
283int Mouse::reportAbsEventToVMMDev(uint32_t mouseXAbs, uint32_t mouseYAbs)
284{
285 VMMDev *pVMMDev = mParent->getVMMDev();
286 ComAssertRet(pVMMDev, E_FAIL);
287 PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
288 ComAssertRet(pVMMDevPort, E_FAIL);
289
290 if (mouseXAbs != mLastAbsX || mouseYAbs != mLastAbsY)
291 {
292 int vrc = pVMMDevPort->pfnSetAbsoluteMouse(pVMMDevPort,
293 mouseXAbs, mouseYAbs);
294 if (RT_FAILURE(vrc))
295 setError(VBOX_E_IPRT_ERROR,
296 tr("Could not send the mouse event to the virtual mouse (%Rrc)"),
297 vrc);
298 AssertRCReturn(vrc, VBOX_E_IPRT_ERROR);
299 }
300 return S_OK;
301}
302
303/**
304 * Send a mouse event.
305 *
306 * @returns COM status code
307 * @param dx X movement
308 * @param dy Y movement
309 * @param dz Z movement
310 * @param buttonState The mouse button state
311 */
312STDMETHODIMP Mouse::PutMouseEvent(LONG dx, LONG dy, LONG dz, LONG dw, LONG buttonState)
313{
314 HRESULT rc = S_OK;
315
316 AutoCaller autoCaller(this);
317 if (FAILED(autoCaller.rc())) return autoCaller.rc();
318
319 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
320
321 CHECK_CONSOLE_DRV (mpDrv);
322
323 LogRel3(("%s: dx=%d, dy=%d, dz=%d, dw=%d\n", __PRETTY_FUNCTION__,
324 dx, dy, dz, dw));
325 if (!(uDevCaps & MOUSE_DEVCAP_ABSOLUTE))
326 {
327 /*
328 * This method being called implies that the host no
329 * longer wants to use absolute coordinates. If the VMM
330 * device isn't aware of that yet, tell it.
331 */
332 uint32_t mouseCaps;
333 rc = getVMMDevMouseCaps(&mouseCaps);
334 ComAssertComRCRet(rc, rc);
335
336 if (mouseCaps & VMMDEV_MOUSE_HOST_CAN_ABSOLUTE)
337 setVMMDevMouseCaps(uHostCaps);
338 }
339
340 uint32_t fButtons = mouseButtonsToPDM(buttonState);
341 rc = reportRelEventToMouseDev(dx, dy, dz, dw, fButtons);
342 if (SUCCEEDED(rc))
343 mLastButtons = fButtons;
344
345 return rc;
346}
347
348/**
349 * Convert an X value in screen co-ordinates to a value from 0 to 0xffff
350 *
351 * @returns COM status value
352 */
353int Mouse::convertDisplayWidth(LONG x, uint32_t *pcX)
354{
355 AssertPtrReturn(pcX, E_POINTER);
356 Display *pDisplay = mParent->getDisplay();
357 ComAssertRet(pDisplay, E_FAIL);
358
359 ULONG displayWidth;
360 int rc = pDisplay->COMGETTER(Width)(&displayWidth);
361 ComAssertComRCRet(rc, rc);
362
363 *pcX = displayWidth ? (x * 0xFFFF) / displayWidth: 0;
364 return S_OK;
365}
366
367/**
368 * Convert a Y value in screen co-ordinates to a value from 0 to 0xffff
369 *
370 * @returns COM status value
371 */
372int Mouse::convertDisplayHeight(LONG y, uint32_t *pcY)
373{
374 AssertPtrReturn(pcY, E_POINTER);
375 Display *pDisplay = mParent->getDisplay();
376 ComAssertRet(pDisplay, E_FAIL);
377
378 ULONG displayHeight;
379 int rc = pDisplay->COMGETTER(Height)(&displayHeight);
380 ComAssertComRCRet(rc, rc);
381
382 *pcY = displayHeight ? (y * 0xFFFF) / displayHeight: 0;
383 return S_OK;
384}
385
386
387/**
388 * Send an absolute mouse event to the VM. This only works
389 * when the required guest support has been installed.
390 *
391 * @returns COM status code
392 * @param x X position (pixel)
393 * @param y Y position (pixel)
394 * @param dz Z movement
395 * @param buttonState The mouse button state
396 */
397STDMETHODIMP Mouse::PutMouseEventAbsolute(LONG x, LONG y, LONG dz, LONG dw,
398 LONG buttonState)
399{
400 HRESULT rc = S_OK;
401
402 AutoCaller autoCaller(this);
403 if (FAILED(autoCaller.rc())) return autoCaller.rc();
404
405 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
406
407 LogRel3(("%s: x=%d, y=%d, dz=%d, dw=%d\n", __PRETTY_FUNCTION__,
408 x, y, dz, dw));
409
410 uint32_t mouseXAbs;
411 rc = convertDisplayWidth(x, &mouseXAbs);
412 ComAssertComRCRet(rc, rc);
413 uint32_t mouseYAbs;
414 rc = convertDisplayHeight(y, &mouseYAbs);
415 ComAssertComRCRet(rc, rc);
416 uint32_t fButtons = mouseButtonsToPDM(buttonState);
417 /* Older guest additions rely on a small phony movement event on the
418 * PS/2 device to notice absolute events. */
419 bool fNeedsJiggle = false;
420
421 if (uDevCaps & MOUSE_DEVCAP_ABSOLUTE)
422 rc = reportAbsEventToMouseDev(mouseXAbs, mouseYAbs);
423 else
424 {
425 uint32_t mouseCaps;
426 rc = getVMMDevMouseCaps(&mouseCaps);
427 ComAssertComRCRet(rc, rc);
428 /*
429 * This method being called implies that the host wants
430 * to use absolute coordinates. If the VMM device isn't
431 * aware of that yet, tell it.
432 */
433 if (!(mouseCaps & VMMDEV_MOUSE_HOST_CAN_ABSOLUTE))
434 setVMMDevMouseCaps(uHostCaps | VMMDEV_MOUSE_HOST_CAN_ABSOLUTE);
435
436 /*
437 * Send the absolute mouse position to the VMM device.
438 */
439 rc = reportAbsEventToVMMDev(mouseXAbs, mouseYAbs);
440 fNeedsJiggle = !(mouseCaps & VMMDEV_MOUSE_GUEST_USES_VMMDEV);
441 }
442 ComAssertComRCRet (rc, rc);
443 mLastAbsX = mouseXAbs;
444 mLastAbsY = mouseYAbs;
445 /* We may need to send a relative event for button information or to
446 * wake the guest up to the changed absolute co-ordinates. */
447 /* If the event is a pure wake up one, we make sure it contains some
448 * (possibly phony) event data to make sure it isn't just discarded on
449 * the way. Note: we ignore dw as it is optional. */
450 if (fNeedsJiggle || fButtons != mLastButtons || dz || dw)
451 rc = reportRelEventToMouseDev(fNeedsJiggle ? 1 : 0, 0, dz, dw,
452 fButtons);
453 if (SUCCEEDED(rc))
454 mLastButtons = fButtons;
455 return rc;
456}
457
458// private methods
459/////////////////////////////////////////////////////////////////////////////
460
461/**
462 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
463 */
464DECLCALLBACK(void *) Mouse::drvQueryInterface(PPDMIBASE pInterface, const char *pszIID)
465{
466 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
467 PDRVMAINMOUSE pDrv = PDMINS_2_DATA(pDrvIns, PDRVMAINMOUSE);
468
469 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
470 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUSECONNECTOR, &pDrv->IConnector);
471 return NULL;
472}
473
474
475/**
476 * Destruct a mouse driver instance.
477 *
478 * @returns VBox status.
479 * @param pDrvIns The driver instance data.
480 */
481DECLCALLBACK(void) Mouse::drvDestruct(PPDMDRVINS pDrvIns)
482{
483 PDRVMAINMOUSE pData = PDMINS_2_DATA(pDrvIns, PDRVMAINMOUSE);
484 LogFlow(("Mouse::drvDestruct: iInstance=%d\n", pDrvIns->iInstance));
485 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
486
487 if (pData->pMouse)
488 {
489 AutoWriteLock mouseLock(pData->pMouse COMMA_LOCKVAL_SRC_POS);
490 pData->pMouse->mpDrv = NULL;
491 }
492}
493
494
495DECLCALLBACK(void) Mouse::mouseAbsModeChange (PPDMIMOUSECONNECTOR pInterface, bool fAbs)
496{
497 PDRVMAINMOUSE pDrv = PPDMIMOUSECONNECTOR_2_MAINMOUSE (pInterface);
498 if (fAbs)
499 pDrv->pMouse->uDevCaps |= MOUSE_DEVCAP_ABSOLUTE;
500 else
501 pDrv->pMouse->uDevCaps &= ~MOUSE_DEVCAP_ABSOLUTE;
502 /** @todo we have to hack around the fact that VMMDev may not be
503 * initialised too close to startup. The real fix is to change the
504 * protocol for onMouseCapabilityChange so that we no longer need to
505 * query VMMDev, but that requires more changes that I want to do in
506 * the next commit, so it must be put off until the followup one. */
507 uint32_t fMouseCaps = 0;
508 int rc = S_OK;
509 if ( pDrv->pMouse->mParent->getVMMDev()
510 && pDrv->pMouse->mParent->getVMMDev()->mpDrv)
511 rc = pDrv->pMouse->getVMMDevMouseCaps(&fMouseCaps);
512 AssertComRCReturnVoid(rc);
513 pDrv->pMouse->getParent()->onMouseCapabilityChange (fAbs, fMouseCaps & VMMDEV_MOUSE_GUEST_NEEDS_HOST_CURSOR);
514}
515
516
517/**
518 * Construct a mouse driver instance.
519 *
520 * @copydoc FNPDMDRVCONSTRUCT
521 */
522DECLCALLBACK(int) Mouse::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
523{
524 PDRVMAINMOUSE pData = PDMINS_2_DATA(pDrvIns, PDRVMAINMOUSE);
525 LogFlow(("drvMainMouse_Construct: iInstance=%d\n", pDrvIns->iInstance));
526 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
527
528 /*
529 * Validate configuration.
530 */
531 if (!CFGMR3AreValuesValid(pCfg, "Object\0"))
532 return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
533 AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
534 ("Configuration error: Not possible to attach anything to this driver!\n"),
535 VERR_PDM_DRVINS_NO_ATTACH);
536
537 /*
538 * IBase.
539 */
540 pDrvIns->IBase.pfnQueryInterface = Mouse::drvQueryInterface;
541
542 pData->IConnector.pfnAbsModeChange = Mouse::mouseAbsModeChange;
543
544 /*
545 * Get the IMousePort interface of the above driver/device.
546 */
547 pData->pUpPort = (PPDMIMOUSEPORT)pDrvIns->pUpBase->pfnQueryInterface(pDrvIns->pUpBase, PDMIMOUSEPORT_IID);
548 if (!pData->pUpPort)
549 {
550 AssertMsgFailed(("Configuration error: No mouse port interface above!\n"));
551 return VERR_PDM_MISSING_INTERFACE_ABOVE;
552 }
553
554 /*
555 * Get the Mouse object pointer and update the mpDrv member.
556 */
557 void *pv;
558 int rc = CFGMR3QueryPtr(pCfg, "Object", &pv);
559 if (RT_FAILURE(rc))
560 {
561 AssertMsgFailed(("Configuration error: No/bad \"Object\" value! rc=%Rrc\n", rc));
562 return rc;
563 }
564 pData->pMouse = (Mouse *)pv; /** @todo Check this cast! */
565 pData->pMouse->mpDrv = pData;
566
567 return VINF_SUCCESS;
568}
569
570
571/**
572 * Main mouse driver registration record.
573 */
574const PDMDRVREG Mouse::DrvReg =
575{
576 /* u32Version */
577 PDM_DRVREG_VERSION,
578 /* szName */
579 "MainMouse",
580 /* szRCMod */
581 "",
582 /* szR0Mod */
583 "",
584 /* pszDescription */
585 "Main mouse driver (Main as in the API).",
586 /* fFlags */
587 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
588 /* fClass. */
589 PDM_DRVREG_CLASS_MOUSE,
590 /* cMaxInstances */
591 ~0,
592 /* cbInstance */
593 sizeof(DRVMAINMOUSE),
594 /* pfnConstruct */
595 Mouse::drvConstruct,
596 /* pfnDestruct */
597 Mouse::drvDestruct,
598 /* pfnRelocate */
599 NULL,
600 /* pfnIOCtl */
601 NULL,
602 /* pfnPowerOn */
603 NULL,
604 /* pfnReset */
605 NULL,
606 /* pfnSuspend */
607 NULL,
608 /* pfnResume */
609 NULL,
610 /* pfnAttach */
611 NULL,
612 /* pfnDetach */
613 NULL,
614 /* pfnPowerOff */
615 NULL,
616 /* pfnSoftReset */
617 NULL,
618 /* u32EndVersion */
619 PDM_DRVREG_VERSION
620};
621/* vi: set tabstop=4 shiftwidth=4 expandtab: */
Note: See TracBrowser for help on using the repository browser.

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