Multi-monitor working for up/down directions to/from macOS.

Very rough implementation.

#1112
This commit is contained in:
Vaughan Rouesnel 2022-03-31 16:02:31 +02:00
parent 653e4badeb
commit 810bf79456
18 changed files with 231 additions and 29 deletions

12
src/lib/barrier/Display.h Normal file
View File

@ -0,0 +1,12 @@
#include <Carbon/Carbon.h>
/**
* Platform-independent display.
*/
class Display {
public:
UInt32 displayId;
SInt32 x;
SInt32 y;
SInt32 width;
SInt32 height;
};

View File

@ -22,6 +22,7 @@
#include "base/Event.h" #include "base/Event.h"
#include "base/EventTypes.h" #include "base/EventTypes.h"
#include "common/IInterface.h" #include "common/IInterface.h"
#include "Display.h"
class IClipboard; class IClipboard;
@ -67,5 +68,7 @@ public:
*/ */
virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; virtual void getCursorPos(SInt32& x, SInt32& y) const = 0;
virtual std::vector<Display*> getDisplays() const = 0;
//@} //@}
}; };

View File

@ -2,11 +2,11 @@
* barrier -- mouse and keyboard sharing utility * barrier -- mouse and keyboard sharing utility
* Copyright (C) 2012-2016 Symless Ltd. * Copyright (C) 2012-2016 Symless Ltd.
* Copyright (C) 2004 Chris Schoeneman * Copyright (C) 2004 Chris Schoeneman
* *
* This package is free software; you can redistribute it and/or * This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file. * found in the file LICENSE that should have accompanied this file.
* *
* This package is distributed in the hope that it will be useful, * This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
@ -97,7 +97,7 @@ public:
virtual void setOptions(const OptionsList& options) = 0; virtual void setOptions(const OptionsList& options) = 0;
virtual void setSequenceNumber(UInt32) = 0; virtual void setSequenceNumber(UInt32) = 0;
virtual bool isPrimary() const = 0; virtual bool isPrimary() const = 0;
virtual void fakeDraggingFiles(DragFileList fileList) { throw std::runtime_error("fakeDraggingFiles not implemented"); } virtual void fakeDraggingFiles(DragFileList fileList) { throw std::runtime_error("fakeDraggingFiles not implemented"); }
virtual const String& virtual const String&
getDropTarget() const { throw std::runtime_error("getDropTarget not implemented"); } getDropTarget() const { throw std::runtime_error("getDropTarget not implemented"); }

View File

@ -562,4 +562,9 @@ Screen::leaveSecondary()
m_screen->fakeAllKeysUp(); m_screen->fakeAllKeysUp();
} }
std::vector<Display*>
Screen::getDisplays() const {
return m_screen->getDisplays();
}
} }

View File

@ -301,6 +301,7 @@ public:
virtual void getShape(SInt32& x, SInt32& y, virtual void getShape(SInt32& x, SInt32& y,
SInt32& width, SInt32& height) const; SInt32& width, SInt32& height) const;
virtual void getCursorPos(SInt32& x, SInt32& y) const; virtual void getCursorPos(SInt32& x, SInt32& y) const;
virtual std::vector<Display*> getDisplays() const;
IPlatformScreen* getPlatformScreen() { return m_screen; } IPlatformScreen* getPlatformScreen() { return m_screen; }

View File

