From b891b1e6309f7ad215f82f2d2165caf4566bed8b Mon Sep 17 00:00:00 2001 From: Florian Hofhammer Date: Tue, 18 Aug 2020 12:03:02 +0200 Subject: [PATCH 1/2] Allow screen switching on local XInput Allows to switch from client to server and vice versa if local input is detected, e.g. a touchscreen input or a mouse click if clients also have input devices --- src/lib/barrier/protocol_types.cpp | 1 + src/lib/barrier/protocol_types.h | 7 +- src/lib/base/EventTypes.cpp | 1 + src/lib/base/EventTypes.h | 10 +++ src/lib/client/Client.cpp | 32 +++++--- src/lib/client/Client.h | 1 + src/lib/client/ServerProxy.cpp | 16 +++- src/lib/client/ServerProxy.h | 7 +- src/lib/platform/XWindowsScreen.cpp | 111 +++++++++++++++++++------- src/lib/platform/XWindowsScreen.h | 3 +- src/lib/server/ClientProxy1_7.cpp | 59 ++++++++++++++ src/lib/server/ClientProxy1_7.h | 38 +++++++++ src/lib/server/ClientProxyUnknown.cpp | 5 ++ src/lib/server/Server.cpp | 28 +++++++ src/lib/server/Server.h | 1 + 15 files changed, 272 insertions(+), 48 deletions(-) create mode 100644 src/lib/server/ClientProxy1_7.cpp create mode 100644 src/lib/server/ClientProxy1_7.h diff --git a/src/lib/barrier/protocol_types.cpp b/src/lib/barrier/protocol_types.cpp index 07acf61e..b21fe7f1 100644 --- a/src/lib/barrier/protocol_types.cpp +++ b/src/lib/barrier/protocol_types.cpp @@ -29,6 +29,7 @@ const char* kMsgCScreenSaver = "CSEC%1i"; const char* kMsgCResetOptions = "CROP"; const char* kMsgCInfoAck = "CIAK"; const char* kMsgCKeepAlive = "CALV"; +const char* kMsgCLocalInput = "CLIN%2i%2i"; const char* kMsgDKeyDown = "DKDN%2i%2i%2i"; const char* kMsgDKeyDown1_0 = "DKDN%2i%2i"; const char* kMsgDKeyRepeat = "DKRP%2i%2i%2i%2i"; diff --git a/src/lib/barrier/protocol_types.h b/src/lib/barrier/protocol_types.h index bc5e0377..c9e715df 100644 --- a/src/lib/barrier/protocol_types.h +++ b/src/lib/barrier/protocol_types.h @@ -29,9 +29,10 @@ // 1.4: adds crypto support // 1.5: adds file transfer and removes home brew crypto // 1.6: adds clipboard streaming +// 1.7: adds focus/screen switch on local input // NOTE: with new version, barrier minor version should increment static const SInt16 kProtocolMajorVersion = 1; -static const SInt16 kProtocolMinorVersion = 6; +static const SInt16 kProtocolMinorVersion = 7; // default contact port number static const UInt16 kDefaultPort = 24800; @@ -172,6 +173,10 @@ extern const char* kMsgCInfoAck; // defined by an option. extern const char* kMsgCKeepAlive; +// Local input: secondary -> primary +// Inform primary about local input to request focus +extern const char* kMsgCLocalInput; + // // data codes // diff --git a/src/lib/base/EventTypes.cpp b/src/lib/base/EventTypes.cpp index 2ba20778..b1fbcc8a 100644 --- a/src/lib/base/EventTypes.cpp +++ b/src/lib/base/EventTypes.cpp @@ -177,6 +177,7 @@ REGISTER_EVENT(IPrimaryScreen, fakeInputEnd) REGISTER_EVENT(IScreen, error) REGISTER_EVENT(IScreen, shapeChanged) +REGISTER_EVENT(IScreen, localInput) REGISTER_EVENT(IScreen, suspend) REGISTER_EVENT(IScreen, resume) diff --git a/src/lib/base/EventTypes.h b/src/lib/base/EventTypes.h index f81617e0..3c11bd8e 100644 --- a/src/lib/base/EventTypes.h +++ b/src/lib/base/EventTypes.h @@ -651,6 +651,7 @@ public: IScreenEvents() : m_error(Event::kUnknown), m_shapeChanged(Event::kUnknown), + m_localInput(Event::kUnknown), m_suspend(Event::kUnknown), m_resume(Event::kUnknown) { } @@ -671,6 +672,14 @@ public: */ Event::Type shapeChanged(); + //! Get local input event type + /*! + Returns the local input event type. This is sent when the cursor + is not on the current screen but a local input is detected (e.g. + via touchscreen inputs or mouse movements). + */ + Event::Type localInput(); + //! Get suspend event type /*! Returns the suspend event type. This is sent whenever the system goes @@ -690,6 +699,7 @@ public: private: Event::Type m_error; Event::Type m_shapeChanged; + Event::Type m_localInput; Event::Type m_suspend; Event::Type m_resume; }; diff --git a/src/lib/client/Client.cpp b/src/lib/client/Client.cpp index 96d2c67b..9236b0d8 100644 --- a/src/lib/client/Client.cpp +++ b/src/lib/client/Client.cpp @@ -2,11 +2,11 @@ * barrier -- mouse and keyboard sharing utility * Copyright (C) 2012-2016 Symless Ltd. * Copyright (C) 2002 Chris Schoeneman - * + * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file LICENSE that should have accompanied this file. - * + * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -134,11 +134,11 @@ Client::connect() // being shuttled between various networks). patch by Brent // Priddy. m_serverAddress.resolve(); - + // m_serverAddress will be null if the hostname address is not reolved if (m_serverAddress.getAddress() != NULL) { // to help users troubleshoot, show server host name (issue: 60) - LOG((CLOG_NOTE "connecting to '%s': %s:%i", + LOG((CLOG_NOTE "connecting to '%s': %s:%i", m_serverAddress.getHostname().c_str(), ARCH->addrToString(m_serverAddress.getAddress()).c_str(), m_serverAddress.getPort())); @@ -255,7 +255,7 @@ Client::leave() m_active = false; m_screen->leave(); - + if (m_enableClipboard) { // send clipboards that we own and that have changed for (ClipboardID id = 0; id < kClipboardEnd; ++id) { @@ -271,7 +271,7 @@ Client::leave() void Client::setClipboard(ClipboardID id, const IClipboard* clipboard) { - m_screen->setClipboard(id, clipboard); + m_screen->setClipboard(id, clipboard); m_ownClipboard[id] = false; m_sentClipboard[id] = false; } @@ -508,6 +508,10 @@ Client::setupScreen() getEventTarget(), new TMethodEventJob(this, &Client::handleClipboardGrabbed)); + m_events->adoptHandler(m_events->forIScreen().localInput(), + getEventTarget(), + new TMethodEventJob(this, + &Client::handleLocalInputEvent)); } void @@ -564,6 +568,8 @@ Client::cleanupScreen() getEventTarget()); m_events->removeHandler(m_events->forClipboard().clipboardGrabbed(), getEventTarget()); + m_events->removeHandler(m_events->forIScreen().localInput(), + getEventTarget()); delete m_server; m_server = NULL; } @@ -767,6 +773,14 @@ Client::handleStopRetry(const Event&, void*) m_args.m_restartable = false; } +void +Client::handleLocalInputEvent(const Event& event, void*) +{ + IPlatformScreen::MotionInfo* info = static_cast(event.getData()); + LOG((CLOG_DEBUG "Trigger screen switching caused by local input, screen coordinates (%d, %d)", info->m_x, info->m_y)); + m_server->onLocalInput(info->m_x, info->m_y); +} + void Client::writeToDropDirThread(void*) { @@ -775,7 +789,7 @@ Client::writeToDropDirThread(void*) while (m_screen->isFakeDraggingStarted()) { ARCH->sleep(.1f); } - + DropHelper::writeToDir(m_screen->getDropTarget(), m_dragFileList, m_receivedFileData); } @@ -790,7 +804,7 @@ Client::dragInfoReceived(UInt32 fileNum, std::string data) } DragInformation::parseDragInfo(m_dragFileList, fileNum, data); - + m_screen->startDraggingFiles(m_dragFileList); } @@ -806,7 +820,7 @@ Client::sendFileToServer(const char* filename) if (m_sendFileThread != NULL) { StreamChunker::interruptFile(); } - + m_sendFileThread = new Thread( new TMethodJob( this, &Client::sendFileThread, diff --git a/src/lib/client/Client.h b/src/lib/client/Client.h index 7e566bef..fd90787b 100644 --- a/src/lib/client/Client.h +++ b/src/lib/client/Client.h @@ -191,6 +191,7 @@ private: void handleFileChunkSending(const Event&, void*); void handleFileRecieveCompleted(const Event&, void*); void handleStopRetry(const Event&, void*); + void handleLocalInputEvent(const Event&, void*); void onFileRecieveCompleted(); void sendClipboardThread(void*); diff --git a/src/lib/client/ServerProxy.cpp b/src/lib/client/ServerProxy.cpp index c067f132..d7c32278 100644 --- a/src/lib/client/ServerProxy.cpp +++ b/src/lib/client/ServerProxy.cpp @@ -2,11 +2,11 @@ * barrier -- mouse and keyboard sharing utility * Copyright (C) 2012-2016 Symless Ltd. * Copyright (C) 2002 Chris Schoeneman - * + * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file LICENSE that should have accompanied this file. - * + * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -367,6 +367,14 @@ ServerProxy::onClipboardChanged(ClipboardID id, const IClipboard* clipboard) StreamChunker::sendClipboard(data, data.size(), id, m_seqNum, m_events, this); } +void +ServerProxy::onLocalInput(SInt32 x, SInt32 y) +{ + // Coordinates as signed 32 bit integers don't make a lot of sense + // but are used for compatibility with e.g. IPlatformScreen::MotionInfo + ProtocolUtil::writef(m_stream, kMsgCLocalInput, x, y); +} + void ServerProxy::flushCompressedMouse() { @@ -553,7 +561,7 @@ ServerProxy::setClipboard() static std::string dataCached; ClipboardID id; UInt32 seq; - + int r = ClipboardChunk::assemble(m_stream, dataCached, id, seq); if (r == kStart) { @@ -562,7 +570,7 @@ ServerProxy::setClipboard() } else if (r == kFinish) { LOG((CLOG_DEBUG "received clipboard %d size=%d", id, dataCached.size())); - + // forward Clipboard clipboard; clipboard.unmarshall(dataCached, 0); diff --git a/src/lib/client/ServerProxy.h b/src/lib/client/ServerProxy.h index eec568bf..39f38a56 100644 --- a/src/lib/client/ServerProxy.h +++ b/src/lib/client/ServerProxy.h @@ -2,11 +2,11 @@ * barrier -- mouse and keyboard sharing utility * Copyright (C) 2012-2016 Symless Ltd. * Copyright (C) 2002 Chris Schoeneman - * + * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file LICENSE that should have accompanied this file. - * + * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -50,6 +50,7 @@ public: void onInfoChanged(); bool onGrabClipboard(ClipboardID); void onClipboardChanged(ClipboardID, const IClipboard*); + void onLocalInput(SInt32 x, SInt32 y); //@} @@ -58,7 +59,7 @@ public: // sending dragging information to server void sendDragInfo(UInt32 fileCount, const char* info, size_t size); - + #ifdef BARRIER_TEST_ENV void handleDataForTest() { handleData(Event(), NULL); } #endif diff --git a/src/lib/platform/XWindowsScreen.cpp b/src/lib/platform/XWindowsScreen.cpp index 97110b5b..1114e6aa 100644 --- a/src/lib/platform/XWindowsScreen.cpp +++ b/src/lib/platform/XWindowsScreen.cpp @@ -2,11 +2,11 @@ * barrier -- mouse and keyboard sharing utility * Copyright (C) 2012-2016 Symless Ltd. * Copyright (C) 2002 Chris Schoeneman - * + * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file LICENSE that should have accompanied this file. - * + * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -97,7 +97,7 @@ XWindowsScreen::XWindowsScreen( if (mouseScrollDelta==0) m_mouseScrollDelta=120; s_screen = this; - + if (!disableXInitThreads) { // initializes Xlib support for concurrent threads. if (m_impl->XInitThreads() == 0) @@ -133,7 +133,7 @@ XWindowsScreen::XWindowsScreen( #ifdef HAVE_XI2 m_xi2detected = detectXI2(); if (m_xi2detected) { - selectXIRawMotion(); + selectXIRawEventsPrimary(); } else #endif { @@ -145,6 +145,12 @@ XWindowsScreen::XWindowsScreen( openIM(); } else { +#ifdef HAVE_XI2 + m_xi2detected = detectXI2(); + if (m_xi2detected) { + selectXIRawEventsSecondary(); + } +#endif // become impervious to server grabs m_impl->XTestGrabControl(m_display, True); } @@ -266,14 +272,14 @@ XWindowsScreen::enter() m_impl->DPMSForceLevel(m_display, DPMSModeOn); } #endif - + // unmap the hider/grab window. this also ungrabs the mouse and // keyboard if they're grabbed. m_impl->XUnmapWindow(m_display, m_window); /* maybe call this if entering for the screensaver // set keyboard focus to root window. the screensaver should then - // pick up key events for when the user enters a password to unlock. + // pick up key events for when the user enters a password to unlock. XSetInputFocus(m_display, PointerRoot, PointerRoot, CurrentTime); */ @@ -1218,22 +1224,41 @@ XWindowsScreen::handleSystemEvent(const Event& event, void*) #ifdef HAVE_XI2 if (m_xi2detected) { - // Process RawMotion + // Process RawEvents XGenericEventCookie *cookie = (XGenericEventCookie*)&xevent->xcookie; - if (m_impl->XGetEventData(m_display, cookie) && - cookie->type == GenericEvent && - cookie->extension == xi_opcode) { - if (cookie->evtype == XI_RawMotion) { - // Get current pointer's position - Window root, child; - XMotionEvent xmotion; - xmotion.type = MotionNotify; - xmotion.send_event = False; // Raw motion - xmotion.display = m_display; - xmotion.window = m_window; - /* xmotion's time, state and is_hint are not used */ - unsigned int msk; - xmotion.same_screen = m_impl->XQueryPointer( + if (m_impl->XGetEventData(m_display, cookie) && + cookie->type == GenericEvent && + cookie->extension == xi_opcode) { + LOGC(cookie->evtype == XI_RawTouchBegin, (CLOG_DEBUG1 "Touch begin on %s, onScreen = %s", + m_isPrimary ? "server" : "client", + m_isOnScreen ? "true" : "false")); + + if (!m_isOnScreen && (cookie->evtype == XI_RawTouchBegin || cookie->evtype == XI_RawButtonPress )) { + // Touch or button press detected on local screen but cursor is not on screen => request focus from server + // Note: focus switching works on touch from client to server and vice versa, on mouse button press due + // to the nature of the shared server mouse only from server to client + SInt32 x, y; + // On touch event, getCursorPos sets x and y to the actual event coordinates. On mouse button press, x and + // y are set to the coordinates of the center of the screen. + // In the latter case, reusing m_x, m_y or m_xCursor, m_yCursor failed as they are not set on the client + // and thus trigger unexpected behavior (unexpected jumps to (0, 0) on button press) + getCursorPos(x, y); + sendEvent(m_events->forIScreen().localInput(), MotionInfo::alloc(x, y)); + m_impl->XFreeEventData(m_display, cookie); + return; + } + + if (cookie->evtype == XI_RawMotion) { + // Mouse motion detected on server => handle mouse motion + // Get current pointer's position + XMotionEvent xmotion; + xmotion.type = MotionNotify; + xmotion.send_event = False; // Raw motion + xmotion.display = m_display; + xmotion.window = m_window; + /* xmotion's time, state and is_hint are not used */ + unsigned int msk; + xmotion.same_screen = m_impl->XQueryPointer( m_display, m_root, &xmotion.root, &xmotion.subwindow, &xmotion.x_root, &xmotion.y_root, @@ -1241,10 +1266,10 @@ XWindowsScreen::handleSystemEvent(const Event& event, void*) &xmotion.y, &msk); onMouseMove(xmotion); - m_impl->XFreeEventData(m_display, cookie); + m_impl->XFreeEventData(m_display, cookie); return; - } - m_impl->XFreeEventData(m_display, cookie); + } + m_impl->XFreeEventData(m_display, cookie); } } #endif @@ -2062,17 +2087,43 @@ XWindowsScreen::detectXI2() #ifdef HAVE_XI2 void -XWindowsScreen::selectXIRawMotion() +XWindowsScreen::selectXIRawEventsPrimary() { XIEventMask mask; - mask.deviceid = XIAllDevices; - mask.mask_len = XIMaskLen(XI_RawMotion); - mask.mask = (unsigned char*)calloc(mask.mask_len, sizeof(char)); mask.deviceid = XIAllMasterDevices; - memset(mask.mask, 0, 2); - XISetMask(mask.mask, XI_RawKeyRelease); + mask.mask_len = XIMaskLen(XI_LASTEVENT); + mask.mask = (unsigned char*) calloc(mask.mask_len, sizeof(char)); + LOGC((mask.mask == nullptr), (CLOG_ERR "Cannot listen on XI2 events due to memory error")); + + XISetMask(mask.mask, XI_RawKeyRelease); XISetMask(mask.mask, XI_RawMotion); + // Detect touchscreen events on primary screen (= server) + XISetMask(mask.mask, XI_RawTouchBegin); + XISetMask(mask.mask, XI_RawTouchUpdate); + XISetMask(mask.mask, XI_RawTouchEnd); + + m_impl->XISelectEvents(m_display, DefaultRootWindow(m_display), &mask, 1); + free(mask.mask); +} + +void +XWindowsScreen::selectXIRawEventsSecondary() +{ + XIEventMask mask; + + mask.deviceid = XIAllMasterDevices; + mask.mask_len = XIMaskLen(XI_LASTEVENT); + mask.mask = (unsigned char*) calloc(mask.mask_len, sizeof(char)); + LOGC((mask.mask == nullptr), (CLOG_ERR "Cannot listen on XI2 events due to memory error")); + + // Detect mouse button press events on secondary screens (= clients) + XISetMask(mask.mask, XI_RawButtonPress); + // Detect touchscreen events on secondary screens (= clients) + XISetMask(mask.mask, XI_RawTouchBegin); + XISetMask(mask.mask, XI_RawTouchUpdate); + XISetMask(mask.mask, XI_RawTouchEnd); + m_impl->XISelectEvents(m_display, DefaultRootWindow(m_display), &mask, 1); free(mask.mask); } diff --git a/src/lib/platform/XWindowsScreen.h b/src/lib/platform/XWindowsScreen.h index 55738397..53546afb 100644 --- a/src/lib/platform/XWindowsScreen.h +++ b/src/lib/platform/XWindowsScreen.h @@ -144,7 +144,8 @@ private: bool detectXI2(); #ifdef HAVE_XI2 - void selectXIRawMotion(); + void selectXIRawEventsPrimary(); + void selectXIRawEventsSecondary(); #endif void selectEvents(Window) const; void doSelectEvents(Window) const; diff --git a/src/lib/server/ClientProxy1_7.cpp b/src/lib/server/ClientProxy1_7.cpp new file mode 100644 index 00000000..0dbb17df --- /dev/null +++ b/src/lib/server/ClientProxy1_7.cpp @@ -0,0 +1,59 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2015-2016 Symless Ltd. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "server/ClientProxy1_7.h" + +#include "server/Server.h" +#include "barrier/ProtocolUtil.h" +#include "barrier/StreamChunker.h" +#include "barrier/ClipboardChunk.h" +#include "io/IStream.h" +#include "base/TMethodEventJob.h" +#include "base/Log.h" + +// +// ClientProxy1_7 +// + +ClientProxy1_7::ClientProxy1_7(const String& name, barrier::IStream* stream, Server* server, IEventQueue* events) : + ClientProxy1_6(name, stream, server, events), + m_events(events) +{ +} + +ClientProxy1_7::~ClientProxy1_7() +{ +} + +bool +ClientProxy1_7::parseMessage(const UInt8* code) +{ + if (memcmp(code, kMsgCLocalInput, 4) == 0) { + SInt16 x, y; + ProtocolUtil::readf(getStream(), kMsgCLocalInput + 4, &x, &y); + + // Set up container for x and y coordinates + IPlatformScreen::MotionInfo* info = IPlatformScreen::MotionInfo::alloc(x, y); + + m_events->addEvent(Event(m_events->forIScreen().localInput(), getEventTarget(), info)); + } + else { + return ClientProxy1_6::parseMessage(code); + } + + return true; +} diff --git a/src/lib/server/ClientProxy1_7.h b/src/lib/server/ClientProxy1_7.h new file mode 100644 index 00000000..f95596c6 --- /dev/null +++ b/src/lib/server/ClientProxy1_7.h @@ -0,0 +1,38 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2015-2016 Symless Ltd. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "server/ClientProxy1_6.h" + +class Server; +class IEventQueue; + +//! Proxy for client implementing protocol version 1.7 +class ClientProxy1_7 : public ClientProxy1_6 { +public: + ClientProxy1_7(const String& name, barrier::IStream* adoptedStream, Server* server, IEventQueue* events); + ~ClientProxy1_7(); + + virtual bool parseMessage(const UInt8* code); + +private: + void handleClipboardSendingEvent(const Event&, void*); + +private: + IEventQueue* m_events; +}; diff --git a/src/lib/server/ClientProxyUnknown.cpp b/src/lib/server/ClientProxyUnknown.cpp index dc79da7d..24517dff 100644 --- a/src/lib/server/ClientProxyUnknown.cpp +++ b/src/lib/server/ClientProxyUnknown.cpp @@ -26,6 +26,7 @@ #include "server/ClientProxy1_4.h" #include "server/ClientProxy1_5.h" #include "server/ClientProxy1_6.h" +#include "server/ClientProxy1_7.h" #include "barrier/protocol_types.h" #include "barrier/ProtocolUtil.h" #include "barrier/XBarrier.h" @@ -231,6 +232,10 @@ ClientProxyUnknown::handleData(const Event&, void*) case 6: m_proxy = new ClientProxy1_6(name, m_stream, m_server, m_events); break; + + case 7: + m_proxy = new ClientProxy1_7(name, m_stream, m_server, m_events); + break; } } diff --git a/src/lib/server/Server.cpp b/src/lib/server/Server.cpp index 334049cf..beb62c76 100644 --- a/src/lib/server/Server.cpp +++ b/src/lib/server/Server.cpp @@ -243,6 +243,14 @@ Server::~Server() m_primaryClient->getEventTarget()); m_events->removeHandler(m_events->forIPrimaryScreen().screensaverDeactivated(), m_primaryClient->getEventTarget()); + m_events->removeHandler(m_events->forServer().switchToScreen(), + m_inputFilter); + m_events->removeHandler(m_events->forServer().switchInDirection(), + m_inputFilter); + m_events->removeHandler(m_events->forServer().keyboardBroadcast(), + m_inputFilter); + m_events->removeHandler(m_events->forServer().lockCursorToScreen(), + m_inputFilter); m_events->removeHandler(m_events->forIPrimaryScreen().fakeInputBegin(), m_inputFilter); m_events->removeHandler(m_events->forIPrimaryScreen().fakeInputEnd(), @@ -1426,6 +1434,20 @@ Server::handleToggleScreenEvent(const Event& event, void*) } } +void +Server::handleLocalInputEvent(const Event& event, void* vclient) +{ + BaseClientProxy* client = static_cast(vclient); + IPlatformScreen::MotionInfo* info = static_cast(event.getData()); + info = (info == nullptr) ? IPlatformScreen::MotionInfo::alloc(0, 0) : info; + LOG((CLOG_DEBUG "Trigger screen switching caused by local input on screen \"%s\", screen coordinates (%d, %d)", client->getName().c_str(), info->m_x, info->m_y)); + + // Record current cursor position on active screen + m_active->setJumpCursorPos(m_x, m_y); + + // Do actual screen switching + switchScreen(client, info->m_x, info->m_y, false); +} void Server::handleSwitchInDirectionEvent(const Event& event, void*) @@ -2127,6 +2149,10 @@ Server::addClient(BaseClientProxy* client) client->getEventTarget(), new TMethodEventJob(this, &Server::handleClipboardChanged, client)); + m_events->adoptHandler(m_events->forIScreen().localInput(), + client->getEventTarget(), + new TMethodEventJob(this, + &Server::handleLocalInputEvent, client)); // add to list m_clientSet.insert(client); @@ -2159,6 +2185,8 @@ Server::removeClient(BaseClientProxy* client) client->getEventTarget()); m_events->removeHandler(m_events->forClipboard().clipboardChanged(), client->getEventTarget()); + m_events->removeHandler(m_events->forIScreen().localInput(), + client->getEventTarget()); // remove from list m_clients.erase(getName(client)); diff --git a/src/lib/server/Server.h b/src/lib/server/Server.h index 4b0bc06f..74bc315c 100644 --- a/src/lib/server/Server.h +++ b/src/lib/server/Server.h @@ -309,6 +309,7 @@ private: void handleClientCloseTimeout(const Event&, void*); void handleSwitchToScreenEvent(const Event&, void*); void handleToggleScreenEvent(const Event&, void*); + void handleLocalInputEvent(const Event&, void*); void handleSwitchInDirectionEvent(const Event&, void*); void handleKeyboardBroadcastEvent(const Event&,void*); void handleLockCursorToScreenEvent(const Event&, void*); From b3fd2335b32b15aa81b741cbd05d0b75614b49e8 Mon Sep 17 00:00:00 2001 From: Florian Hofhammer Date: Tue, 18 Aug 2020 12:03:02 +0200 Subject: [PATCH 2/2] Allow XScreensaver syncing from client to server Allows to deactivate the screensaver and postpone screensaver activation if local input is detected on a client. This fixes the screensavers getting out of sync if a client has a local input device such as a touchscreen --- src/lib/barrier/Screen.cpp | 8 +- src/lib/barrier/protocol_types.cpp | 4 +- src/lib/barrier/protocol_types.h | 16 +++- src/lib/client/Client.cpp | 36 ++++++++- src/lib/client/Client.h | 2 + src/lib/client/ServerProxy.cpp | 17 +++-- src/lib/client/ServerProxy.h | 5 +- src/lib/platform/XWindowsScreen.cpp | 9 ++- src/lib/platform/XWindowsScreen.h | 4 + src/lib/server/ClientProxy1_7.cpp | 24 ++++++ src/lib/server/ClientProxy1_7.h | 8 +- src/lib/server/PrimaryClient.cpp | 8 +- src/lib/server/Server.cpp | 109 ++++++++++++++++------------ src/lib/server/Server.h | 4 +- 14 files changed, 179 insertions(+), 75 deletions(-) diff --git a/src/lib/barrier/Screen.cpp b/src/lib/barrier/Screen.cpp index 32442f6a..2709cdb1 100644 --- a/src/lib/barrier/Screen.cpp +++ b/src/lib/barrier/Screen.cpp @@ -178,11 +178,9 @@ Screen::grabClipboard(ClipboardID id) void Screen::screensaver(bool activate) { - if (!m_isPrimary) { - // activate/deactivation screen saver iff synchronization enabled - if (m_screenSaverSync) { - m_screen->screensaver(activate); - } + // activate/deactivation screen saver iff synchronization enabled + if (m_screenSaverSync) { + m_screen->screensaver(activate); } } diff --git a/src/lib/barrier/protocol_types.cpp b/src/lib/barrier/protocol_types.cpp index b21fe7f1..b9d96281 100644 --- a/src/lib/barrier/protocol_types.cpp +++ b/src/lib/barrier/protocol_types.cpp @@ -2,11 +2,11 @@ * barrier -- mouse and keyboard sharing utility * Copyright (C) 2012-2016 Symless Ltd. * Copyright (C) 2002 Chris Schoeneman - * + * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file LICENSE that should have accompanied this file. - * + * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the diff --git a/src/lib/barrier/protocol_types.h b/src/lib/barrier/protocol_types.h index c9e715df..dd5c906b 100644 --- a/src/lib/barrier/protocol_types.h +++ b/src/lib/barrier/protocol_types.h @@ -30,6 +30,7 @@ // 1.5: adds file transfer and removes home brew crypto // 1.6: adds clipboard streaming // 1.7: adds focus/screen switch on local input +// adds screensaver sync on client local input // NOTE: with new version, barrier minor version should increment static const SInt16 kProtocolMajorVersion = 1; static const SInt16 kProtocolMinorVersion = 7; @@ -130,9 +131,16 @@ extern const char* kMsgCClose; // must return this number with some messages. $4 = modifier key // mask. this will have bits set for each toggle modifier key // that is activated on entry to the screen. the secondary screen -// should adjust its toggle modifiers to reflect that state. +// should adjust its toggle modifiers to reflect that state. $5 = +// forScreensaver flag which denotes whether the screen is only +// entered for screensaver management purposes or not and thus +// whether the client can change its screensaver state or should +// not change the screensaver state extern const char* kMsgCEnter; +// enter screen 1.0: same as above but without respecting the screensaver state +extern const char* kMsgCEnter1_0; + // leave screen: primary -> secondary // leaving screen. the secondary screen should send clipboard // data in response to this message for those clipboards that @@ -149,8 +157,10 @@ extern const char* kMsgCLeave; // most recent kMsgCEnter. the primary always sends 0. extern const char* kMsgCClipboard; -// screensaver change: primary -> secondary -// screensaver on primary has started ($1 == 1) or closed ($1 == 0) +// screensaver change: primary <-> secondary +// screensaver has started ($1 == 1) or closed ($1 == 0). +// sync screensavers by dispatching information to all clients via +// the server extern const char* kMsgCScreenSaver; // reset options: primary -> secondary diff --git a/src/lib/client/Client.cpp b/src/lib/client/Client.cpp index 9236b0d8..d9ef075b 100644 --- a/src/lib/client/Client.cpp +++ b/src/lib/client/Client.cpp @@ -237,10 +237,13 @@ Client::getCursorPos(SInt32& x, SInt32& y) const } void -Client::enter(SInt32 xAbs, SInt32 yAbs, UInt32, KeyModifierMask mask, bool) +Client::enter(SInt32 xAbs, SInt32 yAbs, UInt32, KeyModifierMask mask, bool forScreensaver) { m_active = true; m_screen->mouseMove(xAbs, yAbs); + if (!forScreensaver) { + m_screen->screensaver(false); + } m_screen->enter(mask); if (m_sendFileThread != NULL) { @@ -509,9 +512,17 @@ Client::setupScreen() new TMethodEventJob(this, &Client::handleClipboardGrabbed)); m_events->adoptHandler(m_events->forIScreen().localInput(), - getEventTarget(), - new TMethodEventJob(this, - &Client::handleLocalInputEvent)); + getEventTarget(), + new TMethodEventJob(this, + &Client::handleLocalInputEvent)); + m_events->adoptHandler(m_events->forIPrimaryScreen().screensaverActivated(), + getEventTarget(), + new TMethodEventJob(this, + &Client::handleScreensaverActivatedEvent)); + m_events->adoptHandler(m_events->forIPrimaryScreen().screensaverDeactivated(), + getEventTarget(), + new TMethodEventJob(this, + &Client::handleScreensaverDeactivatedEvent)); } void @@ -570,6 +581,11 @@ Client::cleanupScreen() getEventTarget()); m_events->removeHandler(m_events->forIScreen().localInput(), getEventTarget()); + m_events->removeHandler(m_events->forIPrimaryScreen().screensaverActivated(), + getEventTarget()); + m_events->removeHandler(m_events->forIPrimaryScreen().screensaverDeactivated(), + getEventTarget()); + delete m_server; m_server = NULL; } @@ -745,6 +761,18 @@ Client::handleResume(const Event&, void*) } } +void +Client::handleScreensaverActivatedEvent(const Event&, void*) { + LOG((CLOG_DEBUG "Client received screensaver activate")); + m_server->sendScreensaver(true); +} + +void +Client::handleScreensaverDeactivatedEvent(const Event&, void*) { + LOG((CLOG_DEBUG "Client received screensaver deactivate")); + m_server->sendScreensaver(false); +} + void Client::handleFileChunkSending(const Event& event, void*) { diff --git a/src/lib/client/Client.h b/src/lib/client/Client.h index fd90787b..1bff567c 100644 --- a/src/lib/client/Client.h +++ b/src/lib/client/Client.h @@ -188,6 +188,8 @@ private: void handleHello(const Event&, void*); void handleSuspend(const Event& event, void*); void handleResume(const Event& event, void*); + void handleScreensaverActivatedEvent(const Event&, void*); + void handleScreensaverDeactivatedEvent(const Event&, void*); void handleFileChunkSending(const Event&, void*); void handleFileRecieveCompleted(const Event&, void*); void handleStopRetry(const Event&, void*); diff --git a/src/lib/client/ServerProxy.cpp b/src/lib/client/ServerProxy.cpp index d7c32278..6170a041 100644 --- a/src/lib/client/ServerProxy.cpp +++ b/src/lib/client/ServerProxy.cpp @@ -275,7 +275,7 @@ ServerProxy::parseMessage(const UInt8* code) } else if (memcmp(code, kMsgCScreenSaver, 4) == 0) { - screensaver(); + rcvScreensaver(); } else if (memcmp(code, kMsgQInfo, 4) == 0) { @@ -524,11 +524,12 @@ void ServerProxy::enter() { // parse + SInt8 forScreensaver; SInt16 x, y; UInt16 mask; UInt32 seqNum; - ProtocolUtil::readf(m_stream, kMsgCEnter + 4, &x, &y, &seqNum, &mask); - LOG((CLOG_DEBUG1 "recv enter, %d,%d %d %04x", x, y, seqNum, mask)); + ProtocolUtil::readf(m_stream, kMsgCEnter + 4, &x, &y, &seqNum, &mask, &forScreensaver); + LOG((CLOG_DEBUG1 "recv enter, %d,%d %d %04x, forScreensaver=%d", x, y, seqNum, mask, forScreensaver)); // discard old compressed mouse motion, if any m_compressMouse = false; @@ -538,7 +539,7 @@ ServerProxy::enter() m_seqNum = seqNum; // forward - m_client->enter(x, y, seqNum, static_cast(mask), false); + m_client->enter(x, y, seqNum, static_cast(mask), forScreensaver != 0); } void @@ -777,7 +778,7 @@ ServerProxy::mouseWheel() } void -ServerProxy::screensaver() +ServerProxy::rcvScreensaver() { // parse SInt8 on; @@ -788,6 +789,12 @@ ServerProxy::screensaver() m_client->screensaver(on != 0); } +void +ServerProxy::sendScreensaver(bool activate) { + // Notify server about screensaver state of client + ProtocolUtil::writef(m_stream, kMsgCScreenSaver, activate ? 1 : 0); +} + void ServerProxy::resetOptions() { diff --git a/src/lib/client/ServerProxy.h b/src/lib/client/ServerProxy.h index 39f38a56..16ec12c3 100644 --- a/src/lib/client/ServerProxy.h +++ b/src/lib/client/ServerProxy.h @@ -60,6 +60,9 @@ public: // sending dragging information to server void sendDragInfo(UInt32 fileCount, const char* info, size_t size); + // Send screensaver information to server + void sendScreensaver(bool activate); + #ifdef BARRIER_TEST_ENV void handleDataForTest() { handleData(Event(), NULL); } #endif @@ -99,7 +102,7 @@ private: void mouseMove(); void mouseRelativeMove(); void mouseWheel(); - void screensaver(); + void rcvScreensaver(); void resetOptions(); void setOptions(); void queryInfo(); diff --git a/src/lib/platform/XWindowsScreen.cpp b/src/lib/platform/XWindowsScreen.cpp index 1114e6aa..79072e65 100644 --- a/src/lib/platform/XWindowsScreen.cpp +++ b/src/lib/platform/XWindowsScreen.cpp @@ -244,8 +244,6 @@ XWindowsScreen::disable() void XWindowsScreen::enter() { - screensaver(false); - // release input context focus if (m_ic != NULL) { m_impl->XUnsetICFocus(m_ic); @@ -411,9 +409,11 @@ void XWindowsScreen::screensaver(bool activate) { if (activate) { + m_screensaverNotificationTimer.reset(); m_screensaver->activate(); } else { + m_screensaverNotificationTimer.reset(); m_screensaver->deactivate(); } } @@ -2117,8 +2117,11 @@ XWindowsScreen::selectXIRawEventsSecondary() mask.mask = (unsigned char*) calloc(mask.mask_len, sizeof(char)); LOGC((mask.mask == nullptr), (CLOG_ERR "Cannot listen on XI2 events due to memory error")); - // Detect mouse button press events on secondary screens (= clients) + // Detect mouse events (movement, button press) on secondary screens (= clients) + XISetMask(mask.mask, XI_RawMotion); XISetMask(mask.mask, XI_RawButtonPress); + // Detect key press events on secondary screens (= clients) + XISetMask(mask.mask, XI_RawKeyPress); // Detect touchscreen events on secondary screens (= clients) XISetMask(mask.mask, XI_RawTouchBegin); XISetMask(mask.mask, XI_RawTouchUpdate); diff --git a/src/lib/platform/XWindowsScreen.h b/src/lib/platform/XWindowsScreen.h index 53546afb..e467135a 100644 --- a/src/lib/platform/XWindowsScreen.h +++ b/src/lib/platform/XWindowsScreen.h @@ -20,6 +20,7 @@ #include "barrier/PlatformScreen.h" #include "barrier/KeyMap.h" +#include "base/Stopwatch.h" #include "common/stdset.h" #include "common/stdvector.h" #include "XWindowsImpl.h" @@ -231,6 +232,9 @@ private: // screen saver stuff XWindowsScreenSaver* m_screensaver; bool m_screensaverNotify; + // Timer for server notification to suppress screensaver if necessary + Stopwatch m_screensaverNotificationTimer; + const double NOTIFICATION_TIMEOUT = 10.0; // logical to physical button mapping. m_buttons[i] gives the // physical button for logical button i+1. diff --git a/src/lib/server/ClientProxy1_7.cpp b/src/lib/server/ClientProxy1_7.cpp index 0dbb17df..18b13cc4 100644 --- a/src/lib/server/ClientProxy1_7.cpp +++ b/src/lib/server/ClientProxy1_7.cpp @@ -39,6 +39,15 @@ ClientProxy1_7::~ClientProxy1_7() { } +void +ClientProxy1_7::enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, bool forScreensaver) +{ + LOG((CLOG_DEBUG1 "send enter to \"%s\", %d,%d %d %04x, forScreensaver=%d", getName().c_str(), xAbs, yAbs, seqNum, mask, forScreensaver ? 1 : 0)); + ProtocolUtil::writef(getStream(), kMsgCEnter, + xAbs, yAbs, seqNum, mask, forScreensaver ? 1 : 0); +} + bool ClientProxy1_7::parseMessage(const UInt8* code) { @@ -51,9 +60,24 @@ ClientProxy1_7::parseMessage(const UInt8* code) m_events->addEvent(Event(m_events->forIScreen().localInput(), getEventTarget(), info)); } + else if (memcmp(code, kMsgCScreenSaver, 4) == 0) { + rcvScreensaver(); + } else { return ClientProxy1_6::parseMessage(code); } return true; } + +void +ClientProxy1_7::rcvScreensaver() +{ + SInt8 activate; + ProtocolUtil::readf(getStream(), kMsgCScreenSaver + 4, &activate); + if (activate != 0) { + m_events->addEvent(Event(m_events->forIPrimaryScreen().screensaverActivated(), getEventTarget())); + } else { + m_events->addEvent(Event(m_events->forIPrimaryScreen().screensaverDeactivated(), getEventTarget())); + } +} diff --git a/src/lib/server/ClientProxy1_7.h b/src/lib/server/ClientProxy1_7.h index f95596c6..19d91437 100644 --- a/src/lib/server/ClientProxy1_7.h +++ b/src/lib/server/ClientProxy1_7.h @@ -28,10 +28,14 @@ public: ClientProxy1_7(const String& name, barrier::IStream* adoptedStream, Server* server, IEventQueue* events); ~ClientProxy1_7(); + virtual void enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, + bool forScreensaver); + +protected: virtual bool parseMessage(const UInt8* code); -private: - void handleClipboardSendingEvent(const Event&, void*); + virtual void rcvScreensaver(); private: IEventQueue* m_events; diff --git a/src/lib/server/PrimaryClient.cpp b/src/lib/server/PrimaryClient.cpp index 04ae86cf..7e8b54d9 100644 --- a/src/lib/server/PrimaryClient.cpp +++ b/src/lib/server/PrimaryClient.cpp @@ -139,10 +139,10 @@ PrimaryClient::disable() void PrimaryClient::enter(SInt32 xAbs, SInt32 yAbs, - UInt32 seqNum, KeyModifierMask mask, bool screensaver) + UInt32 seqNum, KeyModifierMask mask, bool forScreensaver) { m_screen->setSequenceNumber(seqNum); - if (!screensaver) { + if (!forScreensaver) { m_screen->warpCursor(xAbs, yAbs); } m_screen->enter(mask); @@ -244,9 +244,9 @@ PrimaryClient::mouseWheel(SInt32, SInt32) } void -PrimaryClient::screensaver(bool) +PrimaryClient::screensaver(bool activate) { - // ignore + m_screen->screensaver(activate); } void diff --git a/src/lib/server/Server.cpp b/src/lib/server/Server.cpp index beb62c76..9ef6dc21 100644 --- a/src/lib/server/Server.cpp +++ b/src/lib/server/Server.cpp @@ -1599,56 +1599,69 @@ Server::onClipboardChanged(BaseClientProxy* sender, void Server::onScreensaver(bool activated) { - LOG((CLOG_DEBUG "onScreenSaver %s", activated ? "activated" : "deactivated")); + LOG((CLOG_DEBUG "onScreenSaver %s", activated ? "activated" : "deactivated")); - if (activated) { - // save current screen and position - m_activeSaver = m_active; - m_xSaver = m_x; - m_ySaver = m_y; + if (m_screensaverDelayTimer.getTime() > SCREENSAVER_DELAY_THRESHOLD) { + // The timer prevents flickering and erronous screensavers caused by a bad combination + // of internal events and XEvents => drop events in the range of a certain delay to + // prevent duplicate screensaver invocations + m_screensaverDelayTimer.reset(); - // jump to primary screen - if (m_active != m_primaryClient) { - switchScreen(m_primaryClient, 0, 0, true); - } - } - else { - // jump back to previous screen and position. we must check - // that the position is still valid since the screen may have - // changed resolutions while the screen saver was running. - if (m_activeSaver != NULL && m_activeSaver != m_primaryClient) { - // check position - BaseClientProxy* screen = m_activeSaver; - SInt32 x, y, w, h; - screen->getShape(x, y, w, h); - SInt32 zoneSize = getJumpZoneSize(screen); - if (m_xSaver < x + zoneSize) { - m_xSaver = x + zoneSize; - } - else if (m_xSaver >= x + w - zoneSize) { - m_xSaver = x + w - zoneSize - 1; - } - if (m_ySaver < y + zoneSize) { - m_ySaver = y + zoneSize; - } - else if (m_ySaver >= y + h - zoneSize) { - m_ySaver = y + h - zoneSize - 1; - } + if (activated) { + // save current screen and position + m_activeSaver = m_active; + m_xSaver = m_x; + m_ySaver = m_y; - // jump - switchScreen(screen, m_xSaver, m_ySaver, false); - } + // jump to primary screen + if (m_active != m_primaryClient) { + switchScreen(m_primaryClient, 0, 0, true); + } + } + else { + // jump back to previous screen and position. we must check + // that the position is still valid since the screen may have + // changed resolutions while the screen saver was running. + // if (m_activeSaver != NULL && m_activeSaver != m_primaryClient) { + // // check position + // BaseClientProxy* screen = m_activeSaver; + // SInt32 x, y, w, h; + // screen->getShape(x, y, w, h); + // SInt32 zoneSize = getJumpZoneSize(screen); + // if (m_xSaver < x + zoneSize) { + // m_xSaver = x + zoneSize; + // } + // else if (m_xSaver >= x + w - zoneSize) { + // m_xSaver = x + w - zoneSize - 1; + // } + // if (m_ySaver < y + zoneSize) { + // m_ySaver = y + zoneSize; + // } + // else if (m_ySaver >= y + h - zoneSize) { + // m_ySaver = y + h - zoneSize - 1; + // } - // reset state - m_activeSaver = NULL; - } + // // jump + // switchScreen(screen, m_xSaver, m_ySaver, false); + // } - // send message to all clients - for (ClientList::const_iterator index = m_clients.begin(); - index != m_clients.end(); ++index) { - BaseClientProxy* client = index->second; - client->screensaver(activated); - } + // reset state + m_activeSaver = NULL; + } + + // send message to all clients + for (ClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + BaseClientProxy* client = index->second; + client->screensaver(activated); + if (!activated && client != m_active) { + // Leave all screens that are not the active screen. This ensures that the + // cursor does not appear on multiple screens again after deactivating the + // screensaver + client->leave(); + } + } + } } void @@ -2153,6 +2166,10 @@ Server::addClient(BaseClientProxy* client) client->getEventTarget(), new TMethodEventJob(this, &Server::handleLocalInputEvent, client)); + m_events->adoptHandler(m_events->forIPrimaryScreen().screensaverDeactivated(), + client->getEventTarget(), + new TMethodEventJob(this, + &Server::handleScreensaverDeactivatedEvent)); // add to list m_clientSet.insert(client); @@ -2187,6 +2204,8 @@ Server::removeClient(BaseClientProxy* client) client->getEventTarget()); m_events->removeHandler(m_events->forIScreen().localInput(), client->getEventTarget()); + m_events->removeHandler(m_events->forIPrimaryScreen().screensaverDeactivated(), + client->getEventTarget()); // remove from list m_clients.erase(getName(client)); diff --git a/src/lib/server/Server.h b/src/lib/server/Server.h index 74bc315c..4f1c82ea 100644 --- a/src/lib/server/Server.h +++ b/src/lib/server/Server.h @@ -426,7 +426,9 @@ private: // state saved when screen saver activates BaseClientProxy* m_activeSaver; - SInt32 m_xSaver, m_ySaver; + SInt32 m_xSaver, m_ySaver; + Stopwatch m_screensaverDelayTimer; // Used for preventing duplicate messages to clients + const double SCREENSAVER_DELAY_THRESHOLD = 0.5; // common state for screen switch tests. all tests are always // trying to reach the same screen in the same direction.