diff --git a/src/gui/src/ServerConfig.cpp b/src/gui/src/ServerConfig.cpp old mode 100644 new mode 100755 index 39d2aa8e6..ee82258f3 --- a/src/gui/src/ServerConfig.cpp +++ b/src/gui/src/ServerConfig.cpp @@ -80,7 +80,8 @@ bool ServerConfig::operator==(const ServerConfig &sc) const m_SwitchCornerSize == sc.m_SwitchCornerSize && m_SwitchCorners == sc.m_SwitchCorners && m_Hotkeys == sc.m_Hotkeys && m_pAppConfig == sc.m_pAppConfig && m_DisableLockToScreen == sc.m_DisableLockToScreen && m_ClipboardSharing == sc.m_ClipboardSharing && - m_ClipboardSharingSize == sc.m_ClipboardSharingSize && m_pMainWindow == sc.m_pMainWindow; + m_ClipboardSharingSize == sc.m_ClipboardSharingSize && m_TouchInputLocal == sc.m_TouchInputLocal && + m_pMainWindow == sc.m_pMainWindow; } void ServerConfig::save(QFile &file) const @@ -127,6 +128,7 @@ void ServerConfig::commit() settings().setValue("disableLockToScreen", disableLockToScreen()); settings().setValue("clipboardSharing", clipboardSharing()); settings().setValue("clipboardSharingSize", QVariant::fromValue(clipboardSharingSize())); + settings().setValue("touchInputLocal", touchInputLocal()); if (!getClientAddress().isEmpty()) { settings().setValue("clientAddress", getClientAddress()); @@ -182,6 +184,7 @@ void ServerConfig::recall() settings().value("clipboardSharingSize", (int)ServerConfig::defaultClipboardSharingSize()).toULongLong() ); setClipboardSharing(settings().value("clipboardSharing", true).toBool()); + setTouchInputLocal(settings().value("touchInputLocal", false).toBool()); setClientAddress(settings().value("clientAddress", "").toString()); readSettings(settings(), switchCorners(), "switchCorner", 0, static_cast(NumSwitchCorners)); @@ -216,6 +219,9 @@ void ServerConfig::recall() if (locked.contains("clipboardSharingSize")) { m_ClipboardSharingSize = locked.value("clipboardSharingSize").toULongLong(); } + if (locked.contains("touchInputLocal")) { + m_TouchInputLocal = locked.value("touchInputLocal").toBool(); + } locked.endGroup(); } @@ -286,6 +292,8 @@ QTextStream &operator<<(QTextStream &outStream, const ServerConfig &config) << "clipboardSharing = " << (config.clipboardSharing() ? "true" : "false") << Qt::endl; outStream << "\t" << "clipboardSharingSize = " << config.clipboardSharingSize() << Qt::endl; + outStream << "\t" + << "touchInputLocal = " << (config.touchInputLocal() ? "true" : "false") << Qt::endl; if (!config.getClientAddress().isEmpty()) { outStream << "\t" diff --git a/src/gui/src/ServerConfig.h b/src/gui/src/ServerConfig.h old mode 100644 new mode 100755 index 2c99fa7fd..005bfe0ac --- a/src/gui/src/ServerConfig.h +++ b/src/gui/src/ServerConfig.h @@ -128,6 +128,10 @@ class ServerConfig : public ScreenConfig, public deskflow::gui::IServerConfig { return m_ClipboardSharingSize; } + bool touchInputLocal() const + { + return m_TouchInputLocal; + } static size_t defaultClipboardSharingSize(); // @@ -224,6 +228,10 @@ class ServerConfig : public ScreenConfig, public deskflow::gui::IServerConfig { m_ClipboardSharing = on; } + void setTouchInputLocal(bool on) + { + m_TouchInputLocal = on; + } void setConfigFile(const QString &configFile); void setUseExternalConfig(bool useExternalConfig); size_t setClipboardSharingSize(size_t size); @@ -253,6 +261,7 @@ class ServerConfig : public ScreenConfig, public deskflow::gui::IServerConfig int m_SwitchCornerSize = 0; bool m_DisableLockToScreen = false; bool m_ClipboardSharing = true; + bool m_TouchInputLocal = false; QString m_ClientAddress = ""; QList m_SwitchCorners; HotkeyList m_Hotkeys; diff --git a/src/gui/src/ServerConfigDialog.cpp b/src/gui/src/ServerConfigDialog.cpp old mode 100644 new mode 100755 index e557d1788..1ec3d7c0f --- a/src/gui/src/ServerConfigDialog.cpp +++ b/src/gui/src/ServerConfigDialog.cpp @@ -73,6 +73,7 @@ ServerConfigDialog::ServerConfigDialog(QWidget *parent, ServerConfig &config, Ap int clipboardSharingSizeM = static_cast(serverConfig().clipboardSharingSize() / 1024); m_pSpinBoxClipboardSizeLimit->setValue(clipboardSharingSizeM); m_pSpinBoxClipboardSizeLimit->setEnabled(serverConfig().clipboardSharing()); + m_pCheckBoxTouchInputLocal->setChecked(serverConfig().touchInputLocal()); foreach (const Hotkey &hotkey, serverConfig().hotkeys()) m_pListHotkeys->addItem(hotkey.text()); @@ -105,6 +106,10 @@ ServerConfigDialog::ServerConfigDialog(QWidget *parent, ServerConfig &config, Ap qDebug() << "locking clipboard size setting"; m_pSpinBoxClipboardSizeLimit->setEnabled(false); } + if (locked.contains("touchInputLocal")) { + qDebug() << "locking touch input local setting"; + m_pCheckBoxTouchInputLocal->setEnabled(false); + } locked.endGroup(); onChange(); @@ -142,6 +147,10 @@ ServerConfigDialog::ServerConfigDialog(QWidget *parent, ServerConfig &config, Ap serverConfig().setDisableLockToScreen(v); onChange(); }); + connect(m_pCheckBoxTouchInputLocal, &QCheckBox::stateChanged, this, [this](const int &v) { + serverConfig().setTouchInputLocal(v); + onChange(); + }); connect(m_pCheckBoxCornerTopLeft, &QCheckBox::stateChanged, this, [this](const int &v) { serverConfig().setSwitchCorner(static_cast(TopLeft), v); onChange(); @@ -192,6 +201,10 @@ ServerConfigDialog::ServerConfigDialog(QWidget *parent, ServerConfig &config, Ap serverConfig().setDisableLockToScreen(v == Qt::Checked); onChange(); }); + connect(m_pCheckBoxTouchInputLocal, &QCheckBox::checkStateChanged, this, [this](const Qt::CheckState &v) { + serverConfig().setTouchInputLocal(v == Qt::Checked); + onChange(); + }); connect(m_pCheckBoxCornerTopLeft, &QCheckBox::checkStateChanged, this, [this](const Qt::CheckState &v) { serverConfig().setSwitchCorner(static_cast(TopLeft), v == Qt::Checked); onChange(); diff --git a/src/gui/src/ServerConfigDialogBase.ui b/src/gui/src/ServerConfigDialogBase.ui old mode 100644 new mode 100755 index a8a420821..f0600ff88 --- a/src/gui/src/ServerConfigDialogBase.ui +++ b/src/gui/src/ServerConfigDialogBase.ui @@ -567,6 +567,16 @@ + + + + Keep touch input on this computer + + + When enabled, touch screen input stays on this computer even when the cursor moves to another screen + + + diff --git a/src/lib/deskflow/option_types.h b/src/lib/deskflow/option_types.h old mode 100644 new mode 100755 index 99db87a47..bf0ba005e --- a/src/lib/deskflow/option_types.h +++ b/src/lib/deskflow/option_types.h @@ -69,6 +69,7 @@ static const OptionID kOptionWin32KeepForeground = OPTION_CODE("_KFW"); static const OptionID kOptionDisableLockToScreen = OPTION_CODE("DLTS"); static const OptionID kOptionClipboardSharing = OPTION_CODE("CLPS"); static const OptionID kOptionClipboardSharingSize = OPTION_CODE("CLSZ"); +static const OptionID kOptionTouchInputLocal = OPTION_CODE("TILC"); //@} //! @name Screen switch corner enumeration diff --git a/src/lib/platform/MSWindowsHook.cpp b/src/lib/platform/MSWindowsHook.cpp old mode 100644 new mode 100755 index b51b1a734..67c79eddb --- a/src/lib/platform/MSWindowsHook.cpp +++ b/src/lib/platform/MSWindowsHook.cpp @@ -44,6 +44,12 @@ static BYTE g_keyState[256] = {0}; static DWORD g_hookThread = 0; static bool g_fakeServerInput = false; static BOOL g_isPrimary = TRUE; +static bool g_touchInputLocal = false; +static bool g_isOnScreen = true; + +// Touch input signature in dwExtraInfo (bit 7 set indicates touch/pen) +#define TOUCH_SIGNATURE_MASK 0xFFFFFF00 +#define TOUCH_SIGNATURE 0xFF515700 MSWindowsHook::MSWindowsHook() { @@ -148,6 +154,17 @@ void MSWindowsHook::setMode(EHookMode mode) g_mode = mode; } +void MSWindowsHook::setTouchInputLocal(bool enable) +{ + g_touchInputLocal = enable; + LOG((CLOG_DEBUG "hook: touchInputLocal = %s", enable ? "true" : "false")); +} + +void MSWindowsHook::setIsOnScreen(bool onScreen) +{ + g_isOnScreen = onScreen; +} + static void keyboardGetState(BYTE keys[256], DWORD vkCode, bool kf_up) { // we have to use GetAsyncKeyState() rather than GetKeyState() because @@ -585,6 +602,19 @@ static LRESULT CALLBACK mouseLLHook(int code, WPARAM wParam, LPARAM lParam) return CallNextHookEx(g_mouseLL, code, wParam, lParam); } + // Check if this mouse event was generated from touch input + // Touch input has a specific signature in dwExtraInfo + bool isTouchGenerated = ((info->dwExtraInfo & TOUCH_SIGNATURE_MASK) == TOUCH_SIGNATURE); + + // If touchInputLocal is enabled and cursor is on client screen, + // let the touch event work locally but don't forward it to the client + if (g_touchInputLocal && !g_isOnScreen && isTouchGenerated) { + LOG((CLOG_DEBUG "touch event - processing locally, not forwarding to client")); + // Don't call mouseHookHandler - this skips forwarding to client + // Don't return 1 - this lets the event proceed to local applications + return CallNextHookEx(g_mouseLL, code, wParam, lParam); + } + SInt32 x = static_cast(info->pt.x); SInt32 y = static_cast(info->pt.y); SInt32 w = static_cast(HIWORD(info->mouseData)); diff --git a/src/lib/platform/MSWindowsHook.h b/src/lib/platform/MSWindowsHook.h old mode 100644 new mode 100755 index 51684395f..039f54d08 --- a/src/lib/platform/MSWindowsHook.h +++ b/src/lib/platform/MSWindowsHook.h @@ -49,4 +49,10 @@ class MSWindowsHook static int installScreenSaver(); static int uninstallScreenSaver(); + + //! Set whether touch input should stay on this computer + void setTouchInputLocal(bool enable); + + //! Set whether cursor is currently on this screen + void setIsOnScreen(bool onScreen); }; diff --git a/src/lib/platform/MSWindowsScreen.cpp b/src/lib/platform/MSWindowsScreen.cpp old mode 100644 new mode 100755 index f585110cf..bef6226f1 --- a/src/lib/platform/MSWindowsScreen.cpp +++ b/src/lib/platform/MSWindowsScreen.cpp @@ -19,6 +19,7 @@ #include "platform/MSWindowsScreen.h" #include "arch/Arch.h" +#include "deskflow/option_types.h" #include "arch/win32/ArchMiscWindows.h" #include "base/IEventQueue.h" #include "base/Log.h" @@ -83,6 +84,28 @@ #define PBT_APMRESUMEAUTOMATIC 0x0012 #endif +// WM_POINTER stuff (Windows 8+) +#if !defined(WM_POINTERDOWN) +#define WM_POINTERDOWN 0x0246 +#define WM_POINTERUP 0x0247 +#define WM_POINTERUPDATE 0x0245 +#define WM_POINTERENTER 0x0249 +#define WM_POINTERLEAVE 0x024A +#define GET_POINTERID_WPARAM(wParam) (LOWORD(wParam)) +#endif + +#if !defined(PT_POINTER) +#define PT_POINTER 1 +#define PT_TOUCH 2 +#define PT_PEN 3 +#define PT_MOUSE 4 +#endif + +// Function pointer type for GetPointerType (loaded dynamically for Win7 compat) +typedef BOOL(WINAPI *GetPointerTypeFunc)(UINT32 pointerId, DWORD *pointerType); +static GetPointerTypeFunc s_getPointerType = NULL; +static bool s_pointerApiChecked = false; + // // MSWindowsScreen // @@ -124,7 +147,9 @@ MSWindowsScreen::MSWindowsScreen( m_hasMouse(GetSystemMetrics(SM_MOUSEPRESENT) != 0), m_events(events), m_dropWindow(NULL), - m_dropWindowSize(20) + m_dropWindowSize(20), + m_touchInputLocal(false), + m_lastInputWasTouch(false) { LOG_DEBUG("settting up %s screen", m_isPrimary ? "primary" : "secondary"); @@ -313,6 +338,7 @@ void MSWindowsScreen::enter() // now on screen m_isOnScreen = true; + m_hook.setIsOnScreen(true); setupMouseKeys(); } @@ -369,6 +395,7 @@ void MSWindowsScreen::leave() // now off screen m_isOnScreen = false; + m_hook.setIsOnScreen(false); if (isDraggingStarted() && !m_isPrimary) { m_sendDragThread = new Thread(new TMethodJob(this, &MSWindowsScreen::sendDragThread)); @@ -475,6 +502,16 @@ void MSWindowsScreen::resetOptions() void MSWindowsScreen::setOptions(const OptionsList &options) { + // Handle touch input local option + for (UInt32 i = 0, n = (UInt32)options.size(); i < n; i += 2) { + if (options[i] == kOptionTouchInputLocal) { + m_touchInputLocal = (options[i + 1] != 0); + LOG((CLOG_DEBUG1 "touchInputLocal = %s", m_touchInputLocal ? "true" : "false")); + // Update the hook so it can block touch-generated mouse events + m_hook.setTouchInputLocal(m_touchInputLocal); + } + } + m_desks->setOptions(options); } @@ -1043,6 +1080,19 @@ bool MSWindowsScreen::onEvent(HWND, UINT msg, WPARAM wParam, LPARAM lParam, LRES case WM_DISPLAYCHANGE: return onDisplayChange(); + // Handle pointer input (touch/pen) - Windows 8+ + // This allows us to detect touch vs mouse input and optionally keep touch local + case WM_POINTERDOWN: + case WM_POINTERUP: + case WM_POINTERUPDATE: + if (m_isPrimary && onPointerInput(wParam, lParam)) { + // Touch input was consumed (kept local) + *result = 0; + return true; + } + // Fall through to let DefWindowProc convert to mouse messages + return false; + /* On windows 10 we don't receive WM_POWERBROADCAST after sleep. We receive only WM_TIMECHANGE hence this message is used to resume.*/ case WM_TIMECHANGE: @@ -1378,6 +1428,58 @@ bool MSWindowsScreen::onMouseWheel(SInt32 xDelta, SInt32 yDelta) return true; } +bool MSWindowsScreen::isPointerTypeTouch(UINT32 pointerId) const +{ + // Dynamically load GetPointerType for Windows 7 compatibility + if (!s_pointerApiChecked) { + s_pointerApiChecked = true; + HMODULE user32 = GetModuleHandle("user32.dll"); + if (user32 != NULL) { + s_getPointerType = (GetPointerTypeFunc)GetProcAddress(user32, "GetPointerType"); + } + } + + if (s_getPointerType == NULL) { + // API not available (Windows 7 or earlier) + return false; + } + + DWORD pointerType = PT_POINTER; + if (s_getPointerType(pointerId, &pointerType)) { + return (pointerType == PT_TOUCH || pointerType == PT_PEN); + } + return false; +} + +bool MSWindowsScreen::onPointerInput(WPARAM wParam, LPARAM lParam) +{ + UINT32 pointerId = GET_POINTERID_WPARAM(wParam); + + LOG((CLOG_DEBUG "onPointerInput: pointerId=%d, touchInputLocal=%s, isOnScreen=%s", + pointerId, m_touchInputLocal ? "true" : "false", m_isOnScreen ? "true" : "false")); + + // Check if this is touch/pen input + if (isPointerTypeTouch(pointerId)) { + m_lastInputWasTouch = true; + LOG((CLOG_DEBUG "pointer is touch input")); + + // If touchInputLocal is enabled and cursor is on another screen, + // consume the event so it stays local + if (m_touchInputLocal && !m_isOnScreen) { + LOG((CLOG_INFO "touch input kept local (cursor on client screen)")); + // Return true to indicate we handled it - this prevents + // DefWindowProc from converting it to mouse input + return true; + } + } else { + m_lastInputWasTouch = false; + } + + // Let the system handle the pointer input normally + // It will be converted to mouse messages and caught by our hook + return false; +} + bool MSWindowsScreen::onScreensaver(bool activated) { // ignore this message if there are any other screen saver diff --git a/src/lib/platform/MSWindowsScreen.h b/src/lib/platform/MSWindowsScreen.h old mode 100644 new mode 100755 index 8688d4286..29b3370d9 --- a/src/lib/platform/MSWindowsScreen.h +++ b/src/lib/platform/MSWindowsScreen.h @@ -187,6 +187,8 @@ class MSWindowsScreen : public PlatformScreen bool onMouseButton(WPARAM, LPARAM); bool onMouseMove(SInt32 x, SInt32 y); bool onMouseWheel(SInt32 xDelta, SInt32 yDelta); + bool onPointerInput(WPARAM wParam, LPARAM lParam); + bool isPointerTypeTouch(UINT32 pointerId) const; bool onScreensaver(bool activated); bool onDisplayChange(); bool onClipboardChange(); @@ -343,6 +345,10 @@ class MSWindowsScreen : public PlatformScreen MSWindowsHook m_hook; + // Touch input local policy - when true, touch stays on primary screen + bool m_touchInputLocal; + bool m_lastInputWasTouch; + static MSWindowsScreen *s_screen; IEventQueue *m_events; diff --git a/src/lib/server/Config.cpp b/src/lib/server/Config.cpp old mode 100644 new mode 100755 index aeeef31b4..72a185df8 --- a/src/lib/server/Config.cpp +++ b/src/lib/server/Config.cpp @@ -699,6 +699,8 @@ void Config::readSectionOptions(ConfigReadContext &s) addOption("", kOptionClipboardSharing, s.parseBoolean(value)); } else if (name == "clipboardSharingSize") { addOption("", kOptionClipboardSharingSize, s.parseInt(value)); + } else if (name == "touchInputLocal") { + addOption("", kOptionTouchInputLocal, s.parseBoolean(value)); } else if (name == "clientAddress") { m_ClientAddress = value; } else { @@ -1268,6 +1270,9 @@ const char *Config::getOptionName(OptionID id) if (id == kOptionClipboardSharingSize) { return "clipboardSharingSize"; } + if (id == kOptionTouchInputLocal) { + return "touchInputLocal"; + } return NULL; } @@ -1277,7 +1282,7 @@ String Config::getOptionValue(OptionID id, OptionValue value) id == kOptionScreenSwitchNeedsShift || id == kOptionScreenSwitchNeedsControl || id == kOptionScreenSwitchNeedsAlt || id == kOptionXTestXineramaUnaware || id == kOptionRelativeMouseMoves || id == kOptionWin32KeepForeground || id == kOptionScreenPreserveFocus || id == kOptionClipboardSharing || - id == kOptionClipboardSharingSize) { + id == kOptionClipboardSharingSize || id == kOptionTouchInputLocal) { return (value != 0) ? "true" : "false"; } if (id == kOptionModifierMapForShift || id == kOptionModifierMapForControl || id == kOptionModifierMapForAlt ||