source: trunk/synergy/lib/platform/CPMSynergyHook.cpp

Last change on this file was 2768, checked in by bird, 19 years ago

Expanded m_client to 64-bit. Finally managed to hack together a getKeyMap and fakeKey for PM.

File size: 18.6 KB
Line 
1/*
2 * synergy -- mouse and keyboard sharing utility
3 * Copyright (C) 2002 Chris Schoeneman
4 * Copyright (C) 2006 Knut St. Osmundsen
5 *
6 * This package is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * found in the file COPYING that should have accompanied this file.
9 *
10 * This package is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 */
15
16#include "CPMSynergyHook.h"
17#include "ProtocolTypes.h"
18#include <sys/builtin.h>
19#if 0
20#include <stdlib.h>
21#include <unistd.h>
22#include <InnoTekLIBC/FastInfoBlocks.h>
23#else
24unsigned long fibGetMsCount(void)
25{
26 ULONG ul = 0;
27 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT, &ul, sizeof(ul));
28 return ul;
29}
30
31int getpid(void)
32{
33 PTIB pTib;
34 PPIB pPib;
35 DosGetInfoBlocks(&pTib, &pPib);
36 return pPib->pib_ulpid;
37}
38
39int _gettid(void)
40{
41 PTIB pTib;
42 PPIB pPib;
43 DosGetInfoBlocks(&pTib, &pPib);
44 return pTib->tib_ptib2->tib2_ultid;
45}
46
47void _assert (__const__ char * pszExpr, __const__ char *pszFile, unsigned iLine)
48{
49 __asm__ ("int3\n");
50}
51
52#endif
53
54//
55// globals - system wide
56//
57/** @name The synergy server/client thread
58 * @{ */
59HMQ g_hmqSynergy;
60int g_pidSynergy;
61int g_tidSynergy;
62/** @} */
63
64/** The current mode.*/
65EHookMode volatile g_enmMode;
66
67/** Which sides do we have warp zones on. */
68UInt32 g_zoneSides;
69/** The warp zone size in pixels. */
70SInt32 g_zoneSize;
71/** @name The screen size.
72 * @{ */
73SInt32 g_xScreen;
74SInt32 g_yScreen;
75SInt32 g_cxScreen;
76SInt32 g_cyScreen;
77/** @} */
78
79/** Set if the hook is installed. */
80bool g_fInputHookInstalled;
81/** Set if we're faking input. */
82bool g_fFakingInput;
83
84/** @name Input Message Faking.
85 * @{ */
86/** The event semaphore we're waiting on while faking messages. */
87HEV g_hev;
88/** The pending message. */
89QMSG g_qmsg;
90/** Indicator set by the MsgInputHook function when the message 'skipped'. */
91volatile bool g_fMsgInjected;
92/** Error description. */
93const char *g_pszErrorMsg;
94/** @} */
95
96/** The handle of the hook dll. */
97HMODULE g_hmod = NULLHANDLE;
98/** Indicator whether the init() function has been called successfully. */
99bool g_fSynergyInitialized = false;
100
101
102
103//
104// Internal Functions
105//
106
107
108/**
109 * Deal with keyboard messages.
110 *
111 * @param pQmsg The WM_CHAR message.
112 */
113static bool InputHookKeyboardMsg(PQMSG pQmsg)
114{
115 /*
116 * Check for special events indicating if we should start or stop
117 * passing events through and not report them to the server.
118 *
119 * This is used to allow the server to synthesize events locally but
120 * not pick them up as user events.
121 */
122 if (pQmsg->mp1 == SYNERGY_PM_HOOK_FAKE_INPUT_MP1_FIXED) {
123 if (pQmsg->mp2 == SYNERGY_PM_HOOK_FAKE_INPUT_MP2_SET) {
124 g_fFakingInput = true;
125 return true;
126 }
127 if (pQmsg->mp2 == SYNERGY_PM_HOOK_FAKE_INPUT_MP2_CLEAR) {
128 g_fFakingInput = false;
129 return true;
130 }
131 }
132
133 /*
134 * If we're expecting fake input then just pass the event through
135 * and do not forward to the server
136 */
137 if (g_fFakingInput)
138 return false;
139
140 /*
141 * Send the key to the server without any more work.
142 * This is probably to simplified, but it'll have to do for now because I'm lazy.
143 */
144 WinPostQueueMsg(g_hmqSynergy, SYNERGY_PM_MSG_KEY, pQmsg->mp1, pQmsg->mp2);
145 return true;
146}
147
148
149/**
150 * Checks if the specified mouse position is in a jump zone.
151 *
152 * @returns true if we're in the jump zone, false if we aren't
153 * @param x X coordinate.
154 * @param y Y coordinate. (1st quadrant)
155 */
156static bool isInJumpZone(SInt32 x, SInt32 y)
157{
158 /* any jump zones at all? */
159 if (g_zoneSides == 0)
160 return false;
161
162 /*
163 * Normalize the coordinates.
164 */
165 bool fBogus = false;
166 if (x < g_xScreen) {
167 x = g_xScreen;
168 fBogus = true;
169 } else if (x >= g_xScreen + g_cxScreen) {
170 x = g_xScreen + g_cxScreen - 1;
171 fBogus = true;
172 }
173
174 if (y < g_yScreen) {
175 y = g_yScreen;
176 fBogus = true;
177 } else if (y >= g_yScreen + g_cyScreen) {
178 y = g_yScreen + g_cyScreen - 1;
179 fBogus = true;
180 }
181
182 /*
183 * check if the mouse is in any of the jump zones.
184 */
185 bool fJump = false;
186 if (g_zoneSides & kLeftMask)
187 fJump |= (unsigned)x - (unsigned)g_xScreen < (unsigned)g_zoneSize;
188 if (g_zoneSides & kRightMask)
189 fJump |= (unsigned)(g_xScreen + g_cxScreen) - (unsigned)x < (unsigned)g_zoneSize;
190 if (g_zoneSides & kTopMask)
191 fJump |= (unsigned)(g_yScreen + g_cyScreen) - (unsigned)y < (unsigned)g_zoneSize;
192 if (g_zoneSides & kBottomMask)
193 fJump |= (unsigned)y - (unsigned)(g_yScreen) < (unsigned)g_zoneSize;
194
195 return fJump && !fBogus;
196}
197
198
199/**
200 * The input hook procedure (any context).
201 *
202 * @returns Processed indicator; TRUE if processed and should be discarded, FALSE if should be passed on.
203 *
204 * @param hab The anchor block of the current thread.
205 * @param pQmsg The message structure containing the message we're called for.
206 * @param fs The removal options (PM_REMOVE / PM_NOREMOVE).
207 */
208BOOL EXPENTRY InputHook(HAB hab, PQMSG pQmsg, ULONG fs)
209{
210 /*
211 * Get hook mode and check if it's enabled.
212 */
213 const EHookMode enmMode = g_enmMode;
214 if (enmMode == kHOOK_DISABLE)
215 return FALSE;
216
217
218 switch (pQmsg->msg) {
219 /*
220 * Keyboard message.
221 */
222 case WM_CHAR:
223 if (enmMode == kHOOK_RELAY_EVENTS)
224 return InputHookKeyboardMsg(pQmsg);
225 break;
226
227 /*
228 * Mouse message.
229 */
230 case WM_MOUSEMOVE:
231 if ( enmMode == kHOOK_RELAY_EVENTS
232 || enmMode == kHOOK_WATCH_JUMP_ZONE) {
233 WinPostQueueMsg(g_hmqSynergy, SYNERGY_PM_MSG_MOUSE_MOVE, MPFROM2SHORT(pQmsg->msg, 0), MPFROM2SHORT(pQmsg->ptl.x, pQmsg->ptl.y));
234 return enmMode == kHOOK_RELAY_EVENTS
235 || isInJumpZone(pQmsg->ptl.x, pQmsg->ptl.y);
236 }
237 break;
238
239 /*
240 * Mouse click message.
241 */
242 case WM_BUTTON1DOWN:
243 case WM_BUTTON1UP:
244 case WM_BUTTON1DBLCLK:
245 case WM_BUTTON2DOWN:
246 case WM_BUTTON2UP:
247 case WM_BUTTON2DBLCLK:
248 case WM_BUTTON3DOWN:
249 case WM_BUTTON3UP:
250 case WM_BUTTON3DBLCLK:
251 if ( enmMode == kHOOK_RELAY_EVENTS
252 || enmMode == kHOOK_WATCH_JUMP_ZONE) {
253 WinPostQueueMsg(g_hmqSynergy, SYNERGY_PM_MSG_MOUSE_BUTTON, MPFROM2SHORT(pQmsg->msg, 0), MPFROM2SHORT(pQmsg->ptl.x, pQmsg->ptl.y));
254 return enmMode == kHOOK_RELAY_EVENTS;
255 }
256 break;
257
258 /*
259 * If I understand it correctly these messages are created
260 * from the ones above and should be discarded. (If I'm wrong,
261 * we'll not be able to drag stuff or open the window list.)
262 */
263 case WM_CHORD:
264 case WM_BUTTON1MOTIONSTART:
265 case WM_BUTTON1MOTIONEND:
266 case WM_BUTTON1CLICK:
267 case WM_BUTTON2MOTIONSTART:
268 case WM_BUTTON2MOTIONEND:
269 case WM_BUTTON2CLICK:
270 case WM_BUTTON3MOTIONSTART:
271 case WM_BUTTON3MOTIONEND:
272 case WM_BUTTON3CLICK:
273 if (enmMode == kHOOK_RELAY_EVENTS) {
274 return true;
275 }
276 break;
277
278 default:
279 // log these.
280 break;
281 }
282
283 /* don't eat it. */
284 return false;
285}
286
287
288//
289// external functions
290//
291
292/**
293 * The DLL init/term function.
294 */
295ULONG _System _DLL_InitTerm(HMODULE hmod, ULONG ulReason)
296{
297 switch (ulReason) {
298 /* init */
299 case 0: {
300 HEV hev = g_hev;
301 if (hev != NULLHANDLE) {
302 DosOpenEventSem(NULL, &hev);
303 }
304 g_hmod = hmod;
305 break;
306 }
307
308 /* term */
309 case 1:
310 if (g_hev != NULLHANDLE) {
311 DosCloseEventSem(g_hev);
312 }
313 break;
314 }
315 return TRUE;
316}
317
318
319
320/**
321 * Initialize the hook dll (shared data mainly) in the synergy context.
322 *
323 * @returns success indicator (true/false)
324 * @param hmqSynergy
325 * @param tidSynergy
326 * @param pidSynergy
327 */
328CSYNERGYHOOK_API bool init(HMQ hmqSynergy, int tidSynergy, int pidSynergy)
329{
330 /*
331 * Check that there isn't any synergy instance already running.
332 * If the verification fails, we will take the place dead synergy instance.
333 */
334 if ( g_pidSynergy
335 && g_tidSynergy
336 && DosVerifyPidTid(g_pidSynergy, g_tidSynergy) == NO_ERROR)
337 return false;
338
339 /*
340 * Create the event semaphore.
341 */
342 ULONG cPosts;
343 if ( g_hev == NULLHANDLE
344 || DosQueryEventSem(g_hev, &cPosts) != NO_ERROR
345 ) {
346 g_hev = NULLHANDLE;
347 APIRET rc = DosCreateEventSem(NULL, &g_hev, DC_SEM_SHARED, FALSE);
348 if (rc != NO_ERROR)
349 return false;
350 }
351
352 /*
353 * Initialize the globals.
354 */
355 g_hmqSynergy = hmqSynergy;
356 g_pidSynergy = pidSynergy;
357 g_tidSynergy = tidSynergy;
358
359 /* only do this if the hook is inactive. */
360 if (g_enmMode == kHOOK_DISABLE) {
361 g_zoneSides = 0;
362 g_zoneSize = 0;
363 g_xScreen = 0;
364 g_yScreen = 0;
365 g_cxScreen = 0;
366 g_cyScreen = 0;
367 }
368
369 g_fSynergyInitialized = true;
370 return true;
371}
372
373
374/**
375 * Cleanup before unloading the dll (synergy context).
376 *
377 * @returns success indicator (true/false)
378 * @param hab The anchor block.
379 */
380CSYNERGYHOOK_API bool cleanup(HAB hab)
381{
382 assert(g_hmod != NULLHANDLE);
383 assert(g_fSynergyInitialized == true);
384
385 /*
386 * If we're the synergy process or if it's dead, shut down everything.
387 */
388 if ( ( g_pidSynergy != getpid()
389 || g_tidSynergy != _gettid())
390 && DosVerifyPidTid(g_pidSynergy, g_tidSynergy) == NO_ERROR)
391 return false;
392
393 /* parnaoia */
394 uninstall(hab);
395
396 g_hmqSynergy = NULLHANDLE;
397 g_pidSynergy = 0;
398 g_tidSynergy = 0;
399 g_fSynergyInitialized = false;
400 return true;
401}
402
403
404CSYNERGYHOOK_API EHookResult install(HAB hab)
405{
406 // must be initialized
407 if ( g_tidSynergy == 0
408 || !g_fSynergyInitialized) {
409 return kHOOK_FAILED;
410 }
411 assert(g_pidSynergy == getpid());
412 assert(g_tidSynergy == _gettid());
413
414 //// discard old dead keys
415 //g_deadVirtKey = 0;
416 //g_deadLParam = 0;
417
418 //// reset fake input flag
419 //g_fakeInput = false;
420
421 /*
422 * Install the hook.
423 */
424 if ( !g_fInputHookInstalled
425 && WinSetHook(hab, NULLHANDLE, HK_INPUT, (PFN)InputHook, g_hmod))
426 __atomic_xchg((unsigned volatile *)&g_fInputHookInstalled, true);
427
428 if (g_fInputHookInstalled)
429 return kHOOK_OKAY;
430
431 uninstall(hab);
432 return kHOOK_FAILED;
433}
434
435
436/**
437 * Uninstall the hooks.
438 *
439 * @returns
440 * @param hab
441 */
442CSYNERGYHOOK_API bool uninstall(HAB hab)
443{
444 assert(g_pidSynergy == getpid());
445 assert(g_tidSynergy == _gettid());
446 assert(g_fSynergyInitialized);
447 if (!g_fSynergyInitialized) {
448 return false;
449 }
450
451 //// discard old dead keys
452 //g_deadVirtKey = 0;
453 //g_deadLParam = 0;
454
455 /*
456 * Release the hooks.
457 */
458 if ( g_fInputHookInstalled
459 && WinReleaseHook(hab, NULLHANDLE, HK_INPUT, (PFN)InputHook, g_hmod))
460 __atomic_xchg((unsigned volatile *)&g_fInputHookInstalled, false);
461
462 return !g_fInputHookInstalled;
463}
464
465
466/**
467 * Sets the sides we should be watching out for jumps in.
468 *
469 * @param sides The new sides flags.
470 */
471CSYNERGYHOOK_API void setSides(UInt32 sides)
472{
473 assert(g_pidSynergy == getpid());
474 assert(g_tidSynergy == _gettid());
475 assert(g_fSynergyInitialized);
476 if (g_fSynergyInitialized) {
477 __atomic_xchg((unsigned volatile *)&g_zoneSides, (unsigned)sides);
478 }
479}
480
481
482/**
483 * Set the jump zone size and screen size.
484 *
485 * @param x The screen x coordinate.
486 * @param y The screen y coordinate.
487 * @param cx The screen width.
488 * @param cy The screen heigth.
489 * @param jumpZoneSize The width of the jump zone (pixels).
490 */
491CSYNERGYHOOK_API void setZone(SInt32 x, SInt32 y, SInt32 cx, SInt32 cy, SInt32 jumpZoneSize)
492{
493 assert(g_pidSynergy == getpid());
494 assert(g_tidSynergy == _gettid());
495 assert(g_fSynergyInitialized);
496 if (g_fSynergyInitialized) {
497 __atomic_xchg((unsigned volatile *)&g_zoneSize, (unsigned)jumpZoneSize);
498 __atomic_xchg((unsigned volatile *)&g_xScreen, (unsigned)x);
499 __atomic_xchg((unsigned volatile *)&g_yScreen, (unsigned)y);
500 __atomic_xchg((unsigned volatile *)&g_cxScreen, (unsigned)cx);
501 __atomic_xchg((unsigned volatile *)&g_cyScreen, (unsigned)cy);
502 }
503}
504
505
506/**
507 * Check the mode of operation.
508 *
509 * @param mode The new mode.
510 */
511CSYNERGYHOOK_API void setMode(EHookMode mode)
512{
513 assert(g_pidSynergy == getpid());
514 assert(g_tidSynergy == _gettid());
515 assert(g_fSynergyInitialized);
516 if (g_fSynergyInitialized) {
517 if (mode != g_enmMode) {
518 switch (mode) {
519 case kHOOK_DISABLE:
520 case kHOOK_WATCH_JUMP_ZONE:
521 case kHOOK_RELAY_EVENTS:
522 __atomic_xchg((unsigned volatile *)&g_enmMode, (unsigned)mode);
523 break;
524 default:
525 __atomic_xchg((unsigned volatile *)&g_enmMode, kHOOK_DISABLE);
526 break;
527 }
528 }
529 }
530}
531
532
533//
534// Message faking.
535//
536
537/**
538 * Hook callback used to insert message into the system queue.
539 *
540 * @returns TRUE if we've got a message.
541 * @returns FALSE if we haven't got a message handy.
542 * @param hab The hab of the current thread.
543 * @param pqmsg Where to put the message.
544 * @param fSkip Skip message flag.
545 * If TRUE we should just skip a message (pqmsg is NULL). If no futher
546 * messages are queued, we must release the hook.
547 * If FALSE we should return the current pending message.
548 * @param pfNoRecord Record message flag.
549 */
550static BOOL EXPENTRY MsgInputHook(HAB hab, PQMSG pqmsg, BOOL fSkip, PBOOL pfNoRecord)
551{
552 /*
553 * We've only got one message, so everything is dead simple.
554 */
555 if (fSkip) {
556 __atomic_xchg((unsigned volatile *)&g_fMsgInjected, 1);
557 if (WinReleaseHook(hab, NULLHANDLE, HK_MSGINPUT, (PFN)MsgInputHook, g_hmod)) {
558 HEV hev = g_hev;
559 APIRET rc = DosPostEventSem(hev);
560 if ( rc != ERROR_INVALID_HANDLE
561 || DosOpenEventSem(NULL, &hev) != NO_ERROR
562 || DosPostEventSem(hev) != NO_ERROR)
563 g_pszErrorMsg = "failed to post event semaphore";
564 } else {
565 g_pszErrorMsg = "failed to release hook";
566 }
567 } else {
568 *pqmsg = g_qmsg;
569 if (pqmsg->msg == WM_VIOCHAR) {
570 /* mp1.s1: KC_ flags.
571 * mp1.c3: repeat
572 * mp1.c4: scancode
573 * mp2.c1: translated char
574 * mp2.c2: translated scancode
575 * mp2.s2: KDD_ flags.
576 */
577 KbdPacket[0].monFlags = 0; // no monitor flag
578 KbdPacket[0].scancode = CHAR4FROMMP(pqmsg->mp1);
579 KbdPacket[0].xlatedchar = CHAR1FROMMP(pqmsg->mp2);
580 KbdPacket[0].xlatedscan = CHAR2FROMMP(pqmsg->mp2);
581 KbdPacket[0].time = pqmsg->time;
582 KbdPacket[0].ddFlags = SHORT2FROMMP(pqmsg->mp2);
583 /** @todo extended stuff, keypad, workarounds. see pmvnc. */
584 }
585 }
586
587 /*
588 * We're not recording, so no chance of a feedback loop here.
589 * This doesn't seem to work, bah!
590 */
591 if (pfNoRecord) {
592 *pfNoRecord = FALSE;
593 }
594 return TRUE;
595}
596
597
598/**
599 * Inserts a fake input message.
600 *
601 * @returns NULL on success.
602 * @returns String describing the error on failure.
603 * @param hab The anchor block of the calling thread.
604 * @param pQmsg Pointer to the message.
605 * @remark The caller must serialize calls to this function!
606 */
607CSYNERGYHOOK_API const char *fakeMsg(HAB hab, const QMSG *pQmsg)
608{
609 const char *pszRet;
610 assert(g_fSynergyInitialized);
611 if (g_fSynergyInitialized) {
612 /*
613 * Reset the event semaphore and queue the message for MsgInputHook.
614 */
615 ULONG cPosts;
616 APIRET rc = DosResetEventSem(g_hev, &cPosts);
617 if (rc == NO_ERROR || rc == ERROR_ALREADY_RESET) {
618 g_qmsg = *pQmsg;
619 g_pszErrorMsg = NULL;
620 __atomic_xchg((unsigned volatile *)&g_fMsgInjected, 0);
621
622 /*
623 * Install the hook and wait for the message to be delivered.
624 */
625 WinCheckInput(hab);
626 if (WinSetHook(hab, NULLHANDLE, HK_MSGINPUT, (PFN)MsgInputHook, g_hmod)) {
627 WinCheckInput(hab);
628 rc = DosWaitEventSem(g_hev, 1000 /* 33 */);
629 if (rc == NO_ERROR || g_fMsgInjected) {
630 return NULL;
631 }
632
633 /*
634 * It took a bit longer than expected. We'll wait for some 2-3
635 * seconds before we give up. This might be a bit paranoid, but
636 * I'd rather drop a message than locking up PM.
637 */
638 if (rc == ERROR_TIMEOUT || rc == ERROR_SEM_TIMEOUT || rc == ERROR_INTERRUPT) {
639 ULONG ulStart = fibGetMsCount(); /* monotone time in milliseconds */
640 for (;;) {
641 WinCheckInput(hab);
642 rc = DosWaitEventSem(g_hev, 33);
643 if (rc == NO_ERROR || g_fMsgInjected) {
644 return NULL;
645 }
646 if (rc != ERROR_TIMEOUT && rc != ERROR_SEM_TIMEOUT && rc != ERROR_INTERRUPT) {
647 break;
648 }
649 if (fibGetMsCount() - ulStart > 2500) {
650 break;
651 }
652 }
653 }
654
655 /*
656 * Since we probably didn't manage to inject the message, we should
657 * release the hook to make sure we're not overwriting g_qmsg while
658 * some other thread is reading it from MsgInputHook.
659 */
660 WinReleaseHook(hab, NULLHANDLE, HK_MSGINPUT, (PFN)MsgInputHook, g_hmod);
661 DosSleep(10);
662 pszRet = g_pszErrorMsg;
663 return pszRet ? pszRet : "timed out";
664 }
665 pszRet = "WinSetHook failed";
666 } else {
667 pszRet = "DosResetEventSem failed";
668 }
669 } else {
670 pszRet = "!g_fSynergyInitialized";
671 }
672 DosSleep(0);
673 return pszRet;
674}
675
Note: See TracBrowser for help on using the repository browser.