VirtualBox

source: vbox/trunk/src/VBox/HostServices/SharedClipboard/win32.cpp@ 4711

Last change on this file since 4711 was 4711, checked in by vboxsync, 18 years ago

Fixed a shared clipboard problem on Windows.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 21.5 KB
Line 
1/** @file
2 * Shared Clipboard: Win32 host.
3 */
4
5/*
6 * Copyright (C) 2006-2007 innotek GmbH
7 *
8 * This file is part of VirtualBox Open Source Edition (OSE), as
9 * available from http://www.virtualbox.org. This file is free software;
10 * you can redistribute it and/or modify it under the terms of the GNU
11 * General Public License as published by the Free Software Foundation,
12 * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
13 * distribution. VirtualBox OSE is distributed in the hope that it will
14 * be useful, but WITHOUT ANY WARRANTY of any kind.
15 */
16
17#include <windows.h>
18
19#include <VBox/HostServices/VBoxClipboardSvc.h>
20
21#include <iprt/alloc.h>
22#include <iprt/string.h>
23#include <iprt/asm.h>
24#include <iprt/assert.h>
25#include <iprt/thread.h>
26#include <process.h>
27
28#include "VBoxClipboard.h"
29
30#define dprintf Log
31
32static char gachWindowClassName[] = "VBoxSharedClipboardClass";
33
34struct _VBOXCLIPBOARDCONTEXT
35{
36 HWND hwnd;
37 HWND hwndNextInChain;
38
39 RTTHREAD thread;
40 bool fTerminate;
41
42 HANDLE hRenderEvent;
43
44 VBOXCLIPBOARDCLIENTDATA *pClient;
45
46 volatile uint32_t u32Announcing;
47};
48
49/* Only one client is supported. There seems to be no need for more clients. */
50static VBOXCLIPBOARDCONTEXT g_ctx;
51
52
53#ifdef LOG_ENABLED
54void vboxClipboardDump(const void *pv, size_t cb, uint32_t u32Format)
55{
56 if (u32Format & VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
57 {
58 Log(("DUMP: VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT:\n"));
59 if (pv && cb)
60 {
61 Log(("%ls\n", pv));
62 }
63 else
64 {
65 Log(("%p %d\n", pv, cb));
66 }
67 }
68 else if (u32Format & VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
69 {
70 dprintf(("DUMP: VBOX_SHARED_CLIPBOARD_FMT_BITMAP\n"));
71 }
72 else if (u32Format & VBOX_SHARED_CLIPBOARD_FMT_HTML)
73 {
74 Log(("DUMP: VBOX_SHARED_CLIPBOARD_FMT_HTML:\n"));
75 if (pv && cb)
76 {
77 Log(("%s\n", pv));
78 }
79 else
80 {
81 Log(("%p %d\n", pv, cb));
82 }
83 }
84 else
85 {
86 dprintf(("DUMP: invalid format %02X\n", u32Format));
87 }
88}
89#else
90#define vboxClipboardDump(__pv, __cb, __format) do { NOREF(__pv); NOREF(__cb); NOREF(__format); } while (0)
91#endif /* LOG_ENABLED */
92
93static void vboxClipboardGetData (uint32_t u32Format, const void *pvSrc, uint32_t cbSrc,
94 void *pvDst, uint32_t cbDst, uint32_t *pcbActualDst)
95{
96 dprintf (("vboxClipboardGetData.\n"));
97
98 *pcbActualDst = cbSrc;
99
100 LogFlow(("vboxClipboardGetData cbSrc = %d, cbDst = %d\n", cbSrc, cbDst));
101
102 if (cbSrc > cbDst)
103 {
104 /* Do not copy data. The dst buffer is not enough. */
105 return;
106 }
107
108 memcpy (pvDst, pvSrc, cbSrc);
109
110 vboxClipboardDump(pvDst, cbSrc, u32Format);
111
112 return;
113}
114
115static int vboxClipboardReadDataFromClient (VBOXCLIPBOARDCONTEXT *pCtx, uint32_t u32Format)
116{
117 Assert(pCtx->pClient);
118 Assert(pCtx->pClient->data.pv == NULL && pCtx->pClient->data.cb == 0 && pCtx->pClient->data.u32Format == 0);
119
120 LogFlow(("vboxClipboardReadDataFromClient u32Format = %02X\n", u32Format));
121
122 ResetEvent (pCtx->hRenderEvent);
123
124 vboxSvcClipboardReportMsg (pCtx->pClient, VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA, u32Format);
125
126 WaitForSingleObject(pCtx->hRenderEvent, INFINITE);
127
128 LogFlow(("vboxClipboardReadDataFromClient wait completed\n"));
129
130 return VINF_SUCCESS;
131}
132
133static void vboxClipboardChanged (VBOXCLIPBOARDCONTEXT *pCtx)
134{
135 LogFlow(("vboxClipboardChanged\n"));
136
137 if (pCtx->pClient == NULL)
138 {
139 return;
140 }
141
142 /* Query list of available formats and report to host. */
143 if (OpenClipboard (pCtx->hwnd))
144 {
145 uint32_t u32Formats = 0;
146
147 UINT format = 0;
148
149 while ((format = EnumClipboardFormats (format)) != 0)
150 {
151 LogFlow(("vboxClipboardChanged format %#x\n", format));
152 switch (format)
153 {
154 case CF_UNICODETEXT:
155 case CF_TEXT:
156 u32Formats |= VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT;
157 break;
158
159 case CF_DIB:
160 case CF_BITMAP:
161 u32Formats |= VBOX_SHARED_CLIPBOARD_FMT_BITMAP;
162 break;
163
164 default:
165 if (format >= 0xC000)
166 {
167 TCHAR szFormatName[256];
168
169 int cActual = GetClipboardFormatName(format, szFormatName, sizeof(szFormatName)/sizeof (TCHAR));
170
171 if (cActual)
172 {
173 if (strcmp (szFormatName, "HTML Format") == 0)
174 {
175 u32Formats |= VBOX_SHARED_CLIPBOARD_FMT_HTML;
176 }
177 }
178 }
179 break;
180 }
181 }
182
183 CloseClipboard ();
184
185 LogFlow(("vboxClipboardChanged u32Formats %02X\n", u32Formats));
186
187 vboxSvcClipboardReportMsg (pCtx->pClient, VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS, u32Formats);
188 }
189}
190
191static LRESULT CALLBACK vboxClipboardWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
192{
193 LRESULT rc = 0;
194
195 VBOXCLIPBOARDCONTEXT *pCtx = &g_ctx;
196
197 switch (msg)
198 {
199 case WM_CHANGECBCHAIN:
200 {
201 Log(("WM_CHANGECBCHAIN\n"));
202
203 HWND hwndRemoved = (HWND)wParam;
204 HWND hwndNext = (HWND)lParam;
205
206 if (hwndRemoved == pCtx->hwndNextInChain)
207 {
208 /* The window that was next to our in the chain is being removed.
209 * Relink to the new next window.
210 */
211 pCtx->hwndNextInChain = hwndNext;
212 }
213 else
214 {
215 if (pCtx->hwndNextInChain)
216 {
217 /* Pass the message further. */
218 rc = SendMessage (pCtx->hwndNextInChain, WM_CHANGECBCHAIN, wParam, lParam);
219 }
220 }
221 } break;
222
223 case WM_DRAWCLIPBOARD:
224 {
225 Log(("WM_DRAWCLIPBOARD next %p\n", pCtx->hwndNextInChain));
226
227 if (ASMAtomicCmpXchgU32 (&pCtx->u32Announcing, 0, 1) == false)
228 {
229 /* Could not do 1->0 transition. That means u32Announcing is 0. */
230 vboxClipboardChanged (pCtx);
231 }
232
233 if (pCtx->hwndNextInChain)
234 {
235 /* Pass the message to next windows in the clipboard chain. */
236 rc = SendMessage (pCtx->hwndNextInChain, msg, wParam, lParam);
237 }
238 } break;
239
240 case WM_CLOSE:
241 {
242 /* Do nothing. Ignore the message. */
243 } break;
244
245 case WM_RENDERFORMAT:
246 {
247 /* Insert the requested clipboard format data into the clipboard. */
248 uint32_t u32Format = 0;
249
250 UINT format = (UINT)wParam;
251
252 Log(("WM_RENDERFORMAT %d\n", format));
253
254 switch (format)
255 {
256 case CF_UNICODETEXT:
257 u32Format |= VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT;
258 break;
259
260 case CF_DIB:
261 u32Format |= VBOX_SHARED_CLIPBOARD_FMT_BITMAP;
262 break;
263
264 default:
265 if (format >= 0xC000)
266 {
267 TCHAR szFormatName[256];
268
269 int cActual = GetClipboardFormatName(format, szFormatName, sizeof(szFormatName)/sizeof (TCHAR));
270
271 if (cActual)
272 {
273 if (strcmp (szFormatName, "HTML Format") == 0)
274 {
275 u32Format |= VBOX_SHARED_CLIPBOARD_FMT_HTML;
276 }
277 }
278 }
279 break;
280 }
281
282 if (u32Format == 0 || pCtx->pClient == NULL)
283 {
284 /* Unsupported clipboard format is requested. */
285 Log(("WM_RENDERFORMAT unsupported format requested or client is not active.\n"));
286 EmptyClipboard ();
287 }
288 else
289 {
290 int vboxrc = vboxClipboardReadDataFromClient (pCtx, u32Format);
291
292 dprintf(("vboxClipboardReadDataFromClient vboxrc = %d\n", vboxrc));
293
294 if ( VBOX_SUCCESS (vboxrc)
295 && pCtx->pClient->data.pv != NULL
296 && pCtx->pClient->data.cb > 0
297 && pCtx->pClient->data.u32Format == u32Format)
298 {
299 HANDLE hMem = GlobalAlloc (GMEM_DDESHARE | GMEM_MOVEABLE, pCtx->pClient->data.cb);
300
301 dprintf(("hMem %p\n", hMem));
302
303 if (hMem)
304 {
305 void *pMem = GlobalLock (hMem);
306
307 dprintf(("pMem %p, GlobalSize %d\n", pMem, GlobalSize (hMem)));
308
309 if (pMem)
310 {
311 Log(("WM_RENDERFORMAT setting data\n"));
312
313 if (pCtx->pClient->data.pv)
314 {
315 memcpy (pMem, pCtx->pClient->data.pv, pCtx->pClient->data.cb);
316
317 RTMemFree (pCtx->pClient->data.pv);
318 pCtx->pClient->data.pv = NULL;
319 }
320
321 pCtx->pClient->data.cb = 0;
322 pCtx->pClient->data.u32Format = 0;
323
324 /* The memory must be unlocked before inserting to the Clipboard. */
325 GlobalUnlock (hMem);
326
327 /* 'hMem' contains the host clipboard data.
328 * size is 'cb' and format is 'format'.
329 */
330 HANDLE hClip = SetClipboardData (format, hMem);
331
332 dprintf(("vboxClipboardHostEvent hClip %p\n", hClip));
333
334 if (hClip)
335 {
336 /* The hMem ownership has gone to the system. Nothing to do. */
337 break;
338 }
339 }
340
341 GlobalFree (hMem);
342 }
343 }
344
345 RTMemFree (pCtx->pClient->data.pv);
346 pCtx->pClient->data.pv = NULL;
347 pCtx->pClient->data.cb = 0;
348 pCtx->pClient->data.u32Format = 0;
349
350 /* Something went wrong. */
351 EmptyClipboard ();
352 }
353 } break;
354
355 case WM_RENDERALLFORMATS:
356 {
357 Log(("WM_RENDERALLFORMATS\n"));
358
359 /* Do nothing. The clipboard formats will be unavailable now, because the
360 * windows is to be destroyed and therefore the guest side becames inactive.
361 */
362 if (OpenClipboard (hwnd))
363 {
364 EmptyClipboard();
365
366 CloseClipboard();
367 }
368 } break;
369
370 case WM_USER:
371 {
372 if (pCtx->pClient == NULL || pCtx->pClient->fMsgFormats)
373 {
374 /* Host has pending formats message. Ignore the guest announcement,
375 * because host clipboard has more priority.
376 */
377 break;
378 }
379
380 /* Announce available formats. Do not insert data, they will be inserted in WM_RENDER*. */
381 uint32_t u32Formats = (uint32_t)lParam;
382
383 Log(("WM_USER u32Formats = %02X\n", u32Formats));
384
385 if (OpenClipboard (hwnd))
386 {
387 EmptyClipboard();
388
389 Log(("WM_USER emptied clipboard\n"));
390
391 HANDLE hClip = NULL;
392
393 if (u32Formats & VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
394 {
395 dprintf(("window proc WM_USER: VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT\n"));
396
397 /* Prevent the WM_DRAWCLIPBOARD processing. Will be reset in WM_DRAWCLIPBOARD. */
398 if (ASMAtomicCmpXchgU32 (&pCtx->u32Announcing, 1, 0) == true)
399 {
400 hClip = SetClipboardData (CF_UNICODETEXT, NULL);
401 }
402 }
403
404 if (u32Formats & VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
405 {
406 dprintf(("window proc WM_USER: VBOX_SHARED_CLIPBOARD_FMT_BITMAP\n"));
407
408 /* Prevent the WM_DRAWCLIPBOARD processing. Will be reset in WM_DRAWCLIPBOARD. */
409 if (ASMAtomicCmpXchgU32 (&pCtx->u32Announcing, 1, 0) == true)
410 {
411 hClip = SetClipboardData (CF_DIB, NULL);
412 }
413 }
414
415 if (u32Formats & VBOX_SHARED_CLIPBOARD_FMT_HTML)
416 {
417 UINT format = RegisterClipboardFormat ("HTML Format");
418 dprintf(("window proc WM_USER: VBOX_SHARED_CLIPBOARD_FMT_HTML 0x%04X\n", format));
419 if (format != 0)
420 {
421 /* Prevent the WM_DRAWCLIPBOARD processing. Will be reset in WM_DRAWCLIPBOARD. */
422 if (ASMAtomicCmpXchgU32 (&pCtx->u32Announcing, 1, 0) == true)
423 {
424 hClip = SetClipboardData (format, NULL);
425 }
426 }
427 }
428
429 CloseClipboard();
430
431 dprintf(("window proc WM_USER: hClip %p, err %d\n", hClip, GetLastError ()));
432 }
433 else
434 {
435 dprintf(("window proc WM_USER: failed to open clipboard\n"));
436 }
437 } break;
438
439 default:
440 {
441 Log(("WM_ %p\n", msg));
442 rc = DefWindowProc (hwnd, msg, wParam, lParam);
443 }
444 }
445
446 Log(("WM_ rc %d\n", rc));
447 return rc;
448}
449
450DECLCALLBACK(int) VBoxClipboardThread (RTTHREAD ThreadSelf, void *pInstance)
451{
452 /* Create a window and make it a clipboard viewer. */
453 int rc = VINF_SUCCESS;
454
455 LogFlow(("VBoxClipboardThread\n"));
456
457 VBOXCLIPBOARDCONTEXT *pCtx = &g_ctx;
458
459 HINSTANCE hInstance = (HINSTANCE)GetModuleHandle (NULL);
460
461 /* Register the Window Class. */
462 WNDCLASS wc;
463
464 wc.style = CS_NOCLOSE;
465 wc.lpfnWndProc = vboxClipboardWndProc;
466 wc.cbClsExtra = 0;
467 wc.cbWndExtra = 0;
468 wc.hInstance = hInstance;
469 wc.hIcon = NULL;
470 wc.hCursor = NULL;
471 wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
472 wc.lpszMenuName = NULL;
473 wc.lpszClassName = gachWindowClassName;
474
475 ATOM atomWindowClass = RegisterClass (&wc);
476
477 if (atomWindowClass == 0)
478 {
479 Log(("Failed to register window class\n"));
480 rc = VERR_NOT_SUPPORTED;
481 }
482 else
483 {
484 /* Create the window. */
485 pCtx->hwnd = CreateWindowEx (WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_TOPMOST,
486 gachWindowClassName, gachWindowClassName,
487 WS_POPUPWINDOW,
488 -200, -200, 100, 100, NULL, NULL, hInstance, NULL);
489
490 if (pCtx->hwnd == NULL)
491 {
492 Log(("Failed to create window\n"));
493 rc = VERR_NOT_SUPPORTED;
494 }
495 else
496 {
497 SetWindowPos(pCtx->hwnd, HWND_TOPMOST, -200, -200, 0, 0,
498 SWP_NOACTIVATE | SWP_HIDEWINDOW | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOSIZE);
499
500 pCtx->hwndNextInChain = SetClipboardViewer (pCtx->hwnd);
501
502 MSG msg;
503 while (GetMessage(&msg, NULL, 0, 0) && !pCtx->fTerminate)
504 {
505 TranslateMessage(&msg);
506 DispatchMessage(&msg);
507 }
508 }
509 }
510
511 if (pCtx->hwnd)
512 {
513 ChangeClipboardChain (pCtx->hwnd, pCtx->hwndNextInChain);
514 pCtx->hwndNextInChain = NULL;
515
516 DestroyWindow (pCtx->hwnd);
517 pCtx->hwnd = NULL;
518 }
519
520 if (atomWindowClass != 0)
521 {
522 UnregisterClass (gachWindowClassName, hInstance);
523 atomWindowClass = 0;
524 }
525
526 return 0;
527}
528
529/*
530 * Public platform dependent functions.
531 */
532int vboxClipboardInit (void)
533{
534 int rc = VINF_SUCCESS;
535
536 g_ctx.hRenderEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
537
538 g_ctx.u32Announcing = 0;
539
540 rc = RTThreadCreate (&g_ctx.thread, VBoxClipboardThread, NULL, 65536,
541 RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "SHCLIP");
542
543 return rc;
544}
545
546void vboxClipboardDestroy (void)
547{
548 Log(("vboxClipboardDestroy\n"));
549
550 /* Set the termination flag and ping the window thread. */
551 g_ctx.fTerminate = true;
552
553 if (g_ctx.hwnd)
554 {
555 PostMessage (g_ctx.hwnd, WM_CLOSE, 0, 0);
556 }
557
558 CloseHandle (g_ctx.hRenderEvent);
559
560 /* Wait for the window thread to terminate. */
561 RTThreadWait (g_ctx.thread, RT_INDEFINITE_WAIT, NULL);
562
563 g_ctx.thread = NIL_RTTHREAD;
564}
565
566int vboxClipboardConnect (VBOXCLIPBOARDCLIENTDATA *pClient)
567{
568 Log(("vboxClipboardConnect\n"));
569
570 if (g_ctx.pClient != NULL)
571 {
572 /* One client only. */
573 return VERR_NOT_SUPPORTED;
574 }
575
576 pClient->pCtx = &g_ctx;
577
578 pClient->pCtx->pClient = pClient;
579
580 /* Synch the host clipboard content with the client. */
581 vboxClipboardSync (pClient);
582
583 return VINF_SUCCESS;
584}
585
586int vboxClipboardSync (VBOXCLIPBOARDCLIENTDATA *pClient)
587{
588 /* Synch the host clipboard content with the client. */
589 vboxClipboardChanged (pClient->pCtx);
590
591 return VINF_SUCCESS;
592}
593
594void vboxClipboardDisconnect (VBOXCLIPBOARDCLIENTDATA *pClient)
595{
596 Log(("vboxClipboardDisconnect\n"));
597
598 g_ctx.pClient = NULL;
599}
600
601void vboxClipboardFormatAnnounce (VBOXCLIPBOARDCLIENTDATA *pClient, uint32_t u32Formats)
602{
603 /*
604 * The guest announces formats. Forward to the window thread.
605 */
606 PostMessage (pClient->pCtx->hwnd, WM_USER, 0, u32Formats);
607}
608
609int vboxClipboardReadData (VBOXCLIPBOARDCLIENTDATA *pClient, uint32_t u32Format, void *pv, uint32_t cb, uint32_t *pcbActual)
610{
611 LogFlow(("vboxClipboardReadData: u32Format = %02X\n", u32Format));
612
613 HANDLE hClip = NULL;
614
615 /*
616 * The guest wants to read data in the given format.
617 */
618 if (OpenClipboard (pClient->pCtx->hwnd))
619 {
620 dprintf(("Clipboard opened.\n"));
621
622 if (u32Format & VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
623 {
624 hClip = GetClipboardData (CF_DIB);
625
626 if (hClip != NULL)
627 {
628 LPVOID lp = GlobalLock (hClip);
629
630 if (lp != NULL)
631 {
632 dprintf(("CF_DIB\n"));
633
634 vboxClipboardGetData (VBOX_SHARED_CLIPBOARD_FMT_BITMAP, lp, GlobalSize (hClip),
635 pv, cb, pcbActual);
636
637 GlobalUnlock(hClip);
638 }
639 else
640 {
641 hClip = NULL;
642 }
643 }
644 }
645 else if (u32Format & VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
646 {
647 hClip = GetClipboardData(CF_UNICODETEXT);
648
649 if (hClip != NULL)
650 {
651 LPWSTR uniString = (LPWSTR)GlobalLock (hClip);
652
653 if (uniString != NULL)
654 {
655 dprintf(("CF_UNICODETEXT\n"));
656
657 vboxClipboardGetData (VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT, uniString, (lstrlenW (uniString) + 1) * 2,
658 pv, cb, pcbActual);
659
660 GlobalUnlock(hClip);
661 }
662 else
663 {
664 hClip = NULL;
665 }
666 }
667 }
668 else if (u32Format & VBOX_SHARED_CLIPBOARD_FMT_HTML)
669 {
670 UINT format = RegisterClipboardFormat ("HTML Format");
671
672 if (format != 0)
673 {
674 hClip = GetClipboardData (format);
675
676 if (hClip != NULL)
677 {
678 LPVOID lp = GlobalLock (hClip);
679
680 if (lp != NULL)
681 {
682 dprintf(("CF_HTML\n"));
683
684 vboxClipboardGetData (VBOX_SHARED_CLIPBOARD_FMT_HTML, lp, GlobalSize (hClip),
685 pv, cb, pcbActual);
686
687 GlobalUnlock(hClip);
688 }
689 else
690 {
691 hClip = NULL;
692 }
693 }
694 }
695 }
696
697 CloseClipboard ();
698 }
699 else
700 {
701 dprintf(("failed to open clipboard\n"));
702 }
703
704 if (hClip == NULL)
705 {
706 /* Reply with empty data. */
707 vboxClipboardGetData (0, NULL, 0,
708 pv, cb, pcbActual);
709 }
710
711 return VINF_SUCCESS;
712}
713
714void vboxClipboardWriteData (VBOXCLIPBOARDCLIENTDATA *pClient, void *pv, uint32_t cb, uint32_t u32Format)
715{
716 LogFlow(("vboxClipboardWriteData\n"));
717
718 /*
719 * The guest returns data that was requested in the WM_RENDERFORMAT handler.
720 */
721 Assert(pClient->data.pv == NULL && pClient->data.cb == 0 && pClient->data.u32Format == 0);
722
723 vboxClipboardDump(pv, cb, u32Format);
724
725 if (cb > 0)
726 {
727 pClient->data.pv = RTMemAlloc (cb);
728
729 if (pClient->data.pv)
730 {
731 memcpy (pClient->data.pv, pv, cb);
732 pClient->data.cb = cb;
733 pClient->data.u32Format = u32Format;
734 }
735 }
736
737 SetEvent(pClient->pCtx->hRenderEvent);
738}
Note: See TracBrowser for help on using the repository browser.

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