VirtualBox

source: vbox/trunk/src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp@ 80581

Last change on this file since 80581 was 80444, checked in by vboxsync, 6 years ago

Shared Clipboard/URI: Added protocol versioning support plus enhanced versions of existing commands (to also provide context IDs, among other stuff). So far only the host service(s) and the Windows guest is using the new(er) protocol.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 40.4 KB
Line 
1/** $Id: VBoxServiceClipboard-os2.cpp 80444 2019-08-27 17:47:44Z vboxsync $ */
2/** @file
3 * VBoxService - Guest Additions Clipboard Service, OS/2.
4 */
5
6/*
7 * Copyright (C) 2007-2019 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/** @page pg_vgsvc_clipboard VBoxService - Clipboard (OS/2)
20 *
21 * The Clipboard subservice provides clipboard sharing for OS/2 guests only.
22 *
23 * This was the second subservice that was added to VBoxService. OS/2 is a
24 * single user system and we don't provide any VBoxTray or VBoxClient like
25 * processes. Because it's kind of simple system, it became natural to put the
26 * clipboard sharing here in VBoxService for OS/2.
27 *
28 * In addition to integrating with the native OS/2 PM clipboard formats, we also
29 * try provide the Odin32, a windows API layer for OS/2 (developed by Sander van
30 * Leeuwen and friends, later mainly InnoTek), with additional formats.
31 *
32 * Bitmaps are currently not supported, but that can easily be added should the
33 * need ever arrise.
34 */
35
36
37/*********************************************************************************************************************************
38* Header Files *
39*********************************************************************************************************************************/
40#define INCL_BASE
41#define INCL_PM
42#define INCL_ERRORS
43#include <os2.h>
44
45#include <iprt/asm.h>
46#include <iprt/assert.h>
47#include <iprt/mem.h>
48#include <iprt/param.h>
49#include <iprt/semaphore.h>
50#include <iprt/string.h>
51#include <iprt/thread.h>
52#include <iprt/time.h>
53#include <iprt/utf16.h>
54#include <VBox/VBoxGuestLib.h>
55#include <VBox/GuestHost/SharedClipboard.h>
56#include <VBox/HostServices/VBoxClipboardSvc.h>
57#include "VBoxServiceInternal.h"
58
59
60/*********************************************************************************************************************************
61* Structures and Typedefs *
62*********************************************************************************************************************************/
63/** Header for Odin32 specific clipboard entries.
64 * (Used to get the correct size of the data.)
65 */
66typedef struct _Odin32ClipboardHeader
67{
68 /** magic number */
69 char achMagic[8];
70 /** Size of the following data.
71 * (The interpretation depends on the type.) */
72 unsigned cbData;
73 /** Odin32 format number. */
74 unsigned uFormat;
75} CLIPHEADER, *PCLIPHEADER;
76
77#define CLIPHEADER_MAGIC "Odin\1\0\1"
78
79
80/*********************************************************************************************************************************
81* Global Variables *
82*********************************************************************************************************************************/
83
84/** The control thread (main) handle.
85 * Only used to avoid some queue creation trouble. */
86static RTTHREAD g_ThreadCtrl = NIL_RTTHREAD;
87/** The HAB of the control thread (main). */
88static HAB g_habCtrl = NULLHANDLE;
89/** The HMQ of the control thread (main). */
90static HMQ g_hmqCtrl = NULLHANDLE;
91
92/** The Listener thread handle. */
93static RTTHREAD g_ThreadListener = NIL_RTTHREAD;
94/** The HAB of the listener thread. */
95static HAB g_habListener = NULLHANDLE;
96/** The HMQ of the listener thread. */
97static HMQ g_hmqListener = NULLHANDLE;
98/** Indicator that gets set if the listener thread is successfully initialized. */
99static bool volatile g_fListenerOkay = false;
100
101/** The HAB of the worker thread. */
102static HAB g_habWorker = NULLHANDLE;
103/** The HMQ of the worker thread. */
104static HMQ g_hmqWorker = NULLHANDLE;
105/** The object window handle. */
106static HWND g_hwndWorker = NULLHANDLE;
107/** The timer id returned by WinStartTimer. */
108static ULONG g_idWorkerTimer = ~0UL;
109/** The state of the clipboard.
110 * @remark I'm trying out the 'k' prefix from the mac here, bear with me. */
111static enum
112{
113 /** The clipboard hasn't been initialized yet. */
114 kClipboardState_Uninitialized = 0,
115 /** WinSetClipbrdViewer call in progress, ignore WM_DRAWCLIPBOARD. */
116 kClipboardState_SettingViewer,
117 /** We're monitoring the clipboard as a viewer. */
118 kClipboardState_Viewer,
119 /** We're monitoring the clipboard using polling.
120 * This usually means something is wrong... */
121 kClipboardState_Polling,
122 /** We're destroying the clipboard content, ignore WM_DESTROYCLIPBOARD. */
123 kClipboardState_Destroying,
124 /** We're owning the clipboard (i.e. we have data on it). */
125 kClipboardState_Owner
126} g_enmState = kClipboardState_Uninitialized;
127/** Set if the clipboard was empty the last time we polled it. */
128static bool g_fEmptyClipboard = false;
129
130/** A clipboard format atom for the dummy clipboard data we insert
131 * watching for clipboard changes. If this format is found on the
132 * clipboard, the empty clipboard function has not been called
133 * since we last polled it. */
134static ATOM g_atomNothingChanged = 0;
135
136/** The clipboard connection client ID. */
137static uint32_t g_u32ClientId;
138/** Odin32 CF_UNICODETEXT. See user32.cpp. */
139static ATOM g_atomOdin32UnicodeText = 0;
140/** Odin32 CF_UNICODETEXT. See user32.cpp. */
141#define SZFMT_ODIN32_UNICODETEXT (PCSZ)"Odin32 UnicodeText"
142
143
144
145
146/**
147 * @interface_method_impl{VBOXSERVICE,pfnPreInit}
148 */
149static DECLCALLBACK(int) vgsvcClipboardOs2PreInit(void)
150{
151 return VINF_SUCCESS;
152}
153
154
155/**
156 * @interface_method_impl{VBOXSERVICE,pfnOption}
157 */
158static DECLCALLBACK(int) vgsvcClipboardOs2Option(const char **ppszShort, int argc, char **argv, int *pi)
159{
160 NOREF(ppszShort);
161 NOREF(argc);
162 NOREF(argv);
163 NOREF(pi);
164
165 return -1;
166}
167
168
169/**
170 * @interface_method_impl{VBOXSERVICE,pfnInit}
171 */
172static DECLCALLBACK(int) vgsvcClipboardOs2Init(void)
173{
174 int rc = VERR_GENERAL_FAILURE;
175 g_ThreadCtrl = RTThreadSelf();
176
177 /*
178 * Make PM happy.
179 */
180 PPIB pPib;
181 PTIB pTib;
182 DosGetInfoBlocks(&pTib, &pPib);
183 pPib->pib_ultype = 3; /* PM session type */
184
185 /*
186 * Since we have to send shutdown messages and such from the
187 * service controller (main) thread, create a HAB and HMQ for it.
188 */
189 g_habCtrl = WinInitialize(0);
190 if (g_habCtrl == NULLHANDLE)
191 {
192 VGSvcError("WinInitialize(0) failed, lasterr=%lx\n", WinGetLastError(NULLHANDLE));
193 return VERR_GENERAL_FAILURE;
194 }
195 g_hmqCtrl = WinCreateMsgQueue(g_habCtrl, 0);
196 if (g_hmqCtrl != NULLHANDLE)
197 {
198 WinCancelShutdown(g_hmqCtrl, TRUE); /* We don't care about shutdown */
199
200 /*
201 * Create the 'nothing-changed' format.
202 */
203 g_atomNothingChanged = WinAddAtom(WinQuerySystemAtomTable(), (PCSZ)"VirtualBox Clipboard Service");
204 LONG lLastError = WinGetLastError(g_habCtrl);
205 if (g_atomNothingChanged == 0)
206 g_atomNothingChanged = WinFindAtom(WinQuerySystemAtomTable(), (PCSZ)"VirtualBox Clipboard Service");
207 if (g_atomNothingChanged)
208 {
209 /*
210 * Connect to the clipboard service.
211 */
212 VGSvcVerbose(4, "clipboard: connecting\n");
213 rc = VbglR3ClipboardConnect(&g_u32ClientId);
214 if (RT_SUCCESS(rc))
215 {
216 /*
217 * Create any extra clipboard type atoms, like the odin unicode text.
218 */
219 g_atomOdin32UnicodeText = WinAddAtom(WinQuerySystemAtomTable(), SZFMT_ODIN32_UNICODETEXT);
220 lLastError = WinGetLastError(g_habCtrl);
221 if (g_atomOdin32UnicodeText == 0)
222 g_atomOdin32UnicodeText = WinFindAtom(WinQuerySystemAtomTable(), SZFMT_ODIN32_UNICODETEXT);
223 if (g_atomOdin32UnicodeText == 0)
224 VGSvcError("WinAddAtom() failed, lasterr=%lx; WinFindAtom() failed, lasterror=%lx\n",
225 lLastError, WinGetLastError(g_habCtrl));
226
227 VGSvcVerbose(2, "g_u32ClientId=%RX32 g_atomNothingChanged=%#x g_atomOdin32UnicodeText=%#x\n",
228 g_u32ClientId, g_atomNothingChanged, g_atomOdin32UnicodeText);
229 return VINF_SUCCESS;
230 }
231
232 VGSvcError("Failed to connect to the clipboard service, rc=%Rrc!\n", rc);
233 }
234 else
235 VGSvcError("WinAddAtom() failed, lasterr=%lx; WinFindAtom() failed, lasterror=%lx\n",
236 lLastError, WinGetLastError(g_habCtrl));
237 }
238 else
239 VGSvcError("WinCreateMsgQueue(,0) failed, lasterr=%lx\n", WinGetLastError(g_habCtrl));
240 WinTerminate(g_habCtrl);
241 return rc;
242}
243
244
245/**
246 * Check that we're still the view / try make us the viewer.
247 */
248static void vgsvcClipboardOs2PollViewer(void)
249{
250 const int iOrgState = g_enmState;
251
252 HWND hwndClipboardViewer = WinQueryClipbrdViewer(g_habWorker);
253 if (hwndClipboardViewer == g_hwndWorker)
254 return;
255
256 if (hwndClipboardViewer == NULLHANDLE)
257 {
258 /* The API will send a WM_DRAWCLIPBOARD message before returning. */
259 g_enmState = kClipboardState_SettingViewer;
260 if (WinSetClipbrdViewer(g_habWorker, g_hwndWorker))
261 g_enmState = kClipboardState_Viewer;
262 else
263 g_enmState = kClipboardState_Polling;
264 }
265 else
266 g_enmState = kClipboardState_Polling;
267 if ((int)g_enmState != iOrgState)
268 {
269 if (g_enmState == kClipboardState_Viewer)
270 VGSvcVerbose(3, "clipboard: viewer\n");
271 else
272 VGSvcVerbose(3, "clipboard: poller\n");
273 }
274}
275
276
277/**
278 * Advertise the formats available from the host.
279 *
280 * @param fFormats The formats available on the host.
281 */
282static void vgsvcClipboardOs2AdvertiseHostFormats(uint32_t fFormats)
283{
284 /*
285 * Open the clipboard and switch to 'destruction' mode.
286 * Make sure we stop being viewer. Temporarily also make sure we're
287 * not the owner so that PM won't send us any WM_DESTROYCLIPBOARD message.
288 */
289 if (WinOpenClipbrd(g_habWorker))
290 {
291 if (g_enmState == kClipboardState_Viewer)
292 WinSetClipbrdViewer(g_habWorker, NULLHANDLE);
293 if (g_enmState == kClipboardState_Owner)
294 WinSetClipbrdOwner(g_habWorker, NULLHANDLE);
295
296 g_enmState = kClipboardState_Destroying;
297 if (WinEmptyClipbrd(g_habWorker))
298 {
299 /*
300 * Take clipboard ownership.
301 */
302 if (WinSetClipbrdOwner(g_habWorker, g_hwndWorker))
303 {
304 g_enmState = kClipboardState_Owner;
305
306 /*
307 * Do the format advertising.
308 */
309 if (fFormats & (VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT/* | VBOX_SHARED_CLIPBOARD_FMT_HTML ?? */))
310 {
311 if (!WinSetClipbrdData(g_habWorker, 0, CF_TEXT, CFI_POINTER))
312 VGSvcError("WinSetClipbrdData(,,CF_TEXT,) failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
313 if ( g_atomOdin32UnicodeText
314 && !WinSetClipbrdData(g_habWorker, 0, g_atomOdin32UnicodeText, CFI_POINTER))
315 VGSvcError("WinSetClipbrdData(,,g_atomOdin32UnicodeText,) failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
316 }
317 if (fFormats & VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
318 {
319 /** @todo bitmaps */
320 }
321 }
322 else
323 {
324 VGSvcError("WinSetClipbrdOwner failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
325 g_enmState = kClipboardState_Polling;
326 }
327 }
328 else
329 {
330 VGSvcError("WinEmptyClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
331 g_enmState = kClipboardState_Polling;
332 }
333
334 if (g_enmState == kClipboardState_Polling)
335 {
336 g_fEmptyClipboard = true;
337 vgsvcClipboardOs2PollViewer();
338 }
339
340 WinCloseClipbrd(g_habWorker);
341 }
342 else
343 VGSvcError("vgsvcClipboardOs2AdvertiseHostFormats: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
344}
345
346
347/**
348 * Converts (render) to an Odin32 clipboard format.
349 *
350 * We ASSUME we get windows data from the host and all we've got to do here is
351 * slapping an Odin32 header on it.
352 *
353 * @returns Pointer to the data (DosFreeMem).
354 * @param fFormat The host format.
355 * @param usFmt The PM/Odin32 format.
356 * @param pv The data in host formatting.
357 * @param cb The size of the data.
358 */
359static void *vgsvcClipboardOs2ConvertToOdin32(uint32_t fFormat, USHORT usFmt, void *pv, uint32_t cb)
360{
361 PVOID pvPM = NULL;
362 APIRET rc = DosAllocSharedMem(&pvPM, NULL, cb + sizeof(CLIPHEADER), OBJ_GIVEABLE | OBJ_GETTABLE | OBJ_TILE | PAG_READ | PAG_WRITE | PAG_COMMIT);
363 if (rc)
364 {
365 PCLIPHEADER pHdr = (PCLIPHEADER)pvPM;
366 memcpy(pHdr->achMagic, CLIPHEADER_MAGIC, sizeof(pHdr->achMagic));
367 pHdr->cbData = cb;
368 if (usFmt == g_atomOdin32UnicodeText)
369 pHdr->uFormat = usFmt;
370 else
371 AssertFailed();
372 memcpy(pHdr + 1, pv, cb);
373 }
374 else
375 {
376 VGSvcError("DosAllocSharedMem(,,%#x,,) -> %ld\n", cb + sizeof(CLIPHEADER), rc);
377 pvPM = NULL;
378 }
379 return pvPM;
380}
381
382
383/**
384 * Converts (render) to a PM clipboard format.
385 *
386 * @returns Pointer to the data (DosFreeMem).
387 * @param fFormat The host format.
388 * @param usFmt The PM/Odin32 format.
389 * @param pv The data in host formatting.
390 * @param cb The size of the data.
391 */
392static void *vgsvcClipboardOs2ConvertToPM(uint32_t fFormat, USHORT usFmt, void *pv, uint32_t cb)
393{
394 void *pvPM = NULL;
395
396 /*
397 * The Odin32 stuff is simple, we just assume windows data from the host
398 * and all we need to do is add the header.
399 */
400 if ( usFmt
401 && ( usFmt == g_atomOdin32UnicodeText
402 /* || usFmt == ...*/
403 )
404 )
405 pvPM = vgsvcClipboardOs2ConvertToOdin32(fFormat, usFmt, pv, cb);
406 else if (usFmt == CF_TEXT)
407 {
408 /*
409 * Convert the unicode text to the current ctype locale.
410 *
411 * Note that we probably should be using the current PM or DOS codepage
412 * here instead of the LC_CTYPE one which iconv uses by default.
413 * -lazybird
414 */
415 Assert(fFormat & VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT);
416 char *pszUtf8;
417 int rc = RTUtf16ToUtf8((PCRTUTF16)pv, &pszUtf8);
418 if (RT_SUCCESS(rc))
419 {
420 char *pszLocale;
421 rc = RTStrUtf8ToCurrentCP(&pszLocale, pszUtf8);
422 if (RT_SUCCESS(rc))
423 {
424 size_t cbPM = strlen(pszLocale) + 1;
425 APIRET orc = DosAllocSharedMem(&pvPM, NULL, cbPM, OBJ_GIVEABLE | OBJ_GETTABLE | OBJ_TILE | PAG_READ | PAG_WRITE | PAG_COMMIT);
426 if (orc == NO_ERROR)
427 memcpy(pvPM, pszLocale, cbPM);
428 else
429 {
430 VGSvcError("DosAllocSharedMem(,,%#x,,) -> %ld\n", cb + sizeof(CLIPHEADER), orc);
431 pvPM = NULL;
432 }
433 RTStrFree(pszLocale);
434 }
435 else
436 VGSvcError("RTStrUtf8ToCurrentCP() -> %Rrc\n", rc);
437 RTStrFree(pszUtf8);
438 }
439 else
440 VGSvcError("RTUtf16ToUtf8() -> %Rrc\n", rc);
441 }
442
443 return pvPM;
444}
445
446
447/**
448 * Tries to deliver an advertised host format.
449 *
450 * @param usFmt The PM format name.
451 *
452 * @remark We must not try open the clipboard here because WM_RENDERFMT is a
453 * request send synchronously by someone who has already opened the
454 * clipboard. We would enter a deadlock trying to open it here.
455 */
456static void vgsvcClipboardOs2RenderFormat(USHORT usFmt)
457{
458 bool fSucceeded = false;
459
460 /*
461 * Determine which format.
462 */
463 uint32_t fFormat;
464 if ( usFmt == CF_TEXT
465 || usFmt == g_atomOdin32UnicodeText)
466 fFormat = VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT;
467 else /** @todo bitmaps */
468 fFormat = 0;
469 if (fFormat)
470 {
471 /*
472 * Query the data from the host.
473 * This might require two iterations because of buffer guessing.
474 */
475 uint32_t cb = _4K;
476 uint32_t cbAllocated = cb;
477 int rc = VERR_NO_MEMORY;
478 void *pv = RTMemPageAllocZ(cbAllocated);
479 if (pv)
480 {
481 VGSvcVerbose(4, "clipboard: reading host data (%#x)\n", fFormat);
482 rc = VbglR3ClipboardReadData(g_u32ClientId, fFormat, pv, cb, &cb);
483 if (rc == VINF_BUFFER_OVERFLOW)
484 {
485 RTMemPageFree(pv, cbAllocated);
486 cbAllocated = cb = RT_ALIGN_32(cb, PAGE_SIZE);
487 pv = RTMemPageAllocZ(cbAllocated);
488 rc = VbglR3ClipboardReadData(g_u32ClientId, fFormat, pv, cb, &cb);
489 }
490 if (RT_FAILURE(rc))
491 RTMemPageFree(pv, cbAllocated);
492 }
493 if (RT_SUCCESS(rc))
494 {
495 VGSvcVerbose(4, "clipboard: read %u bytes\n", cb);
496
497 /*
498 * Convert the host clipboard data to PM clipboard data and set it.
499 */
500 PVOID pvPM = vgsvcClipboardOs2ConvertToPM(fFormat, usFmt, pv, cb);
501 if (pvPM)
502 {
503 if (WinSetClipbrdData(g_habWorker, (ULONG)pvPM, usFmt, CFI_POINTER))
504 fSucceeded = true;
505 else
506 {
507 VGSvcError("vgsvcClipboardOs2RenderFormat: WinSetClipbrdData(,%p,%#x, CF_POINTER) failed, lasterror=%lx\n",
508 pvPM, usFmt, WinGetLastError(g_habWorker));
509 DosFreeMem(pvPM);
510 }
511 }
512 RTMemPageFree(pv, cbAllocated);
513 }
514 else
515 VGSvcError("vgsvcClipboardOs2RenderFormat: Failed to query / allocate data. rc=%Rrc cb=%#RX32\n", rc, cb);
516 }
517
518 /*
519 * Empty the clipboard on failure so we don't end up in any loops.
520 */
521 if (!fSucceeded)
522 {
523 WinSetClipbrdOwner(g_habWorker, NULLHANDLE);
524 g_enmState = kClipboardState_Destroying;
525 WinEmptyClipbrd(g_habWorker);
526 g_enmState = kClipboardState_Polling;
527 g_fEmptyClipboard = true;
528 vgsvcClipboardOs2PollViewer();
529 }
530}
531
532
533/**
534 * Sends data to the host.
535 *
536 * @param fFormat The data format the host is requesting.
537 */
538static void vgsvcClipboardOs2SendDataToHost(uint32_t fFormat)
539{
540 if (WinOpenClipbrd(g_habWorker))
541 {
542 PRTUTF16 pwszFree = NULL;
543 void *pv = NULL;
544 uint32_t cb = 0;
545
546 if (fFormat & VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
547 {
548 /* Got any odin32 unicode text? */
549 PVOID pvPM;
550 PCLIPHEADER pHdr = (PCLIPHEADER)WinQueryClipbrdData(g_habWorker, g_atomOdin32UnicodeText);
551 if ( pHdr
552 && !memcmp(pHdr->achMagic, CLIPHEADER_MAGIC, sizeof(pHdr->achMagic)))
553 {
554 pv = pHdr + 1;
555 cb = pHdr->cbData;
556 }
557
558 /* Got any CF_TEXT? */
559 if ( !pv
560 && (pvPM = (PVOID)WinQueryClipbrdData(g_habWorker, CF_TEXT)) != NULL)
561 {
562 char *pszUtf8;
563 int rc = RTStrCurrentCPToUtf8(&pszUtf8, (const char *)pvPM);
564 if (RT_SUCCESS(rc))
565 {
566 PRTUTF16 pwsz;
567 rc = RTStrToUtf16(pszUtf8, &pwsz);
568 if (RT_SUCCESS(rc))
569 {
570 pv = pwszFree = pwsz;
571 cb = (RTUtf16Len(pwsz) + 1) * sizeof(RTUTF16);
572 }
573 RTStrFree(pszUtf8);
574 }
575 }
576 }
577 if (!pv)
578 VGSvcError("vgsvcClipboardOs2SendDataToHost: couldn't find data for %#x\n", fFormat);
579
580 /*
581 * Now, sent whatever we've got to the host (it's waiting).
582 */
583 VGSvcVerbose(4, "clipboard: writing %pv/%#d (fFormat=%#x)\n", pv, cb, fFormat);
584 VbglR3ClipboardWriteData(g_u32ClientId, fFormat, pv, cb);
585 RTUtf16Free(pwszFree);
586
587 WinCloseClipbrd(g_habWorker);
588 }
589 else
590 {
591 VGSvcError("vgsvcClipboardOs2SendDataToHost: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
592 VGSvcVerbose(4, "clipboard: writing NULL/0 (fFormat=%x)\n", fFormat);
593 VbglR3ClipboardWriteData(g_u32ClientId, fFormat, NULL, 0);
594 }
595}
596
597
598/**
599 * Figure out what's on the clipboard and report it to the host.
600 */
601static void vgsvcClipboardOs2ReportFormats(void)
602{
603 uint32_t fFormats = 0;
604 ULONG ulFormat = 0;
605 while ((ulFormat = WinEnumClipbrdFmts(g_habWorker, ulFormat)) != 0)
606 {
607 if ( ulFormat == CF_TEXT
608 || ulFormat == g_atomOdin32UnicodeText)
609 fFormats |= VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT;
610 /** @todo else bitmaps and stuff. */
611 }
612 VGSvcVerbose(4, "clipboard: reporting fFormats=%#x\n", fFormats);
613 VbglR3ClipboardReportFormats(g_u32ClientId, fFormats);
614}
615
616
617/**
618 * Poll the clipboard for changes.
619 *
620 * This is called both when we're the viewer and when we're
621 * falling back to polling. If something has changed it will
622 * notify the host.
623 */
624static void vgsvcClipboardOs2Poll(void)
625{
626 if (WinOpenClipbrd(g_habWorker))
627 {
628 /*
629 * If our dummy is no longer there, something has actually changed,
630 * unless the clipboard is really empty.
631 */
632 ULONG fFmtInfo;
633 if (!WinQueryClipbrdFmtInfo(g_habWorker, g_atomNothingChanged, &fFmtInfo))
634 {
635 if (WinEnumClipbrdFmts(g_habWorker, 0) != 0)
636 {
637 g_fEmptyClipboard = false;
638 vgsvcClipboardOs2ReportFormats();
639
640 /* inject the dummy */
641 PVOID pv;
642 APIRET rc = DosAllocSharedMem(&pv, NULL, 1, OBJ_GIVEABLE | OBJ_GETTABLE | PAG_READ | PAG_WRITE | PAG_COMMIT);
643 if (rc == NO_ERROR)
644 {
645 if (WinSetClipbrdData(g_habWorker, (ULONG)pv, g_atomNothingChanged, CFI_POINTER))
646 VGSvcVerbose(4, "clipboard: Added dummy item.\n");
647 else
648 {
649 VGSvcError("vgsvcClipboardOs2Poll: WinSetClipbrdData failed, lasterr=%#lx\n", WinGetLastError(g_habWorker));
650 DosFreeMem(pv);
651 }
652 }
653 else
654 VGSvcError("vgsvcClipboardOs2Poll: DosAllocSharedMem(,,1,) -> %ld\n", rc);
655 }
656 else if (!g_fEmptyClipboard)
657 {
658 g_fEmptyClipboard = true;
659 VGSvcVerbose(3, "Reporting empty clipboard\n");
660 VbglR3ClipboardReportFormats(g_u32ClientId, 0);
661 }
662 }
663 WinCloseClipbrd(g_habWorker);
664 }
665 else
666 VGSvcError("vgsvcClipboardOs2Poll: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
667}
668
669
670/**
671 * The clipboard we owned was destroyed by someone else.
672 */
673static void vgsvcClipboardOs2Destroyed(void)
674{
675 /* make sure we're no longer the owner. */
676 if (WinQueryClipbrdOwner(g_habWorker) == g_hwndWorker)
677 WinSetClipbrdOwner(g_habWorker, NULLHANDLE);
678
679 /* switch to polling state and notify the host. */
680 g_enmState = kClipboardState_Polling;
681 g_fEmptyClipboard = true;
682 VGSvcVerbose(3, "Reporting empty clipboard\n");
683 VbglR3ClipboardReportFormats(g_u32ClientId, 0);
684
685 vgsvcClipboardOs2PollViewer();
686}
687
688
689/**
690 * The window procedure for the object window.
691 *
692 * @returns Message result.
693 *
694 * @param hwnd The window handle.
695 * @param msg The message.
696 * @param mp1 Message parameter 1.
697 * @param mp2 Message parameter 2.
698 */
699static MRESULT EXPENTRY vgsvcClipboardOs2WinProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
700{
701 if (msg != WM_TIMER)
702 VGSvcVerbose(6, "vgsvcClipboardOs2WinProc: hwnd=%#lx msg=%#lx mp1=%#lx mp2=%#lx\n", hwnd, msg, mp1, mp2);
703
704 switch (msg)
705 {
706 /*
707 * Handle the two system defined messages for object windows.
708 *
709 * We'll just use the CREATE/DESTROY message to create that timer we're
710 * using for the viewer checks and polling fallback.
711 */
712 case WM_CREATE:
713 g_idWorkerTimer = WinStartTimer(g_habWorker, hwnd, 1 /* id */, 1000 /* 1 second */);
714 g_fEmptyClipboard = true;
715 g_enmState = kClipboardState_Polling;
716 return NULL; /* FALSE(/NULL) == Continue*/
717
718 case WM_DESTROY:
719 WinStopTimer(g_habWorker, hwnd, g_idWorkerTimer);
720 g_idWorkerTimer = ~0UL;
721 g_hwndWorker = NULLHANDLE;
722 break;
723
724 /*
725 * Clipboard viewer message - the content has been changed.
726 * This is sent *after* releasing the clipboard sem
727 * and during the WinSetClipbrdViewer call.
728 */
729 case WM_DRAWCLIPBOARD:
730 if (g_enmState == kClipboardState_SettingViewer)
731 break;
732 AssertMsgBreak(g_enmState == kClipboardState_Viewer, ("g_enmState=%d\n", g_enmState));
733 vgsvcClipboardOs2Poll();
734 break;
735
736 /*
737 * Clipboard owner message - the content was replaced.
738 * This is sent by someone with an open clipboard, so don't try open it now.
739 */
740 case WM_DESTROYCLIPBOARD:
741 if (g_enmState == kClipboardState_Destroying)
742 break; /* it's us doing the replacing, ignore. */
743 AssertMsgBreak(g_enmState == kClipboardState_Owner, ("g_enmState=%d\n", g_enmState));
744 vgsvcClipboardOs2Destroyed();
745 break;
746
747 /*
748 * Clipboard owner message - somebody is requesting us to render a format.
749 * This is called by someone which owns the clipboard, but that's fine.
750 */
751 case WM_RENDERFMT:
752 AssertMsgBreak(g_enmState == kClipboardState_Owner, ("g_enmState=%d\n", g_enmState));
753 vgsvcClipboardOs2RenderFormat(SHORT1FROMMP(mp1));
754 break;
755
756 /*
757 * Clipboard owner message - we're about to quit and should render all formats.
758 *
759 * However, because we're lazy, we'll just ASSUME that since we're quitting
760 * we're probably about to shutdown or something and there is no point in
761 * doing anything here except for emptying the clipboard and removing
762 * ourselves as owner. Any failures at this point are silently ignored.
763 */
764 case WM_RENDERALLFMTS:
765 WinOpenClipbrd(g_habWorker);
766 WinSetClipbrdOwner(g_habWorker, NULLHANDLE);
767 g_enmState = kClipboardState_Destroying;
768 WinEmptyClipbrd(g_habWorker);
769 g_enmState = kClipboardState_Polling;
770 g_fEmptyClipboard = true;
771 WinCloseClipbrd(g_habWorker);
772 break;
773
774 /*
775 * Listener message - the host has new formats to offer.
776 */
777 case WM_USER + VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS_WRITE:
778 vgsvcClipboardOs2AdvertiseHostFormats(LONGFROMMP(mp1));
779 break;
780
781 /*
782 * Listener message - the host wish to read our clipboard data.
783 */
784 case WM_USER + VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA:
785 vgsvcClipboardOs2SendDataToHost(LONGFROMMP(mp1));
786 break;
787
788 /*
789 * This is just a fallback polling strategy in case some other
790 * app is trying to view the clipboard too. We also use this
791 * to try recover from errors.
792 *
793 * Because the way the clipboard service works, we have to monitor
794 * it all the time and cannot get away with simpler solutions like
795 * synergy is employing (basically checking upon entering and leaving
796 * a desktop).
797 */
798 case WM_TIMER:
799 if ( g_enmState != kClipboardState_Viewer
800 && g_enmState != kClipboardState_Polling)
801 break;
802
803 /* Lost the position as clipboard viewer?*/
804 if (g_enmState == kClipboardState_Viewer)
805 {
806 if (WinQueryClipbrdViewer(g_habWorker) == hwnd)
807 break;
808 g_enmState = kClipboardState_Polling;
809 }
810
811 /* poll for changes */
812 vgsvcClipboardOs2Poll();
813 vgsvcClipboardOs2PollViewer();
814 break;
815
816
817 /*
818 * Clipboard owner messages dealing with owner drawn content.
819 * We shouldn't be seeing any of these.
820 */
821 case WM_PAINTCLIPBOARD:
822 case WM_SIZECLIPBOARD:
823 case WM_HSCROLLCLIPBOARD:
824 case WM_VSCROLLCLIPBOARD:
825 AssertMsgFailed(("msg=%lx (%ld)\n", msg, msg));
826 break;
827
828 /*
829 * We shouldn't be seeing any other messages according to the docs.
830 * But for whatever reason, PM sends us a WM_ADJUSTWINDOWPOS message
831 * during WinCreateWindow. So, ignore that and assert on anything else.
832 */
833 default:
834 AssertMsgFailed(("msg=%lx (%ld)\n", msg, msg));
835 case WM_ADJUSTWINDOWPOS:
836 break;
837 }
838 return NULL;
839}
840
841
842/**
843 * The listener thread.
844 *
845 * This thread is dedicated to listening for host messages and forwarding
846 * these to the worker thread (using PM).
847 *
848 * The thread will set g_fListenerOkay and signal its user event when it has
849 * completed initialization. In the case of init failure g_fListenerOkay will
850 * not be set.
851 *
852 * @returns Init error code or VINF_SUCCESS.
853 * @param ThreadSelf Our thread handle.
854 * @param pvUser Pointer to the clipboard service shutdown indicator.
855 */
856static DECLCALLBACK(int) vgsvcClipboardOs2Listener(RTTHREAD ThreadSelf, void *pvUser)
857{
858 bool volatile *pfShutdown = (bool volatile *)pvUser;
859 int rc = VERR_GENERAL_FAILURE;
860 VGSvcVerbose(3, "vgsvcClipboardOs2Listener: ThreadSelf=%RTthrd\n", ThreadSelf);
861
862 g_habListener = WinInitialize(0);
863 if (g_habListener != NULLHANDLE)
864 {
865 g_hmqListener = WinCreateMsgQueue(g_habListener, 0);
866 if (g_hmqListener != NULLHANDLE)
867 {
868 WinCancelShutdown(g_hmqListener, TRUE); /* We don't care about shutdown */
869
870 /*
871 * Tell the worker thread that we're good.
872 */
873 rc = VINF_SUCCESS;
874 ASMAtomicXchgBool(&g_fListenerOkay, true);
875 RTThreadUserSignal(ThreadSelf);
876 VGSvcVerbose(3, "vgsvcClipboardOs2Listener: Started successfully\n");
877
878 /*
879 * Loop until termination is requested.
880 */
881 bool fQuit = false;
882 while (!*pfShutdown && !fQuit)
883 {
884 uint32_t Msg;
885 uint32_t fFormats;
886 rc = VbglR3ClipboardGetHostMsgOld(g_u32ClientId, &Msg, &fFormats);
887 if (RT_SUCCESS(rc))
888 {
889 VGSvcVerbose(3, "vgsvcClipboardOs2Listener: Msg=%#x fFormats=%#x\n", Msg, fFormats);
890 switch (Msg)
891 {
892 /*
893 * The host has announced available clipboard formats.
894 * Forward the information to the window, so it can later
895 * respond do WM_RENDERFORMAT message.
896 */
897 case VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS_WRITE:
898 if (!WinPostMsg(g_hwndWorker, WM_USER + VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS_WRITE,
899 MPFROMLONG(fFormats), 0))
900 VGSvcError("WinPostMsg(%lx, FORMATS,,) failed, lasterr=%#lx\n",
901 g_hwndWorker, WinGetLastError(g_habListener));
902 break;
903
904 /*
905 * The host needs data in the specified format.
906 */
907 case VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA:
908 if (!WinPostMsg(g_hwndWorker, WM_USER + VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA,
909 MPFROMLONG(fFormats), 0))
910 VGSvcError("WinPostMsg(%lx, READ_DATA,,) failed, lasterr=%#lx\n",
911 g_hwndWorker, WinGetLastError(g_habListener));
912 break;
913
914 /*
915 * The host is terminating.
916 */
917 case VBOX_SHARED_CLIPBOARD_HOST_MSG_QUIT:
918 fQuit = true;
919 break;
920
921 default:
922 VGSvcVerbose(1, "vgsvcClipboardOs2Listener: Unknown message %RU32\n", Msg);
923 break;
924 }
925 }
926 else
927 {
928 if (*pfShutdown)
929 break;
930 VGSvcError("VbglR3ClipboardGetHostMsg failed, rc=%Rrc\n", rc);
931 RTThreadSleep(1000);
932 }
933 } /* the loop */
934
935 WinDestroyMsgQueue(g_hmqListener);
936 }
937 WinTerminate(g_habListener);
938 g_habListener = NULLHANDLE;
939 }
940
941 /* Signal our semaphore to make the worker catch on. */
942 RTThreadUserSignal(ThreadSelf);
943 VGSvcVerbose(3, "vgsvcClipboardOs2Listener: terminating, rc=%Rrc\n", rc);
944 return rc;
945}
946
947
948/**
949 * @interface_method_impl{VBOXSERVICE,pfnWorker}
950 */
951static DECLCALLBACK(int) vgsvcClipboardOs2Worker(bool volatile *pfShutdown)
952{
953 int rc = VERR_GENERAL_FAILURE;
954
955 /*
956 * Standard PM init.
957 */
958 g_habWorker = RTThreadSelf() != g_ThreadCtrl ? WinInitialize(0) : g_habCtrl;
959 if (g_habWorker != NULLHANDLE)
960 {
961 g_hmqWorker = RTThreadSelf() != g_ThreadCtrl ? WinCreateMsgQueue(g_habWorker, 0) : g_hmqCtrl;
962 if (g_hmqWorker != NULLHANDLE)
963 {
964 if (g_hmqWorker != g_hmqCtrl)
965 WinCancelShutdown(g_hmqWorker, TRUE); /* We don't care about shutdown */
966
967 /*
968 * Create the object window.
969 */
970 if (WinRegisterClass(g_habWorker, (PCSZ)"VBoxServiceClipboardClass", vgsvcClipboardOs2WinProc, 0, 0))
971 {
972 g_hwndWorker = WinCreateWindow(HWND_OBJECT, /* hwndParent */
973 (PCSZ)"VBoxServiceClipboardClass", /* pszClass */
974 (PCSZ)"VirtualBox Clipboard Service", /* pszName */
975 0, /* flStyle */
976 0, 0, 0, 0, /* x, y, cx, cy */
977 NULLHANDLE, /* hwndOwner */
978 HWND_BOTTOM, /* hwndInsertBehind */
979 42, /* id */
980 NULL, /* pCtlData */
981 NULL); /* pPresParams */
982 if (g_hwndWorker != NULLHANDLE)
983 {
984 VGSvcVerbose(3, "g_hwndWorker=%#lx g_habWorker=%#lx g_hmqWorker=%#lx\n", g_hwndWorker, g_habWorker, g_hmqWorker);
985
986 /*
987 * Create the listener thread.
988 */
989 g_fListenerOkay = false;
990 rc = RTThreadCreate(&g_ThreadListener, vgsvcClipboardOs2Listener, (void *)pfShutdown, 0,
991 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "CLIPLISTEN");
992 if (RT_SUCCESS(rc))
993 {
994 RTThreadUserWait(g_ThreadListener, 30*1000);
995 RTThreadUserReset(g_ThreadListener);
996 if (!g_fListenerOkay)
997 RTThreadWait(g_ThreadListener, 60*1000, NULL);
998 if (g_fListenerOkay)
999 {
1000 /*
1001 * Tell the control thread that it can continue
1002 * spawning services.
1003 */
1004 RTThreadUserSignal(RTThreadSelf());
1005
1006 /*
1007 * The PM event pump.
1008 */
1009 VGSvcVerbose(2, "clipboard: Entering PM message loop.\n");
1010 rc = VINF_SUCCESS;
1011 QMSG qmsg;
1012 while (WinGetMsg(g_habWorker, &qmsg, NULLHANDLE, NULLHANDLE, 0))
1013 {
1014 if (qmsg.msg != WM_TIMER)
1015 VGSvcVerbose(6, "WinGetMsg -> hwnd=%p msg=%#x mp1=%p mp2=%p time=%#x ptl=%d,%d rsrv=%#x\n",
1016 qmsg.hwnd, qmsg.msg, qmsg.mp1, qmsg.mp2, qmsg.time, qmsg.ptl.x, qmsg.ptl.y, qmsg.reserved);
1017 WinDispatchMsg(g_habWorker, &qmsg);
1018 }
1019 VGSvcVerbose(2, "clipboard: Exited PM message loop. *pfShutdown=%RTbool\n", *pfShutdown);
1020
1021 RTThreadWait(g_ThreadListener, 60*1000, NULL);
1022 }
1023 g_ThreadListener = NIL_RTTHREAD;
1024 }
1025
1026 /*
1027 * Got a WM_QUIT, clean up.
1028 */
1029 if (g_hwndWorker != NULLHANDLE)
1030 {
1031 WinDestroyWindow(g_hwndWorker);
1032 g_hwndWorker = NULLHANDLE;
1033 }
1034 }
1035 else
1036 VGSvcError("WinCreateWindow() failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
1037 /* no class deregistration in PM. */
1038 }
1039 else
1040 VGSvcError("WinRegisterClass() failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
1041
1042 if (g_hmqCtrl != g_hmqWorker)
1043 WinDestroyMsgQueue(g_hmqWorker);
1044 g_hmqWorker = NULLHANDLE;
1045 }
1046 else
1047 VGSvcError("WinCreateMsgQueue(,0) failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
1048
1049 if (g_habCtrl != g_habWorker)
1050 WinTerminate(g_habWorker);
1051 g_habWorker = NULLHANDLE;
1052 }
1053 else
1054 VGSvcError("WinInitialize(0) failed, lasterr=%lx\n", WinGetLastError(NULLHANDLE));
1055
1056 return rc;
1057}
1058
1059
1060/**
1061 * @interface_method_impl{VBOXSERVICE,pfnStop}
1062 */
1063static DECLCALLBACK(void) vgsvcClipboardOs2Stop(void)
1064{
1065 if ( g_hmqWorker != NULLHANDLE
1066 && !WinPostQueueMsg(g_hmqWorker, WM_QUIT, NULL, NULL))
1067 VGSvcError("WinPostQueueMsg(g_hmqWorker, WM_QUIT, 0,0) failed, lasterr=%lx\n", WinGetLastError(g_habCtrl));
1068
1069 /* Must disconnect the clipboard here otherwise the listner won't quit and
1070 the service shutdown will not stop. */
1071 if (g_u32ClientId != 0)
1072 {
1073 if (g_hmqWorker != NULLHANDLE)
1074 RTThreadSleep(32); /* fudge */
1075
1076 VGSvcVerbose(4, "clipboard: disconnecting %#x\n", g_u32ClientId);
1077 int rc = VbglR3ClipboardDisconnect(g_u32ClientId);
1078 if (RT_SUCCESS(rc))
1079 g_u32ClientId = 0;
1080 else
1081 VGSvcError("clipboard: VbglR3ClipboardDisconnect(%#x) -> %Rrc\n", g_u32ClientId, rc);
1082 }
1083}
1084
1085
1086/**
1087 * @interface_method_impl{VBOXSERVICE,pfnTerm}
1088 */
1089static DECLCALLBACK(void) vgsvcClipboardOs2Term(void)
1090{
1091 if (g_u32ClientId != 0)
1092 {
1093 VGSvcVerbose(4, "clipboard: disconnecting %#x\n", g_u32ClientId);
1094 int rc = VbglR3ClipboardDisconnect(g_u32ClientId);
1095 if (RT_SUCCESS(rc))
1096 g_u32ClientId = 0;
1097 else
1098 VGSvcError("clipboard: VbglR3ClipboardDisconnect(%#x) -> %Rrc\n", g_u32ClientId, rc);
1099 }
1100 WinDestroyMsgQueue(g_hmqCtrl);
1101 g_hmqCtrl = NULLHANDLE;
1102 WinTerminate(g_habCtrl);
1103 g_habCtrl = NULLHANDLE;
1104}
1105
1106
1107/**
1108 * The OS/2 'clipboard' service description.
1109 */
1110VBOXSERVICE g_Clipboard =
1111{
1112 /* pszName. */
1113 "clipboard",
1114 /* pszDescription. */
1115 "Shared Clipboard",
1116 /* pszUsage. */
1117 ""
1118 ,
1119 /* pszOptions. */
1120 ""
1121 ,
1122 /* methods */
1123 vgsvcClipboardOs2PreInit,
1124 vgsvcClipboardOs2Option,
1125 vgsvcClipboardOs2Init,
1126 vgsvcClipboardOs2Worker,
1127 vgsvcClipboardOs2Stop,
1128 vgsvcClipboardOs2Term
1129};
1130
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