VirtualBox

source: vbox/trunk/src/VBox/Additions/x11/VBoxClient/display-svga-x11.cpp@ 97956

最後變更 在這個檔案從97956是 96876,由 vboxsync 提交於 2 年 前

Additions: X11: Move Wayland env detection to common place, bugref:10134.

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 56.5 KB
 
1/* $Id: display-svga-x11.cpp 96876 2022-09-26 16:11:19Z vboxsync $ */
2/** @file
3 * X11 guest client - VMSVGA emulation resize event pass-through to X.Org
4 * guest driver.
5 */
6
7/*
8 * Copyright (C) 2017-2022 Oracle and/or its affiliates.
9 *
10 * This file is part of VirtualBox base platform packages, as
11 * available from https://www.alldomusa.eu.org.
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation, in version 3 of the
16 * License.
17 *
18 * This program is distributed in the hope that it will be useful, but
19 * WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, see <https://www.gnu.org/licenses>.
25 *
26 * SPDX-License-Identifier: GPL-3.0-only
27 */
28
29/*
30 * Known things to test when changing this code. All assume a guest with VMSVGA
31 * active and controlled by X11 or Wayland, and Guest Additions installed and
32 * running, unless otherwise stated.
33 * - On Linux 4.6 and later guests, VBoxClient --vmsvga should be running as
34 * root and not as the logged-in user. Dynamic resizing should work for all
35 * screens in any environment which handles kernel resize notifications,
36 * including at log-in screens. Test GNOME Shell Wayland and GNOME Shell
37 * under X.Org or Unity or KDE at the log-in screen and after log-in.
38 * - Linux 4.10 changed the user-kernel-ABI introduced in 4.6: test both.
39 * - On other guests (than Linux 4.6 or later) running X.Org Server 1.3 or
40 * later, VBoxClient --vmsvga should never be running as root, and should run
41 * (and dynamic resizing and screen enable/disable should work for all
42 * screens) whenever a user is logged in to a supported desktop environment.
43 * - On guests running X.Org Server 1.2 or older, VBoxClient --vmsvga should
44 * never run as root and should run whenever a user is logged in to a
45 * supported desktop environment. Dynamic resizing should work for the first
46 * screen, and enabling others should not be possible.
47 * - When VMSVGA is not enabled, VBoxClient --vmsvga should never stay running.
48 * - The following assumptions are done and should be taken into account when reading/chaning the code:
49 * # The order of the outputs (monitors) is assumed to be the same in RANDROUTPUT array and
50 XRRScreenResources.outputs array.
51 * - This code does 2 related but separate things: 1- It resizes and enables/disables monitors upon host's
52 requests (see the infinite loop in run()). 2- it listens to RandR events (caused by this or any other X11 client)
53 on a different thread and notifies host about the new monitor positions. See sendMonitorPositions(...). This is
54 mainly a work around since we have realized that vmsvga does not convey correct monitor positions thru FIFO.
55 */
56#include <stdio.h>
57#include <dlfcn.h>
58/** For sleep(..) */
59#include <unistd.h>
60#include "VBoxClient.h"
61
62#include <VBox/VBoxGuestLib.h>
63
64#include <iprt/asm.h>
65#include <iprt/assert.h>
66#include <iprt/err.h>
67#include <iprt/file.h>
68#include <iprt/mem.h>
69#include <iprt/path.h>
70#include <iprt/string.h>
71#include <iprt/thread.h>
72#include <iprt/env.h>
73
74#include <X11/Xlibint.h>
75#include <X11/extensions/Xrandr.h>
76#include <X11/extensions/panoramiXproto.h>
77
78#define MILLIS_PER_INCH (25.4)
79#define DEFAULT_DPI (96.0)
80
81/* Time in milliseconds to relax if no X11 events available. */
82#define VBOX_SVGA_X11_RELAX_TIME_MS (500)
83/* Time in milliseconds to wait for host events. */
84#define VBOX_SVGA_HOST_EVENT_RX_TIMEOUT_MS (500)
85
86/** Maximum number of supported screens. DRM and X11 both limit this to 32. */
87/** @todo if this ever changes, dynamically allocate resizeable arrays in the
88 * context structure. */
89#define VMW_MAX_HEADS 32
90/** Monitor positions array. Allocated here and deallocated in the class descructor. */
91RTPOINT *mpMonitorPositions;
92/** Thread to listen to some of the X server events. */
93RTTHREAD mX11MonitorThread = NIL_RTTHREAD;
94/** Shutdown indicator for the monitor thread. */
95static bool g_fMonitorThreadShutdown = false;
96
97#define X_VMwareCtrlSetRes 1
98
99typedef struct
100{
101 CARD8 reqType;
102 CARD8 VMwareCtrlReqType;
103 CARD16 length B16;
104 CARD32 screen B32;
105 CARD32 x B32;
106 CARD32 y B32;
107} xVMwareCtrlSetResReq;
108#define sz_xVMwareCtrlSetResReq 16
109
110typedef struct
111{
112 BYTE type;
113 BYTE pad1;
114 CARD16 sequenceNumber B16;
115 CARD32 length B32;
116 CARD32 screen B32;
117 CARD32 x B32;
118 CARD32 y B32;
119 CARD32 pad2 B32;
120 CARD32 pad3 B32;
121 CARD32 pad4 B32;
122} xVMwareCtrlSetResReply;
123#define sz_xVMwareCtrlSetResReply 32
124
125typedef struct {
126 CARD8 reqType; /* always X_VMwareCtrlReqCode */
127 CARD8 VMwareCtrlReqType; /* always X_VMwareCtrlSetTopology */
128 CARD16 length B16;
129 CARD32 screen B32;
130 CARD32 number B32;
131 CARD32 pad1 B32;
132} xVMwareCtrlSetTopologyReq;
133#define sz_xVMwareCtrlSetTopologyReq 16
134
135#define X_VMwareCtrlSetTopology 2
136
137typedef struct {
138 BYTE type; /* X_Reply */
139 BYTE pad1;
140 CARD16 sequenceNumber B16;
141 CARD32 length B32;
142 CARD32 screen B32;
143 CARD32 pad2 B32;
144 CARD32 pad3 B32;
145 CARD32 pad4 B32;
146 CARD32 pad5 B32;
147 CARD32 pad6 B32;
148} xVMwareCtrlSetTopologyReply;
149#define sz_xVMwareCtrlSetTopologyReply 32
150
151struct X11VMWRECT
152{
153 int16_t x;
154 int16_t y;
155 uint16_t w;
156 uint16_t h;
157};
158AssertCompileSize(struct X11VMWRECT, 8);
159
160struct X11CONTEXT
161{
162 Display *pDisplay;
163 /* We use a separate connection for randr event listening since sharing a
164 single display object with resizing (main) and event listening threads ends up having a deadlock.*/
165 Display *pDisplayRandRMonitoring;
166 Window rootWindow;
167 int iDefaultScreen;
168 XRRScreenResources *pScreenResources;
169 int hRandRMajor;
170 int hRandRMinor;
171 int hRandREventBase;
172 int hRandRErrorBase;
173 int hEventMask;
174 bool fMonitorInfoAvailable;
175 /** The number of outputs (monitors, including disconnect ones) xrandr reports. */
176 int hOutputCount;
177 void *pRandLibraryHandle;
178 bool fWmwareCtrlExtention;
179 int hVMWCtrlMajorOpCode;
180 /** Function pointers we used if we dlopen libXrandr instead of linking. */
181 void (*pXRRSelectInput) (Display *, Window, int);
182 Bool (*pXRRQueryExtension) (Display *, int *, int *);
183 Status (*pXRRQueryVersion) (Display *, int *, int*);
184 XRRMonitorInfo* (*pXRRGetMonitors)(Display *, Window, Bool, int *);
185 XRRScreenResources* (*pXRRGetScreenResources)(Display *, Window);
186 Status (*pXRRSetCrtcConfig)(Display *, XRRScreenResources *, RRCrtc,
187 Time, int, int, RRMode, Rotation, RROutput *, int);
188 void (*pXRRFreeMonitors)(XRRMonitorInfo *);
189 void (*pXRRFreeScreenResources)(XRRScreenResources *);
190 void (*pXRRFreeModeInfo)(XRRModeInfo *);
191 void (*pXRRFreeOutputInfo)(XRROutputInfo *);
192 void (*pXRRSetScreenSize)(Display *, Window, int, int, int, int);
193 int (*pXRRUpdateConfiguration)(XEvent *event);
194 XRRModeInfo* (*pXRRAllocModeInfo)(_Xconst char *, int);
195 RRMode (*pXRRCreateMode) (Display *, Window, XRRModeInfo *);
196 XRROutputInfo* (*pXRRGetOutputInfo) (Display *, XRRScreenResources *, RROutput);
197 XRRCrtcInfo* (*pXRRGetCrtcInfo) (Display *, XRRScreenResources *, RRCrtc crtc);
198 void (*pXRRFreeCrtcInfo)(XRRCrtcInfo *);
199 void (*pXRRAddOutputMode)(Display *, RROutput, RRMode);
200 void (*pXRRDeleteOutputMode)(Display *, RROutput, RRMode);
201 void (*pXRRDestroyMode)(Display *, RRMode);
202 void (*pXRRSetOutputPrimary)(Display *, Window, RROutput);
203};
204
205static X11CONTEXT x11Context;
206
207struct RANDROUTPUT
208{
209 int32_t x;
210 int32_t y;
211 uint32_t width;
212 uint32_t height;
213 bool fEnabled;
214 bool fPrimary;
215};
216
217struct DisplayModeR {
218
219 /* These are the values that the user sees/provides */
220 int Clock; /* pixel clock freq (kHz) */
221 int HDisplay; /* horizontal timing */
222 int HSyncStart;
223 int HSyncEnd;
224 int HTotal;
225 int HSkew;
226 int VDisplay; /* vertical timing */
227 int VSyncStart;
228 int VSyncEnd;
229 int VTotal;
230 int VScan;
231 float HSync;
232 float VRefresh;
233};
234
235/** Forward declarations. */
236static void x11Connect();
237static int determineOutputCount();
238
239#define checkFunctionPtrReturn(pFunction) \
240 do { \
241 if (!pFunction) \
242 { \
243 VBClLogFatalError("Could not find symbol address (%s)\n", #pFunction); \
244 dlclose(x11Context.pRandLibraryHandle); \
245 x11Context.pRandLibraryHandle = NULL; \
246 return VERR_NOT_FOUND; \
247 } \
248 } while (0)
249
250#define checkFunctionPtr(pFunction) \
251 do { \
252 if (!pFunction) \
253 VBClLogError("Could not find symbol address (%s)\n", #pFunction);\
254 } while (0)
255
256
257/** A slightly modified version of the xf86CVTMode function from xf86cvt.c
258 * from the xserver source code. Computes several parameters of a display mode
259 * out of horizontal and vertical resolutions. Replicated here to avoid further
260 * dependencies. */
261DisplayModeR f86CVTMode(int HDisplay, int VDisplay, float VRefresh /* Herz */, Bool Reduced,
262 Bool Interlaced)
263{
264 DisplayModeR Mode;
265
266 /* 1) top/bottom margin size (% of height) - default: 1.8 */
267#define CVT_MARGIN_PERCENTAGE 1.8
268
269 /* 2) character cell horizontal granularity (pixels) - default 8 */
270#define CVT_H_GRANULARITY 8
271
272 /* 4) Minimum vertical porch (lines) - default 3 */
273#define CVT_MIN_V_PORCH 3
274
275 /* 4) Minimum number of vertical back porch lines - default 6 */
276#define CVT_MIN_V_BPORCH 6
277
278 /* Pixel Clock step (kHz) */
279#define CVT_CLOCK_STEP 250
280
281 Bool Margins = false;
282 float VFieldRate, HPeriod;
283 int HDisplayRnd, HMargin;
284 int VDisplayRnd, VMargin, VSync;
285 float Interlace; /* Please rename this */
286
287 /* CVT default is 60.0Hz */
288 if (!VRefresh)
289 VRefresh = 60.0;
290
291 /* 1. Required field rate */
292 if (Interlaced)
293 VFieldRate = VRefresh * 2;
294 else
295 VFieldRate = VRefresh;
296
297 /* 2. Horizontal pixels */
298 HDisplayRnd = HDisplay - (HDisplay % CVT_H_GRANULARITY);
299
300 /* 3. Determine left and right borders */
301 if (Margins) {
302 /* right margin is actually exactly the same as left */
303 HMargin = (((float) HDisplayRnd) * CVT_MARGIN_PERCENTAGE / 100.0);
304 HMargin -= HMargin % CVT_H_GRANULARITY;
305 }
306 else
307 HMargin = 0;
308
309 /* 4. Find total active pixels */
310 Mode.HDisplay = HDisplayRnd + 2 * HMargin;
311
312 /* 5. Find number of lines per field */
313 if (Interlaced)
314 VDisplayRnd = VDisplay / 2;
315 else
316 VDisplayRnd = VDisplay;
317
318 /* 6. Find top and bottom margins */
319 /* nope. */
320 if (Margins)
321 /* top and bottom margins are equal again. */
322 VMargin = (((float) VDisplayRnd) * CVT_MARGIN_PERCENTAGE / 100.0);
323 else
324 VMargin = 0;
325
326 Mode.VDisplay = VDisplay + 2 * VMargin;
327
328 /* 7. Interlace */
329 if (Interlaced)
330 Interlace = 0.5;
331 else
332 Interlace = 0.0;
333
334 /* Determine VSync Width from aspect ratio */
335 if (!(VDisplay % 3) && ((VDisplay * 4 / 3) == HDisplay))
336 VSync = 4;
337 else if (!(VDisplay % 9) && ((VDisplay * 16 / 9) == HDisplay))
338 VSync = 5;
339 else if (!(VDisplay % 10) && ((VDisplay * 16 / 10) == HDisplay))
340 VSync = 6;
341 else if (!(VDisplay % 4) && ((VDisplay * 5 / 4) == HDisplay))
342 VSync = 7;
343 else if (!(VDisplay % 9) && ((VDisplay * 15 / 9) == HDisplay))
344 VSync = 7;
345 else /* Custom */
346 VSync = 10;
347
348 if (!Reduced) { /* simplified GTF calculation */
349
350 /* 4) Minimum time of vertical sync + back porch interval (µs)
351 * default 550.0 */
352#define CVT_MIN_VSYNC_BP 550.0
353
354 /* 3) Nominal HSync width (% of line period) - default 8 */
355#define CVT_HSYNC_PERCENTAGE 8
356
357 float HBlankPercentage;
358 int VSyncAndBackPorch, VBackPorch;
359 int HBlank;
360
361 /* 8. Estimated Horizontal period */
362 HPeriod = ((float) (1000000.0 / VFieldRate - CVT_MIN_VSYNC_BP)) /
363 (VDisplayRnd + 2 * VMargin + CVT_MIN_V_PORCH + Interlace);
364
365 /* 9. Find number of lines in sync + backporch */
366 if (((int) (CVT_MIN_VSYNC_BP / HPeriod) + 1) <
367 (VSync + CVT_MIN_V_PORCH))
368 VSyncAndBackPorch = VSync + CVT_MIN_V_PORCH;
369 else
370 VSyncAndBackPorch = (int) (CVT_MIN_VSYNC_BP / HPeriod) + 1;
371
372 /* 10. Find number of lines in back porch */
373 VBackPorch = VSyncAndBackPorch - VSync;
374 (void) VBackPorch;
375
376 /* 11. Find total number of lines in vertical field */
377 Mode.VTotal = VDisplayRnd + 2 * VMargin + VSyncAndBackPorch + Interlace
378 + CVT_MIN_V_PORCH;
379
380 /* 5) Definition of Horizontal blanking time limitation */
381 /* Gradient (%/kHz) - default 600 */
382#define CVT_M_FACTOR 600
383
384 /* Offset (%) - default 40 */
385#define CVT_C_FACTOR 40
386
387 /* Blanking time scaling factor - default 128 */
388#define CVT_K_FACTOR 128
389
390 /* Scaling factor weighting - default 20 */
391#define CVT_J_FACTOR 20
392
393#define CVT_M_PRIME CVT_M_FACTOR * CVT_K_FACTOR / 256
394#define CVT_C_PRIME (CVT_C_FACTOR - CVT_J_FACTOR) * CVT_K_FACTOR / 256 + \
395 CVT_J_FACTOR
396
397 /* 12. Find ideal blanking duty cycle from formula */
398 HBlankPercentage = CVT_C_PRIME - CVT_M_PRIME * HPeriod / 1000.0;
399
400 /* 13. Blanking time */
401 if (HBlankPercentage < 20)
402 HBlankPercentage = 20;
403
404 HBlank = Mode.HDisplay * HBlankPercentage / (100.0 - HBlankPercentage);
405 HBlank -= HBlank % (2 * CVT_H_GRANULARITY);
406
407 /* 14. Find total number of pixels in a line. */
408 Mode.HTotal = Mode.HDisplay + HBlank;
409
410 /* Fill in HSync values */
411 Mode.HSyncEnd = Mode.HDisplay + HBlank / 2;
412
413 Mode.HSyncStart = Mode.HSyncEnd -
414 (Mode.HTotal * CVT_HSYNC_PERCENTAGE) / 100;
415 Mode.HSyncStart += CVT_H_GRANULARITY -
416 Mode.HSyncStart % CVT_H_GRANULARITY;
417
418 /* Fill in VSync values */
419 Mode.VSyncStart = Mode.VDisplay + CVT_MIN_V_PORCH;
420 Mode.VSyncEnd = Mode.VSyncStart + VSync;
421
422 }
423 else { /* Reduced blanking */
424 /* Minimum vertical blanking interval time (µs) - default 460 */
425#define CVT_RB_MIN_VBLANK 460.0
426
427 /* Fixed number of clocks for horizontal sync */
428#define CVT_RB_H_SYNC 32.0
429
430 /* Fixed number of clocks for horizontal blanking */
431#define CVT_RB_H_BLANK 160.0
432
433 /* Fixed number of lines for vertical front porch - default 3 */
434#define CVT_RB_VFPORCH 3
435
436 int VBILines;
437
438 /* 8. Estimate Horizontal period. */
439 HPeriod = ((float) (1000000.0 / VFieldRate - CVT_RB_MIN_VBLANK)) /
440 (VDisplayRnd + 2 * VMargin);
441
442 /* 9. Find number of lines in vertical blanking */
443 VBILines = ((float) CVT_RB_MIN_VBLANK) / HPeriod + 1;
444
445 /* 10. Check if vertical blanking is sufficient */
446 if (VBILines < (CVT_RB_VFPORCH + VSync + CVT_MIN_V_BPORCH))
447 VBILines = CVT_RB_VFPORCH + VSync + CVT_MIN_V_BPORCH;
448
449 /* 11. Find total number of lines in vertical field */
450 Mode.VTotal = VDisplayRnd + 2 * VMargin + Interlace + VBILines;
451
452 /* 12. Find total number of pixels in a line */
453 Mode.HTotal = Mode.HDisplay + CVT_RB_H_BLANK;
454
455 /* Fill in HSync values */
456 Mode.HSyncEnd = Mode.HDisplay + CVT_RB_H_BLANK / 2;
457 Mode.HSyncStart = Mode.HSyncEnd - CVT_RB_H_SYNC;
458
459 /* Fill in VSync values */
460 Mode.VSyncStart = Mode.VDisplay + CVT_RB_VFPORCH;
461 Mode.VSyncEnd = Mode.VSyncStart + VSync;
462 }
463 /* 15/13. Find pixel clock frequency (kHz for xf86) */
464 Mode.Clock = Mode.HTotal * 1000.0 / HPeriod;
465 Mode.Clock -= Mode.Clock % CVT_CLOCK_STEP;
466
467 /* 16/14. Find actual Horizontal Frequency (kHz) */
468 Mode.HSync = ((float) Mode.Clock) / ((float) Mode.HTotal);
469
470 /* 17/15. Find actual Field rate */
471 Mode.VRefresh = (1000.0 * ((float) Mode.Clock)) /
472 ((float) (Mode.HTotal * Mode.VTotal));
473
474 /* 18/16. Find actual vertical frame frequency */
475 /* ignore - just set the mode flag for interlaced */
476 if (Interlaced)
477 Mode.VTotal *= 2;
478 return Mode;
479}
480
481#ifdef RT_OS_SOLARIS
482static bool VMwareCtrlSetRes(
483 Display *dpy, int hExtensionMajorOpcode, int screen, int x, int y)
484{
485 xVMwareCtrlSetResReply rep;
486 xVMwareCtrlSetResReq *pReq;
487 bool fResult = false;
488
489 LockDisplay(dpy);
490
491 GetReq(VMwareCtrlSetRes, pReq);
492 AssertPtrReturn(pReq, false);
493
494 pReq->reqType = hExtensionMajorOpcode;
495 pReq->VMwareCtrlReqType = X_VMwareCtrlSetRes;
496 pReq->screen = screen;
497 pReq->x = x;
498 pReq->y = y;
499
500 fResult = !!_XReply(dpy, (xReply *)&rep, (SIZEOF(xVMwareCtrlSetResReply) - SIZEOF(xReply)) >> 2, xFalse);
501
502 UnlockDisplay(dpy);
503
504 return fResult;
505}
506#endif /* RT_OS_SOLARIS */
507
508/** Makes a call to vmwarectrl extension. This updates the
509 * connection information and possible resolutions (modes)
510 * of each monitor on the driver. Also sets the preferred mode
511 * of each output (monitor) to currently selected one. */
512bool VMwareCtrlSetTopology(Display *dpy, int hExtensionMajorOpcode,
513 int screen, xXineramaScreenInfo extents[], int number)
514{
515 xVMwareCtrlSetTopologyReply rep;
516 xVMwareCtrlSetTopologyReq *req;
517
518 long len;
519
520 LockDisplay(dpy);
521
522 GetReq(VMwareCtrlSetTopology, req);
523 req->reqType = hExtensionMajorOpcode;
524 req->VMwareCtrlReqType = X_VMwareCtrlSetTopology;
525 req->screen = screen;
526 req->number = number;
527
528 len = ((long) number) << 1;
529 SetReqLen(req, len, len);
530 len <<= 2;
531 _XSend(dpy, (char *)extents, len);
532
533 if (!_XReply(dpy, (xReply *)&rep,
534 (SIZEOF(xVMwareCtrlSetTopologyReply) - SIZEOF(xReply)) >> 2,
535 xFalse))
536 {
537 UnlockDisplay(dpy);
538 SyncHandle();
539 return false;
540 }
541 UnlockDisplay(dpy);
542 SyncHandle();
543 return true;
544}
545
546/** This function assumes monitors are named as from Virtual1 to VirtualX. */
547static int getMonitorIdFromName(const char *sMonitorName)
548{
549 if (!sMonitorName)
550 return -1;
551#ifdef RT_OS_SOLARIS
552 if (!strcmp(sMonitorName, "default"))
553 return 1;
554#endif
555 int iLen = strlen(sMonitorName);
556 if (iLen <= 0)
557 return -1;
558 int iBase = 10;
559 int iResult = 0;
560 for (int i = iLen - 1; i >= 0; --i)
561 {
562 /* Stop upon seeing the first non-numeric char. */
563 if (sMonitorName[i] < 48 || sMonitorName[i] > 57)
564 break;
565 iResult += (sMonitorName[i] - 48) * iBase / 10;
566 iBase *= 10;
567 }
568 return iResult;
569}
570
571static void sendMonitorPositions(RTPOINT *pPositions, size_t cPositions)
572{
573 if (cPositions && !pPositions)
574 {
575 VBClLogError(("Monitor position update called with NULL pointer!\n"));
576 return;
577 }
578 int rc = VbglR3SeamlessSendMonitorPositions(cPositions, pPositions);
579 if (RT_SUCCESS(rc))
580 VBClLogInfo("Sending monitor positions (%u of them) to the host: %Rrc\n", cPositions, rc);
581 else
582 VBClLogError("Error during sending monitor positions (%u of them) to the host: %Rrc\n", cPositions, rc);
583}
584
585static void queryMonitorPositions()
586{
587 static const int iSentinelPosition = -1;
588 if (mpMonitorPositions)
589 {
590 free(mpMonitorPositions);
591 mpMonitorPositions = NULL;
592 }
593
594 int iMonitorCount = 0;
595 XRRMonitorInfo *pMonitorInfo = NULL;
596#ifdef WITH_DISTRO_XRAND_XINERAMA
597 pMonitorInfo = XRRGetMonitors(x11Context.pDisplayRandRMonitoring,
598 DefaultRootWindow(x11Context.pDisplayRandRMonitoring), true, &iMonitorCount);
599#else
600 if (x11Context.pXRRGetMonitors)
601 pMonitorInfo = x11Context.pXRRGetMonitors(x11Context.pDisplayRandRMonitoring,
602 DefaultRootWindow(x11Context.pDisplayRandRMonitoring), true, &iMonitorCount);
603#endif
604 if (!pMonitorInfo)
605 return;
606 if (iMonitorCount == -1)
607 VBClLogError("Could not get monitor info\n");
608 else
609 {
610 mpMonitorPositions = (RTPOINT*)malloc(x11Context.hOutputCount * sizeof(RTPOINT));
611 /** @todo memset? */
612 for (int i = 0; i < x11Context.hOutputCount; ++i)
613 {
614 mpMonitorPositions[i].x = iSentinelPosition;
615 mpMonitorPositions[i].y = iSentinelPosition;
616 }
617 for (int i = 0; i < iMonitorCount; ++i)
618 {
619 char *pszMonitorName = XGetAtomName(x11Context.pDisplayRandRMonitoring, pMonitorInfo[i].name);
620 if (!pszMonitorName)
621 {
622 VBClLogError("queryMonitorPositions: skip monitor with unknown name %d\n", i);
623 continue;
624 }
625
626 int iMonitorID = getMonitorIdFromName(pszMonitorName) - 1;
627 XFree((void *)pszMonitorName);
628 pszMonitorName = NULL;
629
630 if (iMonitorID >= x11Context.hOutputCount || iMonitorID == -1)
631 {
632 VBClLogInfo("queryMonitorPositions: skip monitor %d (id %d) (w,h)=(%d,%d) (x,y)=(%d,%d)\n",
633 i, iMonitorID,
634 pMonitorInfo[i].width, pMonitorInfo[i].height,
635 pMonitorInfo[i].x, pMonitorInfo[i].y);
636 continue;
637 }
638 VBClLogInfo("Monitor %d (w,h)=(%d,%d) (x,y)=(%d,%d)\n",
639 i,
640 pMonitorInfo[i].width, pMonitorInfo[i].height,
641 pMonitorInfo[i].x, pMonitorInfo[i].y);
642 mpMonitorPositions[iMonitorID].x = pMonitorInfo[i].x;
643 mpMonitorPositions[iMonitorID].y = pMonitorInfo[i].y;
644 }
645 if (iMonitorCount > 0)
646 sendMonitorPositions(mpMonitorPositions, x11Context.hOutputCount);
647 }
648#ifdef WITH_DISTRO_XRAND_XINERAMA
649 XRRFreeMonitors(pMonitorInfo);
650#else
651 if (x11Context.pXRRFreeMonitors)
652 x11Context.pXRRFreeMonitors(pMonitorInfo);
653#endif
654}
655
656static void monitorRandREvents()
657{
658 XEvent event;
659
660 if (XPending(x11Context.pDisplayRandRMonitoring) > 0)
661 {
662 XNextEvent(x11Context.pDisplayRandRMonitoring, &event);
663 int eventTypeOffset = event.type - x11Context.hRandREventBase;
664 VBClLogInfo("received X11 event (%d)\n", event.type);
665 switch (eventTypeOffset)
666 {
667 case RRScreenChangeNotify:
668 VBClLogInfo("RRScreenChangeNotify event received\n");
669 queryMonitorPositions();
670 break;
671 default:
672 break;
673 }
674 } else
675 {
676 RTThreadSleep(VBOX_SVGA_X11_RELAX_TIME_MS);
677 }
678}
679
680/**
681 * @callback_method_impl{FNRTTHREAD}
682 */
683static DECLCALLBACK(int) x11MonitorThreadFunction(RTTHREAD ThreadSelf, void *pvUser)
684{
685 RT_NOREF(ThreadSelf, pvUser);
686 while (!ASMAtomicReadBool(&g_fMonitorThreadShutdown))
687 {
688 monitorRandREvents();
689 }
690
691 VBClLogInfo("X11 thread gracefully terminated\n");
692
693 return 0;
694}
695
696static int startX11MonitorThread()
697{
698 int rc;
699 Assert(g_fMonitorThreadShutdown == false);
700 if (mX11MonitorThread == NIL_RTTHREAD)
701 {
702 rc = RTThreadCreate(&mX11MonitorThread, x11MonitorThreadFunction, NULL /*pvUser*/, 0 /*cbStack*/,
703 RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE, "X11 events");
704 if (RT_FAILURE(rc))
705 VBClLogFatalError("Warning: failed to start X11 monitor thread (VBoxClient) rc=%Rrc!\n", rc);
706 }
707 else
708 rc = VINF_ALREADY_INITIALIZED;
709 return rc;
710}
711
712static int stopX11MonitorThread(void)
713{
714 int rc = VINF_SUCCESS;
715 if (mX11MonitorThread != NIL_RTTHREAD)
716 {
717 ASMAtomicWriteBool(&g_fMonitorThreadShutdown, true);
718 /** @todo Send event to thread to get it out of XNextEvent. */
719 //????????
720 //mX11Monitor.interruptEventWait();
721 rc = RTThreadWait(mX11MonitorThread, RT_MS_1SEC, NULL /*prc*/);
722 if (RT_SUCCESS(rc))
723 {
724 mX11MonitorThread = NIL_RTTHREAD;
725 g_fMonitorThreadShutdown = false;
726 }
727 else
728 VBClLogError("Failed to stop X11 monitor thread, rc=%Rrc!\n", rc);
729 }
730 return rc;
731}
732
733static bool callVMWCTRL(struct RANDROUTPUT *paOutputs)
734{
735 int hHeight = 600;
736 int hWidth = 800;
737 bool fResult = false;
738 int idxDefaultScreen = DefaultScreen(x11Context.pDisplay);
739
740 AssertReturn(idxDefaultScreen >= 0, false);
741 AssertReturn(idxDefaultScreen < x11Context.hOutputCount, false);
742
743 xXineramaScreenInfo *extents = (xXineramaScreenInfo *)malloc(x11Context.hOutputCount * sizeof(xXineramaScreenInfo));
744 if (!extents)
745 return false;
746 int hRunningOffset = 0;
747 for (int i = 0; i < x11Context.hOutputCount; ++i)
748 {
749 if (paOutputs[i].fEnabled)
750 {
751 hHeight = paOutputs[i].height;
752 hWidth = paOutputs[i].width;
753 }
754 else
755 {
756 hHeight = 0;
757 hWidth = 0;
758 }
759 extents[i].x_org = hRunningOffset;
760 extents[i].y_org = 0;
761 extents[i].width = hWidth;
762 extents[i].height = hHeight;
763 hRunningOffset += hWidth;
764 }
765#ifdef RT_OS_SOLARIS
766 fResult = VMwareCtrlSetRes(x11Context.pDisplay, x11Context.hVMWCtrlMajorOpCode,
767 idxDefaultScreen, extents[idxDefaultScreen].width,
768 extents[idxDefaultScreen].height);
769#else
770 fResult = VMwareCtrlSetTopology(x11Context.pDisplay, x11Context.hVMWCtrlMajorOpCode,
771 idxDefaultScreen, extents, x11Context.hOutputCount);
772#endif
773 free(extents);
774 return fResult;
775}
776
777/**
778 * @interface_method_impl{VBCLSERVICE,pfnInit}
779 */
780static DECLCALLBACK(int) vbclSVGAInit(void)
781{
782 int rc;
783
784 /* In 32-bit guests GAs build on our release machines causes an xserver hang.
785 * So for 32-bit GAs we use our DRM client. */
786#if ARCH_BITS == 32
787 rc = VbglR3DrmClientStart();
788 if (RT_FAILURE(rc))
789 VBClLogError("Starting DRM resizing client (32-bit) failed with %Rrc\n", rc);
790 return VERR_NOT_AVAILABLE; /** @todo r=andy Why ignoring rc here? */
791#endif
792
793 /* If DRM client is already running don't start this service. */
794 if (VbglR3DrmClientIsRunning())
795 {
796 VBClLogInfo("DRM resizing is already running. Exiting this service\n");
797 return VERR_NOT_AVAILABLE;
798 }
799
800 if (VBClHasWayland())
801 {
802 rc = VbglR3DrmClientStart();
803 if (RT_SUCCESS(rc))
804 {
805 VBClLogInfo("VBoxDrmClient has been successfully started, exitting parent process\n");
806 exit(0);
807 }
808 else
809 {
810 VBClLogError("Starting DRM resizing client failed with %Rrc\n", rc);
811 }
812 return rc;
813 }
814
815 x11Connect();
816
817 if (x11Context.pDisplay == NULL)
818 return VERR_NOT_AVAILABLE;
819
820 /* don't start the monitoring thread if related randr functionality is not available. */
821 if (x11Context.fMonitorInfoAvailable)
822 {
823 if (RT_FAILURE(startX11MonitorThread()))
824 return VERR_NOT_AVAILABLE;
825 }
826
827 return VINF_SUCCESS;
828}
829
830/**
831 * @interface_method_impl{VBCLSERVICE,pfnStop}
832 */
833static DECLCALLBACK(void) vbclSVGAStop(void)
834{
835 int rc;
836
837 rc = stopX11MonitorThread();
838 if (RT_FAILURE(rc))
839 {
840 VBClLogError("cannot stop X11 monitor thread (%Rrc)\n", rc);
841 return;
842 }
843
844 if (mpMonitorPositions)
845 {
846 free(mpMonitorPositions);
847 mpMonitorPositions = NULL;
848 }
849
850 if (x11Context.pDisplayRandRMonitoring)
851 {
852#ifdef WITH_DISTRO_XRAND_XINERAMA
853 XRRSelectInput(x11Context.pDisplayRandRMonitoring, x11Context.rootWindow, 0);
854#else
855 if (x11Context.pXRRSelectInput)
856 x11Context.pXRRSelectInput(x11Context.pDisplayRandRMonitoring, x11Context.rootWindow, 0);
857#endif
858 }
859
860 if (x11Context.pDisplay)
861 {
862 XCloseDisplay(x11Context.pDisplay);
863 x11Context.pDisplay = NULL;
864 }
865
866 if (x11Context.pDisplayRandRMonitoring)
867 {
868 XCloseDisplay(x11Context.pDisplayRandRMonitoring);
869 x11Context.pDisplayRandRMonitoring = NULL;
870 }
871
872 if (x11Context.pRandLibraryHandle)
873 {
874 dlclose(x11Context.pRandLibraryHandle);
875 x11Context.pRandLibraryHandle = NULL;
876 }
877}
878
879#ifndef WITH_DISTRO_XRAND_XINERAMA
880static int openLibRandR()
881{
882 x11Context.pRandLibraryHandle = dlopen("libXrandr.so", RTLD_LAZY /*| RTLD_LOCAL */);
883 if (!x11Context.pRandLibraryHandle)
884 x11Context.pRandLibraryHandle = dlopen("libXrandr.so.2", RTLD_LAZY /*| RTLD_LOCAL */);
885 if (!x11Context.pRandLibraryHandle)
886 x11Context.pRandLibraryHandle = dlopen("libXrandr.so.2.2.0", RTLD_LAZY /*| RTLD_LOCAL */);
887
888 if (!x11Context.pRandLibraryHandle)
889 {
890 VBClLogFatalError("Could not locate libXrandr for dlopen\n");
891 return VERR_NOT_FOUND;
892 }
893
894 *(void **)(&x11Context.pXRRSelectInput) = dlsym(x11Context.pRandLibraryHandle, "XRRSelectInput");
895 checkFunctionPtrReturn(x11Context.pXRRSelectInput);
896
897 *(void **)(&x11Context.pXRRQueryExtension) = dlsym(x11Context.pRandLibraryHandle, "XRRQueryExtension");
898 checkFunctionPtrReturn(x11Context.pXRRQueryExtension);
899
900 *(void **)(&x11Context.pXRRQueryVersion) = dlsym(x11Context.pRandLibraryHandle, "XRRQueryVersion");
901 checkFunctionPtrReturn(x11Context.pXRRQueryVersion);
902
903 /* Don't bail out when XRRGetMonitors XRRFreeMonitors are missing as in Oracle Solaris 10. It is not crucial esp. for single monitor. */
904 *(void **)(&x11Context.pXRRGetMonitors) = dlsym(x11Context.pRandLibraryHandle, "XRRGetMonitors");
905 checkFunctionPtr(x11Context.pXRRGetMonitors);
906
907 *(void **)(&x11Context.pXRRFreeMonitors) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeMonitors");
908 checkFunctionPtr(x11Context.pXRRFreeMonitors);
909
910 x11Context.fMonitorInfoAvailable = x11Context.pXRRGetMonitors && x11Context.pXRRFreeMonitors;
911
912 *(void **)(&x11Context.pXRRGetScreenResources) = dlsym(x11Context.pRandLibraryHandle, "XRRGetScreenResources");
913 checkFunctionPtr(x11Context.pXRRGetScreenResources);
914
915 *(void **)(&x11Context.pXRRSetCrtcConfig) = dlsym(x11Context.pRandLibraryHandle, "XRRSetCrtcConfig");
916 checkFunctionPtr(x11Context.pXRRSetCrtcConfig);
917
918 *(void **)(&x11Context.pXRRFreeScreenResources) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeScreenResources");
919 checkFunctionPtr(x11Context.pXRRFreeScreenResources);
920
921 *(void **)(&x11Context.pXRRFreeModeInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeModeInfo");
922 checkFunctionPtr(x11Context.pXRRFreeModeInfo);
923
924 *(void **)(&x11Context.pXRRFreeOutputInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeOutputInfo");
925 checkFunctionPtr(x11Context.pXRRFreeOutputInfo);
926
927 *(void **)(&x11Context.pXRRSetScreenSize) = dlsym(x11Context.pRandLibraryHandle, "XRRSetScreenSize");
928 checkFunctionPtr(x11Context.pXRRSetScreenSize);
929
930 *(void **)(&x11Context.pXRRUpdateConfiguration) = dlsym(x11Context.pRandLibraryHandle, "XRRUpdateConfiguration");
931 checkFunctionPtr(x11Context.pXRRUpdateConfiguration);
932
933 *(void **)(&x11Context.pXRRAllocModeInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRAllocModeInfo");
934 checkFunctionPtr(x11Context.pXRRAllocModeInfo);
935
936 *(void **)(&x11Context.pXRRCreateMode) = dlsym(x11Context.pRandLibraryHandle, "XRRCreateMode");
937 checkFunctionPtr(x11Context.pXRRCreateMode);
938
939 *(void **)(&x11Context.pXRRGetOutputInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRGetOutputInfo");
940 checkFunctionPtr(x11Context.pXRRGetOutputInfo);
941
942 *(void **)(&x11Context.pXRRGetCrtcInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRGetCrtcInfo");
943 checkFunctionPtr(x11Context.pXRRGetCrtcInfo);
944
945 *(void **)(&x11Context.pXRRFreeCrtcInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeCrtcInfo");
946 checkFunctionPtr(x11Context.pXRRFreeCrtcInfo);
947
948 *(void **)(&x11Context.pXRRAddOutputMode) = dlsym(x11Context.pRandLibraryHandle, "XRRAddOutputMode");
949 checkFunctionPtr(x11Context.pXRRAddOutputMode);
950
951 *(void **)(&x11Context.pXRRDeleteOutputMode) = dlsym(x11Context.pRandLibraryHandle, "XRRDeleteOutputMode");
952 checkFunctionPtr(x11Context.pXRRDeleteOutputMode);
953
954 *(void **)(&x11Context.pXRRDestroyMode) = dlsym(x11Context.pRandLibraryHandle, "XRRDestroyMode");
955 checkFunctionPtr(x11Context.pXRRDestroyMode);
956
957 *(void **)(&x11Context.pXRRSetOutputPrimary) = dlsym(x11Context.pRandLibraryHandle, "XRRSetOutputPrimary");
958 checkFunctionPtr(x11Context.pXRRSetOutputPrimary);
959
960 return VINF_SUCCESS;
961}
962#endif
963
964static void x11Connect()
965{
966 x11Context.pScreenResources = NULL;
967 x11Context.pXRRSelectInput = NULL;
968 x11Context.pRandLibraryHandle = NULL;
969 x11Context.pXRRQueryExtension = NULL;
970 x11Context.pXRRQueryVersion = NULL;
971 x11Context.pXRRGetMonitors = NULL;
972 x11Context.pXRRGetScreenResources = NULL;
973 x11Context.pXRRSetCrtcConfig = NULL;
974 x11Context.pXRRFreeMonitors = NULL;
975 x11Context.pXRRFreeScreenResources = NULL;
976 x11Context.pXRRFreeOutputInfo = NULL;
977 x11Context.pXRRFreeModeInfo = NULL;
978 x11Context.pXRRSetScreenSize = NULL;
979 x11Context.pXRRUpdateConfiguration = NULL;
980 x11Context.pXRRAllocModeInfo = NULL;
981 x11Context.pXRRCreateMode = NULL;
982 x11Context.pXRRGetOutputInfo = NULL;
983 x11Context.pXRRGetCrtcInfo = NULL;
984 x11Context.pXRRFreeCrtcInfo = NULL;
985 x11Context.pXRRAddOutputMode = NULL;
986 x11Context.pXRRDeleteOutputMode = NULL;
987 x11Context.pXRRDestroyMode = NULL;
988 x11Context.pXRRSetOutputPrimary = NULL;
989 x11Context.fWmwareCtrlExtention = false;
990 x11Context.fMonitorInfoAvailable = false;
991 x11Context.hRandRMajor = 0;
992 x11Context.hRandRMinor = 0;
993
994 int dummy;
995 if (x11Context.pDisplay != NULL)
996 VBClLogFatalError("%s called with bad argument\n", __func__);
997 x11Context.pDisplay = XOpenDisplay(NULL);
998 x11Context.pDisplayRandRMonitoring = XOpenDisplay(NULL);
999 if (x11Context.pDisplay == NULL)
1000 return;
1001#ifndef WITH_DISTRO_XRAND_XINERAMA
1002 if (openLibRandR() != VINF_SUCCESS)
1003 {
1004 XCloseDisplay(x11Context.pDisplay);
1005 XCloseDisplay(x11Context.pDisplayRandRMonitoring);
1006 x11Context.pDisplay = NULL;
1007 x11Context.pDisplayRandRMonitoring = NULL;
1008 return;
1009 }
1010#endif
1011
1012 x11Context.fWmwareCtrlExtention = XQueryExtension(x11Context.pDisplay, "VMWARE_CTRL",
1013 &x11Context.hVMWCtrlMajorOpCode, &dummy, &dummy);
1014 if (!x11Context.fWmwareCtrlExtention)
1015 VBClLogError("VMWARE's ctrl extension is not available! Multi monitor management is not possible\n");
1016 else
1017 VBClLogInfo("VMWARE's ctrl extension is available. Major Opcode is %d.\n", x11Context.hVMWCtrlMajorOpCode);
1018
1019 /* Check Xrandr stuff. */
1020 bool fSuccess = false;
1021#ifdef WITH_DISTRO_XRAND_XINERAMA
1022 fSuccess = XRRQueryExtension(x11Context.pDisplay, &x11Context.hRandREventBase, &x11Context.hRandRErrorBase);
1023#else
1024 if (x11Context.pXRRQueryExtension)
1025 fSuccess = x11Context.pXRRQueryExtension(x11Context.pDisplay, &x11Context.hRandREventBase, &x11Context.hRandRErrorBase);
1026#endif
1027 if (fSuccess)
1028 {
1029 fSuccess = false;
1030#ifdef WITH_DISTRO_XRAND_XINERAMA
1031 fSuccess = XRRQueryVersion(x11Context.pDisplay, &x11Context.hRandRMajor, &x11Context.hRandRMinor);
1032#else
1033 if (x11Context.pXRRQueryVersion)
1034 fSuccess = x11Context.pXRRQueryVersion(x11Context.pDisplay, &x11Context.hRandRMajor, &x11Context.hRandRMinor);
1035#endif
1036 if (!fSuccess)
1037 {
1038 XCloseDisplay(x11Context.pDisplay);
1039 x11Context.pDisplay = NULL;
1040 return;
1041 }
1042 if (x11Context.hRandRMajor < 1 || x11Context.hRandRMinor <= 3)
1043 {
1044 VBClLogError("Resizing service requires libXrandr Version >= 1.4. Detected version is %d.%d\n", x11Context.hRandRMajor, x11Context.hRandRMinor);
1045 XCloseDisplay(x11Context.pDisplay);
1046 x11Context.pDisplay = NULL;
1047
1048 int rc = VbglR3DrmLegacyX11AgentStart();
1049 VBClLogInfo("Attempt to start legacy X11 resize agent, rc=%Rrc\n", rc);
1050
1051 return;
1052 }
1053 }
1054 x11Context.rootWindow = DefaultRootWindow(x11Context.pDisplay);
1055 x11Context.hEventMask = RRScreenChangeNotifyMask;
1056
1057 /* Select the XEvent types we want to listen to. */
1058#ifdef WITH_DISTRO_XRAND_XINERAMA
1059 XRRSelectInput(x11Context.pDisplayRandRMonitoring, x11Context.rootWindow, x11Context.hEventMask);
1060#else
1061 if (x11Context.pXRRSelectInput)
1062 x11Context.pXRRSelectInput(x11Context.pDisplayRandRMonitoring, x11Context.rootWindow, x11Context.hEventMask);
1063#endif
1064 x11Context.iDefaultScreen = DefaultScreen(x11Context.pDisplay);
1065
1066#ifdef WITH_DISTRO_XRAND_XINERAMA
1067 x11Context.pScreenResources = XRRGetScreenResources(x11Context.pDisplay, x11Context.rootWindow);
1068#else
1069 if (x11Context.pXRRGetScreenResources)
1070 x11Context.pScreenResources = x11Context.pXRRGetScreenResources(x11Context.pDisplay, x11Context.rootWindow);
1071#endif
1072 x11Context.hOutputCount = RT_VALID_PTR(x11Context.pScreenResources) ? determineOutputCount() : 0;
1073#ifdef WITH_DISTRO_XRAND_XINERAMA
1074 XRRFreeScreenResources(x11Context.pScreenResources);
1075#else
1076 if (x11Context.pXRRFreeScreenResources)
1077 x11Context.pXRRFreeScreenResources(x11Context.pScreenResources);
1078#endif
1079}
1080
1081static int determineOutputCount()
1082{
1083 if (!x11Context.pScreenResources)
1084 return 0;
1085 return x11Context.pScreenResources->noutput;
1086}
1087
1088static int findExistingModeIndex(unsigned iXRes, unsigned iYRes)
1089{
1090 if (!x11Context.pScreenResources)
1091 return -1;
1092 for (int i = 0; i < x11Context.pScreenResources->nmode; ++i)
1093 {
1094 if (x11Context.pScreenResources->modes[i].width == iXRes && x11Context.pScreenResources->modes[i].height == iYRes)
1095 return i;
1096 }
1097 return -1;
1098}
1099
1100static bool disableCRTC(RRCrtc crtcID)
1101{
1102 XRRCrtcInfo *pCrctInfo = NULL;
1103
1104#ifdef WITH_DISTRO_XRAND_XINERAMA
1105 pCrctInfo = XRRGetCrtcInfo(x11Context.pDisplay, x11Context.pScreenResources, crtcID);
1106#else
1107 if (x11Context.pXRRGetCrtcInfo)
1108 pCrctInfo = x11Context.pXRRGetCrtcInfo(x11Context.pDisplay, x11Context.pScreenResources, crtcID);
1109#endif
1110
1111 if (!pCrctInfo)
1112 return false;
1113
1114 Status ret = Success;
1115#ifdef WITH_DISTRO_XRAND_XINERAMA
1116 ret = XRRSetCrtcConfig(x11Context.pDisplay, x11Context.pScreenResources, crtcID,
1117 CurrentTime, 0, 0, None, RR_Rotate_0, NULL, 0);
1118#else
1119 if (x11Context.pXRRSetCrtcConfig)
1120 ret = x11Context.pXRRSetCrtcConfig(x11Context.pDisplay, x11Context.pScreenResources, crtcID,
1121 CurrentTime, 0, 0, None, RR_Rotate_0, NULL, 0);
1122#endif
1123
1124#ifdef WITH_DISTRO_XRAND_XINERAMA
1125 XRRFreeCrtcInfo(pCrctInfo);
1126#else
1127 if (x11Context.pXRRFreeCrtcInfo)
1128 x11Context.pXRRFreeCrtcInfo(pCrctInfo);
1129#endif
1130
1131 /** @todo In case of unsuccesful crtc config set we have to revert frame buffer size and crtc sizes. */
1132 if (ret == Success)
1133 return true;
1134 else
1135 return false;
1136}
1137
1138static XRRScreenSize currentSize()
1139{
1140 XRRScreenSize cSize;
1141 cSize.width = DisplayWidth(x11Context.pDisplay, x11Context.iDefaultScreen);
1142 cSize.mwidth = DisplayWidthMM(x11Context.pDisplay, x11Context.iDefaultScreen);
1143 cSize.height = DisplayHeight(x11Context.pDisplay, x11Context.iDefaultScreen);
1144 cSize.mheight = DisplayHeightMM(x11Context.pDisplay, x11Context.iDefaultScreen);
1145 return cSize;
1146}
1147
1148static unsigned int computeDpi(unsigned int pixels, unsigned int mm)
1149{
1150 unsigned int dpi = 0;
1151 if (mm > 0) {
1152 dpi = (unsigned int)((double)pixels * MILLIS_PER_INCH /
1153 (double)mm + 0.5);
1154 }
1155 return (dpi > 0) ? dpi : DEFAULT_DPI;
1156}
1157
1158static bool resizeFrameBuffer(struct RANDROUTPUT *paOutputs)
1159{
1160 unsigned int iXRes = 0;
1161 unsigned int iYRes = 0;
1162 /* Don't care about the output positions for now. */
1163 for (int i = 0; i < x11Context.hOutputCount; ++i)
1164 {
1165 if (!paOutputs[i].fEnabled)
1166 continue;
1167 iXRes += paOutputs[i].width;
1168 iYRes = iYRes < paOutputs[i].height ? paOutputs[i].height : iYRes;
1169 }
1170 XRRScreenSize cSize= currentSize();
1171 unsigned int xdpi = computeDpi(cSize.width, cSize.mwidth);
1172 unsigned int ydpi = computeDpi(cSize.height, cSize.mheight);
1173 unsigned int xmm;
1174 unsigned int ymm;
1175 xmm = (int)(MILLIS_PER_INCH * iXRes / ((double)xdpi) + 0.5);
1176 ymm = (int)(MILLIS_PER_INCH * iYRes / ((double)ydpi) + 0.5);
1177#ifdef WITH_DISTRO_XRAND_XINERAMA
1178 XRRSelectInput(x11Context.pDisplay, x11Context.rootWindow, RRScreenChangeNotifyMask);
1179 XRRSetScreenSize(x11Context.pDisplay, x11Context.rootWindow, iXRes, iYRes, xmm, ymm);
1180#else
1181 if (x11Context.pXRRSelectInput)
1182 x11Context.pXRRSelectInput(x11Context.pDisplay, x11Context.rootWindow, RRScreenChangeNotifyMask);
1183 if (x11Context.pXRRSetScreenSize)
1184 x11Context.pXRRSetScreenSize(x11Context.pDisplay, x11Context.rootWindow, iXRes, iYRes, xmm, ymm);
1185#endif
1186 XSync(x11Context.pDisplay, False);
1187 XEvent configEvent;
1188 bool event = false;
1189 while (XCheckTypedEvent(x11Context.pDisplay, RRScreenChangeNotify + x11Context.hRandREventBase, &configEvent))
1190 {
1191#ifdef WITH_DISTRO_XRAND_XINERAMA
1192 XRRUpdateConfiguration(&configEvent);
1193#else
1194 if (x11Context.pXRRUpdateConfiguration)
1195 x11Context.pXRRUpdateConfiguration(&configEvent);
1196#endif
1197 event = true;
1198 }
1199#ifdef WITH_DISTRO_XRAND_XINERAMA
1200 XRRSelectInput(x11Context.pDisplay, x11Context.rootWindow, 0);
1201#else
1202 if (x11Context.pXRRSelectInput)
1203 x11Context.pXRRSelectInput(x11Context.pDisplay, x11Context.rootWindow, 0);
1204#endif
1205 XRRScreenSize newSize = currentSize();
1206
1207 /* On Solaris guest, new screen size is not reported properly despite
1208 * RRScreenChangeNotify event arrives. Hense, only check for event here.
1209 * Linux guests do report new size correctly. */
1210 if ( !event
1211#ifndef RT_OS_SOLARIS
1212 || newSize.width != (int)iXRes || newSize.height != (int)iYRes
1213#endif
1214 )
1215 {
1216 VBClLogError("Resizing frame buffer to %d %d has failed, current mode %d %d\n",
1217 iXRes, iYRes, newSize.width, newSize.height);
1218 return false;
1219 }
1220 return true;
1221}
1222
1223static XRRModeInfo *createMode(int iXRes, int iYRes)
1224{
1225 XRRModeInfo *pModeInfo = NULL;
1226 char sModeName[126];
1227 sprintf(sModeName, "%dx%d_vbox", iXRes, iYRes);
1228#ifdef WITH_DISTRO_XRAND_XINERAMA
1229 pModeInfo = XRRAllocModeInfo(sModeName, strlen(sModeName));
1230#else
1231 if (x11Context.pXRRAllocModeInfo)
1232 pModeInfo = x11Context.pXRRAllocModeInfo(sModeName, strlen(sModeName));
1233#endif
1234 pModeInfo->width = iXRes;
1235 pModeInfo->height = iYRes;
1236
1237 DisplayModeR mode = f86CVTMode(iXRes, iYRes, 60 /*VRefresh */, true /*Reduced */, false /* Interlaced */);
1238
1239 /* Convert kHz to Hz: f86CVTMode returns clock value in units of kHz,
1240 * XRRCreateMode will expect it in units of Hz. */
1241 pModeInfo->dotClock = mode.Clock * 1000;
1242
1243 pModeInfo->hSyncStart = mode.HSyncStart;
1244 pModeInfo->hSyncEnd = mode.HSyncEnd;
1245 pModeInfo->hTotal = mode.HTotal;
1246 pModeInfo->hSkew = mode.HSkew;
1247 pModeInfo->vSyncStart = mode.VSyncStart;
1248 pModeInfo->vSyncEnd = mode.VSyncEnd;
1249 pModeInfo->vTotal = mode.VTotal;
1250
1251 RRMode newMode = None;
1252#ifdef WITH_DISTRO_XRAND_XINERAMA
1253 newMode = XRRCreateMode(x11Context.pDisplay, x11Context.rootWindow, pModeInfo);
1254#else
1255 if (x11Context.pXRRCreateMode)
1256 newMode = x11Context.pXRRCreateMode(x11Context.pDisplay, x11Context.rootWindow, pModeInfo);
1257#endif
1258 if (newMode == None)
1259 {
1260#ifdef WITH_DISTRO_XRAND_XINERAMA
1261 XRRFreeModeInfo(pModeInfo);
1262#else
1263 if (x11Context.pXRRFreeModeInfo)
1264 x11Context.pXRRFreeModeInfo(pModeInfo);
1265#endif
1266 return NULL;
1267 }
1268 pModeInfo->id = newMode;
1269 return pModeInfo;
1270}
1271
1272static bool configureOutput(int iOutputIndex, struct RANDROUTPUT *paOutputs)
1273{
1274 if (iOutputIndex >= x11Context.hOutputCount)
1275 {
1276 VBClLogError("Output index %d is greater than # of oputputs %d\n", iOutputIndex, x11Context.hOutputCount);
1277 return false;
1278 }
1279
1280 AssertReturn(iOutputIndex >= 0, false);
1281 AssertReturn(iOutputIndex < VMW_MAX_HEADS, false);
1282
1283 /* Remember the last instantiated display mode ID here. This mode will be replaced with the
1284 * new one on the next guest screen resize event. */
1285 static RRMode aPrevMode[VMW_MAX_HEADS];
1286
1287 RROutput outputId = x11Context.pScreenResources->outputs[iOutputIndex];
1288 XRROutputInfo *pOutputInfo = NULL;
1289#ifdef WITH_DISTRO_XRAND_XINERAMA
1290 pOutputInfo = XRRGetOutputInfo(x11Context.pDisplay, x11Context.pScreenResources, outputId);
1291#else
1292 if (x11Context.pXRRGetOutputInfo)
1293 pOutputInfo = x11Context.pXRRGetOutputInfo(x11Context.pDisplay, x11Context.pScreenResources, outputId);
1294#endif
1295 if (!pOutputInfo)
1296 return false;
1297 XRRModeInfo *pModeInfo = NULL;
1298 bool fNewMode = false;
1299 /* Index of the mode within the XRRScreenResources.modes array. -1 if such a mode with required resolution does not exists*/
1300 int iModeIndex = findExistingModeIndex(paOutputs[iOutputIndex].width, paOutputs[iOutputIndex].height);
1301 if (iModeIndex != -1 && iModeIndex < x11Context.pScreenResources->nmode)
1302 pModeInfo = &(x11Context.pScreenResources->modes[iModeIndex]);
1303 else
1304 {
1305 /* A mode with required size was not found. Create a new one. */
1306 pModeInfo = createMode(paOutputs[iOutputIndex].width, paOutputs[iOutputIndex].height);
1307 VBClLogInfo("create mode %s (%u) on output %d\n", pModeInfo->name, pModeInfo->id, iOutputIndex);
1308 fNewMode = true;
1309 }
1310 if (!pModeInfo)
1311 {
1312 VBClLogError("Could not create mode for the resolution (%d, %d)\n",
1313 paOutputs[iOutputIndex].width, paOutputs[iOutputIndex].height);
1314 return false;
1315 }
1316
1317#ifdef WITH_DISTRO_XRAND_XINERAMA
1318 XRRAddOutputMode(x11Context.pDisplay, outputId, pModeInfo->id);
1319#else
1320 if (x11Context.pXRRAddOutputMode)
1321 x11Context.pXRRAddOutputMode(x11Context.pDisplay, outputId, pModeInfo->id);
1322#endif
1323
1324 /* If mode has been newly created, destroy and forget mode created on previous guest screen resize event. */
1325 if ( aPrevMode[iOutputIndex] > 0
1326 && pModeInfo->id != aPrevMode[iOutputIndex]
1327 && fNewMode)
1328 {
1329 VBClLogInfo("removing unused mode %u from output %d\n", aPrevMode[iOutputIndex], iOutputIndex);
1330#ifdef WITH_DISTRO_XRAND_XINERAMA
1331 XRRDeleteOutputMode(x11Context.pDisplay, outputId, aPrevMode[iOutputIndex]);
1332 XRRDestroyMode(x11Context.pDisplay, aPrevMode[iOutputIndex]);
1333#else
1334 if (x11Context.pXRRDeleteOutputMode)
1335 x11Context.pXRRDeleteOutputMode(x11Context.pDisplay, outputId, aPrevMode[iOutputIndex]);
1336 if (x11Context.pXRRDestroyMode)
1337 x11Context.pXRRDestroyMode(x11Context.pDisplay, aPrevMode[iOutputIndex]);
1338#endif
1339 /* Forget destroyed mode. */
1340 aPrevMode[iOutputIndex] = 0;
1341 }
1342
1343 /* Only cache modes created "by us". XRRDestroyMode will complain if provided mode
1344 * was not created by XRRCreateMode call. */
1345 if (fNewMode)
1346 aPrevMode[iOutputIndex] = pModeInfo->id;
1347
1348 if (paOutputs[iOutputIndex].fPrimary)
1349 {
1350#ifdef WITH_DISTRO_XRAND_XINERAMA
1351 XRRSetOutputPrimary(x11Context.pDisplay, x11Context.rootWindow, outputId);
1352#else
1353 if (x11Context.pXRRSetOutputPrimary)
1354 x11Context.pXRRSetOutputPrimary(x11Context.pDisplay, x11Context.rootWindow, outputId);
1355#endif
1356 }
1357
1358 /* Make sure outputs crtc is set. */
1359 pOutputInfo->crtc = pOutputInfo->crtcs[0];
1360
1361 RRCrtc crtcId = pOutputInfo->crtcs[0];
1362 Status ret = Success;
1363#ifdef WITH_DISTRO_XRAND_XINERAMA
1364 ret = XRRSetCrtcConfig(x11Context.pDisplay, x11Context.pScreenResources, crtcId, CurrentTime,
1365 paOutputs[iOutputIndex].x, paOutputs[iOutputIndex].y,
1366 pModeInfo->id, RR_Rotate_0, &(outputId), 1 /*int noutputs*/);
1367#else
1368 if (x11Context.pXRRSetCrtcConfig)
1369 ret = x11Context.pXRRSetCrtcConfig(x11Context.pDisplay, x11Context.pScreenResources, crtcId, CurrentTime,
1370 paOutputs[iOutputIndex].x, paOutputs[iOutputIndex].y,
1371 pModeInfo->id, RR_Rotate_0, &(outputId), 1 /*int noutputs*/);
1372#endif
1373 if (ret != Success)
1374 VBClLogError("crtc set config failed for output %d\n", iOutputIndex);
1375
1376#ifdef WITH_DISTRO_XRAND_XINERAMA
1377 XRRFreeOutputInfo(pOutputInfo);
1378#else
1379 if (x11Context.pXRRFreeOutputInfo)
1380 x11Context.pXRRFreeOutputInfo(pOutputInfo);
1381#endif
1382
1383 if (fNewMode)
1384 {
1385#ifdef WITH_DISTRO_XRAND_XINERAMA
1386 XRRFreeModeInfo(pModeInfo);
1387#else
1388 if (x11Context.pXRRFreeModeInfo)
1389 x11Context.pXRRFreeModeInfo(pModeInfo);
1390#endif
1391 }
1392 return true;
1393}
1394
1395/** Construct the xrandr command which sets the whole monitor topology each time. */
1396static void setXrandrTopology(struct RANDROUTPUT *paOutputs)
1397{
1398 if (!x11Context.pDisplay)
1399 {
1400 VBClLogInfo("not connected to X11\n");
1401 return;
1402 }
1403
1404 XGrabServer(x11Context.pDisplay);
1405 if (x11Context.fWmwareCtrlExtention)
1406 callVMWCTRL(paOutputs);
1407
1408#ifdef WITH_DISTRO_XRAND_XINERAMA
1409 x11Context.pScreenResources = XRRGetScreenResources(x11Context.pDisplay, x11Context.rootWindow);
1410#else
1411 if (x11Context.pXRRGetScreenResources)
1412 x11Context.pScreenResources = x11Context.pXRRGetScreenResources(x11Context.pDisplay, x11Context.rootWindow);
1413#endif
1414
1415 x11Context.hOutputCount = RT_VALID_PTR(x11Context.pScreenResources) ? determineOutputCount() : 0;
1416 if (!x11Context.pScreenResources)
1417 {
1418 XUngrabServer(x11Context.pDisplay);
1419 XFlush(x11Context.pDisplay);
1420 return;
1421 }
1422
1423 /* Disable crtcs. */
1424 for (int i = 0; i < x11Context.pScreenResources->noutput; ++i)
1425 {
1426 XRROutputInfo *pOutputInfo = NULL;
1427#ifdef WITH_DISTRO_XRAND_XINERAMA
1428 pOutputInfo = XRRGetOutputInfo(x11Context.pDisplay, x11Context.pScreenResources, x11Context.pScreenResources->outputs[i]);
1429#else
1430 if (x11Context.pXRRGetOutputInfo)
1431 pOutputInfo = x11Context.pXRRGetOutputInfo(x11Context.pDisplay, x11Context.pScreenResources, x11Context.pScreenResources->outputs[i]);
1432#endif
1433 if (!pOutputInfo)
1434 continue;
1435 if (pOutputInfo->crtc == None)
1436 continue;
1437
1438 if (!disableCRTC(pOutputInfo->crtc))
1439 {
1440 VBClLogFatalError("Crtc disable failed %lu\n", pOutputInfo->crtc);
1441 XUngrabServer(x11Context.pDisplay);
1442 XSync(x11Context.pDisplay, False);
1443#ifdef WITH_DISTRO_XRAND_XINERAMA
1444 XRRFreeScreenResources(x11Context.pScreenResources);
1445#else
1446 if (x11Context.pXRRFreeScreenResources)
1447 x11Context.pXRRFreeScreenResources(x11Context.pScreenResources);
1448#endif
1449 XFlush(x11Context.pDisplay);
1450 return;
1451 }
1452#ifdef WITH_DISTRO_XRAND_XINERAMA
1453 XRRFreeOutputInfo(pOutputInfo);
1454#else
1455 if (x11Context.pXRRFreeOutputInfo)
1456 x11Context.pXRRFreeOutputInfo(pOutputInfo);
1457#endif
1458 }
1459 /* Resize the frame buffer. */
1460 if (!resizeFrameBuffer(paOutputs))
1461 {
1462 XUngrabServer(x11Context.pDisplay);
1463 XSync(x11Context.pDisplay, False);
1464#ifdef WITH_DISTRO_XRAND_XINERAMA
1465 XRRFreeScreenResources(x11Context.pScreenResources);
1466#else
1467 if (x11Context.pXRRFreeScreenResources)
1468 x11Context.pXRRFreeScreenResources(x11Context.pScreenResources);
1469#endif
1470 XFlush(x11Context.pDisplay);
1471 return;
1472 }
1473
1474 /* Configure the outputs. */
1475 for (int i = 0; i < x11Context.hOutputCount; ++i)
1476 {
1477 /* be paranoid. */
1478 if (i >= x11Context.pScreenResources->noutput)
1479 break;
1480 if (!paOutputs[i].fEnabled)
1481 continue;
1482 if (configureOutput(i, paOutputs))
1483 VBClLogInfo("output[%d] successfully configured\n", i);
1484 else
1485 VBClLogError("failed to configure output[%d]\n", i);
1486 }
1487 XSync(x11Context.pDisplay, False);
1488#ifdef WITH_DISTRO_XRAND_XINERAMA
1489 XRRFreeScreenResources(x11Context.pScreenResources);
1490#else
1491 if (x11Context.pXRRFreeScreenResources)
1492 x11Context.pXRRFreeScreenResources(x11Context.pScreenResources);
1493#endif
1494 XUngrabServer(x11Context.pDisplay);
1495 XFlush(x11Context.pDisplay);
1496}
1497
1498/**
1499 * @interface_method_impl{VBCLSERVICE,pfnWorker}
1500 */
1501static DECLCALLBACK(int) vbclSVGAWorker(bool volatile *pfShutdown)
1502{
1503 /* Do not acknowledge the first event we query for to pick up old events,
1504 * e.g. from before a guest reboot. */
1505 bool fAck = false;
1506 bool fFirstRun = true;
1507 static struct VMMDevDisplayDef aMonitors[VMW_MAX_HEADS];
1508
1509 int rc = VbglR3CtlFilterMask(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0);
1510 if (RT_FAILURE(rc))
1511 VBClLogFatalError("Failed to request display change events, rc=%Rrc\n", rc);
1512 rc = VbglR3AcquireGuestCaps(VMMDEV_GUEST_SUPPORTS_GRAPHICS, 0, false);
1513 if (RT_FAILURE(rc))
1514 VBClLogFatalError("Failed to register resizing support, rc=%Rrc\n", rc);
1515 if (rc == VERR_RESOURCE_BUSY) /* Someone else has already acquired it. */
1516 return VERR_RESOURCE_BUSY;
1517
1518 /* Let the main thread know that it can continue spawning services. */
1519 RTThreadUserSignal(RTThreadSelf());
1520
1521 for (;;)
1522 {
1523 struct VMMDevDisplayDef aDisplays[VMW_MAX_HEADS];
1524 uint32_t cDisplaysOut;
1525 /* Query the first size without waiting. This lets us e.g. pick up
1526 * the last event before a guest reboot when we start again after. */
1527 rc = VbglR3GetDisplayChangeRequestMulti(VMW_MAX_HEADS, &cDisplaysOut, aDisplays, fAck);
1528 fAck = true;
1529 if (RT_FAILURE(rc))
1530 VBClLogError("Failed to get display change request, rc=%Rrc\n", rc);
1531 if (cDisplaysOut > VMW_MAX_HEADS)
1532 VBClLogError("Display change request contained, rc=%Rrc\n", rc);
1533 if (cDisplaysOut > 0)
1534 {
1535 for (unsigned i = 0; i < cDisplaysOut && i < VMW_MAX_HEADS; ++i)
1536 {
1537 uint32_t idDisplay = aDisplays[i].idDisplay;
1538 if (idDisplay >= VMW_MAX_HEADS)
1539 continue;
1540 aMonitors[idDisplay].fDisplayFlags = aDisplays[i].fDisplayFlags;
1541 if (!(aDisplays[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED))
1542 {
1543 if (idDisplay == 0 || (aDisplays[i].fDisplayFlags & VMMDEV_DISPLAY_ORIGIN))
1544 {
1545 aMonitors[idDisplay].xOrigin = aDisplays[i].xOrigin;
1546 aMonitors[idDisplay].yOrigin = aDisplays[i].yOrigin;
1547 } else {
1548 aMonitors[idDisplay].xOrigin = aMonitors[idDisplay - 1].xOrigin + aMonitors[idDisplay - 1].cx;
1549 aMonitors[idDisplay].yOrigin = aMonitors[idDisplay - 1].yOrigin;
1550 }
1551 aMonitors[idDisplay].cx = aDisplays[i].cx;
1552 aMonitors[idDisplay].cy = aDisplays[i].cy;
1553 }
1554 }
1555 /* Create a whole topology and send it to xrandr. */
1556 struct RANDROUTPUT aOutputs[VMW_MAX_HEADS];
1557 int iRunningX = 0;
1558 for (int j = 0; j < x11Context.hOutputCount; ++j)
1559 {
1560 aOutputs[j].x = iRunningX;
1561 aOutputs[j].y = aMonitors[j].yOrigin;
1562 aOutputs[j].width = aMonitors[j].cx;
1563 aOutputs[j].height = aMonitors[j].cy;
1564 aOutputs[j].fEnabled = !(aMonitors[j].fDisplayFlags & VMMDEV_DISPLAY_DISABLED);
1565 aOutputs[j].fPrimary = (aMonitors[j].fDisplayFlags & VMMDEV_DISPLAY_PRIMARY);
1566 if (aOutputs[j].fEnabled)
1567 iRunningX += aOutputs[j].width;
1568 }
1569 /* In 32-bit guests GAs build on our release machines causes an xserver lock during vmware_ctrl extention
1570 if we do the call withing XGrab. We make the call the said extension only once (to connect the outputs)
1571 rather than at each resize iteration. */
1572#if ARCH_BITS == 32
1573 if (fFirstRun)
1574 callVMWCTRL(aOutputs);
1575#endif
1576 setXrandrTopology(aOutputs);
1577 /* Wait for some seconds and set toplogy again after the boot. In some desktop environments (cinnamon) where
1578 DE get into our resizing our first resize is reverted by the DE. Sleeping for some secs. helps. Setting
1579 topology a 2nd time resolves the black screen I get after resizing.*/
1580 if (fFirstRun)
1581 {
1582 sleep(4);
1583 setXrandrTopology(aOutputs);
1584 fFirstRun = false;
1585 }
1586 }
1587 uint32_t events;
1588 do
1589 {
1590 rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, VBOX_SVGA_HOST_EVENT_RX_TIMEOUT_MS, &events);
1591 } while (rc == VERR_TIMEOUT && !ASMAtomicReadBool(pfShutdown));
1592
1593 if (ASMAtomicReadBool(pfShutdown))
1594 {
1595 /* Shutdown requested. */
1596 break;
1597 }
1598 else if (RT_FAILURE(rc))
1599 {
1600 VBClLogFatalError("Failure waiting for event, rc=%Rrc\n", rc);
1601 }
1602
1603 };
1604
1605 return VINF_SUCCESS;
1606}
1607
1608VBCLSERVICE g_SvcDisplaySVGA =
1609{
1610 "dp-svga-x11", /* szName */
1611 "SVGA X11 display", /* pszDescription */
1612 ".vboxclient-display-svga-x11.pid", /* pszPidFilePath */
1613 NULL, /* pszUsage */
1614 NULL, /* pszOptions */
1615 NULL, /* pfnOption */
1616 vbclSVGAInit, /* pfnInit */
1617 vbclSVGAWorker, /* pfnWorker */
1618 vbclSVGAStop, /* pfnStop*/
1619 NULL /* pfnTerm */
1620};
1621
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

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