VirtualBox

source: vbox/trunk/src/VBox/Frontends/VBoxBFE/SDLConsole.cpp@ 113

Last change on this file since 113 was 1, checked in by vboxsync, 55 years ago

import

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 44.3 KB
Line 
1/** @file
2 *
3 * VBox frontends: Basic Frontend (BFE):
4 * Implementation of SDLConsole class
5 */
6
7/*
8 * Copyright (C) 2006 InnoTek Systemberatung GmbH
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License as published by the Free Software Foundation,
14 * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
15 * distribution. VirtualBox OSE is distributed in the hope that it will
16 * be useful, but WITHOUT ANY WARRANTY of any kind.
17 *
18 * If you received this file as part of a commercial VirtualBox
19 * distribution, then only the terms of your commercial VirtualBox
20 * license agreement apply instead of the previous paragraph.
21 */
22
23/*******************************************************************************
24* Header Files *
25*******************************************************************************/
26#define LOG_GROUP LOG_GROUP_GUI
27
28#ifdef VBOXBFE_WITHOUT_COM
29# include "COMDefs.h"
30#else
31# include <VBox/com/defs.h>
32#endif
33#include <VBox/types.h>
34#include <VBox/err.h>
35#include <VBox/param.h>
36#include <VBox/pdm.h>
37#include <VBox/log.h>
38#include <iprt/path.h>
39#include <iprt/string.h>
40#include <iprt/runtime.h>
41#include <iprt/assert.h>
42#include <iprt/semaphore.h>
43#include <iprt/stream.h>
44#include <iprt/uuid.h>
45#include <iprt/alloca.h>
46
47#ifdef VBOXBFE_WITH_X11
48# include <X11/Xlib.h>
49# include <X11/Xcursor/Xcursor.h>
50#endif
51
52#include "VBoxBFE.h"
53
54#include <vector>
55
56#include "DisplayImpl.h"
57#include "MouseImpl.h"
58#include "KeyboardImpl.h"
59#include "VMMDevInterface.h"
60#include "Framebuffer.h"
61#include "MachineDebuggerImpl.h"
62
63#include "ConsoleImpl.h"
64#include "SDLConsole.h"
65
66/*******************************************************************************
67* Internal Functions *
68*******************************************************************************/
69
70SDLConsole::SDLConsole() : Console()
71{
72 int rc;
73
74 fInputGrab = false;
75 gpDefaultCursor = NULL;
76 gpCustomCursor = NULL;
77 /** Custom window manager cursor? */
78 gpCustomWMcursor = NULL;
79 mfInitialized = false;
80
81 memset(gaModifiersState, 0, sizeof(gaModifiersState));
82
83 rc = SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE);
84 if (rc != 0)
85 {
86 RTPrintf("SDL Error: '%s'\n", SDL_GetError());
87 return;
88 }
89
90 /* memorize the default cursor */
91 gpDefaultCursor = SDL_GetCursor();
92 /* create a fake empty cursor */
93 {
94 uint8_t cursorData[1] = {0};
95 gpCustomCursor = SDL_CreateCursor(cursorData, cursorData, 8, 1, 0, 0);
96 gpCustomWMcursor = gpCustomCursor->wm_cursor;
97 gpCustomCursor->wm_cursor = NULL;
98 }
99#ifdef VBOXBFE_WITH_X11
100 /* get Window Manager info */
101 SDL_VERSION(&gSdlInfo.version);
102 if (!SDL_GetWMInfo(&gSdlInfo))
103 {
104 /** @todo: Is this fatal? */
105 AssertMsgFailed(("Error: could not get SDL Window Manager info!\n"));
106 }
107#endif
108
109 /*
110 * Enable keyboard repeats
111 */
112 SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
113 mfInitialized = true;
114}
115
116SDLConsole::~SDLConsole()
117{
118 if (fInputGrab)
119 inputGrabEnd();
120}
121
122CONEVENT SDLConsole::eventWait()
123{
124 SDL_Event *ev = &ev1;
125
126 if (SDL_WaitEvent(ev) != 1)
127 {
128 return CONEVENT_QUIT;
129 }
130
131 switch (ev->type)
132 {
133
134 /*
135 * The screen needs to be repainted.
136 */
137 case SDL_VIDEOEXPOSE:
138 {
139 return CONEVENT_SCREENUPDATE;
140 }
141
142 /*
143 * Keyboard events.
144 */
145 case SDL_KEYDOWN:
146 case SDL_KEYUP:
147 {
148 switch (enmHKeyState)
149 {
150 case HKEYSTATE_NORMAL:
151 {
152 if ( ev->type == SDL_KEYDOWN
153 && ev->key.keysym.sym == gHostKeySym
154 && (SDL_GetModState() & ~(KMOD_MODE | KMOD_NUM | KMOD_RESERVED)) == gHostKey)
155 {
156 EvHKeyDown = *ev;
157 enmHKeyState = HKEYSTATE_DOWN;
158 break;
159 }
160 processKey(&ev->key);
161 break;
162 }
163
164 case HKEYSTATE_DOWN:
165 {
166
167 if (ev->type == SDL_KEYDOWN)
168 {
169 /* potential host key combination, try execute it */
170 int rc = handleHostKey(&ev->key);
171 if (rc == VINF_SUCCESS)
172 {
173 enmHKeyState = HKEYSTATE_USED;
174 break;
175 }
176 if (VBOX_SUCCESS(rc))
177 {
178 return CONEVENT_QUIT;
179 }
180 }
181 else /* SDL_KEYUP */
182 {
183 if (ev->key.keysym.sym == gHostKeySym)
184 {
185 /* toggle grabbing state */
186 if (!fInputGrab)
187 {
188 inputGrabStart();
189 }
190 else
191 {
192 inputGrabEnd();
193 }
194
195 /* SDL doesn't always reset the keystates, correct it */
196 resetKeys();
197 enmHKeyState = HKEYSTATE_NORMAL;
198 break;
199 }
200 }
201
202 /* not host key */
203 enmHKeyState = HKEYSTATE_NOT_IT;
204 ev1 = *ev;
205 processKey(&EvHKeyDown.key);
206 processKey(&ev->key);
207 break;
208 }
209
210 case HKEYSTATE_USED:
211 {
212 if ((SDL_GetModState() & ~(KMOD_MODE | KMOD_NUM | KMOD_RESERVED)) == 0)
213 {
214 enmHKeyState = HKEYSTATE_NORMAL;
215 }
216 if (ev->type == SDL_KEYDOWN)
217 {
218 int rc = handleHostKey(&ev->key);
219 if (VBOX_SUCCESS(rc) && rc != VINF_SUCCESS)
220 {
221 return CONEVENT_QUIT;
222 }
223 }
224 break;
225 }
226
227 default:
228 AssertMsgFailed(("enmHKeyState=%d\n", enmHKeyState));
229 /* fall thru */
230 case HKEYSTATE_NOT_IT:
231 {
232 if ((SDL_GetModState() & ~(KMOD_MODE | KMOD_NUM | KMOD_RESERVED)) == 0)
233 {
234 enmHKeyState = HKEYSTATE_NORMAL;
235 }
236 processKey(&ev->key);
237 break;
238 }
239 } /* state switch */
240 break;
241 }
242
243 /*
244 * The window was closed.
245 */
246 case SDL_QUIT:
247 {
248 return CONEVENT_QUIT;
249 }
250
251 /*
252 * The mouse has moved
253 */
254 case SDL_MOUSEMOTION:
255 {
256 if (fInputGrab || gMouse->getAbsoluteCoordinates())
257 {
258 mouseSendEvent(0);
259 }
260 break;
261 }
262
263 /*
264 * A mouse button has been clicked or released.
265 */
266 case SDL_MOUSEBUTTONDOWN:
267 case SDL_MOUSEBUTTONUP:
268 {
269 SDL_MouseButtonEvent *bev = &ev->button;
270 if (!fInputGrab && !gMouse->getAbsoluteCoordinates())
271 {
272 if (ev->type == SDL_MOUSEBUTTONDOWN && (bev->state & SDL_BUTTON_LMASK))
273 {
274 /* start grabbing all events */
275 inputGrabStart();
276 }
277 }
278 else
279 {
280 int dz = 0;
281 if (bev->button == SDL_BUTTON_WHEELUP)
282 {
283 dz = -1;
284 }
285 else if (bev->button == SDL_BUTTON_WHEELDOWN)
286 {
287 dz = 1;
288 }
289 mouseSendEvent(dz);
290 }
291 break;
292 }
293
294 /*
295 * The window has gained or lost focus.
296 */
297 case SDL_ACTIVEEVENT:
298 {
299 if (fInputGrab && (SDL_GetAppState() & SDL_ACTIVEEVENTMASK) == 0)
300 {
301 inputGrabEnd();
302 }
303 break;
304 }
305
306
307 /*
308 * User specific update event.
309 */
310 /** @todo use a common user event handler so that SDL_PeepEvents() won't
311 * possibly remove other events in the queue!
312 */
313 case SDL_USER_EVENT_UPDATERECT:
314 {
315
316 /*
317 * Decode event parameters.
318 */
319 #define DECODEX(ev) ((int)(ev)->user.data1 >> 16)
320 #define DECODEY(ev) ((int)(ev)->user.data1 & 0xFFFF)
321 #define DECODEW(ev) ((int)(ev)->user.data2 >> 16)
322 #define DECODEH(ev) ((int)(ev)->user.data2 & 0xFFFF)
323 int x = DECODEX(ev);
324 int y = DECODEY(ev);
325 int w = DECODEW(ev);
326 int h = DECODEH(ev);
327 LogFlow(("SDL_USER_EVENT_UPDATERECT: x = %d, y = %d, w = %d, h = %d\n",
328 x, y, w, h));
329
330 Assert(gFramebuffer);
331 /*
332 * Lock the framebuffer, perform the update and lock again
333 */
334 gFramebuffer->Lock();
335 gFramebuffer->update(x, y, w, h);
336 gFramebuffer->Unlock();
337
338 #undef DECODEX
339 #undef DECODEY
340 #undef DECODEW
341 #undef DECODEH
342 break;
343 }
344
345 /*
346 * User specific resize event.
347 */
348 case SDL_USER_EVENT_RESIZE:
349 return CONEVENT_USR_SCREENRESIZE;
350
351 /*
352 * User specific update title bar notification event
353 */
354 case SDL_USER_EVENT_UPDATE_TITLEBAR:
355 return CONEVENT_USR_TITLEBARUPDATE;
356
357 /*
358 * User specific termination event
359 */
360 case SDL_USER_EVENT_TERMINATE:
361 {
362 if (ev->user.code != VBOXSDL_TERM_NORMAL)
363 RTPrintf("Error: VM terminated abnormally!\n");
364 return CONEVENT_USR_QUIT;
365 }
366
367#ifdef VBOX_SECURELABEL
368 /*
369 * User specific secure label update event
370 */
371 case SDL_USER_EVENT_SECURELABEL_UPDATE:
372 return CONEVENT_USR_SECURELABELUPDATE;
373
374#endif /* VBOX_SECURELABEL */
375
376 /*
377 * User specific pointer shape change event
378 */
379 case SDL_USER_EVENT_POINTER_CHANGE:
380 {
381 PointerShapeChangeData *data =
382 (PointerShapeChangeData *) ev->user.data1;
383 setPointerShape (data);
384 delete data;
385 break;
386 }
387
388 default:
389 {
390 printf("%s:%d unknown SDL event %d\n",__FILE__,__LINE__,ev->type);
391 LogBird(("unknown SDL event %d\n", ev->type));
392 break;
393 }
394 }
395 return CONEVENT_NONE;
396}
397
398/**
399 * Push the exit event forcing the main event loop to terminate.
400 */
401void SDLConsole::eventQuit()
402{
403 SDL_Event event;
404
405 event.type = SDL_USEREVENT;
406 event.user.type = SDL_USER_EVENT_TERMINATE;
407 SDL_PushEvent(&event);
408}
409
410/**
411 * Converts an SDL keyboard eventcode to a XT scancode.
412 *
413 * @returns XT scancode
414 * @param ev SDL scancode
415 */
416uint8_t SDLConsole::keyEventToKeyCode(const SDL_KeyboardEvent *ev)
417{
418 int keycode;
419
420 // start with the scancode determined by SDL
421 keycode = ev->keysym.scancode;
422
423#ifdef VBOXBFE_WITH_X11 /// @todo verify that these are X11 issues and not unique linux issues.
424 // workaround for SDL keyboard translation issues on X11
425 static const uint8_t x_keycode_to_pc_keycode[61] =
426 {
427 0xc7, /* 97 Home */
428 0xc8, /* 98 Up */
429 0xc9, /* 99 PgUp */
430 0xcb, /* 100 Left */
431 0x4c, /* 101 KP-5 */
432 0xcd, /* 102 Right */
433 0xcf, /* 103 End */
434 0xd0, /* 104 Down */
435 0xd1, /* 105 PgDn */
436 0xd2, /* 106 Ins */
437 0xd3, /* 107 Del */
438 0x9c, /* 108 Enter */
439 0x9d, /* 109 Ctrl-R */
440 0x0, /* 110 Pause */
441 0xb7, /* 111 Print */
442 0xb5, /* 112 Divide */
443 0xb8, /* 113 Alt-R */
444 0xc6, /* 114 Break */
445 0x0, /* 115 */
446 0x0, /* 116 */
447 0x0, /* 117 */
448 0x0, /* 118 */
449 0x0, /* 119 */
450 0x70, /* 120 Hiragana_Katakana */
451 0x0, /* 121 */
452 0x0, /* 122 */
453 0x73, /* 123 backslash */
454 0x0, /* 124 */
455 0x0, /* 125 */
456 0x0, /* 126 */
457 0x0, /* 127 */
458 0x0, /* 128 */
459 0x79, /* 129 Henkan */
460 0x0, /* 130 */
461 0x7b, /* 131 Muhenkan */
462 0x0, /* 132 */
463 0x7d, /* 133 Yen */
464 0x0, /* 134 */
465 0x0, /* 135 */
466 0x47, /* 136 KP_7 */
467 0x48, /* 137 KP_8 */
468 0x49, /* 138 KP_9 */
469 0x4b, /* 139 KP_4 */
470 0x4c, /* 140 KP_5 */
471 0x4d, /* 141 KP_6 */
472 0x4f, /* 142 KP_1 */
473 0x50, /* 143 KP_2 */
474 0x51, /* 144 KP_3 */
475 0x52, /* 145 KP_0 */
476 0x53, /* 146 KP_. */
477 0x47, /* 147 KP_HOME */
478 0x48, /* 148 KP_UP */
479 0x49, /* 149 KP_PgUp */
480 0x4b, /* 150 KP_Left */
481 0x4c, /* 151 KP_ */
482 0x4d, /* 152 KP_Right */
483 0x4f, /* 153 KP_End */
484 0x50, /* 154 KP_Down */
485 0x51, /* 155 KP_PgDn */
486 0x52, /* 156 KP_Ins */
487 0x53, /* 157 KP_Del */
488 };
489
490 if (keycode < 9)
491 {
492 keycode = 0;
493 }
494 else if (keycode < 97)
495 {
496 // just an offset
497 keycode -= 8;
498 }
499 else if (keycode < 158)
500 {
501 // apply conversion table
502 keycode = x_keycode_to_pc_keycode[keycode - 97];
503 }
504 else
505 {
506 keycode = 0;
507 }
508
509#elif defined(__DARWIN__)
510 /*
511 * The keycode on darwin is more or less the same as the SDL key symbol.
512 * This means we'll have to assume a keyboard layout and translate
513 * the SDL / Quartz keycodes via it.
514 *
515 * At first I'll just do a hardcoded us internaltional keyboard mapping
516 * here to try this out.
517 */
518/* from SDL:
519 key.scancode = [ event keyCode ];
520 key.sym = keymap [ key.scancode ];
521 key.unicode = [ chars characterAtIndex:0 ];
522 key.mod = KMOD_NONE; */
523 const SDLKey sym = ev->keysym.sym;
524 if (sym != SDLK_UNKNOWN)
525 {
526 Log(("SDL key event: sym=%d scancode=%#x unicode=%#x\n",
527 sym, ev->keysym.scancode, ev->keysym.unicode));
528 switch (sym)
529 { /* set 1 scan code */
530 case SDLK_ESCAPE: return 0x01;
531 case SDLK_EXCLAIM:
532 case SDLK_1: return 0x02;
533 case SDLK_AT:
534 case SDLK_2: return 0x03;
535 case SDLK_HASH:
536 case SDLK_3: return 0x04;
537 case SDLK_DOLLAR:
538 case SDLK_4: return 0x05;
539 /* % */
540 case SDLK_5: return 0x06;
541 case SDLK_CARET:
542 case SDLK_6: return 0x07;
543 case SDLK_AMPERSAND:
544 case SDLK_7: return 0x08;
545 case SDLK_ASTERISK:
546 case SDLK_8: return 0x09;
547 case SDLK_LEFTPAREN:
548 case SDLK_9: return 0x0a;
549 case SDLK_RIGHTPAREN:
550 case SDLK_0: return 0x0b;
551 case SDLK_UNDERSCORE:
552 case SDLK_MINUS: return 0x0c;
553 case SDLK_EQUALS:
554 case SDLK_PLUS: return 0x0d;
555 case SDLK_BACKSPACE: return 0x0e;
556 case SDLK_TAB: return 0x0f;
557 case SDLK_q: return 0x10;
558 case SDLK_w: return 0x11;
559 case SDLK_e: return 0x12;
560 case SDLK_r: return 0x13;
561 case SDLK_t: return 0x14;
562 case SDLK_y: return 0x15;
563 case SDLK_u: return 0x16;
564 case SDLK_i: return 0x17;
565 case SDLK_o: return 0x18;
566 case SDLK_p: return 0x19;
567 case SDLK_LEFTBRACKET: return 0x1a;
568 case SDLK_RIGHTBRACKET: return 0x1b;
569 case SDLK_RETURN: return 0x1c;
570 case SDLK_KP_ENTER: return 0x1c | 0x80;
571 case SDLK_LCTRL: return 0x1d;
572 case SDLK_RCTRL: return 0x1d | 0x80;
573 case SDLK_a: return 0x1e;
574 case SDLK_s: return 0x1f;
575 case SDLK_d: return 0x20;
576 case SDLK_f: return 0x21;
577 case SDLK_g: return 0x22;
578 case SDLK_h: return 0x23;
579 case SDLK_j: return 0x24;
580 case SDLK_k: return 0x25;
581 case SDLK_l: return 0x26;
582 case SDLK_COLON:
583 case SDLK_SEMICOLON: return 0x27;
584 case SDLK_QUOTEDBL:
585 case SDLK_QUOTE: return 0x28;
586 case SDLK_BACKQUOTE: return 0x29;
587 case SDLK_LSHIFT: return 0x2a;
588 case SDLK_BACKSLASH: return 0x2b;
589 case SDLK_z: return 0x2c;
590 case SDLK_x: return 0x2d;
591 case SDLK_c: return 0x2e;
592 case SDLK_v: return 0x2f;
593 case SDLK_b: return 0x30;
594 case SDLK_n: return 0x31;
595 case SDLK_m: return 0x32;
596 case SDLK_LESS:
597 case SDLK_COMMA: return 0x33;
598 case SDLK_GREATER:
599 case SDLK_PERIOD: return 0x34;
600 case SDLK_KP_DIVIDE: /*??*/
601 case SDLK_QUESTION:
602 case SDLK_SLASH: return 0x35;
603 case SDLK_RSHIFT: return 0x36;
604 case SDLK_KP_MULTIPLY:
605 case SDLK_PRINT: return 0x37; /* fixme */
606 case SDLK_LALT: return 0x38;
607 case SDLK_MODE: /* alt gr*/
608 case SDLK_RALT: return 0x38 | 0x80;
609 case SDLK_SPACE: return 0x39;
610 case SDLK_CAPSLOCK: return 0x3a;
611 case SDLK_F1: return 0x3b;
612 case SDLK_F2: return 0x3c;
613 case SDLK_F3: return 0x3d;
614 case SDLK_F4: return 0x3e;
615 case SDLK_F5: return 0x3f;
616 case SDLK_F6: return 0x40;
617 case SDLK_F7: return 0x41;
618 case SDLK_F8: return 0x42;
619 case SDLK_F9: return 0x43;
620 case SDLK_F10: return 0x44;
621 case SDLK_PAUSE: return 0x45; /* fixme */
622 case SDLK_NUMLOCK: return 0x45;
623 case SDLK_SCROLLOCK: return 0x46;
624 case SDLK_KP7: return 0x47;
625 case SDLK_HOME: return 0x47 | 0x80;
626 case SDLK_KP8: return 0x48;
627 case SDLK_UP: return 0x48 | 0x80;
628 case SDLK_KP9: return 0x49;
629 case SDLK_PAGEUP: return 0x49 | 0x80;
630 case SDLK_KP_MINUS: return 0x4a;
631 case SDLK_KP4: return 0x4b;
632 case SDLK_LEFT: return 0x4b | 0x80;
633 case SDLK_KP5: return 0x4c;
634 case SDLK_KP6: return 0x4d;
635 case SDLK_RIGHT: return 0x4d | 0x80;
636 case SDLK_KP_PLUS: return 0x4e;
637 case SDLK_KP1: return 0x4f;
638 case SDLK_END: return 0x4f | 0x80;
639 case SDLK_KP2: return 0x50;
640 case SDLK_DOWN: return 0x50 | 0x80;
641 case SDLK_KP3: return 0x51;
642 case SDLK_PAGEDOWN: return 0x51 | 0x80;
643 case SDLK_KP0: return 0x52;
644 case SDLK_INSERT: return 0x52 | 0x80;
645 case SDLK_KP_PERIOD: return 0x53;
646 case SDLK_DELETE: return 0x53 | 0x80;
647 case SDLK_SYSREQ: return 0x54;
648 case SDLK_F11: return 0x56;
649 case SDLK_F12: return 0x57;
650 case SDLK_F13: return 0x5b;
651 case SDLK_LSUPER: return 0x5b | 0x80;
652 case SDLK_F14: return 0x5c;
653 case SDLK_RSUPER: return 0x5c | 0x80;
654 case SDLK_F15: return 0x5d;
655 case SDLK_MENU: return 0x5d | 0x80;
656#if 0 /* @todo */
657 case SDLK_CLEAR: return 0x;
658 case SDLK_KP_EQUALS: return 0x;
659 case SDLK_RMETA: return 0x;
660 case SDLK_LMETA: return 0x;
661 case SDLK_COMPOSE: return 0x;
662 case SDLK_HELP: return 0x;
663 case SDLK_BREAK: return 0x;
664 case SDLK_POWER: return 0x;
665 case SDLK_EURO: return 0x;
666 case SDLK_UNDO: return 0x;
667#endif
668 default:
669 Log(("Unhandled sdl key event: sym=%d scancode=%#x unicode=%#x\n",
670 ev->keysym.sym, ev->keysym.scancode, ev->keysym.unicode));
671 keycode = 0;
672 break;
673 }
674 }
675 else
676 {
677 /* deal with this as needed. mac can emit pure unicode events */
678 Log(("Unhandled key event: scancode=%#x unicode=%#x\n",
679 ev->keysym.scancode, ev->keysym.unicode));
680 }
681#endif
682 return keycode;
683}
684
685/**
686 * Releases any modifier keys that are currently in pressed state.
687 */
688void SDLConsole::resetKeys(void)
689{
690 int i;
691 for(i = 0; i < 256; i++)
692 {
693 if (gaModifiersState[i])
694 {
695 if (i & 0x80)
696 gKeyboard->PutScancode(0xe0);
697 gKeyboard->PutScancode(i | 0x80);
698 gaModifiersState[i] = 0;
699 }
700 }
701}
702
703/**
704 * Keyboard event handler.
705 *
706 * @param ev SDL keyboard event.
707 */
708void SDLConsole::processKey(SDL_KeyboardEvent *ev)
709{
710 int keycode, v;
711
712#if defined(VBOXSDL_ADVANCED_OPTIONS) && defined(DEBUG) && 0
713#error
714 // first handle the debugger hotkeys
715 uint8_t *keystate = SDL_GetKeyState(NULL);
716 if ((keystate[SDLK_LALT]) && (ev->type == SDL_KEYDOWN))
717 {
718 switch (ev->keysym.sym)
719 {
720 // pressing Alt-F12 toggles the supervisor recompiler
721 case SDLK_F12:
722 {
723 if (gMachineDebugger)
724 {
725 BOOL recompileSupervisor;
726 gMachineDebugger->COMGETTER(RecompileSupervisor)(&recompileSupervisor);
727 gMachineDebugger->COMSETTER(RecompileSupervisor)(!recompileSupervisor);
728 }
729 break;
730 }
731 // pressing Alt-F11 toggles the user recompiler
732 case SDLK_F11:
733 {
734 if (gMachineDebugger)
735 {
736 BOOL recompileUser;
737 gMachineDebugger->COMGETTER(RecompileUser)(&recompileUser);
738 gMachineDebugger->COMSETTER(RecompileUser)(!recompileUser);
739 }
740 break;
741 }
742 // pressing Alt-F10 toggles the patch manager
743 case SDLK_F10:
744 {
745 if (gMachineDebugger)
746 {
747 BOOL patmEnabled;
748 gMachineDebugger->COMGETTER(PATMEnabled)(&patmEnabled);
749 gMachineDebugger->COMSETTER(PATMEnabled)(!patmEnabled);
750 }
751 break;
752 }
753 // pressing Alt-F9 toggles CSAM
754 case SDLK_F9:
755 {
756 if (gMachineDebugger)
757 {
758 BOOL csamEnabled;
759 gMachineDebugger->COMGETTER(CSAMEnabled)(&csamEnabled);
760 gMachineDebugger->COMSETTER(CSAMEnabled)(!csamEnabled);
761 }
762 break;
763 }
764 // pressing Alt-F8 toggles singlestepping mode
765 case SDLK_F8:
766 {
767 if (gMachineDebugger)
768 {
769 BOOL singlestepEnabled;
770 gMachineDebugger->COMGETTER(Singlestep)(&singlestepEnabled);
771 gMachineDebugger->COMSETTER(Singlestep)(!singlestepEnabled);
772 }
773 break;
774 }
775
776 default:
777 break;
778 }
779 }
780 // pressing Ctrl-F12 toggles the logger
781 else if ((keystate[SDLK_RCTRL] || keystate[SDLK_LCTRL]) &&
782 (ev->keysym.sym == SDLK_F12) && (ev->type == SDL_KEYDOWN))
783 {
784 PRTLOGGER pLogger = RTLogDefaultInstance();
785 bool fEnabled = (pLogger && !(pLogger->fFlags & RTLOGFLAGS_DISABLED));
786 if (fEnabled)
787 {
788 RTLogFlags(pLogger, "disabled");
789 }
790 else
791 {
792 RTLogFlags(pLogger, "nodisabled");
793 }
794 }
795 // pressing F12 sets a logmark
796 else if ((ev->keysym.sym == SDLK_F12) && (ev->type == SDL_KEYDOWN))
797 {
798 RTLogPrintf("****** LOGGING MARK ******\n");
799 RTLogFlush(NULL);
800 }
801 // now update the titlebar flags
802 updateTitlebar();
803#endif
804
805 // the pause key is the weirdest, needs special handling
806 if (ev->keysym.sym == SDLK_PAUSE)
807 {
808 v = 0;
809 if (ev->type == SDL_KEYUP)
810 v |= 0x80;
811 gKeyboard->PutScancode(0xe1);
812 gKeyboard->PutScancode(0x1d | v);
813 gKeyboard->PutScancode(0x45 | v);
814 return;
815 }
816
817 /*
818 * Perform SDL key event to scancode conversion
819 */
820 keycode = keyEventToKeyCode(ev);
821
822 switch(keycode)
823 {
824 case 0x00:
825 {
826 /* sent when leaving window: reset the modifiers state */
827 resetKeys();
828 return;
829 }
830
831 case 0x2a: /* Left Shift */
832 case 0x36: /* Right Shift */
833 case 0x1d: /* Left CTRL */
834 case 0x9d: /* Right CTRL */
835 case 0x38: /* Left ALT */
836 case 0xb8: /* Right ALT */
837 {
838 if (ev->type == SDL_KEYUP)
839 gaModifiersState[keycode] = 0;
840 else
841 gaModifiersState[keycode] = 1;
842 break;
843 }
844
845 case 0x45: /* num lock */
846 case 0x3a: /* caps lock */
847 {
848 /* SDL does not send the key up event, so we generate it */
849 gKeyboard->PutScancode(keycode);
850 gKeyboard->PutScancode(keycode | 0x80);
851 return;
852 }
853 }
854
855 /*
856 * Now we send the event. Apply extended and release prefixes.
857 */
858 if (keycode & 0x80)
859 gKeyboard->PutScancode(0xe0);
860 if (ev->type == SDL_KEYUP)
861 gKeyboard->PutScancode(keycode | 0x80);
862 else
863 gKeyboard->PutScancode(keycode & 0x7f);
864}
865
866/**
867 * Start grabbing the mouse.
868 */
869void SDLConsole::inputGrabStart()
870{
871 if (!gMouse->getNeedsHostCursor())
872 SDL_ShowCursor(SDL_DISABLE);
873 SDL_WM_GrabInput(SDL_GRAB_ON);
874 // dummy read to avoid moving the mouse
875 SDL_GetRelativeMouseState(NULL, NULL);
876 fInputGrab = 1;
877 updateTitlebar();
878}
879
880/**
881 * End mouse grabbing.
882 */
883void SDLConsole::inputGrabEnd()
884{
885 SDL_WM_GrabInput(SDL_GRAB_OFF);
886 if (!gMouse->getNeedsHostCursor())
887 SDL_ShowCursor(SDL_ENABLE);
888 fInputGrab = 0;
889 updateTitlebar();
890}
891
892/**
893 * Query mouse position and button state from SDL and send to the VM
894 *
895 * @param dz Relative mouse wheel movement
896 */
897
898extern int GetRelativeMouseState(int *, int*);
899extern int GetMouseState(int *, int*);
900
901void SDLConsole::mouseSendEvent(int dz)
902{
903 int x, y, state, buttons;
904 bool abs;
905
906 abs = (gMouse->getAbsoluteCoordinates() && !fInputGrab) || gMouse->getNeedsHostCursor();
907
908 state = abs ? SDL_GetMouseState(&x, &y) : SDL_GetRelativeMouseState(&x, &y);
909
910 // process buttons
911 buttons = 0;
912 if (state & SDL_BUTTON(SDL_BUTTON_LEFT))
913 buttons |= PDMIMOUSEPORT_BUTTON_LEFT;
914 if (state & SDL_BUTTON(SDL_BUTTON_RIGHT))
915 buttons |= PDMIMOUSEPORT_BUTTON_RIGHT;
916 if (state & SDL_BUTTON(SDL_BUTTON_MIDDLE))
917 buttons |= PDMIMOUSEPORT_BUTTON_MIDDLE;
918
919 // now send the mouse event
920 if (abs)
921 {
922 /**
923 * @todo
924 * PutMouseEventAbsolute() expects x and y starting from 1,1.
925 * should we do the increment internally in PutMouseEventAbsolute()
926 * or state it in PutMouseEventAbsolute() docs?
927 */
928 /* only send if outside the extra offset area */
929 if (y >= gFramebuffer->getYOffset())
930 gMouse->PutMouseEventAbsolute(x + 1, y + 1 - gFramebuffer->getYOffset(), dz, buttons);
931 }
932 else
933 {
934 gMouse->PutMouseEvent(x, y, dz, buttons);
935 }
936}
937
938/**
939 * Update the pointer shape or visibility.
940 *
941 * This is called when the mouse pointer shape changes or pointer is
942 * hidden/displaying. The new shape is passed as a caller allocated
943 * buffer that will be freed after returning.
944 *
945 * @param fVisible Whether the pointer is visible or not.
946 * @param fAlpha Alpha channel information is present.
947 * @param xHot Horizontal coordinate of the pointer hot spot.
948 * @param yHot Vertical coordinate of the pointer hot spot.
949 * @param width Pointer width in pixels.
950 * @param height Pointer height in pixels.
951 * @param pShape The shape buffer. If NULL, then only
952 * pointer visibility is being changed
953 */
954void SDLConsole::onMousePointerShapeChange(bool fVisible,
955 bool fAlpha, uint32_t xHot,
956 uint32_t yHot, uint32_t width,
957 uint32_t height, void *pShape)
958{
959 PointerShapeChangeData *data;
960 data = new PointerShapeChangeData (fVisible, fAlpha, xHot, yHot,
961 width, height, (const uint8_t *) pShape);
962 Assert (data);
963 if (!data)
964 return;
965
966 SDL_Event event = {0};
967 event.type = SDL_USEREVENT;
968 event.user.type = SDL_USER_EVENT_POINTER_CHANGE;
969 event.user.data1 = data;
970
971 int rc = SDL_PushEvent (&event);
972 AssertMsg (!rc, ("Error: SDL_PushEvent was not successful!\n"));
973 if (rc)
974 delete data;
975}
976
977/**
978 * Build the titlebar string
979 */
980void SDLConsole::updateTitlebar()
981{
982 char title[1024];
983
984 strcpy(title, "InnoTek VirtualBox");
985
986 if (machineState == VMSTATE_SUSPENDED)
987 strcat(title, " - [Paused]");
988
989 if (fInputGrab)
990 strcat(title, " - [Input captured]");
991
992#if defined(VBOXSDL_ADVANCED_OPTIONS) && defined(DEBUG)
993 // do we have a debugger interface
994 if (gMachineDebugger)
995 {
996 // query the machine state
997 BOOL recompileSupervisor = FALSE;
998 BOOL recompileUser = FALSE;
999 BOOL patmEnabled = FALSE;
1000 BOOL csamEnabled = FALSE;
1001 BOOL singlestepEnabled = FALSE;
1002 gMachineDebugger->COMGETTER(RecompileSupervisor)(&recompileSupervisor);
1003 gMachineDebugger->COMGETTER(RecompileUser)(&recompileUser);
1004 gMachineDebugger->COMGETTER(PATMEnabled)(&patmEnabled);
1005 gMachineDebugger->COMGETTER(CSAMEnabled)(&csamEnabled);
1006 gMachineDebugger->COMGETTER(Singlestep)(&singlestepEnabled);
1007 PRTLOGGER pLogger = RTLogDefaultInstance();
1008 bool fEnabled = (pLogger && !(pLogger->fFlags & RTLOGFLAGS_DISABLED));
1009 RTStrPrintf(title + strlen(title), sizeof(title) - strlen(title),
1010 " [STEP=%d CS=%d PAT=%d RR0=%d RR3=%d LOG=%d]",
1011 singlestepEnabled == TRUE, csamEnabled == TRUE, patmEnabled == TRUE,
1012 recompileSupervisor == FALSE, recompileUser == FALSE, fEnabled == TRUE);
1013 }
1014#endif /* DEBUG */
1015
1016 SDL_WM_SetCaption(title, "InnoTek VirtualBox");
1017}
1018
1019/**
1020 * Updates the title bar while saving the state.
1021 * @param iPercent Percentage.
1022 */
1023void SDLConsole::updateTitlebarSave(int iPercent)
1024{
1025 char szTitle[256];
1026 AssertMsg(iPercent >= 0 && iPercent <= 100, ("%d\n", iPercent));
1027 RTStrPrintf(szTitle, sizeof(szTitle), "InnoTek VirtualBox - Saving %d%%...", iPercent);
1028 SDL_WM_SetCaption(szTitle, "InnoTek VirtualBox");
1029}
1030
1031/**
1032 * Sets the pointer shape according to parameters.
1033 * Must be called only from the main SDL thread.
1034 */
1035void SDLConsole::setPointerShape (const PointerShapeChangeData *data)
1036{
1037 /*
1038 * don't do anything if there are no guest additions loaded (anymore)
1039 */
1040 if (!gMouse->getAbsoluteCoordinates())
1041 return;
1042
1043 if (data->shape)
1044 {
1045 bool ok = false;
1046
1047 uint32_t andMaskSize = (data->width + 7) / 8 * data->height;
1048 uint32_t srcShapePtrScan = data->width * 4;
1049
1050 const uint8_t *srcAndMaskPtr = data->shape;
1051 const uint8_t *srcShapePtr = data->shape + ((andMaskSize + 3) & ~3);
1052
1053#if defined (__WIN__)
1054
1055 BITMAPV5HEADER bi;
1056 HBITMAP hBitmap;
1057 void *lpBits;
1058 HCURSOR hAlphaCursor = NULL;
1059
1060 ::ZeroMemory (&bi, sizeof (BITMAPV5HEADER));
1061 bi.bV5Size = sizeof (BITMAPV5HEADER);
1062 bi.bV5Width = data->width;
1063 bi.bV5Height = - (LONG) data->height;
1064 bi.bV5Planes = 1;
1065 bi.bV5BitCount = 32;
1066 bi.bV5Compression = BI_BITFIELDS;
1067 // specifiy a supported 32 BPP alpha format for Windows XP
1068 bi.bV5RedMask = 0x00FF0000;
1069 bi.bV5GreenMask = 0x0000FF00;
1070 bi.bV5BlueMask = 0x000000FF;
1071 if (data->alpha)
1072 bi.bV5AlphaMask = 0xFF000000;
1073 else
1074 bi.bV5AlphaMask = 0;
1075
1076 HDC hdc = ::GetDC (NULL);
1077
1078 // create the DIB section with an alpha channel
1079 hBitmap = ::CreateDIBSection (hdc, (BITMAPINFO *) &bi, DIB_RGB_COLORS,
1080 (void **) &lpBits, NULL, (DWORD) 0);
1081
1082 ::ReleaseDC (NULL, hdc);
1083
1084 HBITMAP hMonoBitmap = NULL;
1085 if (data->alpha)
1086 {
1087 // create an empty mask bitmap
1088 hMonoBitmap = ::CreateBitmap (data->width, data->height, 1, 1, NULL);
1089 }
1090 else
1091 {
1092 // for now, we assert if width is not multiple of 16. the
1093 // alternative is to manually align the AND mask to 16 bits.
1094 AssertMsg (!(data->width % 16), ("AND mask must be word-aligned!\n"));
1095
1096 // create the AND mask bitmap
1097 hMonoBitmap = ::CreateBitmap (data->width, data->height, 1, 1,
1098 srcAndMaskPtr);
1099 }
1100
1101 Assert (hBitmap);
1102 Assert (hMonoBitmap);
1103 if (hBitmap && hMonoBitmap)
1104 {
1105 DWORD *dstShapePtr = (DWORD *) lpBits;
1106
1107 for (uint32_t y = 0; y < data->height; y ++)
1108 {
1109 memcpy (dstShapePtr, srcShapePtr, srcShapePtrScan);
1110 srcShapePtr += srcShapePtrScan;
1111 dstShapePtr += data->width;
1112 }
1113
1114 ICONINFO ii;
1115 ii.fIcon = FALSE;
1116 ii.xHotspot = data->xHot;
1117 ii.yHotspot = data->yHot;
1118 ii.hbmMask = hMonoBitmap;
1119 ii.hbmColor = hBitmap;
1120
1121 hAlphaCursor = ::CreateIconIndirect (&ii);
1122 Assert (hAlphaCursor);
1123 if (hAlphaCursor)
1124 {
1125 // here we do a dirty trick by substituting a Window Manager's
1126 // cursor handle with the handle we created
1127
1128 WMcursor *old_wm_cursor = gpCustomCursor->wm_cursor;
1129
1130 // see SDL12/src/video/wincommon/SDL_sysmouse.c
1131 void *wm_cursor = malloc (sizeof (HCURSOR) + sizeof (uint8_t *) * 2);
1132 *(HCURSOR *) wm_cursor = hAlphaCursor;
1133
1134 gpCustomCursor->wm_cursor = (WMcursor *) wm_cursor;
1135 SDL_SetCursor (gpCustomCursor);
1136 SDL_ShowCursor (SDL_ENABLE);
1137
1138 if (old_wm_cursor)
1139 {
1140 ::DestroyCursor (* (HCURSOR *) old_wm_cursor);
1141 free (old_wm_cursor);
1142 }
1143
1144 ok = true;
1145 }
1146 }
1147
1148 if (hMonoBitmap)
1149 ::DeleteObject (hMonoBitmap);
1150 if (hBitmap)
1151 ::DeleteObject (hBitmap);
1152
1153#elif defined(VBOXBFE_WITH_X11)
1154
1155 XcursorImage *img = XcursorImageCreate (data->width, data->height);
1156 Assert (img);
1157 if (img)
1158 {
1159 img->xhot = data->xHot;
1160 img->yhot = data->yHot;
1161
1162 XcursorPixel *dstShapePtr = img->pixels;
1163
1164 for (uint32_t y = 0; y < data->height; y ++)
1165 {
1166 memcpy (dstShapePtr, srcShapePtr, srcShapePtrScan);
1167
1168 if (!data->alpha)
1169 {
1170 // convert AND mask to the alpha channel
1171 uint8_t byte = 0;
1172 for (uint32_t x = 0; x < data->width; x ++)
1173 {
1174 if (!(x % 8))
1175 byte = *(srcAndMaskPtr ++);
1176 else
1177 byte <<= 1;
1178
1179 if (byte & 0x80)
1180 {
1181 // X11 doesn't support inverted pixels (XOR ops,
1182 // to be exact) in cursor shapes, so we detect such
1183 // pixels and always replace them with black ones to
1184 // make them visible at least over light colors
1185 if (dstShapePtr [x] & 0x00FFFFFF)
1186 dstShapePtr [x] = 0xFF000000;
1187 else
1188 dstShapePtr [x] = 0x00000000;
1189 }
1190 else
1191 dstShapePtr [x] |= 0xFF000000;
1192 }
1193 }
1194
1195 srcShapePtr += srcShapePtrScan;
1196 dstShapePtr += data->width;
1197 }
1198
1199 Cursor cur = XcursorImageLoadCursor (gSdlInfo.info.x11.display, img);
1200 Assert (cur);
1201 if (cur)
1202 {
1203 // here we do a dirty trick by substituting a Window Manager's
1204 // cursor handle with the handle we created
1205
1206 WMcursor *old_wm_cursor = gpCustomCursor->wm_cursor;
1207
1208 // see SDL12/src/video/x11/SDL_x11mouse.c
1209 void *wm_cursor = malloc (sizeof (Cursor));
1210 *(Cursor *) wm_cursor = cur;
1211
1212 gpCustomCursor->wm_cursor = (WMcursor *) wm_cursor;
1213 SDL_SetCursor (gpCustomCursor);
1214 SDL_ShowCursor (SDL_ENABLE);
1215
1216 if (old_wm_cursor)
1217 {
1218 XFreeCursor (gSdlInfo.info.x11.display, *(Cursor *) old_wm_cursor);
1219 free (old_wm_cursor);
1220 }
1221
1222 ok = true;
1223 }
1224
1225 XcursorImageDestroy (img);
1226 }
1227
1228#endif /* VBOXBFE_WITH_X11 */
1229
1230 if (!ok)
1231 {
1232 SDL_SetCursor (gpDefaultCursor);
1233 SDL_ShowCursor (SDL_ENABLE);
1234 }
1235 }
1236 else
1237 {
1238 if (data->visible)
1239 {
1240 SDL_ShowCursor (SDL_ENABLE);
1241 }
1242 else
1243 {
1244 SDL_ShowCursor (SDL_DISABLE);
1245 }
1246 }
1247}
1248
1249void SDLConsole::resetCursor(void)
1250{
1251 SDL_SetCursor (gpDefaultCursor);
1252 SDL_ShowCursor (SDL_ENABLE);
1253}
1254
1255/**
1256 * Handles a host key down event
1257 */
1258int SDLConsole::handleHostKey(const SDL_KeyboardEvent *pEv)
1259{
1260 /*
1261 * Revalidate the host key modifier
1262 */
1263 if ((SDL_GetModState() & ~(KMOD_MODE | KMOD_NUM | KMOD_RESERVED)) != gHostKey)
1264 return VERR_NOT_SUPPORTED;
1265
1266 /*
1267 * What was pressed?
1268 */
1269 switch (pEv->keysym.sym)
1270 {
1271 /* Control-Alt-Delete */
1272 case SDLK_DELETE:
1273 {
1274 gKeyboard->PutCAD();
1275 break;
1276 }
1277
1278 /*
1279 * Fullscreen / Windowed toggle.
1280 */
1281 case SDLK_f:
1282 {
1283 if (gfAllowFullscreenToggle)
1284 {
1285 gFramebuffer->setFullscreen(!gFramebuffer->getFullscreen());
1286
1287 /*
1288 * We have switched from/to fullscreen, so request a full
1289 * screen repaint, just to be sure.
1290 */
1291 gDisplay->InvalidateAndUpdate();
1292 }
1293 break;
1294 }
1295
1296 /*
1297 * Pause / Resume toggle.
1298 */
1299 case SDLK_p:
1300 {
1301 if (machineState == VMSTATE_RUNNING)
1302 {
1303 if (fInputGrab)
1304 inputGrabEnd();
1305
1306 PVMREQ pReq;
1307 int rcVBox = VMR3ReqCall(pVM, &pReq, RT_INDEFINITE_WAIT,
1308 (PFNRT)VMR3Suspend, 1, pVM);
1309 AssertRC(rcVBox);
1310 if (VBOX_SUCCESS(rcVBox))
1311 {
1312 rcVBox = pReq->iStatus;
1313 VMR3ReqFree(pReq);
1314 }
1315 }
1316 else
1317 if (machineState == VMSTATE_SUSPENDED)
1318 {
1319 PVMREQ pReq;
1320 int rcVBox = VMR3ReqCall(pVM, &pReq, RT_INDEFINITE_WAIT,
1321 (PFNRT)VMR3Resume, 1, pVM);
1322 AssertRC(rcVBox);
1323 if (VBOX_SUCCESS(rcVBox))
1324 {
1325 rcVBox = pReq->iStatus;
1326 VMR3ReqFree(pReq);
1327 }
1328 }
1329 updateTitlebar();
1330 break;
1331 }
1332
1333 /*
1334 * Reset the VM
1335 */
1336 case SDLK_r:
1337 {
1338 PVMREQ pReq;
1339 int rcVBox = VMR3ReqCall(pVM, &pReq, RT_INDEFINITE_WAIT,
1340 (PFNRT)VMR3Reset, 1, pVM);
1341 AssertRC(rcVBox);
1342 if (VBOX_SUCCESS(rcVBox))
1343 {
1344 rcVBox = pReq->iStatus;
1345 VMR3ReqFree(pReq);
1346 }
1347 break;
1348 }
1349
1350 /*
1351 * Terminate the VM
1352 */
1353 case SDLK_q:
1354 {
1355 return VINF_EM_TERMINATE;
1356 break;
1357 }
1358
1359#if 0
1360 /*
1361 * Save the machine's state and exit
1362 */
1363 case SDLK_s:
1364 {
1365 resetKeys();
1366 RTThreadYield();
1367 if (fInputGrab)
1368 inputGrabEnd();
1369 RTThreadYield();
1370 updateTitlebarSave(0);
1371 gProgress = NULL;
1372 int rc = gConsole->SaveState(gProgress.asOutParam());
1373 if (rc != S_OK)
1374 {
1375 RTPrintf("Error saving state! rc = 0x%x\n", rc);
1376 return VINF_EM_TERMINATE;
1377 }
1378 Assert(gProgress);
1379
1380 /*
1381 * Wait for the operation to be completed and work
1382 * the title bar in the mean while.
1383 */
1384 LONG cPercent = 0;
1385 for (;;)
1386 {
1387 BOOL fCompleted;
1388 rc = gProgress->COMGETTER(Completed)(&fCompleted);
1389 if (FAILED(rc) || fCompleted)
1390 break;
1391 LONG cPercentNow;
1392 rc = gProgress->COMGETTER(Percent)(&cPercentNow);
1393 if (FAILED(rc))
1394 break;
1395 if (cPercentNow != cPercent)
1396 {
1397 UpdateTitlebarSave(cPercent);
1398 cPercent = cPercentNow;
1399 }
1400
1401 /* wait */
1402 rc = gProgress->WaitForCompletion(100);
1403 if (FAILED(rc))
1404 break;
1405 /// @todo process gui events.
1406 }
1407
1408 /*
1409 * What's the result of the operation?
1410 */
1411 HRESULT lrc;
1412 rc = gProgress->COMGETTER(ResultCode)(&lrc);
1413 if (FAILED(rc))
1414 lrc = ~0;
1415 if (!lrc)
1416 {
1417 UpdateTitlebarSave(100);
1418 RTThreadYield();
1419 RTPrintf("Saved the state successfully.\n");
1420 }
1421 else
1422 RTPrintf("Error saving state, lrc=%d (%#x)\n", lrc, lrc);
1423 return VINF_EM_TERMINATE;
1424 }
1425#endif
1426 /*
1427 * Not a host key combination.
1428 * Indicate this by returning false.
1429 */
1430 default:
1431 return VERR_NOT_SUPPORTED;
1432 }
1433
1434 return VINF_SUCCESS;
1435}
1436
1437
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