@ -234,6 +234,12 @@ Client::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const
m_screen->getShape(x, y, w, h); m_screen->getShape(x, y, w, h);
} }
std::vector<Display*>
Client::getDisplays() const
{
// TODO(vjpr):
}
void void
Client::getCursorPos(SInt32& x, SInt32& y) const Client::getCursorPos(SInt32& x, SInt32& y) const
{ {

View File

@ -139,6 +139,7 @@ public:
virtual void getShape(SInt32& x, SInt32& y, virtual void getShape(SInt32& x, SInt32& y,
SInt32& width, SInt32& height) const; SInt32& width, SInt32& height) const;
virtual void getCursorPos(SInt32& x, SInt32& y) const; virtual void getCursorPos(SInt32& x, SInt32& y) const;
virtual std::vector<Display*> getDisplays() const;
// IClient overrides // IClient overrides
virtual void enter(SInt32 xAbs, SInt32 yAbs, virtual void enter(SInt32 xAbs, SInt32 yAbs,

View File

@ -24,6 +24,7 @@
#include "base/EventTypes.h" #include "base/EventTypes.h"
#include "common/stdmap.h" #include "common/stdmap.h"
#include "common/stdvector.h" #include "common/stdvector.h"
#import "barrier/Display.h"
#include <bitset> #include <bitset>
#include <Carbon/Carbon.h> #include <Carbon/Carbon.h>
@ -53,6 +54,9 @@ class Mutex;
//! Implementation of IPlatformScreen for OS X //! Implementation of IPlatformScreen for OS X
class OSXScreen : public PlatformScreen { class OSXScreen : public PlatformScreen {
public: public:
// TODO(vjpr): make private.
std::vector<Display*> m_displays;
OSXScreen(IEventQueue* events, bool isPrimary, bool autoShowHideCursor=true); OSXScreen(IEventQueue* events, bool isPrimary, bool autoShowHideCursor=true);
virtual ~OSXScreen(); virtual ~OSXScreen();
@ -64,6 +68,7 @@ public:
virtual void getShape(SInt32& x, SInt32& y, virtual void getShape(SInt32& x, SInt32& y,
SInt32& width, SInt32& height) const; SInt32& width, SInt32& height) const;
virtual void getCursorPos(SInt32& x, SInt32& y) const; virtual void getCursorPos(SInt32& x, SInt32& y) const;
virtual std::vector<Display*> getDisplays() const;
// IPrimaryScreen overrides // IPrimaryScreen overrides
virtual void reconfigure(UInt32 activeSides); virtual void reconfigure(UInt32 activeSides);

View File

@ -38,6 +38,7 @@
#include "base/Log.h" #include "base/Log.h"
#include "base/IEventQueue.h" #include "base/IEventQueue.h"
#include "base/TMethodEventJob.h" #include "base/TMethodEventJob.h"
#import "barrier/Display.h"
#include <math.h> #include <math.h>
#include <mach-o/dyld.h> #include <mach-o/dyld.h>
@ -244,6 +245,11 @@ OSXScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const
h = m_h; h = m_h;
} }
std::vector<Display*>
OSXScreen::getDisplays() const {
return m_displays;
}
void void
OSXScreen::getCursorPos(SInt32& x, SInt32& y) const OSXScreen::getCursorPos(SInt32& x, SInt32& y) const
{ {
@ -270,6 +276,7 @@ OSXScreen::warpCursor(SInt32 x, SInt32 y)
CGPoint pos; CGPoint pos;
pos.x = x; pos.x = x;
pos.y = y; pos.y = y;
// NOTE: If y is invalid, it sets it to 0.
CGWarpMouseCursorPosition(pos); CGWarpMouseCursorPosition(pos);
// save new cursor position // save new cursor position
@ -1546,6 +1553,21 @@ OSXScreen::updateScreenShape()
return; return;
} }
// save displays as generic displays.
for (CGDisplayCount i = 0; i < displayCount; ++i) {
CGRect bounds = CGDisplayBounds(displays[i]);
Display* display = new Display{
// TODO(vjpr): Display numbers are simply the enumeration order. This is not stable between reboots I don't think.
i,
// --
(SInt32)bounds.origin.x,
(SInt32)bounds.origin.y,
(SInt32)bounds.size.width,
(SInt32)bounds.size.height};
m_displays.push_back(display);
}
// get smallest rect enclosing all display rects // get smallest rect enclosing all display rects
CGRect totalBounds = CGRectZero; CGRect totalBounds = CGRectZero;
for (CGDisplayCount i = 0; i < displayCount; ++i) { for (CGDisplayCount i = 0; i < displayCount; ++i) {
@ -1566,6 +1588,7 @@ OSXScreen::updateScreenShape()
m_yCenter = (rect.origin.y + rect.size.height) / 2; m_yCenter = (rect.origin.y + rect.size.height) / 2;
delete[] displays; delete[] displays;
// We want to notify the peer screen whether we are primary screen or not // We want to notify the peer screen whether we are primary screen or not
sendEvent(m_events->forIScreen().shapeChanged()); sendEvent(m_events->forIScreen().shapeChanged());

View File

@ -63,6 +63,7 @@ public:
virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0;
virtual void getShape(SInt32& x, SInt32& y, virtual void getShape(SInt32& x, SInt32& y,
SInt32& width, SInt32& height) const = 0; SInt32& width, SInt32& height) const = 0;
virtual std::vector<Display*> getDisplays() const = 0;
virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; virtual void getCursorPos(SInt32& x, SInt32& y) const = 0;
// IClient overrides // IClient overrides

View File

@ -500,3 +500,12 @@ ClientProxy1_0::ClientClipboard::ClientClipboard() :
{ {
// do nothing // do nothing
} }
std::vector<Display*>
ClientProxy1_0::getDisplays() const {
// TODO(vjpr):
std::vector<Display*> vec;
return vec;
}

View File

@ -37,6 +37,7 @@ public:
virtual void getShape(SInt32& x, SInt32& y, virtual void getShape(SInt32& x, SInt32& y,
SInt32& width, SInt32& height) const; SInt32& width, SInt32& height) const;
virtual void getCursorPos(SInt32& x, SInt32& y) const; virtual void getCursorPos(SInt32& x, SInt32& y) const;
virtual std::vector<Display*> getDisplays() const;
// IClient overrides // IClient overrides
virtual void enter(SInt32 xAbs, SInt32 yAbs, virtual void enter(SInt32 xAbs, SInt32 yAbs,

View File

@ -99,3 +99,11 @@ ClientProxy1_6::recvClipboard()
return true; return true;
} }
std::vector<Display*> getDisplays() {
// TODO(vjpr):
std::vector<Display*> vec;
return vec;
}

View File

@ -31,6 +31,7 @@ public:
virtual void setClipboard(ClipboardID id, const IClipboard* clipboard); virtual void setClipboard(ClipboardID id, const IClipboard* clipboard);
virtual bool recvClipboard(); virtual bool recvClipboard();
//virtual std::vector<Display*> getDisplays();
private: private:
void handleClipboardSendingEvent(const Event&, void*); void handleClipboardSendingEvent(const Event&, void*);

View File

@ -21,6 +21,7 @@
#include "barrier/Screen.h" #include "barrier/Screen.h"
#include "barrier/Clipboard.h" #include "barrier/Clipboard.h"
#include "base/Log.h" #include "base/Log.h"
#include "platform/OSXScreen.h"
// //
// PrimaryClient // PrimaryClient
@ -272,3 +273,14 @@ PrimaryClient::setOptions(const OptionsList& options)
{ {
m_screen->setOptions(options); m_screen->setOptions(options);
} }
std::vector<Display*>
PrimaryClient::getDisplays() const {
// TODO(vjpr): Only works for osx!
IPlatformScreen* screen = m_screen->getPlatformScreen();
OSXScreen* platformScreen = dynamic_cast<OSXScreen*>(screen);
std::vector<Display*> displays = platformScreen->m_displays;
return displays;
}

View File

@ -122,6 +122,7 @@ public:
virtual void getShape(SInt32& x, SInt32& y, virtual void getShape(SInt32& x, SInt32& y,
SInt32& width, SInt32& height) const; SInt32& width, SInt32& height) const;
virtual void getCursorPos(SInt32& x, SInt32& y) const; virtual void getCursorPos(SInt32& x, SInt32& y) const;
virtual std::vector<Display*> getDisplays() const;
// IClient overrides // IClient overrides
virtual void enter(SInt32 xAbs, SInt32 yAbs, virtual void enter(SInt32 xAbs, SInt32 yAbs,

View File

@ -42,6 +42,7 @@
#include "base/IEventQueue.h" #include "base/IEventQueue.h"
#include "base/Log.h" #include "base/Log.h"
#include "base/TMethodEventJob.h" #include "base/TMethodEventJob.h"
#include "platform/OSXScreen.h"
#include <cstring> #include <cstring>
#include <cstdlib> #include <cstdlib>
@ -456,7 +457,8 @@ Server::switchScreen(BaseClientProxy* dst,
{ {
SInt32 dx, dy, dw, dh; SInt32 dx, dy, dw, dh;
dst->getShape(dx, dy, dw, dh); dst->getShape(dx, dy, dw, dh);
assert(x >= dx && y >= dy && x < dx + dw && y < dy + dh); // TODO(vjpr): This doesn't make sense anymore when we use current display bounds.
//assert(x >= dx && y >= dy && x < dx + dw && y < dy + dh);
} }
#endif #endif
assert(m_active != NULL); assert(m_active != NULL);
@ -640,9 +642,38 @@ Server::getNeighbor(BaseClientProxy* src,
} }
} }
// TODO(vjpr): For now its just bottom-most.
// For a given direction, find the display furthest in that direction.
Display* findDisplay(BaseClientProxy* dst, SInt32& x, SInt32& y) {
Display* found = NULL;
std::vector<Display*> displays = dst->getDisplays();
LOG((CLOG_DEBUG1 "findDisplay - mouse pos %d,%d", x, y));
// Get display that mouse is on.
for (Display* display : displays) {
//LOG((CLOG_DEBUG1 "check x %d <= %d <= %d true=%d", display->x, x, display->x + display->width, display->x <= x <= (display->x + display->width)));
if (display->x <= x && x <= (display->x + display->width)) {
//LOG((CLOG_DEBUG1 "check y %d <= %d <= %d", display->y, y, display->y + display->width));
if (found) {
if (display->y < found->y) {
found = display;
}
} else {
found = display;
}
}
}
return found;
}
BaseClientProxy* BaseClientProxy*
Server::mapToNeighbor(BaseClientProxy* src, Server::mapToNeighbor(BaseClientProxy* src,
EDirection srcSide, SInt32& x, SInt32& y) const EDirection srcSide, SInt32& x, SInt32& y, Display* currentDisplay) const
{ {
// note -- must be locked on entry // note -- must be locked on entry
@ -698,8 +729,18 @@ Server::mapToNeighbor(BaseClientProxy* src,
break; break;
case kTop: case kTop:
y -= dy; // vjpr
while (dst != NULL) { // Initially:
// y = mouse cursor position relative to primary display top-left corner.
// dy = highest display's top-left corner relative to primary display top-left corner. That is, (0,0) in the "canonical screen space".
//y -= dy;
// vjpr: We need to get y relative to our screen instead.
if (currentDisplay) {
y = y - currentDisplay->y;
} else {
y -= dy;
}
while (dst != NULL) {
lastGoodScreen = dst; lastGoodScreen = dst;
lastGoodScreen->getShape(dx, dy, dw, dh); lastGoodScreen->getShape(dx, dy, dw, dh);
y += dh; y += dh;
@ -713,21 +754,36 @@ Server::mapToNeighbor(BaseClientProxy* src,
y += dy; y += dy;
break; break;
case kBottom: case kBottom: {
y -= dy; y -= dy;
while (dst != NULL) { while (dst != NULL) {
y -= dh; // vjpr
lastGoodScreen = dst; // y = pixels we need to position cursor in dest screen.
lastGoodScreen->getShape(dx, dy, dw, dh); y -= dh;
if (y < dh) { lastGoodScreen = dst;
break; lastGoodScreen->getShape(dx, dy, dw, dh);
} if (y < dh) {
LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str())); break;
dst = getNeighbor(lastGoodScreen, srcSide, x, y); }
} LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str()));
assert(lastGoodScreen != NULL); dst = getNeighbor(lastGoodScreen, srcSide, x, y);
y += dy; }
break; assert(lastGoodScreen != NULL);
// vjpr: `dy` is the top-left of all merged display bounds.
// Instead, we want to look at `x`, find the top-most dest display, then offset `y` from this display's origin.
// y += dy;
// TODO(vjpr): Have to add ability to read displays from dest.
auto display = findDisplay(dst, x, y);
if (!display) {
// TODO(vjpr): Shouldn't happen.
y += dy;
}
y = y + display->y;
break;
}
case kNoDirection: case kNoDirection:
assert(0 && "bad direction"); assert(0 && "bad direction");
@ -1733,6 +1789,9 @@ Server::onMouseUp(ButtonID id)
} }
} }
#import <CoreGraphics/CGGeometry.h>
#import <CoreGraphics/CGDirectDisplay.h>
bool bool
Server::onMouseMovePrimary(SInt32 x, SInt32 y) Server::onMouseMovePrimary(SInt32 x, SInt32 y)
{ {
@ -1776,6 +1835,43 @@ Server::onMouseMovePrimary(SInt32 x, SInt32 y)
yc = ay + ah - 1; yc = ay + ah - 1;
} }
// --------------------
// TODO(vjpr):
// TODO(vjpr): This CG function call is too slow! Adds lag.
//uint32_t displayCount = 0;
//uint32_t displayID;
//CGGetDisplaysWithPoint(CGPointMake(x, y), 1, &displayID, &displayCount);
//LOG((CLOG_DEBUG4 "onMouseMovePrimary displayID %d", displayID));
// ---
Display* currentDisplay = NULL;
// TODO(vjpr): Assumes OSX is always primary screen. Maybe this will break things.
OSXScreen* screen = dynamic_cast<OSXScreen*>(m_screen->getPlatformScreen());
std::vector<Display*> displays = screen->m_displays;
//LOG((CLOG_DEBUG1 "mouse pos %d,%d", x, y));
// Get display that mouse is on.
for (Display* display : displays) {
//LOG((CLOG_DEBUG1 "check x %d <= %d <= %d true=%d", display->x, x, display->x + display->width, display->x <= x <= (display->x + display->width)));
if (display->x <= x && x <= (display->x + display->width)) {
//LOG((CLOG_DEBUG1 "check y %d <= %d <= %d", display->y, y, display->y + display->width));
if (display->y <= y && y <= (display->y + display->height)) {
//LOG((CLOG_DEBUG1 "match x=%d y=%d w=%d h=%d", display->x, display->y, display->width, display->height));
currentDisplay = display;
}
}
}
//LOG((CLOG_DEBUG1 "currentDisplay x=%d,y=%d,w=%d,h=%d", currentDisplay->x, currentDisplay->y, currentDisplay->width, currentDisplay->height));
ax = currentDisplay->x;
ay = currentDisplay->y;
// --------------------
// see if we should change screens // see if we should change screens
// when the cursor is in a corner, there may be a screen either // when the cursor is in a corner, there may be a screen either
// horizontally or vertically. check both directions. // horizontally or vertically. check both directions.
@ -1783,19 +1879,36 @@ Server::onMouseMovePrimary(SInt32 x, SInt32 y)
SInt32 xh = x, yv = y; SInt32 xh = x, yv = y;
if (x < ax + zoneSize) { if (x < ax + zoneSize) {
xh -= zoneSize; xh -= zoneSize;
dirh = kLeft; //dirh = kLeft;
} }
else if (x >= ax + aw - zoneSize) { else if (x >= ax + aw - zoneSize) {
xh += zoneSize; xh += zoneSize;
dirh = kRight; //dirh = kRight;
} }
if (y < ay + zoneSize) { if (y < ay + zoneSize) {
yv -= zoneSize; yv -= zoneSize;
dirv = kTop;
// Is another display above?
for (Display* display : displays) {
if (display->displayId == currentDisplay->displayId) continue; // Skip current monitor.
SInt32 x0 = display->x;
SInt32 y0 = display->y + display->height;
SInt32 x1 = display->x + display->width;
SInt32 y1 = display->y;
if (y0 - 1 == display->y) {
// This display is directly above. Don't switch screen.
break;
}
if (x0 < x && x > x1) {
// The cursor is within the x bounds.
dirv = kTop;
}
}
} }
else if (y >= ay + ah - zoneSize) { else if (y >= ay + ah - zoneSize) {
yv += zoneSize; yv += zoneSize;
dirv = kBottom; //dirv = kBottom;
} }
if (dirh == kNoDirection && dirv == kNoDirection) { if (dirh == kNoDirection && dirv == kNoDirection) {
// still on local screen // still on local screen
@ -1814,7 +1927,7 @@ Server::onMouseMovePrimary(SInt32 x, SInt32 y)
x = xs[i], y = ys[i]; x = xs[i], y = ys[i];
// get jump destination // get jump destination
BaseClientProxy* newScreen = mapToNeighbor(m_active, dir, x, y); BaseClientProxy* newScreen = mapToNeighbor(m_active, dir, x, y, currentDisplay);
// should we switch or not? // should we switch or not?
if (isSwitchOkay(newScreen, dir, x, y, xc, yc)) { if (isSwitchOkay(newScreen, dir, x, y, xc, yc)) {
@ -2005,7 +2118,7 @@ Server::onMouseMoveSecondary(SInt32 dx, SInt32 dy)
} }
// try to switch screen. get the neighbor. // try to switch screen. get the neighbor.
newScreen = mapToNeighbor(m_active, dir, m_x, m_y); newScreen = mapToNeighbor(m_active, dir, m_x, m_y, NULL);
// see if we should switch // see if we should switch
if (!isSwitchOkay(newScreen, dir, m_x, m_y, xc, yc)) { if (!isSwitchOkay(newScreen, dir, m_x, m_y, xc, yc)) {

View File

@ -232,7 +232,7 @@ private:
// cross multiple screens. if there is no suitable screen then // cross multiple screens. if there is no suitable screen then
// return NULL and x,y are not modified. // return NULL and x,y are not modified.
BaseClientProxy* mapToNeighbor(BaseClientProxy*, EDirection, BaseClientProxy* mapToNeighbor(BaseClientProxy*, EDirection,
SInt32& x, SInt32& y) const; SInt32& x, SInt32& y, Display*) const;
// adjusts x and y or neither to avoid ending up in a jump zone // adjusts x and y or neither to avoid ending up in a jump zone
// after entering the client in the given direction. // after entering the client in the given direction.