VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/utils/clipboard/ClipUtil.cpp@ 106061

Last change on this file since 106061 was 106061, checked in by vboxsync, 2 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 62.1 KB
Line 
1/* $Id: ClipUtil.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
2/** @file
3 * ClipUtil - Clipboard Utility
4 */
5
6/*
7 * Copyright (C) 2021-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * The contents of this file may alternatively be used under the terms
26 * of the Common Development and Distribution License Version 1.0
27 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
28 * in the VirtualBox distribution, in which case the provisions of the
29 * CDDL are applicable instead of those of the GPL.
30 *
31 * You may elect to license modified versions of this file under the
32 * terms and conditions of either the GPL or the CDDL or both.
33 *
34 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
35 */
36
37
38/*********************************************************************************************************************************
39* Header Files *
40*********************************************************************************************************************************/
41#ifdef RT_OS_OS2
42# define INCL_BASE
43# define INCL_PM
44# define INCL_ERRORS
45# include <os2.h>
46# undef RT_MAX
47#endif
48
49#include <iprt/assert.h>
50#include <iprt/errcore.h>
51#include <iprt/file.h>
52#include <iprt/getopt.h>
53#include <iprt/initterm.h>
54#include <iprt/mem.h>
55#include <iprt/message.h>
56#include <iprt/process.h>
57#include <iprt/string.h>
58#include <iprt/stream.h>
59#include <iprt/utf16.h>
60#include <iprt/zero.h>
61
62#ifdef RT_OS_DARWIN
63/** @todo */
64#elif defined(RT_OS_WINDOWS)
65# include <iprt/nt/nt-and-windows.h>
66#elif !defined(RT_OS_OS2)
67# include <X11/Xlib.h>
68# include <X11/Xatom.h>
69#endif
70
71
72/*********************************************************************************************************************************
73* Defined Constants And Macros *
74*********************************************************************************************************************************/
75#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) || defined(RT_OS_DARWIN)
76# undef MULTI_TARGET_CLIPBOARD
77# undef CU_X11
78#else
79# define MULTI_TARGET_CLIPBOARD
80# define CU_X11
81#endif
82
83
84/*********************************************************************************************************************************
85* Structures and Typedefs *
86*********************************************************************************************************************************/
87/**
88 * Clipboard format descriptor.
89 */
90typedef struct CLIPUTILFORMAT
91{
92 /** Format name. */
93 const char *pszName;
94
95#if defined(RT_OS_WINDOWS)
96 /** Windows integer format (CF_XXXX). */
97 UINT fFormat;
98 /** Windows string format name. */
99 const WCHAR *pwszFormat;
100
101#elif defined(RT_OS_OS2)
102 /** OS/2 integer format. */
103 ULONG fFormat;
104 /** OS/2 string format name. */
105 const char *pszFormat;
106
107#elif defined(RT_OS_DARWIN)
108 /** Native format (flavor). */
109 CFStringRef *hStrFormat;
110#else
111 /** The X11 atom for the format. */
112 Atom uAtom;
113 /** The X11 atom name if uAtom must be termined dynamically. */
114 const char *pszAtomName;
115 /** @todo X11 */
116#endif
117
118 /** Description. */
119 const char *pszDesc;
120 /** CLIPUTILFORMAT_F_XXX. */
121 uint32_t fFlags;
122} CLIPUTILFORMAT;
123/** Pointer to a clipobard format descriptor. */
124typedef CLIPUTILFORMAT const *PCCLIPUTILFORMAT;
125
126/** Convert to/from UTF-8. */
127#define CLIPUTILFORMAT_F_CONVERT_UTF8 RT_BIT_32(0)
128/** Ad hoc entry. */
129#define CLIPUTILFORMAT_F_AD_HOC RT_BIT_32(1)
130
131
132#ifdef MULTI_TARGET_CLIPBOARD
133/**
134 * Clipboard target descriptor.
135 */
136typedef struct CLIPUTILTARGET
137{
138 /** Target name. */
139 const char *pszName;
140 /** The X11 atom for the target. */
141 Atom uAtom;
142 /** The X11 atom name if uAtom must be termined dynamically. */
143 const char *pszAtomName;
144 /** Description. */
145 const char *pszDesc;
146} CLIPUTILTARGET;
147/** Pointer to clipboard target descriptor. */
148typedef CLIPUTILTARGET const *PCCLIPUTILTARGET;
149#endif /* MULTI_TARGET_CLIPBOARD */
150
151
152#ifdef RT_OS_OS2
153/** Header for Odin32 specific clipboard entries.
154 * (Used to get the correct size of the data.)
155 */
156typedef struct _Odin32ClipboardHeader
157{
158 /** Magic (CLIPHEADER_MAGIC) */
159 char achMagic[8];
160 /** Size of the following data.
161 * (The interpretation depends on the type.) */
162 unsigned cbData;
163 /** Odin32 format number. */
164 unsigned uFormat;
165} CLIPHEADER, *PCLIPHEADER;
166
167#define CLIPHEADER_MAGIC "Odin\1\0\1"
168#endif
169
170
171/*********************************************************************************************************************************
172* Global Variables *
173*********************************************************************************************************************************/
174/** Command line parameters */
175static const RTGETOPTDEF g_aCmdOptions[] =
176{
177 { "--list", 'l', RTGETOPT_REQ_NOTHING },
178 { "--get", 'g', RTGETOPT_REQ_STRING },
179 { "--get-file", 'G', RTGETOPT_REQ_STRING },
180 { "--put", 'p', RTGETOPT_REQ_STRING },
181 { "--put-file", 'P', RTGETOPT_REQ_STRING },
182 { "--check", 'c', RTGETOPT_REQ_STRING },
183 { "--check-file", 'C', RTGETOPT_REQ_STRING },
184 { "--check-not", 'n', RTGETOPT_REQ_STRING },
185 { "--zap", 'z', RTGETOPT_REQ_NOTHING },
186#ifdef MULTI_TARGET_CLIPBOARD
187 { "--target", 't', RTGETOPT_REQ_STRING },
188#endif
189#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
190 { "--close", 'k', RTGETOPT_REQ_NOTHING },
191#endif
192 { "--wait", 'w', RTGETOPT_REQ_UINT32 },
193 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
194 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
195 { "--version", 'V', RTGETOPT_REQ_NOTHING },
196 { "--help", 'h', RTGETOPT_REQ_NOTHING }, /* for Usage() */
197};
198
199/** Format descriptors. */
200static CLIPUTILFORMAT g_aFormats[] =
201{
202#if defined(RT_OS_WINDOWS)
203 { "text/ansi", CF_TEXT, NULL, "ANSI text", 0 },
204 { "text/utf-16", CF_UNICODETEXT, NULL, "UTF-16 text", 0 },
205 { "text/utf-8", CF_UNICODETEXT, NULL, "UTF-8 text", CLIPUTILFORMAT_F_CONVERT_UTF8 },
206 /* https://docs.microsoft.com/en-us/windows/desktop/dataxchg/html-clipboard-format */
207 { "text/html", 0, L"HTML Format", "HTML text", 0 },
208 { "bitmap", CF_DIB, NULL, "Bitmap (DIB)", 0 },
209 { "bitmap/v5", CF_DIBV5, NULL, "Bitmap version 5 (DIBv5)", 0 },
210#elif defined(RT_OS_OS2)
211 { "text/ascii", CF_TEXT, NULL, "ASCII text", 0 },
212 { "text/utf-8", CF_TEXT, NULL, "UTF-8 text", CLIPUTILFORMAT_F_CONVERT_UTF8 },
213 { "text/utf-16", 0, "Odin32 UnicodeText", "UTF-16 text", 0},
214#elif defined(RT_OS_DARWIN)
215 { "text/utf-8", kUTTypeUTF8PlainText, "UTF-8 text", 0 },
216 { "text/utf-16", kUTTypeUTF16PlainText, "UTF-16 text", 0 },
217#else
218 /** @todo X11 */
219 { "text/utf-8", None, "UTF8_STRING", "UTF-8 text", 0 },
220#endif
221};
222
223#ifdef MULTI_TARGET_CLIPBOARD
224/** Target descriptors. */
225static CLIPUTILTARGET g_aTargets[] =
226{
227 { "clipboard", 0, "CLIPBOARD", "XA_CLIPBOARD: The clipboard (default)" },
228 { "primary", XA_PRIMARY, NULL, "XA_PRIMARY: Primary selected text (middle mouse button)" },
229 { "secondary", XA_SECONDARY, NULL, "XA_SECONDARY: Secondary selected text (with ctrl)" },
230};
231
232/** The current clipboard target. */
233static CLIPUTILTARGET *g_pTarget = &g_aTargets[0];
234#endif /* MULTI_TARGET_CLIPBOARD */
235
236/** The -v/-q state. */
237static unsigned g_uVerbosity = 1;
238
239#ifdef RT_OS_DARWIN
240
241#elif defined(RT_OS_OS2)
242/** Anchorblock handle. */
243static HAB g_hOs2Ab = NULLHANDLE;
244/** The message queue handle. */
245static HMQ g_hOs2MsgQueue = NULLHANDLE;
246/** Windows that becomes clipboard owner when setting data. */
247static HWND g_hOs2Wnd = NULLHANDLE;
248/** Set if we've opened the clipboard. */
249static bool g_fOs2OpenedClipboard = false;
250/** Set if we're the clipboard owner. */
251static bool g_fOs2ClipboardOwner = false;
252/** Set when we receive a WM_TIMER message during DoWait(). */
253static bool volatile g_fOs2TimerTicked = false;
254
255#elif defined(RT_OS_WINDOWS)
256/** Set if we've opened the clipboard. */
257static bool g_fWinOpenedClipboard = false;
258/** Set when we receive a WM_TIMER message during DoWait(). */
259static bool volatile g_fWinTimerTicked = false;
260/** Window that becomes clipboard owner when setting data. */
261static HWND g_hWinWnd = NULL;
262
263#else
264/** Number of errors (incremented by error handle callback). */
265static uint32_t volatile g_cX11Errors;
266/** The X11 display. */
267static Display *g_pX11Display = NULL;
268/** The X11 dummy window. */
269static Window g_hX11Window = 0;
270/** TARGETS */
271static Atom g_uX11AtomTargets;
272/** MULTIPLE */
273static Atom g_uX11AtomMultiple;
274
275#endif
276
277
278/**
279 * Gets a format descriptor, complaining if invalid format.
280 *
281 * @returns Pointer to the descriptor if found, NULL + msg if not.
282 * @param pszFormat The format to get.
283 */
284static PCCLIPUTILFORMAT GetFormatDesc(const char *pszFormat)
285{
286 for (size_t i = 0; i < RT_ELEMENTS(g_aFormats); i++)
287 if (strcmp(pszFormat, g_aFormats[i].pszName) == 0)
288 {
289#if defined(RT_OS_DARWIN)
290 /** @todo */
291
292#elif defined(RT_OS_OS2)
293 if (g_aFormats[i].pszFormat && g_aFormats[i].fFormat == 0)
294 {
295 g_aFormats[i].fFormat = WinAddAtom(WinQuerySystemAtomTable(), g_aFormats[i].pszFormat);
296 if (g_aFormats[i].fFormat == 0)
297 RTMsgError("WinAddAtom(,%s) failed: %#x", g_aFormats[i].pszFormat, WinGetLastError(g_hOs2Ab));
298 }
299
300#elif defined(RT_OS_WINDOWS)
301 if (g_aFormats[i].pwszFormat && g_aFormats[i].fFormat == 0)
302 {
303 g_aFormats[i].fFormat = RegisterClipboardFormatW(g_aFormats[i].pwszFormat);
304 if (g_aFormats[i].fFormat == 0)
305 RTMsgError("RegisterClipboardFormatW(%ls) failed: %u (%#x)",
306 g_aFormats[i].pwszFormat, GetLastError(), GetLastError());
307 }
308#elif defined(CU_X11)
309 if (g_aFormats[i].pszAtomName && g_aFormats[i].uAtom == 0)
310 g_aFormats[i].uAtom = XInternAtom(g_pX11Display, g_aFormats[i].pszAtomName, False);
311#endif
312 return &g_aFormats[i];
313 }
314
315 /*
316 * Try register the format.
317 */
318 static CLIPUTILFORMAT AdHoc;
319 AdHoc.pszName = pszFormat;
320 AdHoc.pszDesc = pszFormat;
321 AdHoc.fFlags = CLIPUTILFORMAT_F_AD_HOC;
322#ifdef RT_OS_DARWIN
323/** @todo */
324
325#elif defined(RT_OS_OS2)
326 AdHoc.pszFormat = pszFormat;
327 AdHoc.fFormat = WinAddAtom(WinQuerySystemAtomTable(), pszFormat);
328 if (AdHoc.fFormat == 0)
329 {
330 RTMsgError("Invalid format '%s' (%#x)", pszFormat, WinGetLastError(g_hOs2Ab));
331 return NULL;
332 }
333
334#elif defined(RT_OS_WINDOWS)
335 AdHoc.pwszFormat = NULL;
336 AdHoc.fFormat = RegisterClipboardFormatA(pszFormat);
337 if (AdHoc.fFormat == 0)
338 {
339 RTMsgError("RegisterClipboardFormatA(%s) failed: %u (%#x)", pszFormat, GetLastError(), GetLastError());
340 return NULL;
341 }
342
343#else
344 AdHoc.pszAtomName = pszFormat;
345 AdHoc.uAtom = XInternAtom(g_pX11Display, pszFormat, False);
346 if (AdHoc.uAtom == None)
347 {
348 RTMsgError("Invalid format '%s' or out of memory for X11 atoms", pszFormat);
349 return NULL;
350 }
351
352#endif
353 return &AdHoc;
354}
355
356
357#ifdef RT_OS_DARWIN
358
359/** @todo */
360
361
362#elif defined(RT_OS_OS2)
363
364/**
365 * The window procedure for the object window.
366 *
367 * @returns Message result.
368 *
369 * @param hwnd The window handle.
370 * @param msg The message.
371 * @param mp1 Message parameter 1.
372 * @param mp2 Message parameter 2.
373 */
374static MRESULT EXPENTRY CuOs2WinProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
375{
376 if (g_uVerbosity > 2)
377 RTMsgInfo("CuOs2WinProc: hwnd=%#lx msg=%#lx mp1=%#lx mp2=%#lx\n", hwnd, msg, mp1, mp2);
378
379 switch (msg)
380 {
381 case WM_CREATE:
382 return NULL; /* FALSE(/NULL) == Continue*/
383 case WM_DESTROY:
384 break;
385
386 /*
387 * Clipboard viewer message - the content has been changed.
388 * This is sent *after* releasing the clipboard sem
389 * and during the WinSetClipbrdViewer call.
390 */
391 case WM_DRAWCLIPBOARD:
392 break;
393
394 /*
395 * Clipboard owner message - the content was replaced.
396 * This is sent by someone with an open clipboard, so don't try open it now.
397 */
398 case WM_DESTROYCLIPBOARD:
399 break;
400
401 /*
402 * Clipboard owner message - somebody is requesting us to render a format.
403 * This is called by someone which owns the clipboard, but that's fine.
404 */
405 case WM_RENDERFMT:
406 break;
407
408 /*
409 * Clipboard owner message - we're about to quit and should render all formats.
410 */
411 case WM_RENDERALLFMTS:
412 break;
413
414 /*
415 * Clipboard owner messages dealing with owner drawn content.
416 * We shouldn't be seeing any of these.
417 */
418 case WM_PAINTCLIPBOARD:
419 case WM_SIZECLIPBOARD:
420 case WM_HSCROLLCLIPBOARD:
421 case WM_VSCROLLCLIPBOARD:
422 AssertMsgFailed(("msg=%lx (%ld)\n", msg, msg));
423 break;
424
425 /*
426 * We shouldn't be seeing any other messages according to the docs.
427 * But for whatever reason, PM sends us a WM_ADJUSTWINDOWPOS message
428 * during WinCreateWindow. So, ignore that and assert on anything else.
429 */
430 default:
431 AssertMsgFailed(("msg=%lx (%ld)\n", msg, msg));
432 case WM_ADJUSTWINDOWPOS:
433 break;
434
435 /*
436 * We use this window fielding WM_TIMER during DoWait.
437 */
438 case WM_TIMER:
439 if (SHORT1FROMMP(mp1) == 1)
440 g_fOs2TimerTicked = true;
441 break;
442 }
443 return NULL;
444}
445
446
447/**
448 * Initialize the OS/2 bits.
449 */
450static RTEXITCODE CuOs2Init(void)
451{
452 g_hOs2Ab = WinInitialize(0);
453 if (g_hOs2Ab == NULLHANDLE)
454 return RTMsgErrorExitFailure("WinInitialize failed!");
455
456 g_hOs2MsgQueue = WinCreateMsgQueue(g_hOs2Ab, 10);
457 if (g_hOs2MsgQueue == NULLHANDLE)
458 return RTMsgErrorExitFailure("WinCreateMsgQueue failed: %#x", WinGetLastError(g_hOs2Ab));
459
460 static char s_szClass[] = "VBox-ClipUtilClipboardClass";
461 if (!WinRegisterClass(g_hOs2Wnd, (PCSZ)s_szClass, CuOs2WinProc, 0, 0))
462 return RTMsgErrorExitFailure("WinRegisterClass failed: %#x", WinGetLastError(g_hOs2Ab));
463
464 g_hOs2Wnd = WinCreateWindow(HWND_OBJECT, /* hwndParent */
465 (PCSZ)s_szClass, /* pszClass */
466 (PCSZ)"VirtualBox Clipboard Utility", /* pszName */
467 0, /* flStyle */
468 0, 0, 0, 0, /* x, y, cx, cy */
469 NULLHANDLE, /* hwndOwner */
470 HWND_BOTTOM, /* hwndInsertBehind */
471 42, /* id */
472 NULL, /* pCtlData */
473 NULL); /* pPresParams */
474 if (g_hOs2Wnd == NULLHANDLE)
475 return RTMsgErrorExitFailure("WinCreateWindow failed: %#x", WinGetLastError(g_hOs2Ab));
476
477 return RTEXITCODE_SUCCESS;
478}
479
480
481/**
482 * Terminates the OS/2 bits.
483 */
484static RTEXITCODE CuOs2Term(void)
485{
486 if (g_fOs2OpenedClipboard)
487 {
488 if (!WinCloseClipbrd(g_hOs2Ab))
489 return RTMsgErrorExitFailure("WinCloseClipbrd failed: %#x", WinGetLastError(g_hOs2Ab));
490 g_fOs2OpenedClipboard = false;
491 }
492
493 WinDestroyWindow(g_hOs2Wnd);
494 g_hOs2Wnd = NULLHANDLE;
495
496 WinDestroyMsgQueue(g_hOs2MsgQueue);
497 g_hOs2MsgQueue = NULLHANDLE;
498
499 WinTerminate(g_hOs2Ab);
500 g_hOs2Ab = NULLHANDLE;
501
502 return RTEXITCODE_SUCCESS;
503}
504
505
506/**
507 * Opens the OS/2 clipboard.
508 */
509static RTEXITCODE CuOs2OpenClipboardIfNecessary(void)
510{
511 if (g_fOs2OpenedClipboard)
512 return RTEXITCODE_SUCCESS;
513 if (WinOpenClipbrd(g_hOs2Ab))
514 {
515 if (g_uVerbosity > 0)
516 RTMsgInfo("Opened the clipboard\n");
517 g_fOs2OpenedClipboard = true;
518 return RTEXITCODE_SUCCESS;
519 }
520 return RTMsgErrorExitFailure("WinOpenClipbrd failed: %#x", WinGetLastError(g_hOs2Ab));
521}
522
523
524#elif defined(RT_OS_WINDOWS)
525
526/**
527 * Window procedure for the clipboard owner window on Windows.
528 */
529static LRESULT CALLBACK CuWinWndProc(HWND hWnd, UINT idMsg, WPARAM wParam, LPARAM lParam) RT_NOTHROW_DEF
530{
531 if (g_uVerbosity > 2)
532 RTMsgInfo("CuWinWndProc: hWnd=%p idMsg=%#05x wParam=%#zx lParam=%#zx\n", hWnd, idMsg, wParam, lParam);
533
534 switch (idMsg)
535 {
536 case WM_TIMER:
537 if (wParam == 1)
538 g_fWinTimerTicked = true;
539 break;
540 }
541 return DefWindowProc(hWnd, idMsg, wParam, lParam);
542}
543
544
545/**
546 * Initialize the Windows bits.
547 */
548static RTEXITCODE CuWinInit(void)
549{
550 /* Register the window class: */
551 static wchar_t s_wszClass[] = L"VBox-ClipUtilClipboardClass";
552 WNDCLASSW WndCls = {0};
553 WndCls.style = CS_NOCLOSE;
554 WndCls.lpfnWndProc = CuWinWndProc;
555 WndCls.cbClsExtra = 0;
556 WndCls.cbWndExtra = 0;
557 WndCls.hInstance = (HINSTANCE)GetModuleHandle(NULL);
558 WndCls.hIcon = NULL;
559 WndCls.hCursor = NULL;
560 WndCls.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
561 WndCls.lpszMenuName = NULL;
562 WndCls.lpszClassName = s_wszClass;
563
564 ATOM uAtomWndClass = RegisterClassW(&WndCls);
565 if (!uAtomWndClass)
566 return RTMsgErrorExitFailure("RegisterClassW failed: %u (%#x)", GetLastError(), GetLastError());
567
568 /* Create the clipboard owner window: */
569 g_hWinWnd = CreateWindowExW(WS_EX_TRANSPARENT, /* fExStyle */
570 s_wszClass, /* pwszClass */
571 L"VirtualBox Clipboard Utility", /* pwszName */
572 0, /* fStyle */
573 0, 0, 0, 0, /* x, y, cx, cy */
574 HWND_MESSAGE, /* hWndParent */
575 NULL, /* hMenu */
576 (HINSTANCE)GetModuleHandle(NULL), /* hinstance */
577 NULL); /* pParam */
578 if (g_hWinWnd == NULL)
579 return RTMsgErrorExitFailure("CreateWindowExW failed: %u (%#x)", GetLastError(), GetLastError());
580
581 return RTEXITCODE_SUCCESS;
582}
583
584/**
585 * Terminates the Windows bits.
586 */
587static RTEXITCODE CuWinTerm(void)
588{
589 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
590 if (g_fWinOpenedClipboard)
591 {
592 if (CloseClipboard())
593 g_fWinOpenedClipboard = false;
594 else
595 rcExit = RTMsgErrorExitFailure("CloseClipboard failed: %u (%#x)", GetLastError(), GetLastError());
596 }
597
598 if (g_hWinWnd != NULL)
599 {
600 if (!DestroyWindow(g_hWinWnd))
601 rcExit = RTMsgErrorExitFailure("DestroyWindow failed: %u (%#x)", GetLastError(), GetLastError());
602 g_hWinWnd = NULL;
603 }
604
605 return rcExit;
606}
607
608
609/**
610 * Opens the window clipboard.
611 */
612static RTEXITCODE WinOpenClipboardIfNecessary(void)
613{
614 if (g_fWinOpenedClipboard)
615 return RTEXITCODE_SUCCESS;
616 if (OpenClipboard(g_hWinWnd))
617 {
618 if (g_uVerbosity > 0)
619 RTMsgInfo("Opened the clipboard\n");
620 g_fWinOpenedClipboard = true;
621 return RTEXITCODE_SUCCESS;
622 }
623 return RTMsgErrorExitFailure("OpenClipboard failed: %u (%#x)", GetLastError(), GetLastError());
624}
625
626
627#else /* X11: */
628
629/**
630 * Error handler callback.
631 */
632static int CuX11ErrorCallback(Display *pX11Display, XErrorEvent *pErrEvt)
633{
634 g_cX11Errors++;
635 char szErr[2048];
636 XGetErrorText(pX11Display, pErrEvt->error_code, szErr, sizeof(szErr));
637 RTMsgError("An X Window protocol error occurred: %s\n"
638 " Request code: %u\n"
639 " Minor code: %u\n"
640 " Serial number of the failed request: %u\n",
641 szErr, pErrEvt->request_code, pErrEvt->minor_code, pErrEvt->serial);
642 return 0;
643}
644
645
646/**
647 * Initialize the X11 bits.
648 */
649static RTEXITCODE CuX11Init(void)
650{
651 /*
652 * Open the X11 display and create a little dummy window.
653 */
654 XSetErrorHandler(CuX11ErrorCallback);
655 g_pX11Display = XOpenDisplay(NULL);
656 if (!g_pX11Display)
657 return RTMsgErrorExitFailure("XOpenDisplay failed");
658
659 int const iDefaultScreen = DefaultScreen(g_pX11Display);
660 g_hX11Window = XCreateSimpleWindow(g_pX11Display,
661 RootWindow(g_pX11Display, iDefaultScreen),
662 0 /*x*/, 0 /*y*/,
663 1 /*cx*/, 1 /*cy*/,
664 0 /*cPxlBorder*/,
665 BlackPixel(g_pX11Display, iDefaultScreen) /*Border*/,
666 WhitePixel(g_pX11Display, iDefaultScreen) /*Background*/);
667
668 /*
669 * Resolve any unknown atom values we might need later.
670 */
671 for (size_t i = 0; i < RT_ELEMENTS(g_aTargets); i++)
672 if (g_aTargets[i].pszAtomName)
673 {
674 g_aTargets[i].uAtom = XInternAtom(g_pX11Display, g_aTargets[i].pszAtomName, False);
675 if (g_uVerbosity > 2)
676 RTPrintf("target %s atom=%#x\n", g_aTargets[i].pszName, g_aTargets[i].uAtom);
677 }
678
679 g_uX11AtomTargets = XInternAtom(g_pX11Display, "TARGETS", False);
680 g_uX11AtomMultiple = XInternAtom(g_pX11Display, "MULTIPLE", False);
681
682 return RTEXITCODE_SUCCESS;
683}
684
685#endif /* X11 */
686
687
688#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
689/**
690 * Closes the clipboard if open.
691 */
692static RTEXITCODE CuCloseClipboard(void)
693{
694# if defined(RT_OS_OS2)
695 if (g_fOs2OpenedClipboard)
696 {
697 if (!WinCloseClipbrd(g_hOs2Ab))
698 return RTMsgErrorExitFailure("WinCloseClipbrd failed: %#x", WinGetLastError(g_hOs2Ab));
699 g_fOs2OpenedClipboard = false;
700 if (g_uVerbosity > 0)
701 RTMsgInfo("Closed the clipboard.\n");
702 }
703# else
704 if (g_fWinOpenedClipboard)
705 {
706 if (!CloseClipboard())
707 return RTMsgErrorExitFailure("CloseClipboard failed: %u (%#x)", GetLastError(), GetLastError());
708 g_fWinOpenedClipboard = false;
709 if (g_uVerbosity > 0)
710 RTMsgInfo("Closed the clipboard.\n");
711 }
712# endif
713 else if (g_uVerbosity > 0)
714 RTMsgInfo("No need to close clipboard, not opened.\n");
715
716 return RTEXITCODE_SUCCESS;
717}
718#endif /* RT_OS_OS2 || RT_OS_WINDOWS */
719
720
721/**
722 * Lists the clipboard format.
723 */
724static RTEXITCODE ListClipboardContent(void)
725{
726#if defined(RT_OS_OS2)
727 RTEXITCODE rcExit = CuOs2OpenClipboardIfNecessary();
728 if (rcExit == RTEXITCODE_SUCCESS)
729 {
730 HATOMTBL const hAtomTbl = WinQuerySystemAtomTable();
731 uint32_t idx = 0;
732 ULONG fFormat = 0;
733 while ((fFormat = WinEnumClipbrdFmts(g_hOs2Ab)) != 0)
734 {
735 char szName[256] = {0};
736 ULONG cchRet = WinQueryAtomName(hAtomTbl, fFormat, szName, sizeof(szName));
737 if (cchRet != 0)
738 RTPrintf("#%02u: %#06x - %s\n", idx, fFormat, szName);
739 else
740 {
741 const char *pszName = NULL;
742 switch (fFormat)
743 {
744 case CF_TEXT: pszName = "CF_TEXT"; break;
745 case CF_BITMAP: pszName = "CF_BITMAP"; break;
746 case CF_DSPTEXT: pszName = "CF_DSPTEXT"; break;
747 case CF_DSPBITMAP: pszName = "CF_DSPBITMAP"; break;
748 case CF_METAFILE: pszName = "CF_METAFILE"; break;
749 case CF_DSPMETAFILE: pszName = "CF_DSPMETAFILE"; break;
750 case CF_PALETTE: pszName = "CF_PALETTE"; break;
751 default:
752 break;
753 }
754 if (pszName)
755 RTPrintf("#%02u: %#06x - %s\n", idx, fFormat, pszName);
756 else
757 RTPrintf("#%02u: %#06x\n", idx, fFormat);
758 }
759
760 idx++;
761 }
762 }
763
764 return rcExit;
765
766#elif defined(RT_OS_WINDOWS)
767 RTEXITCODE rcExit = WinOpenClipboardIfNecessary();
768 if (rcExit == RTEXITCODE_SUCCESS)
769 {
770 SetLastError(0);
771 uint32_t idx = 0;
772 UINT fFormat = 0;
773 while ((fFormat = EnumClipboardFormats(fFormat)) != 0)
774 {
775 WCHAR wszName[256];
776 int cchName = GetClipboardFormatNameW(fFormat, wszName, RT_ELEMENTS(wszName));
777 if (cchName > 0)
778 RTPrintf("#%02u: %#06x - %ls\n", idx, fFormat, wszName);
779 else
780 {
781 const char *pszName = NULL;
782 switch (fFormat)
783 {
784 case CF_TEXT: pszName = "CF_TEXT"; break;
785 case CF_BITMAP: pszName = "CF_BITMAP"; break;
786 case CF_METAFILEPICT: pszName = "CF_METAFILEPICT"; break;
787 case CF_SYLK: pszName = "CF_SYLK"; break;
788 case CF_DIF: pszName = "CF_DIF"; break;
789 case CF_TIFF: pszName = "CF_TIFF"; break;
790 case CF_OEMTEXT: pszName = "CF_OEMTEXT"; break;
791 case CF_DIB: pszName = "CF_DIB"; break;
792 case CF_PALETTE: pszName = "CF_PALETTE"; break;
793 case CF_PENDATA: pszName = "CF_PENDATA"; break;
794 case CF_RIFF: pszName = "CF_RIFF"; break;
795 case CF_WAVE: pszName = "CF_WAVE"; break;
796 case CF_UNICODETEXT: pszName = "CF_UNICODETEXT"; break;
797 case CF_ENHMETAFILE: pszName = "CF_ENHMETAFILE"; break;
798 case CF_HDROP: pszName = "CF_HDROP"; break;
799 case CF_LOCALE: pszName = "CF_LOCALE"; break;
800 case CF_DIBV5: pszName = "CF_DIBV5"; break;
801 default:
802 break;
803 }
804 if (pszName)
805 RTPrintf("#%02u: %#06x - %s\n", idx, fFormat, pszName);
806 else
807 RTPrintf("#%02u: %#06x\n", idx, fFormat);
808 }
809
810 idx++;
811 }
812 if (idx == 0)
813 RTPrintf("Empty\n");
814 }
815 return rcExit;
816
817#elif defined(CU_X11)
818 /* Request the TARGETS property: */
819 Atom uAtomDst = g_uX11AtomTargets;
820 int rc = XConvertSelection(g_pX11Display, g_pTarget->uAtom, g_uX11AtomTargets, uAtomDst, g_hX11Window, CurrentTime);
821 if (g_uVerbosity > 1)
822 RTPrintf("XConvertSelection -> %d\n", rc);
823
824 /* Wait for the reply: */
825 for (;;)
826 {
827 XEvent Evt = {0};
828 rc = XNextEvent(g_pX11Display, &Evt);
829 if (Evt.type == SelectionNotify)
830 {
831 if (g_uVerbosity > 1)
832 RTPrintf("XNextEvent -> %d; type=SelectionNotify\n", rc);
833 if (Evt.xselection.selection == g_pTarget->uAtom)
834 {
835 if (Evt.xselection.property == None)
836 return RTMsgErrorExitFailure("XConvertSelection(,%s,TARGETS,) failed", g_pTarget->pszName);
837
838 /* Get the TARGETS property data: */
839 Atom uAtomRetType = 0;
840 int iActualFmt = 0;
841 unsigned long cbLeftToRead = 0;
842 unsigned long cItems = 0;
843 unsigned char *pbData = NULL;
844 rc = XGetWindowProperty(g_pX11Display, g_hX11Window, uAtomDst,
845 0 /*offset*/, sizeof(Atom) * 4096 /* should be enough */, True /*fDelete*/, XA_ATOM,
846 &uAtomRetType, &iActualFmt, &cItems, &cbLeftToRead, &pbData);
847 if (g_uVerbosity > 1)
848 RTPrintf("XConvertSelection -> %d; uAtomRetType=%u iActualFmt=%d cItems=%lu cbLeftToRead=%lu pbData=%p\n",
849 rc, uAtomRetType, iActualFmt, cItems, cbLeftToRead, pbData);
850 if (pbData && cItems > 0)
851 {
852 /* Display the TARGETS: */
853 Atom const *paTargets = (Atom const *)pbData;
854 for (unsigned long i = 0; i < cItems; i++)
855 {
856 const char *pszName = XGetAtomName(g_pX11Display, paTargets[i]);
857 if (pszName)
858 RTPrintf("#%02u: %#06x - %s\n", i, paTargets[i], pszName);
859 else
860 RTPrintf("#%02u: %#06x\n", i, paTargets[i]);
861 }
862 }
863 else
864 RTMsgInfo("Empty");
865 if (pbData)
866 XFree(pbData);
867 return RTEXITCODE_SUCCESS;
868 }
869 }
870 else if (g_uVerbosity > 1)
871 RTPrintf("XNextEvent -> %d; type=%d\n", rc, Evt.type);
872 }
873
874#else
875 return RTMsgErrorExitFailure("ListClipboardContent is not implemented");
876#endif
877}
878
879
880/**
881 * Reads the given clipboard format and stores it in on the heap.
882 *
883 * @returns Success indicator.
884 * @param pFmtDesc The format to get.
885 * @param ppvData Where to return the pointer to the data. Free using
886 * RTMemFree when done.
887 * @param pcbData Where to return the amount of data returned.
888 */
889static RTEXITCODE ReadClipboardData(PCCLIPUTILFORMAT pFmtDesc, void **ppvData, size_t *pcbData)
890{
891 *ppvData = NULL;
892 *pcbData = 0;
893
894#if defined(RT_OS_OS2)
895 RTEXITCODE rcExit = CuOs2OpenClipboardIfNecessary();
896 if (rcExit == RTEXITCODE_SUCCESS)
897 {
898 ULONG fFmtInfo = 0;
899 if (WinQueryClipbrdFmtInfo(g_hOs2Ab, pFmtDesc->fFormat, &fFmtInfo))
900 {
901 ULONG uData = WinQueryClipbrdData(g_hOs2Ab, pFmtDesc->fFormat);
902 if (fFmtInfo & CFI_POINTER)
903 {
904 PCLIPHEADER pOdinHdr = (PCLIPHEADER)uData;
905 if (pFmtDesc->fFormat == CF_TEXT)
906 {
907 if (pFmtDesc->fFlags & CLIPUTILFORMAT_F_CONVERT_UTF8)
908 {
909 char *pszUtf8 = NULL;
910 int rc = RTStrCurrentCPToUtf8(&pszUtf8, (const char *)uData);
911 if (RT_SUCCESS(rc))
912 {
913 *pcbData = strlen(pszUtf8) + 1;
914 *ppvData = RTMemDup(pszUtf8, *pcbData);
915 RTStrFree(pszUtf8);
916 }
917 else
918 return RTMsgErrorExitFailure("RTStrCurrentCPToUtf8 failed: %Rrc", rc);
919 }
920 else
921 {
922 *pcbData = strlen((const char *)uData) + 1;
923 *ppvData = RTMemDup((const char *)uData, *pcbData);
924 }
925 }
926 else if ( strcmp(pFmtDesc->pszFormat, "Odin32 UnicodeText") == 0
927 && memcmp(pOdinHdr->achMagic, CLIPHEADER_MAGIC, sizeof(pOdinHdr->achMagic)) == 0)
928 {
929 *pcbData = pOdinHdr->cbData;
930 *ppvData = RTMemDup(pOdinHdr + 1, pOdinHdr->cbData);
931 }
932 else
933 {
934 /* We could use DosQueryMem here to figure out the size of the allocation... */
935 *pcbData = PAGE_SIZE - (uData & PAGE_OFFSET_MASK);
936 *ppvData = RTMemDup((void const *)uData, *pcbData);
937 }
938 }
939 else
940 {
941 *pcbData = sizeof(uData);
942 *ppvData = RTMemDup(&uData, sizeof(uData));
943 }
944 if (!*ppvData)
945 rcExit = RTMsgErrorExitFailure("Out of memory allocating %#zx bytes.", *pcbData);
946 }
947 else
948 rcExit = RTMsgErrorExitFailure("WinQueryClipbrdFmtInfo(,%s,) failed: %#x\n",
949 pFmtDesc->pszName, WinGetLastError(g_hOs2Ab));
950 }
951 return rcExit;
952
953#elif defined(RT_OS_WINDOWS)
954 RTEXITCODE rcExit = WinOpenClipboardIfNecessary();
955 if (rcExit == RTEXITCODE_SUCCESS)
956 {
957 HANDLE hData = GetClipboardData(pFmtDesc->fFormat);
958 if (hData != NULL)
959 {
960 SIZE_T const cbData = GlobalSize(hData);
961 PVOID const pvData = GlobalLock(hData);
962 if (pvData != NULL)
963 {
964 *pcbData = cbData;
965 if (cbData != 0)
966 {
967 if (pFmtDesc->fFlags & CLIPUTILFORMAT_F_CONVERT_UTF8)
968 {
969 char *pszUtf8 = NULL;
970 size_t cchUtf8 = 0;
971 int rc = RTUtf16ToUtf8Ex((PCRTUTF16)pvData, cbData / sizeof(RTUTF16), &pszUtf8, 0, &cchUtf8);
972 if (RT_SUCCESS(rc))
973 {
974 *pcbData = cchUtf8 + 1;
975 *ppvData = RTMemDup(pszUtf8, cchUtf8 + 1);
976 RTStrFree(pszUtf8);
977 if (!*ppvData)
978 rcExit = RTMsgErrorExitFailure("Out of memory allocating %#zx bytes.", cbData);
979 }
980 else
981 rcExit = RTMsgErrorExitFailure("RTUtf16ToUtf8Ex failed: %Rrc", rc);
982 }
983 else
984 {
985 *ppvData = RTMemDup(pvData, cbData);
986 if (!*ppvData)
987 rcExit = RTMsgErrorExitFailure("Out of memory allocating %#zx bytes.", cbData);
988 }
989 }
990 GlobalUnlock(hData);
991 }
992 else
993 rcExit = RTMsgErrorExitFailure("GetClipboardData(%s) failed: %u (%#x)\n",
994 pFmtDesc->pszName, GetLastError(), GetLastError());
995 }
996 else
997 rcExit = RTMsgErrorExitFailure("GetClipboardData(%s) failed: %u (%#x)\n",
998 pFmtDesc->pszName, GetLastError(), GetLastError());
999 }
1000 return rcExit;
1001
1002#elif defined(CU_X11)
1003
1004 /* Request the data: */
1005 Atom const uAtomDst = pFmtDesc->uAtom;
1006 int rc = XConvertSelection(g_pX11Display, g_pTarget->uAtom, pFmtDesc->uAtom, uAtomDst, g_hX11Window, CurrentTime);
1007 if (g_uVerbosity > 1)
1008 RTPrintf("XConvertSelection -> %d\n", rc);
1009
1010 /* Wait for the reply: */
1011 for (;;)
1012 {
1013 XEvent Evt = {0};
1014 rc = XNextEvent(g_pX11Display, &Evt);
1015 if (Evt.type == SelectionNotify)
1016 {
1017 if (g_uVerbosity > 1)
1018 RTPrintf("XNextEvent -> %d; type=SelectionNotify\n", rc);
1019 if (Evt.xselection.selection == g_pTarget->uAtom)
1020 {
1021 if (Evt.xselection.property == None)
1022 return RTMsgErrorExitFailure("XConvertSelection(,%s,%s,) failed", g_pTarget->pszName, pFmtDesc->pszName);
1023
1024 /*
1025 * Retrieve the data.
1026 */
1027 Atom uAtomRetType = 0;
1028 int cBitsActualFmt = 0;
1029 unsigned long cbLeftToRead = 0;
1030 unsigned long cItems = 0;
1031 unsigned char *pbData = NULL;
1032 rc = XGetWindowProperty(g_pX11Display, g_hX11Window, uAtomDst,
1033 0 /*offset*/, _64M, False/*fDelete*/, AnyPropertyType,
1034 &uAtomRetType, &cBitsActualFmt, &cItems, &cbLeftToRead, &pbData);
1035 if (g_uVerbosity > 1)
1036 RTPrintf("XConvertSelection -> %d; uAtomRetType=%u cBitsActualFmt=%d cItems=%lu cbLeftToRead=%lu pbData=%p\n",
1037 rc, uAtomRetType, cBitsActualFmt, cItems, cbLeftToRead, pbData);
1038 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1039 if (pbData && cItems > 0)
1040 {
1041 *pcbData = cItems * (cBitsActualFmt / 8);
1042 *ppvData = RTMemDup(pbData, *pcbData);
1043 if (!*ppvData)
1044 rcExit = RTMsgErrorExitFailure("Out of memory allocating %#zx bytes.", *pcbData);
1045 }
1046 if (pbData)
1047 XFree(pbData);
1048 XDeleteProperty(g_pX11Display, g_hX11Window, uAtomDst);
1049 return rcExit;
1050 }
1051 }
1052 else if (g_uVerbosity > 1)
1053 RTPrintf("XNextEvent -> %d; type=%d\n", rc, Evt.type);
1054 }
1055
1056#else
1057 RT_NOREF(pFmtDesc);
1058 return RTMsgErrorExitFailure("ReadClipboardData is not implemented\n");
1059#endif
1060}
1061
1062
1063/**
1064 * Puts the given data and format on the clipboard.
1065 *
1066 * @returns Success indicator.
1067 * @param pFmtDesc The format.
1068 * @param pvData The data.
1069 * @param cbData The amount of data in bytes.
1070 */
1071static RTEXITCODE WriteClipboardData(PCCLIPUTILFORMAT pFmtDesc, void const *pvData, size_t cbData)
1072{
1073#if defined(RT_OS_OS2)
1074 RTEXITCODE rcExit = CuOs2OpenClipboardIfNecessary();
1075 if (rcExit == RTEXITCODE_SUCCESS)
1076 {
1077 /** @todo do we need to become owner? */
1078
1079 /* Convert to local code page if needed: */
1080 char *pszLocale = NULL;
1081 if (pFmtDesc->fFlags & CLIPUTILFORMAT_F_CONVERT_UTF8)
1082 {
1083 int rc = RTStrUtf8ToCurrentCPEx(&pszLocale, (char *)pvData, cbData);
1084 if (RT_SUCCESS(rc))
1085 {
1086 pvData = pszLocale;
1087 cbData = strlen(pszLocale) + 1;
1088 }
1089 else
1090 return RTMsgErrorExitFailure("RTStrUtf8ToCurrentCPEx failed: %Rrc\n", rc);
1091 }
1092
1093 /* Allocate a bunch of shared memory for the object. */
1094 PVOID pvShared = NULL;
1095 APIRET orc = DosAllocSharedMem(&pvShared, NULL, cbData,
1096 OBJ_GIVEABLE | OBJ_GETTABLE | OBJ_TILE | PAG_READ | PAG_WRITE | PAG_COMMIT);
1097 if (orc == NO_ERROR)
1098 {
1099 memcpy(pvShared, pvData, cbData);
1100
1101 if (WinSetClipbrdData(g_hOs2Ab, (uintptr_t)pvShared, pFmtDesc->fFormat, CFI_POINTER))
1102 {
1103 if (g_uVerbosity > 0)
1104 RTMsgInfo("Put '%s' on the clipboard: %p LB %zu\n", pFmtDesc->pszName, pvShared, cbData);
1105 rcExit = RTEXITCODE_SUCCESS;
1106 }
1107 else
1108 {
1109 rcExit = RTMsgErrorExitFailure("WinSetClipbrdData(,%p LB %#x,%s,) failed: %#x\n",
1110 pvShared, cbData, pFmtDesc->pszName, WinGetLastError(g_hOs2Ab));
1111 DosFreeMem(pvShared);
1112 }
1113 }
1114 else
1115 rcExit = RTMsgErrorExitFailure("DosAllocSharedMem(,, %#x,) -> %u", cbData, orc);
1116 RTStrFree(pszLocale);
1117 }
1118 return rcExit;
1119
1120
1121#elif defined(RT_OS_WINDOWS)
1122 RTEXITCODE rcExit = WinOpenClipboardIfNecessary();
1123 if (rcExit == RTEXITCODE_SUCCESS)
1124 {
1125 /*
1126 * Do input data conversion.
1127 */
1128 PRTUTF16 pwszFree = NULL;
1129 if (pFmtDesc->fFlags & CLIPUTILFORMAT_F_CONVERT_UTF8)
1130 {
1131 size_t cwcConv = 0;
1132 int rc = RTStrToUtf16Ex((char const *)pvData, cbData, &pwszFree, 0, &cwcConv);
1133 if (RT_SUCCESS(rc))
1134 {
1135 pvData = pwszFree;
1136 cbData = cwcConv * sizeof(RTUTF16);
1137 }
1138 else
1139 return RTMsgErrorExitFailure("RTStrToTUtf16Ex failed: %Rrc\n", rc);
1140 }
1141
1142 /*
1143 * Text formats generally include the zero terminator.
1144 */
1145 uint32_t cbZeroPadding = 0;
1146 if (pFmtDesc->fFormat == CF_UNICODETEXT)
1147 cbZeroPadding = sizeof(WCHAR);
1148 else if (pFmtDesc->fFormat == CF_TEXT)
1149 cbZeroPadding = sizeof(char);
1150
1151 HANDLE hDstData = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, cbData + cbZeroPadding);
1152 if (hDstData)
1153 {
1154 if (cbData)
1155 {
1156 PVOID pvDstData = GlobalLock(hDstData);
1157 if (pvDstData)
1158 memcpy(pvDstData, pvData, cbData);
1159 else
1160 rcExit = RTMsgErrorExitFailure("GlobalLock failed: %u (%#x)\n", GetLastError(), GetLastError());
1161 }
1162 if (rcExit == RTEXITCODE_SUCCESS)
1163 {
1164 if (SetClipboardData(pFmtDesc->fFormat, hDstData))
1165 {
1166 if (g_uVerbosity > 0)
1167 RTMsgInfo("Put '%s' on the clipboard: %p LB %zu\n", pFmtDesc->pszName, hDstData, cbData + cbZeroPadding);
1168 }
1169 else
1170 {
1171 rcExit = RTMsgErrorExitFailure("SetClipboardData(%s) failed: %u (%#x)\n",
1172 pFmtDesc->pszName, GetLastError(), GetLastError());
1173 GlobalFree(hDstData);
1174 }
1175 }
1176 else
1177 GlobalFree(hDstData);
1178 }
1179 else
1180 rcExit = RTMsgErrorExitFailure("GlobalAlloc(,%#zx) failed: %u (%#x)\n",
1181 cbData + cbZeroPadding, GetLastError(), GetLastError());
1182 }
1183 return rcExit;
1184
1185#else
1186 RT_NOREF(pFmtDesc, pvData, cbData);
1187 return RTMsgErrorExitFailure("WriteClipboardData is not implemented\n");
1188#endif
1189}
1190
1191
1192/**
1193 * Check if the given data + format matches what's actually on the clipboard.
1194 *
1195 * @returns Success indicator.
1196 * @param pFmtDesc The format to compare.
1197 * @param pvExpect The expected clipboard data.
1198 * @param cbExpect The size of the expected clipboard data.
1199 */
1200static RTEXITCODE CompareDataWithClipboard(PCCLIPUTILFORMAT pFmtDesc, void const *pvExpect, size_t cbExpect)
1201{
1202 void *pvData = NULL;
1203 size_t cbData = 0;
1204 RTEXITCODE rcExit = ReadClipboardData(pFmtDesc, &pvData, &cbData);
1205 if (rcExit == RTEXITCODE_SUCCESS)
1206 {
1207 if ( cbData == cbExpect
1208 && memcmp(pvData, pvExpect, cbData) == 0)
1209 rcExit = RTEXITCODE_SUCCESS;
1210 else
1211 rcExit = RTMsgErrorExitFailure("Mismatch for '%s' (cbData=%#zx cbExpect=%#zx)\n",
1212 pFmtDesc->pszName, cbData, cbExpect);
1213 RTMemFree(pvData);
1214 }
1215 return rcExit;
1216}
1217
1218
1219/**
1220 * Gets the given clipboard format.
1221 *
1222 * @returns Success indicator.
1223 * @param pFmtDesc The format to get.
1224 * @param pStrmOut Where to output the data.
1225 * @param fIsStdOut Set if @a pStrmOut is standard output, clear if not.
1226 */
1227static RTEXITCODE ClipboardContentToStdOut(PCCLIPUTILFORMAT pFmtDesc)
1228{
1229 void *pvData = NULL;
1230 size_t cbData = 0;
1231 RTEXITCODE rcExit = ReadClipboardData(pFmtDesc, &pvData, &cbData);
1232 if (rcExit == RTEXITCODE_SUCCESS)
1233 {
1234 int rc = RTStrmWrite(g_pStdOut, pvData, cbData);
1235 RTMemFree(pvData);
1236 if (RT_FAILURE(rc))
1237 rcExit = RTMsgErrorExitFailure("Error writing %#zx bytes to standard output: %Rrc", cbData, rc);
1238 }
1239 return rcExit;
1240}
1241
1242
1243/**
1244 * Gets the given clipboard format.
1245 *
1246 * @returns Success indicator.
1247 * @param pFmtDesc The format to get.
1248 * @param pszFilename The output filename.
1249 */
1250static RTEXITCODE ClipboardContentToFile(PCCLIPUTILFORMAT pFmtDesc, const char *pszFilename)
1251{
1252 void *pvData = NULL;
1253 size_t cbData = 0;
1254 RTEXITCODE rcExit = ReadClipboardData(pFmtDesc, &pvData, &cbData);
1255 if (rcExit == RTEXITCODE_SUCCESS)
1256 {
1257 RTFILE hFile = NIL_RTFILE;
1258 int rc = RTFileOpen(&hFile, pszFilename,
1259 RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE
1260 | (0770 << RTFILE_O_CREATE_MODE_SHIFT));
1261 if (RT_SUCCESS(rc))
1262 {
1263 rc = RTFileWrite(hFile, pvData, cbData, NULL);
1264 int const rc2 = RTFileClose(hFile);
1265 if (RT_FAILURE(rc) || RT_FAILURE(rc2))
1266 {
1267 if (RT_FAILURE_NP(rc))
1268 RTMsgError("Writing %#z bytes to '%s' failed: %Rrc", cbData, pszFilename, rc);
1269 else
1270 RTMsgError("Closing '%s' failed: %Rrc", pszFilename, rc2);
1271 RTMsgInfo("Deleting '%s'.", pszFilename);
1272 RTFileDelete(pszFilename);
1273 rcExit = RTEXITCODE_FAILURE;
1274 }
1275 }
1276 else
1277 rcExit = RTMsgErrorExitFailure("Failed to open '%s' for writing: %Rrc", pszFilename, rc);
1278 RTMemFree(pvData);
1279 }
1280 return rcExit;
1281}
1282
1283
1284/**
1285 * Puts the given format + data onto the clipboard.
1286 *
1287 * @returns Success indicator.
1288 * @param pFmtDesc The format to put.
1289 * @param pszData The string data.
1290 */
1291static RTEXITCODE PutStringOnClipboard(PCCLIPUTILFORMAT pFmtDesc, const char *pszData)
1292{
1293 return WriteClipboardData(pFmtDesc, pszData, strlen(pszData));
1294}
1295
1296
1297/**
1298 * Puts a format + file content onto the clipboard.
1299 *
1300 * @returns Success indicator.
1301 * @param pFmtDesc The format to put.
1302 * @param pszFilename The filename.
1303 */
1304static RTEXITCODE PutFileOnClipboard(PCCLIPUTILFORMAT pFmtDesc, const char *pszFilename)
1305{
1306 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1307 void *pvData = NULL;
1308 size_t cbData = 0;
1309 int rc = RTFileReadAll(pszFilename, &pvData, &cbData);
1310 if (RT_SUCCESS(rc))
1311 {
1312 rcExit = WriteClipboardData(pFmtDesc, pvData, cbData);
1313 RTFileReadAllFree(pvData, cbData);
1314 }
1315 else
1316 rcExit = RTMsgErrorExitFailure("Failed to open and read '%s' into memory: %Rrc", pszFilename, rc);
1317 return rcExit;
1318}
1319
1320
1321/**
1322 * Checks if the given format + data matches what's on the clipboard.
1323 *
1324 * @returns Success indicator.
1325 * @param pFmtDesc The format to check.
1326 * @param pszData The string data.
1327 */
1328static RTEXITCODE CheckStringAgainstClipboard(PCCLIPUTILFORMAT pFmtDesc, const char *pszData)
1329{
1330 return CompareDataWithClipboard(pFmtDesc, pszData, strlen(pszData));
1331}
1332
1333
1334/**
1335 * Check if the given format + file content matches what's on the clipboard.
1336 *
1337 * @returns Success indicator.
1338 * @param pFmtDesc The format to check.
1339 * @param pszFilename The filename.
1340 */
1341static RTEXITCODE CheckFileAgainstClipboard(PCCLIPUTILFORMAT pFmtDesc, const char *pszFilename)
1342{
1343 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1344 void *pvData = NULL;
1345 size_t cbData = 0;
1346 int rc = RTFileReadAll(pszFilename, &pvData, &cbData);
1347 if (RT_SUCCESS(rc))
1348 {
1349 rcExit = CompareDataWithClipboard(pFmtDesc, pvData, cbData);
1350 RTFileReadAllFree(pvData, cbData);
1351 }
1352 else
1353 rcExit = RTMsgErrorExitFailure("Failed to open and read '%s' into memory: %Rrc", pszFilename, rc);
1354 return rcExit;
1355}
1356
1357
1358/**
1359 * Check that the given format is not on the clipboard.
1360 *
1361 * @returns Success indicator.
1362 * @param pFmtDesc The format to check.
1363 */
1364static RTEXITCODE CheckFormatNotOnClipboard(PCCLIPUTILFORMAT pFmtDesc)
1365{
1366#if defined(RT_OS_OS2)
1367 RTEXITCODE rcExit = CuOs2OpenClipboardIfNecessary();
1368 if (rcExit == RTEXITCODE_SUCCESS)
1369 {
1370 ULONG fFmtInfo = 0;
1371 if (WinQueryClipbrdFmtInfo(g_hOs2Ab, pFmtDesc->fFormat, &fFmtInfo))
1372 rcExit = RTMsgErrorExitFailure("Format '%s' is present");
1373 }
1374 return rcExit;
1375
1376#elif defined(RT_OS_WINDOWS)
1377 RTEXITCODE rcExit = WinOpenClipboardIfNecessary();
1378 if (rcExit == RTEXITCODE_SUCCESS)
1379 {
1380 if (IsClipboardFormatAvailable(pFmtDesc->fFormat))
1381 rcExit = RTMsgErrorExitFailure("Format '%s' is present");
1382 }
1383 return rcExit;
1384
1385#else
1386 RT_NOREF(pFmtDesc);
1387 return RTMsgErrorExitFailure("CheckFormatNotOnClipboard is not implemented");
1388#endif
1389}
1390
1391
1392/**
1393 * Empties the clipboard.
1394 *
1395 * @returns Success indicator.
1396 */
1397static RTEXITCODE ZapAllClipboardData(void)
1398{
1399#if defined(RT_OS_OS2)
1400 RTEXITCODE rcExit = CuOs2OpenClipboardIfNecessary();
1401 if (rcExit == RTEXITCODE_SUCCESS)
1402 {
1403 ULONG fFmtInfo = 0;
1404 if (WinEmptyClipbrd(g_hOs2Ab))
1405 {
1406 WinSetClipbrdOwner(g_hOs2Ab, g_hOs2Wnd); /* Probably unnecessary? */
1407 WinSetClipbrdOwner(g_hOs2Ab, NULLHANDLE);
1408 g_fOs2ClipboardOwner = false;
1409 }
1410 else
1411 rcExit = RTMsgErrorExitFailure("WinEmptyClipbrd() failed: %#x\n", WinGetLastError(g_hOs2Ab));
1412 }
1413 return rcExit;
1414
1415#elif defined(RT_OS_WINDOWS)
1416 RTEXITCODE rcExit = WinOpenClipboardIfNecessary();
1417 if (rcExit == RTEXITCODE_SUCCESS)
1418 {
1419 if (!EmptyClipboard())
1420 rcExit = RTMsgErrorExitFailure("EmptyClipboard() failed: %u (%#x)\n", GetLastError(), GetLastError());
1421 }
1422 return rcExit;
1423
1424#else
1425 return RTMsgErrorExitFailure("ZapAllClipboardData is not implemented");
1426#endif
1427}
1428
1429
1430/**
1431 * Waits/delays at least @a cMsWait milliseconds.
1432 *
1433 * @returns Success indicator.
1434 * @param cMsWait Minimum wait/delay time in milliseconds.
1435 */
1436static RTEXITCODE DoWait(uint32_t cMsWait)
1437{
1438 uint64_t const msStart = RTTimeMilliTS();
1439 if (g_uVerbosity > 1)
1440 RTMsgInfo("Waiting %u ms...\n", cMsWait);
1441
1442#if defined(RT_OS_OS2)
1443 /*
1444 * Arm a timer which will timeout after the desired period and
1445 * quit when we've dispatched it.
1446 */
1447 g_fOs2TimerTicked = false;
1448 if (WinStartTimer(g_hOs2Ab, g_hOs2Wnd, 1 /*idEvent*/, cMsWait + 1) != 0)
1449 {
1450 QMSG Msg;
1451 while (WinGetMsg(g_hOs2Ab, &Msg, NULL, 0, 0))
1452 {
1453 WinDispatchMsg(g_hOs2Ab, &Msg);
1454 if (g_fOs2TimerTicked || RTTimeMilliTS() - msStart >= cMsWait)
1455 break;
1456 }
1457
1458 if (!WinStopTimer(g_hOs2Ab, g_hOs2Wnd, 1 /*idEvent*/))
1459 RTMsgWarning("WinStopTimer failed: %#x", WinGetLastError(g_hOs2Ab));
1460 }
1461 else
1462 return RTMsgErrorExitFailure("WinStartTimer(,,,%u ms) failed: %#x", cMsWait + 1, WinGetLastError(g_hOs2Ab));
1463
1464#elif defined(RT_OS_WINDOWS)
1465 /*
1466 * Arm a timer which will timeout after the desired period and
1467 * quit when we've dispatched it.
1468 */
1469 g_fWinTimerTicked = false;
1470 if (SetTimer(g_hWinWnd, 1 /*idEvent*/, cMsWait + 1, NULL /*pfnTimerProc*/) != 0)
1471 {
1472 MSG Msg;
1473 while (GetMessageW(&Msg, NULL, 0, 0))
1474 {
1475 TranslateMessage(&Msg);
1476 DispatchMessageW(&Msg);
1477 if (g_fWinTimerTicked || RTTimeMilliTS() - msStart >= cMsWait)
1478 break;
1479 }
1480
1481 if (!KillTimer(g_hWinWnd, 1 /*idEvent*/))
1482 RTMsgWarning("KillTimer failed: %u (%#x)", GetLastError(), GetLastError());
1483 }
1484 else
1485 return RTMsgErrorExitFailure("SetTimer(,,%u ms,) failed: %u (%#x)", cMsWait + 1, GetLastError(), GetLastError());
1486
1487#else
1488/** @todo X11 needs to run it's message queue too, because if we're offering
1489 * things on the "clipboard" we must reply to requests for them. */
1490 /*
1491 * Just a plain simple RTThreadSleep option (will probably not be used in the end):
1492 */
1493 for (;;)
1494 {
1495 uint64_t cMsElapsed = RTTimeMilliTS() - msStart;
1496 if (cMsElapsed >= cMsWait)
1497 break;
1498 RTThreadSleep(cMsWait - cMsElapsed);
1499 }
1500#endif
1501
1502 if (g_uVerbosity > 2)
1503 RTMsgInfo("Done waiting after %u ms.\n", RTTimeMilliTS() - msStart);
1504 return RTEXITCODE_SUCCESS;
1505}
1506
1507
1508/**
1509 * Display the usage to @a pStrm.
1510 */
1511static void Usage(PRTSTREAM pStrm)
1512{
1513 RTStrmPrintf(pStrm,
1514 "usage: %s [--get <fmt> [--get ...]] [--get-file <fmt> <file> [--get-file ...]]\n"
1515 " %s [--zap] [--put <fmt> <content> [--put ...]] [--put-file <fmt> <file> [--put-file ...]] [--wait <ms>]\n"
1516 " %s [--check <fmt> <expected> [--check ...]] [--check-file <fmt> <file> [--check-file ...]]\n"
1517 " [--check-no <fmt> [--check-no ...]]\n"
1518 , RTProcShortName(), RTProcShortName(), RTProcShortName());
1519 RTStrmPrintf(pStrm, "\n");
1520 RTStrmPrintf(pStrm, "Actions/Options:\n");
1521
1522 for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++)
1523 {
1524 const char *pszHelp;
1525 switch (g_aCmdOptions[i].iShort)
1526 {
1527 case 'l': pszHelp = "List the clipboard content."; break;
1528 case 'g': pszHelp = "Get given clipboard format and writes it to standard output."; break;
1529 case 'G': pszHelp = "Get given clipboard format and writes it to the specified file."; break;
1530 case 'p': pszHelp = "Puts given format and content on the clipboard."; break;
1531 case 'P': pszHelp = "Puts given format and file content on the clipboard."; break;
1532 case 'c': pszHelp = "Checks that the given format and content matches the clipboard."; break;
1533 case 'C': pszHelp = "Checks that the given format and file content matches the clipboard."; break;
1534 case 'n': pszHelp = "Checks that the given format is not on the clipboard."; break;
1535 case 'z': pszHelp = "Zaps the clipboard content."; break;
1536#ifdef MULTI_TARGET_CLIPBOARD
1537 case 't': pszHelp = "Selects the target clipboard."; break;
1538#endif
1539#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
1540 case 'k': pszHelp = "Closes the clipboard if open (win,os2)."; break;
1541#endif
1542 case 'w': pszHelp = "Waits a given number of milliseconds before continuing."; break;
1543 case 'v': pszHelp = "More verbose execution."; break;
1544 case 'q': pszHelp = "Quiet execution."; break;
1545 case 'h': pszHelp = "Displays this help and exit"; break;
1546 case 'V': pszHelp = "Displays the program revision"; break;
1547
1548 default:
1549 pszHelp = "Option undocumented";
1550 break;
1551 }
1552 if ((unsigned)g_aCmdOptions[i].iShort < 127U)
1553 {
1554 char szOpt[64];
1555 RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort);
1556 RTStrmPrintf(pStrm, " %-19s %s\n", szOpt, pszHelp);
1557 }
1558 else
1559 RTStrmPrintf(pStrm, " %-19s %s\n", g_aCmdOptions[i].pszLong, pszHelp);
1560 }
1561 RTStrmPrintf(pStrm,
1562 "\n"
1563 "Note! Options are processed in the order they are given.\n");
1564
1565 RTStrmPrintf(pStrm, "\nFormats:\n");
1566 for (size_t i = 0; i < RT_ELEMENTS(g_aFormats); i++)
1567 RTStrmPrintf(pStrm, " %-12s: %s\n", g_aFormats[i].pszName, g_aFormats[i].pszDesc);
1568
1569#ifdef MULTI_TARGET_CLIPBOARD
1570 RTStrmPrintf(pStrm, "\nTarget:\n");
1571 for (size_t i = 0; i < RT_ELEMENTS(g_aTargets); i++)
1572 RTStrmPrintf(pStrm, " %-12s: %s\n", g_aTargets[i].pszName, g_aTargets[i].pszDesc);
1573#endif
1574}
1575
1576
1577int main(int argc, char *argv[])
1578{
1579 /*
1580 * Init IPRT.
1581 */
1582 int rc = RTR3InitExe(argc, &argv, 0);
1583 if (RT_FAILURE(rc))
1584 return RTMsgInitFailure(rc);
1585
1586 /*
1587 * Host specific init.
1588 */
1589#ifdef RT_OS_DARWIN
1590 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1591#elif defined(RT_OS_OS2)
1592 RTEXITCODE rcExit = CuOs2Init();
1593#elif defined(RT_OS_WINDOWS)
1594 RTEXITCODE rcExit = CuWinInit();
1595#else
1596 RTEXITCODE rcExit = CuX11Init();
1597#endif
1598 if (rcExit != RTEXITCODE_SUCCESS)
1599 return rcExit;
1600
1601 /*
1602 * Process options (in order).
1603 */
1604 RTGETOPTUNION ValueUnion;
1605 RTGETOPTSTATE GetState;
1606 RTGetOptInit(&GetState, argc, argv, g_aCmdOptions, RT_ELEMENTS(g_aCmdOptions), 1, 0 /* fFlags */);
1607 while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0)
1608 {
1609 RTEXITCODE rcExit2 = RTEXITCODE_SUCCESS;
1610 switch (rc)
1611 {
1612#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
1613 case 'k':
1614 rcExit2 = CuCloseClipboard();
1615 break;
1616#endif
1617
1618 case 'l':
1619 rcExit2 = ListClipboardContent();
1620 break;
1621
1622 case 'g':
1623 {
1624 PCCLIPUTILFORMAT pFmtDesc = GetFormatDesc(ValueUnion.psz);
1625 if (pFmtDesc)
1626 rcExit2 = ClipboardContentToStdOut(pFmtDesc);
1627 else
1628 rcExit2 = RTEXITCODE_FAILURE;
1629 break;
1630 }
1631
1632 case 'G':
1633 {
1634 PCCLIPUTILFORMAT pFmtDesc = GetFormatDesc(ValueUnion.psz);
1635 if (pFmtDesc)
1636 {
1637 rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_STRING);
1638 if (RT_SUCCESS(rc))
1639 rcExit2 = ClipboardContentToFile(pFmtDesc, ValueUnion.psz);
1640 else
1641 return RTMsgErrorExitFailure("No filename given with --get-file");
1642 }
1643 else
1644 rcExit2 = RTEXITCODE_FAILURE;
1645 break;
1646 }
1647
1648 case 'p':
1649 {
1650 PCCLIPUTILFORMAT pFmtDesc = GetFormatDesc(ValueUnion.psz);
1651 if (pFmtDesc)
1652 {
1653 rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_STRING);
1654 if (RT_SUCCESS(rc))
1655 rcExit2 = PutStringOnClipboard(pFmtDesc, ValueUnion.psz);
1656 else
1657 return RTMsgErrorExitFailure("No data string given with --put");
1658 }
1659 else
1660 rcExit2 = RTEXITCODE_FAILURE;
1661 break;
1662 }
1663
1664 case 'P':
1665 {
1666 PCCLIPUTILFORMAT pFmtDesc = GetFormatDesc(ValueUnion.psz);
1667 if (pFmtDesc)
1668 {
1669 rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_STRING);
1670 if (RT_SUCCESS(rc))
1671 rcExit2 = PutFileOnClipboard(pFmtDesc, ValueUnion.psz);
1672 else
1673 return RTMsgErrorExitFailure("No filename given with --put-file");
1674 }
1675 else
1676 rcExit2 = RTEXITCODE_FAILURE;
1677 break;
1678 }
1679
1680 case 'c':
1681 {
1682 PCCLIPUTILFORMAT pFmtDesc = GetFormatDesc(ValueUnion.psz);
1683 if (pFmtDesc)
1684 {
1685 rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_STRING);
1686 if (RT_SUCCESS(rc))
1687 rcExit2 = CheckStringAgainstClipboard(pFmtDesc, ValueUnion.psz);
1688 else
1689 return RTMsgErrorExitFailure("No data string given with --check");
1690 }
1691 else
1692 rcExit2 = RTEXITCODE_FAILURE;
1693 break;
1694 }
1695
1696 case 'C':
1697 {
1698 PCCLIPUTILFORMAT pFmtDesc = GetFormatDesc(ValueUnion.psz);
1699 if (pFmtDesc)
1700 {
1701 rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_STRING);
1702 if (RT_SUCCESS(rc))
1703 rcExit2 = CheckFileAgainstClipboard(pFmtDesc, ValueUnion.psz);
1704 else
1705 return RTMsgErrorExitFailure("No filename given with --check-file");
1706 }
1707 else
1708 rcExit2 = RTEXITCODE_FAILURE;
1709 break;
1710 }
1711
1712 case 'n':
1713 {
1714 PCCLIPUTILFORMAT pFmtDesc = GetFormatDesc(ValueUnion.psz);
1715 if (pFmtDesc)
1716 rcExit2 = CheckFormatNotOnClipboard(pFmtDesc);
1717 else
1718 rcExit2 = RTEXITCODE_FAILURE;
1719 break;
1720 }
1721
1722
1723 case 'z':
1724 rcExit2 = ZapAllClipboardData();
1725 break;
1726
1727#ifdef MULTI_TARGET_CLIPBOARD
1728 case 't':
1729 {
1730 CLIPUTILTARGET *pNewTarget = NULL;
1731 for (size_t i = 0; i < RT_ELEMENTS(g_aTargets); i++)
1732 if (strcmp(ValueUnion.psz, g_aTargets[i].pszName) == 0)
1733 {
1734 pNewTarget = &g_aTargets[i];
1735 break;
1736 }
1737 if (!pNewTarget)
1738 return RTMsgErrorExitFailure("Unknown target '%s'", ValueUnion.psz);
1739 if (pNewTarget != g_pTarget && g_uVerbosity > 0)
1740 RTMsgInfo("Switching from '%s' to '%s'\n", g_pTarget->pszName, pNewTarget->pszName);
1741 g_pTarget = pNewTarget;
1742 break;
1743 }
1744#endif
1745
1746 case 'w':
1747 rcExit2 = DoWait(ValueUnion.u32);
1748 break;
1749
1750 case 'q':
1751 g_uVerbosity = 0;
1752 break;
1753
1754 case 'v':
1755 g_uVerbosity++;
1756 break;
1757
1758 case 'h':
1759 Usage(g_pStdOut);
1760 return RTEXITCODE_SUCCESS;
1761
1762 case 'V':
1763 {
1764 char szRev[] = "$Revision: 106061 $";
1765 szRev[RT_ELEMENTS(szRev) - 2] = '\0';
1766 RTPrintf(RTStrStrip(strchr(szRev, ':') + 1));
1767 return RTEXITCODE_SUCCESS;
1768 }
1769
1770 default:
1771 return RTGetOptPrintError(rc, &ValueUnion);
1772 }
1773
1774 if (rcExit2 != RTEXITCODE_SUCCESS && rcExit == RTEXITCODE_SUCCESS)
1775 rcExit = rcExit2;
1776 }
1777
1778 /*
1779 * Host specific cleanup.
1780 */
1781#if defined(RT_OS_OS2)
1782 RTEXITCODE rcExit2 = CuOs2Term();
1783#elif defined(RT_OS_WINDOWS)
1784 RTEXITCODE rcExit2 = CuWinTerm();
1785#else
1786 RTEXITCODE rcExit2 = RTEXITCODE_SUCCESS;
1787#endif
1788 if (rcExit2 != RTEXITCODE_SUCCESS && rcExit != RTEXITCODE_SUCCESS)
1789 rcExit = rcExit2;
1790
1791 return rcExit;
1792}
1793
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