2012-06-10 16:50:54 +00:00
|
|
|
/*
|
2024-09-17 19:00:25 +00:00
|
|
|
* Deskflow -- mouse and keyboard sharing utility
|
2025-01-24 00:11:17 +00:00
|
|
|
* SPDX-FileCopyrightText: (C) 2012 - 2016 Symless Ltd.
|
|
|
|
|
* SPDX-FileCopyrightText: (C) 2004 Chris Schoeneman
|
|
|
|
|
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
2012-06-10 16:50:54 +00:00
|
|
|
*/
|
|
|
|
|
|
2014-02-28 12:36:45 +00:00
|
|
|
#include "platform/OSXScreen.h"
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
#include "arch/XArch.h"
|
2014-04-17 10:56:25 +00:00
|
|
|
#include "base/EventQueue.h"
|
2024-12-18 01:42:03 +00:00
|
|
|
#include "base/IEventQueue.h"
|
|
|
|
|
#include "base/Log.h"
|
|
|
|
|
#include "base/TMethodEventJob.h"
|
|
|
|
|
#include "base/TMethodJob.h"
|
2014-02-28 13:06:25 +00:00
|
|
|
#include "client/Client.h"
|
2024-12-18 01:42:03 +00:00
|
|
|
#include "deskflow/ClientApp.h"
|
2024-09-17 19:00:25 +00:00
|
|
|
#include "deskflow/Clipboard.h"
|
2024-12-18 01:42:03 +00:00
|
|
|
#include "deskflow/DisplayInvalidException.h"
|
2024-09-17 19:00:25 +00:00
|
|
|
#include "deskflow/KeyMap.h"
|
2014-02-28 12:36:45 +00:00
|
|
|
#include "mt/CondVar.h"
|
|
|
|
|
#include "mt/Lock.h"
|
|
|
|
|
#include "mt/Mutex.h"
|
|
|
|
|
#include "mt/Thread.h"
|
2024-12-18 01:42:03 +00:00
|
|
|
#include "platform/OSXClipboard.h"
|
|
|
|
|
#include "platform/OSXDragSimulator.h"
|
|
|
|
|
#include "platform/OSXEventQueueBuffer.h"
|
|
|
|
|
#include "platform/OSXKeyState.h"
|
|
|
|
|
#include "platform/OSXMediaKeySupport.h"
|
|
|
|
|
#include "platform/OSXPasteboardPeeker.h"
|
|
|
|
|
#include "platform/OSXScreenSaver.h"
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
#include <AppKit/NSEvent.h>
|
2012-06-10 16:50:54 +00:00
|
|
|
#include <AvailabilityMacros.h>
|
|
|
|
|
#include <IOKit/hidsystem/event_status_driver.h>
|
2021-06-29 10:20:56 +00:00
|
|
|
#include <libproc.h>
|
2024-12-18 01:42:03 +00:00
|
|
|
#include <mach-o/dyld.h>
|
|
|
|
|
#include <math.h>
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-08-07 17:11:29 +00:00
|
|
|
#pragma clang diagnostic push
|
|
|
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
|
|
|
|
2021-08-17 13:40:36 +00:00
|
|
|
// The following creates a section that tells Mac OS X
|
|
|
|
|
// that it is OK to let us inject input in the login screen.
|
|
|
|
|
// Just the name of the section is important, not its contents.
|
2024-12-18 01:42:03 +00:00
|
|
|
__attribute__((used)) __attribute__((section("__CGPreLoginApp,__cgpreloginapp"))) static const char magic_section[] =
|
|
|
|
|
"";
|
2021-08-17 13:40:36 +00:00
|
|
|
////////////////////////////////////////////////////////////
|
|
|
|
|
|
2012-06-10 16:50:54 +00:00
|
|
|
// This isn't in any Apple SDK that I know of as of yet.
|
2024-12-18 01:42:03 +00:00
|
|
|
enum
|
|
|
|
|
{
|
|
|
|
|
kDeskflowEventMouseScroll = 11,
|
|
|
|
|
kDeskflowMouseScrollAxisX = 'saxx',
|
|
|
|
|
kDeskflowMouseScrollAxisY = 'saxy'
|
2012-06-10 16:50:54 +00:00
|
|
|
};
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
enum
|
|
|
|
|
{
|
|
|
|
|
kCarbonLoopWaitTimeout = 10
|
2014-10-21 14:48:04 +00:00
|
|
|
};
|
|
|
|
|
|
2021-06-29 10:20:56 +00:00
|
|
|
int getSecureInputEventPID();
|
2025-01-06 00:55:07 +00:00
|
|
|
std::string getProcessName(int pid);
|
2021-06-29 10:20:56 +00:00
|
|
|
|
2014-02-25 19:10:54 +00:00
|
|
|
// TODO: upgrade deprecated function usage in these functions.
|
|
|
|
|
void setZeroSuppressionInterval();
|
|
|
|
|
void avoidSupression();
|
|
|
|
|
void logCursorVisibility();
|
|
|
|
|
void avoidHesitatingCursor();
|
|
|
|
|
|
2012-06-10 16:50:54 +00:00
|
|
|
//
|
2014-11-11 13:51:47 +00:00
|
|
|
// OSXScreen
|
2012-06-10 16:50:54 +00:00
|
|
|
//
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
bool OSXScreen::s_testedForGHOM = false;
|
|
|
|
|
bool OSXScreen::s_hasGHOM = false;
|
|
|
|
|
|
|
|
|
|
OSXScreen::OSXScreen(
|
|
|
|
|
IEventQueue *events, bool isPrimary, bool enableLangSync, deskflow::ClientScrollDirection scrollDirection
|
|
|
|
|
)
|
|
|
|
|
: PlatformScreen(events, scrollDirection),
|
|
|
|
|
m_isPrimary(isPrimary),
|
|
|
|
|
m_isOnScreen(m_isPrimary),
|
|
|
|
|
m_cursorPosValid(false),
|
|
|
|
|
MouseButtonEventMap(NumButtonIDs),
|
|
|
|
|
m_cursorHidden(false),
|
|
|
|
|
m_dragNumButtonsDown(0),
|
|
|
|
|
m_dragTimer(NULL),
|
|
|
|
|
m_keyState(NULL),
|
|
|
|
|
m_sequenceNumber(0),
|
|
|
|
|
m_screensaver(NULL),
|
|
|
|
|
m_screensaverNotify(false),
|
|
|
|
|
m_ownClipboard(false),
|
|
|
|
|
m_clipboardTimer(NULL),
|
|
|
|
|
m_hiddenWindow(NULL),
|
|
|
|
|
m_userInputWindow(NULL),
|
|
|
|
|
m_switchEventHandlerRef(0),
|
|
|
|
|
m_pmMutex(new Mutex),
|
|
|
|
|
m_pmWatchThread(NULL),
|
|
|
|
|
m_pmThreadReady(new CondVar<bool>(m_pmMutex, false)),
|
|
|
|
|
m_pmRootPort(0),
|
|
|
|
|
m_activeModifierHotKey(0),
|
|
|
|
|
m_activeModifierHotKeyMask(0),
|
|
|
|
|
m_eventTapPort(nullptr),
|
|
|
|
|
m_eventTapRLSR(nullptr),
|
|
|
|
|
m_lastClickTime(0),
|
|
|
|
|
m_clickState(1),
|
|
|
|
|
m_lastSingleClickXCursor(0),
|
|
|
|
|
m_lastSingleClickYCursor(0),
|
|
|
|
|
m_events(events),
|
|
|
|
|
m_getDropTargetThread(nullptr),
|
|
|
|
|
m_impl(NULL)
|
|
|
|
|
{
|
|
|
|
|
m_displayID = CGMainDisplayID();
|
|
|
|
|
if (!updateScreenShape(m_displayID, 0)) {
|
|
|
|
|
throw DisplayInvalidException("failed to initialize screen shape");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
m_screensaver = new OSXScreenSaver(m_events, getEventTarget());
|
|
|
|
|
m_keyState = new OSXKeyState(m_events, AppUtil::instance().getKeyboardLayoutList(), enableLangSync);
|
|
|
|
|
|
|
|
|
|
if (App::instance().argsBase().m_preventSleep) {
|
|
|
|
|
m_powerManager.disableSleep();
|
2018-03-14 13:35:42 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// only needed when running as a server.
|
|
|
|
|
if (m_isPrimary) {
|
|
|
|
|
// we can't pass options to show the dialog, this must be done by the gui.
|
|
|
|
|
if (!AXIsProcessTrusted()) {
|
|
|
|
|
throw XArch("assistive devices does not trust this process, allow it in system settings.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// install display manager notification handler
|
|
|
|
|
CGDisplayRegisterReconfigurationCallback(displayReconfigurationCallback, this);
|
|
|
|
|
|
|
|
|
|
// install fast user switching event handler
|
|
|
|
|
EventTypeSpec switchEventTypes[2];
|
|
|
|
|
switchEventTypes[0].eventClass = kEventClassSystem;
|
|
|
|
|
switchEventTypes[0].eventKind = kEventSystemUserSessionDeactivated;
|
|
|
|
|
switchEventTypes[1].eventClass = kEventClassSystem;
|
|
|
|
|
switchEventTypes[1].eventKind = kEventSystemUserSessionActivated;
|
|
|
|
|
EventHandlerUPP switchEventHandler = NewEventHandlerUPP(userSwitchCallback);
|
|
|
|
|
InstallApplicationEventHandler(switchEventHandler, 2, switchEventTypes, this, &m_switchEventHandlerRef);
|
|
|
|
|
DisposeEventHandlerUPP(switchEventHandler);
|
|
|
|
|
|
|
|
|
|
constructMouseButtonEventMap();
|
|
|
|
|
|
|
|
|
|
// watch for requests to sleep
|
|
|
|
|
m_events->adoptHandler(
|
|
|
|
|
m_events->forOSXScreen().confirmSleep(), getEventTarget(),
|
|
|
|
|
new TMethodEventJob<OSXScreen>(this, &OSXScreen::handleConfirmSleep)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// create thread for monitoring system power state.
|
|
|
|
|
*m_pmThreadReady = false;
|
|
|
|
|
m_carbonLoopMutex = new Mutex();
|
|
|
|
|
m_carbonLoopReady = new CondVar<bool>(m_carbonLoopMutex, false);
|
|
|
|
|
LOG((CLOG_DEBUG "starting watchSystemPowerThread"));
|
|
|
|
|
m_pmWatchThread = new Thread(new TMethodJob<OSXScreen>(this, &OSXScreen::watchSystemPowerThread));
|
|
|
|
|
} catch (...) {
|
|
|
|
|
m_events->removeHandler(m_events->forOSXScreen().confirmSleep(), getEventTarget());
|
|
|
|
|
if (m_switchEventHandlerRef != 0) {
|
|
|
|
|
RemoveEventHandler(m_switchEventHandlerRef);
|
|
|
|
|
}
|
2021-07-23 11:48:29 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
CGDisplayRemoveReconfigurationCallback(displayReconfigurationCallback, this);
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
delete m_keyState;
|
|
|
|
|
delete m_screensaver;
|
|
|
|
|
throw;
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// install event handlers
|
|
|
|
|
m_events->adoptHandler(
|
|
|
|
|
Event::kSystem, m_events->getSystemTarget(), new TMethodEventJob<OSXScreen>(this, &OSXScreen::handleSystemEvent)
|
|
|
|
|
);
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// install the platform event queue
|
|
|
|
|
m_events->adoptBuffer(new OSXEventQueueBuffer(m_events));
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2014-11-11 13:51:47 +00:00
|
|
|
OSXScreen::~OSXScreen()
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
disable();
|
2021-07-23 11:48:29 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
m_events->adoptBuffer(NULL);
|
|
|
|
|
m_events->removeHandler(Event::kSystem, m_events->getSystemTarget());
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
if (m_pmWatchThread) {
|
|
|
|
|
// make sure the thread has setup the runloop.
|
|
|
|
|
{
|
|
|
|
|
Lock lock(m_pmMutex);
|
|
|
|
|
while (!(bool)*m_pmThreadReady) {
|
|
|
|
|
m_pmThreadReady->wait();
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// now exit the thread's runloop and wait for it to exit
|
|
|
|
|
LOG((CLOG_DEBUG "stopping watchSystemPowerThread"));
|
|
|
|
|
CFRunLoopStop(m_pmRunloop);
|
|
|
|
|
m_pmWatchThread->wait();
|
|
|
|
|
delete m_pmWatchThread;
|
|
|
|
|
m_pmWatchThread = NULL;
|
|
|
|
|
}
|
|
|
|
|
delete m_pmThreadReady;
|
|
|
|
|
delete m_pmMutex;
|
|
|
|
|
|
|
|
|
|
m_events->removeHandler(m_events->forOSXScreen().confirmSleep(), getEventTarget());
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
RemoveEventHandler(m_switchEventHandlerRef);
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
CGDisplayRemoveReconfigurationCallback(displayReconfigurationCallback, this);
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
delete m_keyState;
|
|
|
|
|
delete m_screensaver;
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
delete m_carbonLoopMutex;
|
|
|
|
|
delete m_carbonLoopReady;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void *OSXScreen::getEventTarget() const
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
return const_cast<OSXScreen *>(this);
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
bool OSXScreen::getClipboard(ClipboardID, IClipboard *dst) const
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
Clipboard::copy(dst, &m_pasteboard);
|
|
|
|
|
return true;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 01:11:56 +00:00
|
|
|
void OSXScreen::getShape(int32_t &x, int32_t &y, int32_t &w, int32_t &h) const
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
x = m_x;
|
|
|
|
|
y = m_y;
|
|
|
|
|
w = m_w;
|
|
|
|
|
h = m_h;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 01:11:56 +00:00
|
|
|
void OSXScreen::getCursorPos(int32_t &x, int32_t &y) const
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
CGEventRef event = CGEventCreate(NULL);
|
|
|
|
|
CGPoint mouse = CGEventGetLocation(event);
|
|
|
|
|
x = mouse.x;
|
|
|
|
|
y = mouse.y;
|
|
|
|
|
m_cursorPosValid = true;
|
|
|
|
|
m_xCursor = x;
|
|
|
|
|
m_yCursor = y;
|
|
|
|
|
CFRelease(event);
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 02:13:19 +00:00
|
|
|
void OSXScreen::reconfigure(uint32_t)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
// do nothing
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 01:11:56 +00:00
|
|
|
void OSXScreen::warpCursor(int32_t x, int32_t y)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
// move cursor without generating events
|
|
|
|
|
CGPoint pos;
|
|
|
|
|
pos.x = x;
|
|
|
|
|
pos.y = y;
|
|
|
|
|
CGWarpMouseCursorPosition(pos);
|
|
|
|
|
|
|
|
|
|
// save new cursor position
|
|
|
|
|
m_xCursor = x;
|
|
|
|
|
m_yCursor = y;
|
|
|
|
|
m_cursorPosValid = true;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::fakeInputBegin()
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
// FIXME -- not implemented
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::fakeInputEnd()
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
// FIXME -- not implemented
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 01:11:56 +00:00
|
|
|
int32_t OSXScreen::getJumpZoneSize() const
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
return 1;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 02:13:19 +00:00
|
|
|
bool OSXScreen::isAnyMouseButtonDown(uint32_t &buttonID) const
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
if (m_buttonState.test(0)) {
|
|
|
|
|
buttonID = kButtonLeft;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2013-09-18 14:12:19 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
return (GetCurrentButtonState() != 0);
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 01:11:56 +00:00
|
|
|
void OSXScreen::getCursorCenter(int32_t &x, int32_t &y) const
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
x = m_xCenter;
|
|
|
|
|
y = m_yCenter;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 02:13:19 +00:00
|
|
|
uint32_t OSXScreen::registerHotKey(KeyID key, KeyModifierMask mask)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
// get mac virtual key and modifier mask matching deskflow key and mask
|
2025-01-11 02:13:19 +00:00
|
|
|
uint32_t macKey, macMask;
|
2024-12-18 01:42:03 +00:00
|
|
|
if (!m_keyState->mapDeskflowHotKeyToMac(key, mask, macKey, macMask)) {
|
|
|
|
|
LOG((CLOG_DEBUG "could not map hotkey id=%04x mask=%04x", key, mask));
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// choose hotkey id
|
2025-01-11 02:13:19 +00:00
|
|
|
uint32_t id;
|
2024-12-18 01:42:03 +00:00
|
|
|
if (!m_oldHotKeyIDs.empty()) {
|
|
|
|
|
id = m_oldHotKeyIDs.back();
|
|
|
|
|
m_oldHotKeyIDs.pop_back();
|
|
|
|
|
} else {
|
|
|
|
|
id = m_hotKeys.size() + 1;
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// if this hot key has modifiers only then we'll handle it specially
|
|
|
|
|
EventHotKeyRef ref = NULL;
|
|
|
|
|
bool okay;
|
|
|
|
|
if (key == kKeyNone) {
|
|
|
|
|
if (m_modifierHotKeys.count(mask) > 0) {
|
|
|
|
|
// already registered
|
|
|
|
|
okay = false;
|
|
|
|
|
} else {
|
|
|
|
|
m_modifierHotKeys[mask] = id;
|
|
|
|
|
okay = true;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2025-01-11 02:13:19 +00:00
|
|
|
EventHotKeyID hkid = {'SNRG', (uint32_t)id};
|
2024-12-18 01:42:03 +00:00
|
|
|
OSStatus status = RegisterEventHotKey(macKey, macMask, hkid, GetApplicationEventTarget(), 0, &ref);
|
|
|
|
|
okay = (status == noErr);
|
|
|
|
|
m_hotKeyToIDMap[HotKeyItem(macKey, macMask)] = id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!okay) {
|
|
|
|
|
m_oldHotKeyIDs.push_back(id);
|
|
|
|
|
m_hotKeyToIDMap.erase(HotKeyItem(macKey, macMask));
|
|
|
|
|
LOG(
|
|
|
|
|
(CLOG_WARN "failed to register hotkey %s (id=%04x mask=%04x)", deskflow::KeyMap::formatKey(key, mask).c_str(),
|
|
|
|
|
key, mask)
|
|
|
|
|
);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_hotKeys.insert(std::make_pair(id, HotKeyItem(ref, macKey, macMask)));
|
|
|
|
|
|
|
|
|
|
LOG(
|
|
|
|
|
(CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", deskflow::KeyMap::formatKey(key, mask).c_str(),
|
|
|
|
|
key, mask, id)
|
|
|
|
|
);
|
|
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-11 02:13:19 +00:00
|
|
|
void OSXScreen::unregisterHotKey(uint32_t id)
|
2024-12-18 01:42:03 +00:00
|
|
|
{
|
|
|
|
|
// look up hotkey
|
|
|
|
|
HotKeyMap::iterator i = m_hotKeys.find(id);
|
|
|
|
|
if (i == m_hotKeys.end()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// unregister with OS
|
|
|
|
|
bool okay;
|
|
|
|
|
if (i->second.getRef() != NULL) {
|
|
|
|
|
okay = (UnregisterEventHotKey(i->second.getRef()) == noErr);
|
|
|
|
|
} else {
|
|
|
|
|
okay = false;
|
|
|
|
|
// XXX -- this is inefficient
|
|
|
|
|
for (ModifierHotKeyMap::iterator j = m_modifierHotKeys.begin(); j != m_modifierHotKeys.end(); ++j) {
|
|
|
|
|
if (j->second == id) {
|
|
|
|
|
m_modifierHotKeys.erase(j);
|
|
|
|
|
okay = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!okay) {
|
|
|
|
|
LOG((CLOG_WARN "failed to unregister hotkey id=%d", id));
|
|
|
|
|
} else {
|
|
|
|
|
LOG((CLOG_DEBUG "unregistered hotkey id=%d", id));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// discard hot key from map and record old id for reuse
|
|
|
|
|
m_hotKeyToIDMap.erase(i->second);
|
|
|
|
|
m_hotKeys.erase(i);
|
|
|
|
|
m_oldHotKeyIDs.push_back(id);
|
|
|
|
|
if (m_activeModifierHotKey == id) {
|
|
|
|
|
m_activeModifierHotKey = 0;
|
|
|
|
|
m_activeModifierHotKeyMask = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OSXScreen::constructMouseButtonEventMap()
|
|
|
|
|
{
|
|
|
|
|
const CGEventType source[NumButtonIDs][3] = {
|
|
|
|
|
{kCGEventLeftMouseUp, kCGEventLeftMouseDragged, kCGEventLeftMouseDown},
|
|
|
|
|
{kCGEventRightMouseUp, kCGEventRightMouseDragged, kCGEventRightMouseDown},
|
|
|
|
|
{kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown},
|
|
|
|
|
{kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown},
|
|
|
|
|
{kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown}
|
|
|
|
|
};
|
|
|
|
|
|
2025-01-11 00:58:26 +00:00
|
|
|
for (uint16_t button = 0; button < NumButtonIDs; button++) {
|
2024-12-18 01:42:03 +00:00
|
|
|
MouseButtonEventMapType new_map;
|
2025-01-11 02:13:19 +00:00
|
|
|
for (uint16_t state = (uint32_t)kMouseButtonUp; state < kMouseButtonStateMax; state++) {
|
2024-12-18 01:42:03 +00:00
|
|
|
CGEventType curEvent = source[button][state];
|
|
|
|
|
new_map[state] = curEvent;
|
|
|
|
|
}
|
|
|
|
|
MouseButtonEventMap[button] = new_map;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OSXScreen::postMouseEvent(CGPoint &pos) const
|
|
|
|
|
{
|
|
|
|
|
// check if cursor position is valid on the client display configuration
|
|
|
|
|
// stkamp@users.sourceforge.net
|
|
|
|
|
CGDisplayCount displayCount = 0;
|
|
|
|
|
CGGetDisplaysWithPoint(pos, 0, NULL, &displayCount);
|
|
|
|
|
if (displayCount == 0) {
|
|
|
|
|
// cursor position invalid - clamp to bounds of last valid display.
|
|
|
|
|
// find the last valid display using the last cursor position.
|
|
|
|
|
displayCount = 0;
|
|
|
|
|
CGDirectDisplayID displayID;
|
|
|
|
|
CGGetDisplaysWithPoint(CGPointMake(m_xCursor, m_yCursor), 1, &displayID, &displayCount);
|
|
|
|
|
if (displayCount != 0) {
|
|
|
|
|
CGRect displayRect = CGDisplayBounds(displayID);
|
|
|
|
|
if (pos.x < displayRect.origin.x) {
|
|
|
|
|
pos.x = displayRect.origin.x;
|
|
|
|
|
} else if (pos.x > displayRect.origin.x + displayRect.size.width - 1) {
|
|
|
|
|
pos.x = displayRect.origin.x + displayRect.size.width - 1;
|
|
|
|
|
}
|
|
|
|
|
if (pos.y < displayRect.origin.y) {
|
|
|
|
|
pos.y = displayRect.origin.y;
|
|
|
|
|
} else if (pos.y > displayRect.origin.y + displayRect.size.height - 1) {
|
|
|
|
|
pos.y = displayRect.origin.y + displayRect.size.height - 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CGEventType type = kCGEventMouseMoved;
|
|
|
|
|
|
2025-01-10 23:42:34 +00:00
|
|
|
int8_t button = m_buttonState.getFirstButtonDown();
|
2024-12-18 01:42:03 +00:00
|
|
|
if (button != -1) {
|
|
|
|
|
MouseButtonEventMapType thisButtonType = MouseButtonEventMap[button];
|
|
|
|
|
type = thisButtonType[kMouseButtonDragged];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CGEventRef event = CGEventCreateMouseEvent(NULL, type, pos, static_cast<CGMouseButton>(button));
|
|
|
|
|
|
|
|
|
|
// Dragging events also need the click state
|
|
|
|
|
CGEventSetIntegerValueField(event, kCGMouseEventClickState, m_clickState);
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// Fix for sticky keys
|
|
|
|
|
CGEventFlags modifiers = m_keyState->getModifierStateAsOSXFlags();
|
|
|
|
|
CGEventSetFlags(event, modifiers);
|
|
|
|
|
|
|
|
|
|
// Set movement deltas to fix issues with certain 3D programs
|
|
|
|
|
SInt64 deltaX = pos.x;
|
|
|
|
|
deltaX -= m_xCursor;
|
|
|
|
|
|
|
|
|
|
SInt64 deltaY = pos.y;
|
|
|
|
|
deltaY -= m_yCursor;
|
|
|
|
|
|
|
|
|
|
CGEventSetIntegerValueField(event, kCGMouseEventDeltaX, deltaX);
|
|
|
|
|
CGEventSetIntegerValueField(event, kCGMouseEventDeltaY, deltaY);
|
|
|
|
|
|
|
|
|
|
double deltaFX = deltaX;
|
|
|
|
|
double deltaFY = deltaY;
|
|
|
|
|
|
|
|
|
|
CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaFX);
|
|
|
|
|
CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaFY);
|
|
|
|
|
|
|
|
|
|
CGEventPost(kCGHIDEventTap, event);
|
|
|
|
|
|
|
|
|
|
CFRelease(event);
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::fakeMouseButton(ButtonID id, bool press)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
// Buttons are indexed from one, but the button down array is indexed from zero
|
2025-01-11 02:13:19 +00:00
|
|
|
uint32_t index = mapDeskflowButtonToMac(id) - kButtonLeft;
|
2024-12-18 01:42:03 +00:00
|
|
|
if (index >= NumButtonIDs) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
CGPoint pos;
|
|
|
|
|
if (!m_cursorPosValid) {
|
2025-01-11 01:11:56 +00:00
|
|
|
int32_t x, y;
|
2024-12-18 01:42:03 +00:00
|
|
|
getCursorPos(x, y);
|
|
|
|
|
}
|
|
|
|
|
pos.x = m_xCursor;
|
|
|
|
|
pos.y = m_yCursor;
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// variable used to detect mouse coordinate differences between
|
|
|
|
|
// old & new mouse clicks. Used in double click detection.
|
2025-01-11 01:11:56 +00:00
|
|
|
int32_t xDiff = m_xCursor - m_lastSingleClickXCursor;
|
|
|
|
|
int32_t yDiff = m_yCursor - m_lastSingleClickYCursor;
|
2024-12-18 01:42:03 +00:00
|
|
|
double diff = sqrt(xDiff * xDiff + yDiff * yDiff);
|
|
|
|
|
// max sqrt(x^2 + y^2) difference allowed to double click
|
|
|
|
|
// since we don't have double click distance in NX APIs
|
|
|
|
|
// we define our own defaults.
|
|
|
|
|
const double maxDiff = sqrt(2) + 0.0001;
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
double clickTime = [NSEvent doubleClickInterval];
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// As long as the click is within the time window and distance window
|
|
|
|
|
// increase clickState (double click, triple click, etc)
|
|
|
|
|
// This will allow for higher than triple click but the quartz documenation
|
|
|
|
|
// does not specify that this should be limited to triple click
|
|
|
|
|
if (press) {
|
|
|
|
|
if ((ARCH->time() - m_lastClickTime) <= clickTime && diff <= maxDiff) {
|
|
|
|
|
m_clickState++;
|
|
|
|
|
} else {
|
|
|
|
|
m_clickState = 1;
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
m_lastClickTime = ARCH->time();
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
if (m_clickState == 1) {
|
|
|
|
|
m_lastSingleClickXCursor = m_xCursor;
|
|
|
|
|
m_lastSingleClickYCursor = m_yCursor;
|
|
|
|
|
}
|
2014-12-03 21:47:05 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
EMouseButtonState state = press ? kMouseButtonDown : kMouseButtonUp;
|
2014-12-03 21:47:05 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
LOG((CLOG_DEBUG1 "faking mouse button id: %d press: %s", index, press ? "pressed" : "released"));
|
2014-12-03 21:47:05 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
MouseButtonEventMapType thisButtonMap = MouseButtonEventMap[index];
|
|
|
|
|
CGEventType type = thisButtonMap[state];
|
2014-12-03 21:47:05 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
CGEventRef event = CGEventCreateMouseEvent(NULL, type, pos, static_cast<CGMouseButton>(index));
|
2014-12-03 21:47:05 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
CGEventSetIntegerValueField(event, kCGMouseEventClickState, m_clickState);
|
2014-12-03 21:47:05 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// Fix for sticky keys
|
|
|
|
|
CGEventFlags modifiers = m_keyState->getModifierStateAsOSXFlags();
|
|
|
|
|
CGEventSetFlags(event, modifiers);
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
m_buttonState.set(index, state);
|
|
|
|
|
CGEventPost(kCGHIDEventTap, event);
|
|
|
|
|
|
|
|
|
|
CFRelease(event);
|
|
|
|
|
|
|
|
|
|
if (!press && (id == kButtonLeft)) {
|
|
|
|
|
if (m_fakeDraggingStarted) {
|
|
|
|
|
auto method = new TMethodJob<OSXScreen>(this, &OSXScreen::getDropTargetThread);
|
|
|
|
|
m_getDropTargetThread.reset(new Thread(method));
|
2014-12-04 20:15:38 +00:00
|
|
|
}
|
2024-12-18 01:42:03 +00:00
|
|
|
|
|
|
|
|
m_draggingStarted = false;
|
|
|
|
|
}
|
2013-08-30 14:38:43 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::getDropTargetThread(void *)
|
2013-08-30 14:38:43 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
// wait for 5 secs for the drop destinaiton string to be filled.
|
2025-01-11 02:13:19 +00:00
|
|
|
uint32_t timeout = ARCH->time() + 5;
|
2024-12-18 01:42:03 +00:00
|
|
|
m_dropTarget.clear();
|
|
|
|
|
|
|
|
|
|
while (ARCH->time() < timeout) {
|
|
|
|
|
CFStringRef cfstr = getCocoaDropTarget();
|
|
|
|
|
char *cstr = CFStringRefToUTF8String(cfstr);
|
|
|
|
|
CFRelease(cfstr);
|
|
|
|
|
|
|
|
|
|
if (cstr != NULL) {
|
|
|
|
|
LOG((CLOG_DEBUG "drop target: %s", cstr));
|
|
|
|
|
m_dropTarget = cstr;
|
|
|
|
|
free(cstr);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
ARCH->sleep(.1f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_dropTarget.empty()) {
|
|
|
|
|
LOG((CLOG_ERR "failed to get drop target"));
|
|
|
|
|
}
|
2022-05-19 15:29:11 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
m_fakeDraggingStarted = false;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 01:11:56 +00:00
|
|
|
void OSXScreen::fakeMouseMove(int32_t x, int32_t y)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
if (m_fakeDraggingStarted) {
|
|
|
|
|
m_buttonState.set(0, kMouseButtonDown);
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// index 0 means left mouse button
|
|
|
|
|
if (m_buttonState.test(0)) {
|
|
|
|
|
m_draggingStarted = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// synthesize event
|
|
|
|
|
CGPoint pos;
|
|
|
|
|
pos.x = x;
|
|
|
|
|
pos.y = y;
|
|
|
|
|
postMouseEvent(pos);
|
|
|
|
|
|
|
|
|
|
// save new cursor position
|
2025-01-11 01:11:56 +00:00
|
|
|
m_xCursor = static_cast<int32_t>(pos.x);
|
|
|
|
|
m_yCursor = static_cast<int32_t>(pos.y);
|
2024-12-18 01:42:03 +00:00
|
|
|
m_cursorPosValid = true;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 01:11:56 +00:00
|
|
|
void OSXScreen::fakeMouseRelativeMove(int32_t dx, int32_t dy) const
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
// OS X does not appear to have a fake relative mouse move function.
|
|
|
|
|
// simulate it by getting the current mouse position and adding to
|
|
|
|
|
// that. this can yield the wrong answer but there's not much else
|
|
|
|
|
// we can do.
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// get current position
|
|
|
|
|
CGEventRef event = CGEventCreate(NULL);
|
|
|
|
|
CGPoint oldPos = CGEventGetLocation(event);
|
|
|
|
|
CFRelease(event);
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// synthesize event
|
|
|
|
|
CGPoint pos;
|
2025-01-11 01:11:56 +00:00
|
|
|
m_xCursor = static_cast<int32_t>(oldPos.x);
|
|
|
|
|
m_yCursor = static_cast<int32_t>(oldPos.y);
|
2024-12-18 01:42:03 +00:00
|
|
|
pos.x = oldPos.x + dx;
|
|
|
|
|
pos.y = oldPos.y + dy;
|
|
|
|
|
postMouseEvent(pos);
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// we now assume we don't know the current cursor position
|
|
|
|
|
m_cursorPosValid = false;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 01:11:56 +00:00
|
|
|
void OSXScreen::fakeMouseWheel(int32_t xDelta, int32_t yDelta) const
|
2024-12-18 01:42:03 +00:00
|
|
|
{
|
|
|
|
|
if (xDelta != 0 || yDelta != 0) {
|
|
|
|
|
// create a scroll event, post it and release it. not sure if kCGScrollEventUnitLine
|
|
|
|
|
// is the right choice here over kCGScrollEventUnitPixel
|
|
|
|
|
CGEventRef scrollEvent = CGEventCreateScrollWheelEvent(
|
|
|
|
|
NULL, kCGScrollEventUnitLine, 2, mapScrollWheelFromDeskflow(yDelta), mapScrollWheelFromDeskflow(xDelta)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Fix for sticky keys
|
|
|
|
|
CGEventFlags modifiers = m_keyState->getModifierStateAsOSXFlags();
|
|
|
|
|
CGEventSetFlags(scrollEvent, modifiers);
|
|
|
|
|
|
|
|
|
|
CGEventPost(kCGHIDEventTap, scrollEvent);
|
|
|
|
|
CFRelease(scrollEvent);
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::showCursor()
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
LOG((CLOG_DEBUG "showing cursor"));
|
2012-10-26 00:21:22 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
CFStringRef propertyString = CFStringCreateWithCString(NULL, "SetsCursorInBackground", kCFStringEncodingMacRoman);
|
2012-10-29 13:43:08 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
CGSSetConnectionProperty(_CGSDefaultConnection(), _CGSDefaultConnection(), propertyString, kCFBooleanTrue);
|
2012-10-29 13:43:08 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
CFRelease(propertyString);
|
2012-10-30 18:42:58 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
CGError error = CGDisplayShowCursor(m_displayID);
|
|
|
|
|
if (error != kCGErrorSuccess) {
|
|
|
|
|
LOG((CLOG_ERR "failed to show cursor, error=%d", error));
|
|
|
|
|
}
|
2012-10-26 00:21:22 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// appears to fix "mouse randomly not showing" bug
|
|
|
|
|
CGAssociateMouseAndMouseCursorPosition(true);
|
2012-10-28 11:36:30 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
logCursorVisibility();
|
2012-10-26 00:21:22 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
m_cursorHidden = false;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::hideCursor()
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
LOG((CLOG_DEBUG "hiding cursor"));
|
2012-10-26 00:21:22 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
CFStringRef propertyString = CFStringCreateWithCString(NULL, "SetsCursorInBackground", kCFStringEncodingMacRoman);
|
2012-10-29 13:43:08 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
CGSSetConnectionProperty(_CGSDefaultConnection(), _CGSDefaultConnection(), propertyString, kCFBooleanTrue);
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
CFRelease(propertyString);
|
2012-10-30 18:42:58 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
CGError error = CGDisplayHideCursor(m_displayID);
|
|
|
|
|
if (error != kCGErrorSuccess) {
|
|
|
|
|
LOG((CLOG_ERR "failed to hide cursor, error=%d", error));
|
|
|
|
|
}
|
2012-10-26 00:21:22 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// appears to fix "mouse randomly not hiding" bug
|
|
|
|
|
CGAssociateMouseAndMouseCursorPosition(true);
|
2012-10-30 18:42:58 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
logCursorVisibility();
|
2012-10-26 00:21:22 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
m_cursorHidden = true;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::enable()
|
|
|
|
|
{
|
|
|
|
|
// watch the clipboard
|
|
|
|
|
m_clipboardTimer = m_events->newTimer(1.0, NULL);
|
|
|
|
|
m_events->adoptHandler(
|
|
|
|
|
Event::kTimer, m_clipboardTimer, new TMethodEventJob<OSXScreen>(this, &OSXScreen::handleClipboardCheck)
|
|
|
|
|
);
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
if (m_isPrimary) {
|
|
|
|
|
// FIXME -- start watching jump zones
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// kCGEventTapOptionDefault = 0x00000000 (Missing in 10.4, so specified literally)
|
|
|
|
|
m_eventTapPort = CGEventTapCreate(
|
|
|
|
|
kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, kCGEventMaskForAllEvents, handleCGInputEvent,
|
|
|
|
|
this
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
// FIXME -- prevent system from entering power save mode
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
hideCursor();
|
2012-10-28 11:36:30 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// warp the mouse to the cursor center
|
|
|
|
|
fakeMouseMove(m_xCenter, m_yCenter);
|
|
|
|
|
|
|
|
|
|
// there may be a better way to do this, but we register an event handler even if we're
|
|
|
|
|
// not on the primary display (acting as a client). This way, if a local event comes in
|
|
|
|
|
// (either keyboard or mouse), we can make sure to show the cursor if we've hidden it.
|
|
|
|
|
m_eventTapPort = CGEventTapCreate(
|
|
|
|
|
kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, kCGEventMaskForAllEvents,
|
|
|
|
|
handleCGInputEventSecondary, this
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_eventTapPort) {
|
|
|
|
|
m_eventTapRLSR = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, m_eventTapPort, 0);
|
|
|
|
|
if (m_eventTapRLSR) {
|
|
|
|
|
CFRunLoopAddSource(CFRunLoopGetCurrent(), m_eventTapRLSR, kCFRunLoopDefaultMode);
|
|
|
|
|
} else {
|
|
|
|
|
LOG((CLOG_ERR "failed to create a CFRunLoopSourceRef for the quartz event tap"));
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
LOG((CLOG_ERR "failed to create quartz event tap"));
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::disable()
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
showCursor();
|
2022-01-27 11:23:36 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// FIXME -- stop watching jump zones, stop capturing input
|
2012-10-28 11:36:30 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
if (m_eventTapRLSR) {
|
|
|
|
|
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), m_eventTapRLSR, kCFRunLoopDefaultMode);
|
|
|
|
|
CFRelease(m_eventTapRLSR);
|
|
|
|
|
m_eventTapRLSR = nullptr;
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
if (m_eventTapPort) {
|
|
|
|
|
CGEventTapEnable(m_eventTapPort, false);
|
|
|
|
|
CFRelease(m_eventTapPort);
|
|
|
|
|
m_eventTapPort = nullptr;
|
|
|
|
|
}
|
|
|
|
|
// FIXME -- allow system to enter power saving mode
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// disable drag handling
|
|
|
|
|
m_dragNumButtonsDown = 0;
|
|
|
|
|
enableDragTimer(false);
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// uninstall clipboard timer
|
|
|
|
|
if (m_clipboardTimer != NULL) {
|
|
|
|
|
m_events->removeHandler(Event::kTimer, m_clipboardTimer);
|
|
|
|
|
m_events->deleteTimer(m_clipboardTimer);
|
|
|
|
|
m_clipboardTimer = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_isOnScreen = m_isPrimary;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::enter()
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2025-01-08 20:31:31 +00:00
|
|
|
m_isOnScreen = true;
|
2024-12-18 01:42:03 +00:00
|
|
|
showCursor();
|
2012-10-26 00:21:22 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
if (m_isPrimary) {
|
|
|
|
|
setZeroSuppressionInterval();
|
|
|
|
|
} else {
|
|
|
|
|
// reset buttons
|
|
|
|
|
m_buttonState.reset();
|
2013-08-02 16:48:09 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// wakes the client screen
|
|
|
|
|
io_registry_entry_t entry =
|
|
|
|
|
IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/IOResources/IODisplayWrangler");
|
|
|
|
|
|
|
|
|
|
if (entry != MACH_PORT_NULL) {
|
|
|
|
|
IORegistryEntrySetCFProperty(entry, CFSTR("IORequestIdle"), kCFBooleanFalse);
|
|
|
|
|
IOObjectRelease(entry);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
avoidSupression();
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
bool OSXScreen::canLeave()
|
2024-09-17 03:19:52 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
return true;
|
2024-09-17 03:19:52 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::leave()
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
hideCursor();
|
2021-08-04 12:12:01 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
if (isDraggingStarted()) {
|
2025-01-06 00:55:07 +00:00
|
|
|
std::string &fileList = getDraggingFilename();
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
if (!m_isPrimary) {
|
|
|
|
|
if (fileList.empty() == false) {
|
|
|
|
|
ClientApp &app = ClientApp::instance();
|
|
|
|
|
Client *client = app.getClientPtr();
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
DragInformation di;
|
|
|
|
|
di.setFilename(fileList);
|
|
|
|
|
DragFileList dragFileList;
|
|
|
|
|
dragFileList.push_back(di);
|
2025-01-06 00:55:07 +00:00
|
|
|
std::string info;
|
2025-01-11 02:13:19 +00:00
|
|
|
uint32_t fileCount = DragInformation::setupDragInfo(dragFileList, info);
|
2024-12-18 01:42:03 +00:00
|
|
|
client->sendDragInfo(fileCount, info, info.size());
|
|
|
|
|
LOG((CLOG_DEBUG "send dragging file to server"));
|
|
|
|
|
|
|
|
|
|
// TODO: what to do with multiple file or even
|
|
|
|
|
// a folder
|
|
|
|
|
client->sendFileToServer(fileList.c_str());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
m_draggingStarted = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_isPrimary) {
|
|
|
|
|
avoidHesitatingCursor();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// now off screen
|
|
|
|
|
m_isOnScreen = false;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
bool OSXScreen::setClipboard(ClipboardID, const IClipboard *src)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
if (src != NULL) {
|
|
|
|
|
LOG((CLOG_DEBUG "setting clipboard"));
|
|
|
|
|
Clipboard::copy(&m_pasteboard, src);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::checkClipboards()
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
LOG((CLOG_DEBUG2 "checking clipboard"));
|
|
|
|
|
if (m_pasteboard.synchronize()) {
|
|
|
|
|
LOG((CLOG_DEBUG "clipboard changed"));
|
|
|
|
|
sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardClipboard);
|
|
|
|
|
sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardSelection);
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::openScreensaver(bool notify)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
m_screensaverNotify = notify;
|
|
|
|
|
if (!m_screensaverNotify) {
|
|
|
|
|
m_screensaver->disable();
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::closeScreensaver()
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
if (!m_screensaverNotify) {
|
|
|
|
|
m_screensaver->enable();
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::screensaver(bool activate)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
if (activate) {
|
|
|
|
|
m_screensaver->activate();
|
|
|
|
|
} else {
|
|
|
|
|
m_screensaver->deactivate();
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::resetOptions()
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
// no options
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::setOptions(const OptionsList &)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
// no options
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 02:13:19 +00:00
|
|
|
void OSXScreen::setSequenceNumber(uint32_t seqNum)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
m_sequenceNumber = seqNum;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
bool OSXScreen::isPrimary() const
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
return m_isPrimary;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::sendEvent(Event::Type type, void *data) const
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
m_events->addEvent(Event(type, getEventTarget(), data));
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::sendClipboardEvent(Event::Type type, ClipboardID id) const
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
ClipboardInfo *info = (ClipboardInfo *)malloc(sizeof(ClipboardInfo));
|
|
|
|
|
info->m_id = id;
|
|
|
|
|
info->m_sequenceNumber = m_sequenceNumber;
|
|
|
|
|
sendEvent(type, info);
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::handleSystemEvent(const Event &event, void *)
|
|
|
|
|
{
|
|
|
|
|
EventRef *carbonEvent = static_cast<EventRef *>(event.getData());
|
|
|
|
|
assert(carbonEvent != NULL);
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2025-01-11 02:13:19 +00:00
|
|
|
uint32_t eventClass = GetEventClass(*carbonEvent);
|
2024-12-18 01:42:03 +00:00
|
|
|
|
|
|
|
|
switch (eventClass) {
|
|
|
|
|
case kEventClassMouse:
|
|
|
|
|
switch (GetEventKind(*carbonEvent)) {
|
|
|
|
|
case kDeskflowEventMouseScroll: {
|
|
|
|
|
OSStatus r;
|
|
|
|
|
long xScroll;
|
|
|
|
|
long yScroll;
|
|
|
|
|
|
|
|
|
|
// get scroll amount
|
|
|
|
|
r = GetEventParameter(*carbonEvent, kDeskflowMouseScrollAxisX, typeSInt32, NULL, sizeof(xScroll), NULL, &xScroll);
|
|
|
|
|
if (r != noErr) {
|
|
|
|
|
xScroll = 0;
|
|
|
|
|
}
|
|
|
|
|
r = GetEventParameter(*carbonEvent, kDeskflowMouseScrollAxisY, typeSInt32, NULL, sizeof(yScroll), NULL, &yScroll);
|
|
|
|
|
if (r != noErr) {
|
|
|
|
|
yScroll = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (xScroll != 0 || yScroll != 0) {
|
|
|
|
|
onMouseWheel(-mapScrollWheelToDeskflow(xScroll), mapScrollWheelToDeskflow(yScroll));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case kEventClassKeyboard:
|
|
|
|
|
switch (GetEventKind(*carbonEvent)) {
|
|
|
|
|
case kEventHotKeyPressed:
|
|
|
|
|
case kEventHotKeyReleased:
|
|
|
|
|
onHotKey(*carbonEvent);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case kEventClassWindow:
|
|
|
|
|
// 2nd param was formerly GetWindowEventTarget(m_userInputWindow) which is 32-bit only,
|
|
|
|
|
// however as m_userInputWindow is never initialized to anything we can take advantage of
|
|
|
|
|
// the fact that GetWindowEventTarget(NULL) == NULL
|
|
|
|
|
SendEventToEventTarget(*carbonEvent, NULL);
|
|
|
|
|
switch (GetEventKind(*carbonEvent)) {
|
|
|
|
|
case kEventWindowActivated:
|
|
|
|
|
LOG((CLOG_DEBUG1 "window activated"));
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case kEventWindowDeactivated:
|
|
|
|
|
LOG((CLOG_DEBUG1 "window deactivated"));
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case kEventWindowFocusAcquired:
|
|
|
|
|
LOG((CLOG_DEBUG1 "focus acquired"));
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case kEventWindowFocusRelinquish:
|
|
|
|
|
LOG((CLOG_DEBUG1 "focus released"));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
SendEventToEventTarget(*carbonEvent, GetEventDispatcherTarget());
|
|
|
|
|
break;
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
bool OSXScreen::onMouseMove(CGFloat mx, CGFloat my)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
LOG((CLOG_DEBUG2 "mouse move %+f,%+f", mx, my));
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
CGFloat x = mx - m_xCursor;
|
|
|
|
|
CGFloat y = my - m_yCursor;
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
if ((x == 0 && y == 0) || (mx == m_xCenter && mx == m_yCenter)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// save position to compute delta of next motion
|
2025-01-11 01:11:56 +00:00
|
|
|
m_xCursor = (int32_t)mx;
|
|
|
|
|
m_yCursor = (int32_t)my;
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
if (m_isOnScreen) {
|
|
|
|
|
// motion on primary screen
|
|
|
|
|
sendEvent(m_events->forIPrimaryScreen().motionOnPrimary(), MotionInfo::alloc(m_xCursor, m_yCursor));
|
|
|
|
|
if (m_buttonState.test(0)) {
|
|
|
|
|
m_draggingStarted = true;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// motion on secondary screen. warp mouse back to
|
|
|
|
|
// center.
|
|
|
|
|
warpCursor(m_xCenter, m_yCenter);
|
|
|
|
|
|
|
|
|
|
// examine the motion. if it's about the distance
|
|
|
|
|
// from the center of the screen to an edge then
|
|
|
|
|
// it's probably a bogus motion that we want to
|
|
|
|
|
// ignore (see warpCursorNoFlush() for a further
|
|
|
|
|
// description).
|
2025-01-11 01:11:56 +00:00
|
|
|
static int32_t bogusZoneSize = 10;
|
2024-12-18 01:42:03 +00:00
|
|
|
if (-x + bogusZoneSize > m_xCenter - m_x || x + bogusZoneSize > m_x + m_w - m_xCenter ||
|
|
|
|
|
-y + bogusZoneSize > m_yCenter - m_y || y + bogusZoneSize > m_y + m_h - m_yCenter) {
|
|
|
|
|
LOG((CLOG_DEBUG "dropped bogus motion %+d,%+d", x, y));
|
|
|
|
|
} else {
|
|
|
|
|
// send motion
|
|
|
|
|
// Accumulate together the move into the running total
|
|
|
|
|
static CGFloat m_xFractionalMove = 0;
|
|
|
|
|
static CGFloat m_yFractionalMove = 0;
|
|
|
|
|
|
|
|
|
|
m_xFractionalMove += x;
|
|
|
|
|
m_yFractionalMove += y;
|
|
|
|
|
|
|
|
|
|
// Return the integer part
|
2025-01-11 01:11:56 +00:00
|
|
|
int32_t intX = (int32_t)m_xFractionalMove;
|
|
|
|
|
int32_t intY = (int32_t)m_yFractionalMove;
|
2024-12-18 01:42:03 +00:00
|
|
|
|
|
|
|
|
// And keep only the fractional part
|
|
|
|
|
m_xFractionalMove -= intX;
|
|
|
|
|
m_yFractionalMove -= intY;
|
|
|
|
|
sendEvent(m_events->forIPrimaryScreen().motionOnSecondary(), MotionInfo::alloc(intX, intY));
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
return true;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 00:58:26 +00:00
|
|
|
bool OSXScreen::onMouseButton(bool pressed, uint16_t macButton)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
// Buttons 2 and 3 are inverted on the mac
|
|
|
|
|
ButtonID button = mapMacButtonToDeskflow(macButton);
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
if (pressed) {
|
|
|
|
|
LOG((CLOG_DEBUG1 "event: button press button=%d", button));
|
|
|
|
|
if (button != kButtonNone) {
|
|
|
|
|
KeyModifierMask mask = m_keyState->getActiveModifiers();
|
|
|
|
|
sendEvent(m_events->forIPrimaryScreen().buttonDown(), ButtonInfo::alloc(button, mask));
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
LOG((CLOG_DEBUG1 "event: button release button=%d", button));
|
|
|
|
|
if (button != kButtonNone) {
|
|
|
|
|
KeyModifierMask mask = m_keyState->getActiveModifiers();
|
|
|
|
|
sendEvent(m_events->forIPrimaryScreen().buttonUp(), ButtonInfo::alloc(button, mask));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// handle drags with any button other than button 1 or 2
|
|
|
|
|
if (macButton > 2) {
|
|
|
|
|
if (pressed) {
|
|
|
|
|
// one more button
|
|
|
|
|
if (m_dragNumButtonsDown++ == 0) {
|
|
|
|
|
enableDragTimer(true);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// one less button
|
|
|
|
|
if (--m_dragNumButtonsDown == 0) {
|
|
|
|
|
enableDragTimer(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (macButton == kButtonLeft) {
|
|
|
|
|
EMouseButtonState state = pressed ? kMouseButtonDown : kMouseButtonUp;
|
|
|
|
|
m_buttonState.set(kButtonLeft - 1, state);
|
|
|
|
|
if (pressed) {
|
|
|
|
|
m_draggingFilename.clear();
|
|
|
|
|
LOG((CLOG_DEBUG2 "dragging file directory is cleared"));
|
|
|
|
|
} else {
|
|
|
|
|
if (m_fakeDraggingStarted) {
|
|
|
|
|
auto method = new TMethodJob<OSXScreen>(this, &OSXScreen::getDropTargetThread);
|
|
|
|
|
m_getDropTargetThread.reset(new Thread(method));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_draggingStarted = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
return true;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 01:11:56 +00:00
|
|
|
bool OSXScreen::onMouseWheel(int32_t xDelta, int32_t yDelta) const
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
LOG((CLOG_DEBUG1 "event: button wheel delta=%+d,%+d", xDelta, yDelta));
|
|
|
|
|
sendEvent(m_events->forIPrimaryScreen().wheel(), WheelInfo::alloc(xDelta, yDelta));
|
|
|
|
|
return true;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::handleClipboardCheck(const Event &, void *)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
checkClipboards();
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::displayReconfigurationCallback(
|
|
|
|
|
CGDirectDisplayID displayID, CGDisplayChangeSummaryFlags flags, void *inUserData
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
OSXScreen *screen = (OSXScreen *)inUserData;
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// Closing or opening the lid when an external monitor is
|
|
|
|
|
// connected causes an kCGDisplayBeginConfigurationFlag event
|
|
|
|
|
CGDisplayChangeSummaryFlags mask = kCGDisplayBeginConfigurationFlag | kCGDisplayMovedFlag | kCGDisplaySetModeFlag |
|
|
|
|
|
kCGDisplayAddFlag | kCGDisplayRemoveFlag | kCGDisplayEnabledFlag |
|
|
|
|
|
kCGDisplayDisabledFlag | kCGDisplayMirrorFlag | kCGDisplayUnMirrorFlag |
|
|
|
|
|
kCGDisplayDesktopShapeChangedFlag;
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
LOG((CLOG_DEBUG1 "event: display was reconfigured: %x %x %x", flags, mask, flags & mask));
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
if (flags & mask) { /* Something actually did change */
|
|
|
|
|
LOG((CLOG_DEBUG1 "event: screen changed shape; refreshing dimensions"));
|
|
|
|
|
if (!screen->updateScreenShape(displayID, flags)) {
|
|
|
|
|
LOG((CLOG_ERR "failed to update screen shape during display reconfiguration"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool OSXScreen::onKey(CGEventRef event)
|
|
|
|
|
{
|
|
|
|
|
CGEventType eventKind = CGEventGetType(event);
|
|
|
|
|
|
|
|
|
|
// get the key and active modifiers
|
2025-01-11 02:13:19 +00:00
|
|
|
uint32_t virtualKey = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);
|
2024-12-18 01:42:03 +00:00
|
|
|
CGEventFlags macMask = CGEventGetFlags(event);
|
|
|
|
|
LOG((CLOG_DEBUG1 "event: Key event kind: %d, keycode=%d", eventKind, virtualKey));
|
|
|
|
|
|
|
|
|
|
// Special handling to track state of modifiers
|
|
|
|
|
if (eventKind == kCGEventFlagsChanged) {
|
|
|
|
|
// get old and new modifier state
|
|
|
|
|
KeyModifierMask oldMask = getActiveModifiers();
|
|
|
|
|
KeyModifierMask newMask = m_keyState->mapModifiersFromOSX(macMask);
|
|
|
|
|
m_keyState->handleModifierKeys(getEventTarget(), oldMask, newMask);
|
|
|
|
|
|
|
|
|
|
// if the current set of modifiers exactly matches a modifiers-only
|
|
|
|
|
// hot key then generate a hot key down event.
|
|
|
|
|
if (m_activeModifierHotKey == 0) {
|
|
|
|
|
if (m_modifierHotKeys.count(newMask) > 0) {
|
|
|
|
|
m_activeModifierHotKey = m_modifierHotKeys[newMask];
|
|
|
|
|
m_activeModifierHotKeyMask = newMask;
|
|
|
|
|
m_events->addEvent(Event(
|
|
|
|
|
m_events->forIPrimaryScreen().hotKeyDown(), getEventTarget(), HotKeyInfo::alloc(m_activeModifierHotKey)
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// if a modifiers-only hot key is active and should no longer be
|
|
|
|
|
// then generate a hot key up event.
|
|
|
|
|
else if (m_activeModifierHotKey != 0) {
|
|
|
|
|
KeyModifierMask mask = (newMask & m_activeModifierHotKeyMask);
|
|
|
|
|
if (mask != m_activeModifierHotKeyMask) {
|
|
|
|
|
m_events->addEvent(
|
|
|
|
|
Event(m_events->forIPrimaryScreen().hotKeyUp(), getEventTarget(), HotKeyInfo::alloc(m_activeModifierHotKey))
|
|
|
|
|
);
|
|
|
|
|
m_activeModifierHotKey = 0;
|
|
|
|
|
m_activeModifierHotKeyMask = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// check for hot key
|
|
|
|
|
HotKeyToIDMap::const_iterator i =
|
|
|
|
|
m_hotKeyToIDMap.find(HotKeyItem(virtualKey, m_keyState->mapModifiersToCarbon(macMask) & 0xff00u));
|
|
|
|
|
if (i != m_hotKeyToIDMap.end()) {
|
2025-01-11 02:13:19 +00:00
|
|
|
uint32_t id = i->second;
|
2024-12-18 01:42:03 +00:00
|
|
|
|
|
|
|
|
// determine event type
|
|
|
|
|
Event::Type type;
|
|
|
|
|
if (eventKind == kCGEventKeyDown) {
|
|
|
|
|
type = m_events->forIPrimaryScreen().hotKeyDown();
|
|
|
|
|
} else if (eventKind == kCGEventKeyUp) {
|
|
|
|
|
type = m_events->forIPrimaryScreen().hotKeyUp();
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
m_events->addEvent(Event(type, getEventTarget(), HotKeyInfo::alloc(id)));
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// decode event type
|
|
|
|
|
bool down = (eventKind == kCGEventKeyDown);
|
|
|
|
|
bool up = (eventKind == kCGEventKeyUp);
|
|
|
|
|
bool isRepeat = (CGEventGetIntegerValueField(event, kCGKeyboardEventAutorepeat) == 1);
|
|
|
|
|
|
|
|
|
|
// map event to keys
|
|
|
|
|
KeyModifierMask mask;
|
|
|
|
|
OSXKeyState::KeyIDs keys;
|
|
|
|
|
KeyButton button = m_keyState->mapKeyFromEvent(keys, &mask, event);
|
|
|
|
|
if (button == 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// check for AltGr in mask. if set we send neither the AltGr nor
|
|
|
|
|
// the super modifiers to clients then remove AltGr before passing
|
|
|
|
|
// the modifiers to onKey.
|
|
|
|
|
KeyModifierMask sendMask = (mask & ~KeyModifierAltGr);
|
|
|
|
|
if ((mask & KeyModifierAltGr) != 0) {
|
|
|
|
|
sendMask &= ~KeyModifierSuper;
|
|
|
|
|
}
|
|
|
|
|
mask &= ~KeyModifierAltGr;
|
|
|
|
|
|
|
|
|
|
// update button state
|
|
|
|
|
if (down) {
|
|
|
|
|
m_keyState->onKey(button, true, mask);
|
|
|
|
|
} else if (up) {
|
|
|
|
|
if (!m_keyState->isKeyDown(button)) {
|
|
|
|
|
// up event for a dead key. throw it away.
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
m_keyState->onKey(button, false, mask);
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// send key events
|
|
|
|
|
for (OSXKeyState::KeyIDs::const_iterator i = keys.begin(); i != keys.end(); ++i) {
|
|
|
|
|
m_keyState->sendKeyEvent(getEventTarget(), down, isRepeat, *i, sendMask, 1, button);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::onMediaKey(CGEventRef event)
|
2016-09-15 15:39:10 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
KeyID keyID;
|
|
|
|
|
bool down;
|
|
|
|
|
bool isRepeat;
|
2016-09-15 15:39:10 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
if (!getMediaKeyEventInfo(event, &keyID, &down, &isRepeat)) {
|
|
|
|
|
LOG((CLOG_ERR "Failed to decode media key event"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
2016-09-15 15:39:10 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
LOG(
|
|
|
|
|
(CLOG_DEBUG2 "Media key event: keyID=0x%02x, %s, repeat=%s", keyID, (down ? "down" : "up"),
|
|
|
|
|
(isRepeat ? "yes" : "no"))
|
|
|
|
|
);
|
2016-09-16 16:11:22 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
KeyButton button = 0;
|
|
|
|
|
KeyModifierMask mask = m_keyState->getActiveModifiers();
|
|
|
|
|
m_keyState->sendKeyEvent(getEventTarget(), down, isRepeat, keyID, mask, 1, button);
|
2016-09-15 15:39:10 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
bool OSXScreen::onHotKey(EventRef event) const
|
|
|
|
|
{
|
|
|
|
|
// get the hotkey id
|
|
|
|
|
EventHotKeyID hkid;
|
|
|
|
|
GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(EventHotKeyID), NULL, &hkid);
|
2025-01-11 02:13:19 +00:00
|
|
|
uint32_t id = hkid.id;
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// determine event type
|
|
|
|
|
Event::Type type;
|
2025-01-11 02:13:19 +00:00
|
|
|
uint32_t eventKind = GetEventKind(event);
|
2024-12-18 01:42:03 +00:00
|
|
|
if (eventKind == kEventHotKeyPressed) {
|
|
|
|
|
type = m_events->forIPrimaryScreen().hotKeyDown();
|
|
|
|
|
} else if (eventKind == kEventHotKeyReleased) {
|
|
|
|
|
type = m_events->forIPrimaryScreen().hotKeyUp();
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
m_events->addEvent(Event(type, getEventTarget(), HotKeyInfo::alloc(id)));
|
|
|
|
|
|
|
|
|
|
return true;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 00:58:26 +00:00
|
|
|
ButtonID OSXScreen::mapDeskflowButtonToMac(uint16_t button) const
|
2015-10-29 02:48:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
switch (button) {
|
|
|
|
|
case 1:
|
|
|
|
|
return kButtonLeft;
|
|
|
|
|
case 2:
|
|
|
|
|
return kMacButtonMiddle;
|
|
|
|
|
case 3:
|
|
|
|
|
return kMacButtonRight;
|
|
|
|
|
}
|
2015-10-29 02:48:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
return static_cast<ButtonID>(button);
|
2015-10-29 02:48:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 00:58:26 +00:00
|
|
|
ButtonID OSXScreen::mapMacButtonToDeskflow(uint16_t macButton) const
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
switch (macButton) {
|
|
|
|
|
case 1:
|
|
|
|
|
return kButtonLeft;
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
case 2:
|
|
|
|
|
return kButtonRight;
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
case 3:
|
|
|
|
|
return kButtonMiddle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return static_cast<ButtonID>(macButton);
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 01:11:56 +00:00
|
|
|
int32_t OSXScreen::mapScrollWheelToDeskflow(int32_t x) const
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
// return accelerated scrolling
|
|
|
|
|
double d = (1.0 + getScrollSpeed()) * x;
|
2025-01-11 01:11:56 +00:00
|
|
|
return static_cast<int32_t>(120.0 * d);
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 01:11:56 +00:00
|
|
|
int32_t OSXScreen::mapScrollWheelFromDeskflow(int32_t x) const
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
// use server's acceleration with a little boost since other platforms
|
|
|
|
|
// take one wheel step as a larger step than the mac does.
|
2025-01-11 01:11:56 +00:00
|
|
|
auto result = static_cast<int32_t>(3.0 * x / 120.0);
|
2024-12-18 01:42:03 +00:00
|
|
|
return mapClientScrollDirection(result);
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
double OSXScreen::getScrollSpeed() const
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
double scaling = 0.0;
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
CFPropertyListRef pref = ::CFPreferencesCopyValue(
|
|
|
|
|
CFSTR("com.apple.scrollwheel.scaling"), kCFPreferencesAnyApplication, kCFPreferencesCurrentUser,
|
|
|
|
|
kCFPreferencesAnyHost
|
|
|
|
|
);
|
|
|
|
|
if (pref != NULL) {
|
|
|
|
|
CFTypeID id = CFGetTypeID(pref);
|
|
|
|
|
if (id == CFNumberGetTypeID()) {
|
|
|
|
|
CFNumberRef value = static_cast<CFNumberRef>(pref);
|
|
|
|
|
if (CFNumberGetValue(value, kCFNumberDoubleType, &scaling)) {
|
|
|
|
|
if (scaling < 0.0) {
|
|
|
|
|
scaling = 0.0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
CFRelease(pref);
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
return scaling;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::enableDragTimer(bool enable)
|
|
|
|
|
{
|
|
|
|
|
if (enable && m_dragTimer == NULL) {
|
|
|
|
|
m_dragTimer = m_events->newTimer(0.01, NULL);
|
|
|
|
|
m_events->adoptHandler(Event::kTimer, m_dragTimer, new TMethodEventJob<OSXScreen>(this, &OSXScreen::handleDrag));
|
|
|
|
|
CGEventRef event = CGEventCreate(NULL);
|
|
|
|
|
CGPoint mouse = CGEventGetLocation(event);
|
|
|
|
|
m_dragLastPoint.h = (short)mouse.x;
|
|
|
|
|
m_dragLastPoint.v = (short)mouse.y;
|
|
|
|
|
CFRelease(event);
|
|
|
|
|
} else if (!enable && m_dragTimer != NULL) {
|
|
|
|
|
m_events->removeHandler(Event::kTimer, m_dragTimer);
|
|
|
|
|
m_events->deleteTimer(m_dragTimer);
|
|
|
|
|
m_dragTimer = NULL;
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::handleDrag(const Event &, void *)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
CGEventRef event = CGEventCreate(NULL);
|
|
|
|
|
CGPoint p = CGEventGetLocation(event);
|
|
|
|
|
CFRelease(event);
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
if ((short)p.x != m_dragLastPoint.h || (short)p.y != m_dragLastPoint.v) {
|
|
|
|
|
m_dragLastPoint.h = (short)p.x;
|
|
|
|
|
m_dragLastPoint.v = (short)p.y;
|
2025-01-11 01:11:56 +00:00
|
|
|
onMouseMove((int32_t)p.x, (int32_t)p.y);
|
2024-12-18 01:42:03 +00:00
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::updateButtons()
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2025-01-11 02:13:19 +00:00
|
|
|
uint32_t buttons = GetCurrentButtonState();
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
m_buttonState.overwrite(buttons);
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
IKeyState *OSXScreen::getKeyState() const
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
return m_keyState;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2018-03-14 13:35:42 +00:00
|
|
|
bool OSXScreen::updateScreenShape(const CGDirectDisplayID, const CGDisplayChangeSummaryFlags flags)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
return updateScreenShape();
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
bool OSXScreen::updateScreenShape()
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
// get info for each display
|
|
|
|
|
CGDisplayCount displayCount = 0;
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
if (CGGetActiveDisplayList(0, NULL, &displayCount) != CGDisplayNoErr) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
if (displayCount == 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
CGDirectDisplayID *displays = new CGDirectDisplayID[displayCount];
|
|
|
|
|
if (displays == NULL) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
if (CGGetActiveDisplayList(displayCount, displays, &displayCount) != CGDisplayNoErr) {
|
|
|
|
|
delete[] displays;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// get smallest rect enclosing all display rects
|
|
|
|
|
CGRect totalBounds = CGRectZero;
|
|
|
|
|
for (CGDisplayCount i = 0; i < displayCount; ++i) {
|
|
|
|
|
CGRect bounds = CGDisplayBounds(displays[i]);
|
|
|
|
|
totalBounds = CGRectUnion(totalBounds, bounds);
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// get shape of default screen
|
2025-01-11 01:11:56 +00:00
|
|
|
m_x = (int32_t)totalBounds.origin.x;
|
|
|
|
|
m_y = (int32_t)totalBounds.origin.y;
|
|
|
|
|
m_w = (int32_t)totalBounds.size.width;
|
|
|
|
|
m_h = (int32_t)totalBounds.size.height;
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// get center of default screen
|
2012-06-10 16:50:54 +00:00
|
|
|
CGDirectDisplayID main = CGMainDisplayID();
|
|
|
|
|
const CGRect rect = CGDisplayBounds(main);
|
|
|
|
|
m_xCenter = (rect.origin.x + rect.size.width) / 2;
|
|
|
|
|
m_yCenter = (rect.origin.y + rect.size.height) / 2;
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
delete[] displays;
|
|
|
|
|
// We want to notify the peer screen whether we are primary screen or not
|
|
|
|
|
sendEvent(m_events->forIScreen().shapeChanged());
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
LOG(
|
|
|
|
|
(CLOG_DEBUG "screen shape: center=%d,%d size=%dx%d on %u %s", m_x, m_y, m_w, m_h, displayCount,
|
|
|
|
|
(displayCount == 1) ? "display" : "displays")
|
|
|
|
|
);
|
2018-03-14 13:35:42 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
return true;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
#pragma mark -
|
2012-06-10 16:50:54 +00:00
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// FAST USER SWITCH NOTIFICATION SUPPORT
|
|
|
|
|
//
|
2014-11-11 13:51:47 +00:00
|
|
|
// OSXScreen::userSwitchCallback(void*)
|
2024-12-18 01:42:03 +00:00
|
|
|
//
|
2012-06-10 16:50:54 +00:00
|
|
|
// gets called if a fast user switch occurs
|
|
|
|
|
//
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
pascal OSStatus OSXScreen::userSwitchCallback(EventHandlerCallRef nextHandler, EventRef theEvent, void *inUserData)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
OSXScreen *screen = (OSXScreen *)inUserData;
|
2025-01-11 02:13:19 +00:00
|
|
|
uint32_t kind = GetEventKind(theEvent);
|
2024-12-18 01:42:03 +00:00
|
|
|
IEventQueue *events = screen->getEvents();
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
if (kind == kEventSystemUserSessionDeactivated) {
|
|
|
|
|
LOG((CLOG_DEBUG "user session deactivated"));
|
|
|
|
|
events->addEvent(Event(events->forIScreen().suspend(), screen->getEventTarget()));
|
|
|
|
|
} else if (kind == kEventSystemUserSessionActivated) {
|
|
|
|
|
LOG((CLOG_DEBUG "user session activated"));
|
|
|
|
|
events->addEvent(Event(events->forIScreen().resume(), screen->getEventTarget()));
|
|
|
|
|
}
|
|
|
|
|
return (CallNextEventHandler(nextHandler, theEvent));
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
#pragma mark -
|
2012-06-10 16:50:54 +00:00
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// SLEEP/WAKEUP NOTIFICATION SUPPORT
|
|
|
|
|
//
|
2014-11-11 13:51:47 +00:00
|
|
|
// OSXScreen::watchSystemPowerThread(void*)
|
2024-12-18 01:42:03 +00:00
|
|
|
//
|
2012-06-10 16:50:54 +00:00
|
|
|
// main of thread monitoring system power (sleep/wakup) using a CFRunLoop
|
|
|
|
|
//
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::watchSystemPowerThread(void *)
|
|
|
|
|
{
|
|
|
|
|
io_object_t notifier;
|
|
|
|
|
IONotificationPortRef notificationPortRef;
|
|
|
|
|
CFRunLoopSourceRef runloopSourceRef = 0;
|
|
|
|
|
|
|
|
|
|
m_pmRunloop = CFRunLoopGetCurrent();
|
|
|
|
|
// install system power change callback
|
|
|
|
|
m_pmRootPort = IORegisterForSystemPower(this, ¬ificationPortRef, powerChangeCallback, ¬ifier);
|
|
|
|
|
if (m_pmRootPort == 0) {
|
|
|
|
|
LOG((CLOG_WARN "IORegisterForSystemPower failed"));
|
|
|
|
|
} else {
|
|
|
|
|
runloopSourceRef = IONotificationPortGetRunLoopSource(notificationPortRef);
|
|
|
|
|
CFRunLoopAddSource(m_pmRunloop, runloopSourceRef, kCFRunLoopCommonModes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// thread is ready
|
|
|
|
|
{
|
|
|
|
|
Lock lock(m_pmMutex);
|
|
|
|
|
*m_pmThreadReady = true;
|
|
|
|
|
m_pmThreadReady->signal();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if we were unable to initialize then exit. we must do this after
|
|
|
|
|
// setting m_pmThreadReady to true otherwise the parent thread will
|
|
|
|
|
// block waiting for it.
|
|
|
|
|
if (m_pmRootPort == 0) {
|
|
|
|
|
LOG((CLOG_WARN "failed to init watchSystemPowerThread"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG((CLOG_DEBUG "started watchSystemPowerThread"));
|
|
|
|
|
|
|
|
|
|
LOG((CLOG_DEBUG "waiting for event loop"));
|
|
|
|
|
m_events->waitForReady();
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
{
|
|
|
|
|
Lock lockCarbon(m_carbonLoopMutex);
|
|
|
|
|
if (*m_carbonLoopReady == false) {
|
|
|
|
|
|
|
|
|
|
// we signalling carbon loop ready before starting
|
|
|
|
|
// unless we know how to do it within the loop
|
|
|
|
|
LOG((CLOG_DEBUG "signalling carbon loop ready"));
|
|
|
|
|
|
|
|
|
|
*m_carbonLoopReady = true;
|
|
|
|
|
m_carbonLoopReady->signal();
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// start the run loop
|
|
|
|
|
LOG((CLOG_DEBUG "starting carbon loop"));
|
|
|
|
|
CFRunLoopRun();
|
|
|
|
|
LOG((CLOG_DEBUG "carbon loop has stopped"));
|
|
|
|
|
|
|
|
|
|
// cleanup
|
|
|
|
|
if (notificationPortRef) {
|
|
|
|
|
CFRunLoopRemoveSource(m_pmRunloop, runloopSourceRef, kCFRunLoopDefaultMode);
|
|
|
|
|
CFRunLoopSourceInvalidate(runloopSourceRef);
|
|
|
|
|
CFRelease(runloopSourceRef);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Lock lock(m_pmMutex);
|
|
|
|
|
IODeregisterForSystemPower(¬ifier);
|
|
|
|
|
m_pmRootPort = 0;
|
|
|
|
|
LOG((CLOG_DEBUG "stopped watchSystemPowerThread"));
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::powerChangeCallback(void *refcon, io_service_t service, natural_t messageType, void *messageArg)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
((OSXScreen *)refcon)->handlePowerChangeRequest(messageType, messageArg);
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::handlePowerChangeRequest(natural_t messageType, void *messageArg)
|
|
|
|
|
{
|
|
|
|
|
// we've received a power change notification
|
|
|
|
|
switch (messageType) {
|
|
|
|
|
case kIOMessageSystemWillSleep:
|
|
|
|
|
// OSXScreen has to handle this in the main thread so we have to
|
|
|
|
|
// queue a confirm sleep event here. we actually don't allow the
|
|
|
|
|
// system to sleep until the event is handled.
|
|
|
|
|
m_events->addEvent(
|
|
|
|
|
Event(m_events->forOSXScreen().confirmSleep(), getEventTarget(), messageArg, Event::kDontFreeData)
|
|
|
|
|
);
|
|
|
|
|
return;
|
2012-06-10 16:50:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
case kIOMessageSystemHasPoweredOn:
|
|
|
|
|
LOG((CLOG_DEBUG "system wakeup"));
|
|
|
|
|
m_events->addEvent(Event(m_events->forIScreen().resume(), getEventTarget()));
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Lock lock(m_pmMutex);
|
|
|
|
|
if (m_pmRootPort != 0) {
|
|
|
|
|
IOAllowPowerChange(m_pmRootPort, (long)messageArg);
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::handleConfirmSleep(const Event &event, void *)
|
|
|
|
|
{
|
|
|
|
|
long messageArg = (long)event.getData();
|
|
|
|
|
if (messageArg != 0) {
|
|
|
|
|
Lock lock(m_pmMutex);
|
|
|
|
|
if (m_pmRootPort != 0) {
|
|
|
|
|
// deliver suspend event immediately.
|
|
|
|
|
m_events->addEvent(Event(m_events->forIScreen().suspend(), getEventTarget(), NULL, Event::kDeliverImmediately));
|
|
|
|
|
|
|
|
|
|
LOG((CLOG_DEBUG "system will sleep"));
|
|
|
|
|
IOAllowPowerChange(m_pmRootPort, messageArg);
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
#pragma mark -
|
2012-06-10 16:50:54 +00:00
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// GLOBAL HOTKEY OPERATING MODE SUPPORT (10.3)
|
|
|
|
|
//
|
|
|
|
|
// CoreGraphics private API (OSX 10.3)
|
|
|
|
|
// Source: http://ichiro.nnip.org/osx/Cocoa/GlobalHotkey.html
|
|
|
|
|
//
|
|
|
|
|
// We load the functions dynamically because they're not available in
|
|
|
|
|
// older SDKs. We don't use weak linking because we want users of
|
|
|
|
|
// older SDKs to build an app that works on newer systems and older
|
|
|
|
|
// SDKs will not provide the symbols.
|
|
|
|
|
//
|
|
|
|
|
// FIXME: This is hosed as of OS 10.5; patches to repair this are
|
|
|
|
|
// a good thing.
|
|
|
|
|
//
|
|
|
|
|
#if 0
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
#ifdef __cplusplus
|
2012-06-10 16:50:54 +00:00
|
|
|
extern "C" {
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
typedef int CGSConnection;
|
|
|
|
|
typedef enum {
|
|
|
|
|
CGSGlobalHotKeyEnable = 0,
|
|
|
|
|
CGSGlobalHotKeyDisable = 1,
|
|
|
|
|
} CGSGlobalHotKeyOperatingMode;
|
|
|
|
|
|
|
|
|
|
extern CGSConnection _CGSDefaultConnection(void) WEAK_IMPORT_ATTRIBUTE;
|
|
|
|
|
extern CGError CGSGetGlobalHotKeyOperatingMode(CGSConnection connection, CGSGlobalHotKeyOperatingMode *mode) WEAK_IMPORT_ATTRIBUTE;
|
|
|
|
|
extern CGError CGSSetGlobalHotKeyOperatingMode(CGSConnection connection, CGSGlobalHotKeyOperatingMode mode) WEAK_IMPORT_ATTRIBUTE;
|
|
|
|
|
|
|
|
|
|
typedef CGSConnection (*_CGSDefaultConnection_t)(void);
|
|
|
|
|
typedef CGError (*CGSGetGlobalHotKeyOperatingMode_t)(CGSConnection connection, CGSGlobalHotKeyOperatingMode *mode);
|
|
|
|
|
typedef CGError (*CGSSetGlobalHotKeyOperatingMode_t)(CGSConnection connection, CGSGlobalHotKeyOperatingMode mode);
|
|
|
|
|
|
|
|
|
|
static _CGSDefaultConnection_t s__CGSDefaultConnection;
|
|
|
|
|
static CGSGetGlobalHotKeyOperatingMode_t s_CGSGetGlobalHotKeyOperatingMode;
|
|
|
|
|
static CGSSetGlobalHotKeyOperatingMode_t s_CGSSetGlobalHotKeyOperatingMode;
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
#ifdef __cplusplus
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
#define LOOKUP(name_) \
|
|
|
|
|
s_##name_ = NULL; \
|
|
|
|
|
if (NSIsSymbolNameDefinedWithHint("_" #name_, "CoreGraphics")) { \
|
|
|
|
|
s_##name_ = (name_##_t)NSAddressOfSymbol(NSLookupAndBindSymbolWithHint("_" #name_, "CoreGraphics")); \
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
|
|
|
|
|
bool
|
2014-11-11 13:51:47 +00:00
|
|
|
OSXScreen::isGlobalHotKeyOperatingModeAvailable()
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
|
|
|
|
if (!s_testedForGHOM) {
|
|
|
|
|
s_testedForGHOM = true;
|
|
|
|
|
LOOKUP(_CGSDefaultConnection);
|
|
|
|
|
LOOKUP(CGSGetGlobalHotKeyOperatingMode);
|
|
|
|
|
LOOKUP(CGSSetGlobalHotKeyOperatingMode);
|
|
|
|
|
s_hasGHOM = (s__CGSDefaultConnection != NULL &&
|
|
|
|
|
s_CGSGetGlobalHotKeyOperatingMode != NULL &&
|
|
|
|
|
s_CGSSetGlobalHotKeyOperatingMode != NULL);
|
|
|
|
|
}
|
|
|
|
|
return s_hasGHOM;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2014-11-11 13:51:47 +00:00
|
|
|
OSXScreen::setGlobalHotKeysEnabled(bool enabled)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
|
|
|
|
if (isGlobalHotKeyOperatingModeAvailable()) {
|
|
|
|
|
CGSConnection conn = s__CGSDefaultConnection();
|
|
|
|
|
|
|
|
|
|
CGSGlobalHotKeyOperatingMode mode;
|
|
|
|
|
s_CGSGetGlobalHotKeyOperatingMode(conn, &mode);
|
|
|
|
|
|
|
|
|
|
if (enabled && mode == CGSGlobalHotKeyDisable) {
|
|
|
|
|
s_CGSSetGlobalHotKeyOperatingMode(conn, CGSGlobalHotKeyEnable);
|
|
|
|
|
}
|
|
|
|
|
else if (!enabled && mode == CGSGlobalHotKeyEnable) {
|
|
|
|
|
s_CGSSetGlobalHotKeyOperatingMode(conn, CGSGlobalHotKeyDisable);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2014-11-11 13:51:47 +00:00
|
|
|
OSXScreen::getGlobalHotKeysEnabled()
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
|
|
|
|
CGSGlobalHotKeyOperatingMode mode;
|
|
|
|
|
if (isGlobalHotKeyOperatingModeAvailable()) {
|
|
|
|
|
CGSConnection conn = s__CGSDefaultConnection();
|
|
|
|
|
s_CGSGetGlobalHotKeyOperatingMode(conn, &mode);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
mode = CGSGlobalHotKeyEnable;
|
|
|
|
|
}
|
|
|
|
|
return (mode == CGSGlobalHotKeyEnable);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
//
|
2014-11-11 13:51:47 +00:00
|
|
|
// OSXScreen::HotKeyItem
|
2012-06-10 16:50:54 +00:00
|
|
|
//
|
|
|
|
|
|
2025-01-11 02:13:19 +00:00
|
|
|
OSXScreen::HotKeyItem::HotKeyItem(uint32_t keycode, uint32_t mask) : m_ref(NULL), m_keycode(keycode), m_mask(mask)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
// do nothing
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 02:13:19 +00:00
|
|
|
OSXScreen::HotKeyItem::HotKeyItem(EventHotKeyRef ref, uint32_t keycode, uint32_t mask)
|
2024-12-18 01:42:03 +00:00
|
|
|
: m_ref(ref),
|
|
|
|
|
m_keycode(keycode),
|
|
|
|
|
m_mask(mask)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
// do nothing
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
EventHotKeyRef OSXScreen::HotKeyItem::getRef() const
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
return m_ref;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
bool OSXScreen::HotKeyItem::operator<(const HotKeyItem &x) const
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
return (m_keycode < x.m_keycode || (m_keycode == x.m_keycode && m_mask < x.m_mask));
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2012-10-26 00:21:22 +00:00
|
|
|
// Quartz event tap support for the secondary display. This makes sure that we
|
2024-09-17 19:00:25 +00:00
|
|
|
// will show the cursor if a local event comes in while deskflow has the cursor
|
2012-10-26 00:21:22 +00:00
|
|
|
// off the screen.
|
2012-06-10 16:50:54 +00:00
|
|
|
CGEventRef
|
2024-12-18 01:42:03 +00:00
|
|
|
OSXScreen::handleCGInputEventSecondary(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
// this fix is really screwing with the correct show/hide behavior. it
|
|
|
|
|
// should be tested better before reintroducing.
|
|
|
|
|
return event;
|
2012-10-30 18:42:58 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
OSXScreen *screen = (OSXScreen *)refcon;
|
|
|
|
|
if (screen->m_cursorHidden && type == kCGEventMouseMoved) {
|
2012-10-26 00:21:22 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
CGPoint pos = CGEventGetLocation(event);
|
|
|
|
|
if (pos.x != screen->m_xCenter || pos.y != screen->m_yCenter) {
|
2012-10-26 00:21:22 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
LOG((CLOG_DEBUG "show cursor on secondary, type=%d pos=%d,%d", type, pos.x, pos.y));
|
|
|
|
|
screen->showCursor();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return event;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Quartz event tap support
|
2024-12-18 01:42:03 +00:00
|
|
|
CGEventRef OSXScreen::handleCGInputEvent(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
|
|
|
|
|
{
|
|
|
|
|
OSXScreen *screen = (OSXScreen *)refcon;
|
|
|
|
|
CGPoint pos;
|
|
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
case kCGEventLeftMouseDown:
|
|
|
|
|
case kCGEventRightMouseDown:
|
|
|
|
|
case kCGEventOtherMouseDown:
|
|
|
|
|
screen->onMouseButton(true, CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber) + 1);
|
|
|
|
|
break;
|
|
|
|
|
case kCGEventLeftMouseUp:
|
|
|
|
|
case kCGEventRightMouseUp:
|
|
|
|
|
case kCGEventOtherMouseUp:
|
|
|
|
|
screen->onMouseButton(false, CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber) + 1);
|
|
|
|
|
break;
|
|
|
|
|
case kCGEventLeftMouseDragged:
|
|
|
|
|
case kCGEventRightMouseDragged:
|
|
|
|
|
case kCGEventOtherMouseDragged:
|
|
|
|
|
case kCGEventMouseMoved:
|
|
|
|
|
pos = CGEventGetLocation(event);
|
|
|
|
|
screen->onMouseMove(pos.x, pos.y);
|
|
|
|
|
|
|
|
|
|
// The system ignores our cursor-centering calls if
|
|
|
|
|
// we don't return the event. This should be harmless,
|
|
|
|
|
// but might register as slight movement to other apps
|
|
|
|
|
// on the system. It hasn't been a problem before, though.
|
|
|
|
|
return event;
|
|
|
|
|
break;
|
|
|
|
|
case kCGEventScrollWheel:
|
|
|
|
|
screen->onMouseWheel(
|
|
|
|
|
screen->mapScrollWheelToDeskflow(CGEventGetIntegerValueField(event, kCGScrollWheelEventDeltaAxis2)),
|
|
|
|
|
screen->mapScrollWheelToDeskflow(CGEventGetIntegerValueField(event, kCGScrollWheelEventDeltaAxis1))
|
|
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
case kCGEventKeyDown:
|
|
|
|
|
case kCGEventKeyUp:
|
|
|
|
|
case kCGEventFlagsChanged:
|
|
|
|
|
screen->onKey(event);
|
|
|
|
|
break;
|
|
|
|
|
case kCGEventTapDisabledByTimeout:
|
|
|
|
|
// Re-enable our event-tap
|
|
|
|
|
CGEventTapEnable(screen->m_eventTapPort, true);
|
|
|
|
|
LOG((CLOG_INFO "quartz event tap was disabled by timeout, re-enabling"));
|
|
|
|
|
break;
|
|
|
|
|
case kCGEventTapDisabledByUserInput:
|
|
|
|
|
LOG((CLOG_ERR "quartz event tap was disabled by user input"));
|
|
|
|
|
break;
|
|
|
|
|
case NX_NULLEVENT:
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
if (type == NX_SYSDEFINED) {
|
|
|
|
|
if (isMediaKeyEvent(event)) {
|
|
|
|
|
LOG((CLOG_DEBUG2 "detected media key event"));
|
|
|
|
|
screen->onMediaKey(event);
|
|
|
|
|
} else {
|
|
|
|
|
LOG((CLOG_DEBUG2 "ignoring unknown system defined event"));
|
|
|
|
|
return event;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG((CLOG_DEBUG3 "unknown quartz event type: 0x%02x", type));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (screen->m_isOnScreen) {
|
|
|
|
|
return event;
|
|
|
|
|
} else {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 02:13:19 +00:00
|
|
|
void OSXScreen::MouseButtonState::set(uint32_t button, EMouseButtonState state)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
bool newState = (state == kMouseButtonDown);
|
|
|
|
|
m_buttons.set(button, newState);
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
bool OSXScreen::MouseButtonState::any()
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
return m_buttons.any();
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::MouseButtonState::reset()
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
m_buttons.reset();
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 02:13:19 +00:00
|
|
|
void OSXScreen::MouseButtonState::overwrite(uint32_t buttons)
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
m_buttons = std::bitset<NumButtonIDs>(buttons);
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-11 02:13:19 +00:00
|
|
|
bool OSXScreen::MouseButtonState::test(uint32_t button) const
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
return m_buttons.test(button);
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-10 23:42:34 +00:00
|
|
|
int8_t OSXScreen::MouseButtonState::getFirstButtonDown() const
|
2012-06-10 16:50:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
if (m_buttons.any()) {
|
|
|
|
|
for (unsigned short button = 0; button < m_buttons.size(); button++) {
|
|
|
|
|
if (m_buttons.test(button)) {
|
|
|
|
|
return button;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
2012-06-10 16:50:54 +00:00
|
|
|
}
|
2013-08-30 14:38:43 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
char *OSXScreen::CFStringRefToUTF8String(CFStringRef aString)
|
2013-08-30 14:38:43 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
if (aString == NULL) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CFIndex length = CFStringGetLength(aString);
|
|
|
|
|
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8);
|
|
|
|
|
char *buffer = (char *)malloc(maxSize);
|
|
|
|
|
|
|
|
|
|
if (!CFStringGetCString(aString, buffer, maxSize, kCFStringEncodingUTF8)) {
|
|
|
|
|
free(buffer);
|
|
|
|
|
buffer = NULL;
|
|
|
|
|
}
|
2022-05-19 15:29:11 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
return buffer;
|
2013-08-30 14:38:43 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::fakeDraggingFiles(DragFileList fileList)
|
2013-08-30 14:38:43 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
m_fakeDraggingStarted = true;
|
2025-01-06 00:55:07 +00:00
|
|
|
std::string fileExt;
|
2024-12-18 01:42:03 +00:00
|
|
|
if (fileList.size() == 1) {
|
|
|
|
|
fileExt = DragInformation::getDragFileExtension(fileList.at(0).getFilename());
|
|
|
|
|
}
|
2014-04-30 18:54:32 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
fakeDragging(fileExt.c_str(), m_xCursor, m_yCursor);
|
2013-08-30 14:38:43 +00:00
|
|
|
}
|
2013-09-18 14:12:19 +00:00
|
|
|
|
2025-01-06 00:55:07 +00:00
|
|
|
std::string &OSXScreen::getDraggingFilename()
|
2013-09-18 14:12:19 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
if (m_draggingStarted) {
|
|
|
|
|
m_draggingFilename.clear();
|
2022-05-19 15:29:11 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
CFStringRef dragInfo = getDraggedFileURL();
|
|
|
|
|
char *info = CFStringRefToUTF8String(dragInfo);
|
|
|
|
|
CFRelease(dragInfo);
|
2022-05-19 15:29:11 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
if (info != NULL) {
|
|
|
|
|
LOG((CLOG_DEBUG "drag info: %s", info));
|
|
|
|
|
m_draggingFilename = info;
|
|
|
|
|
free(info);
|
|
|
|
|
}
|
2014-04-22 10:33:16 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// fake a escape key down and up then left mouse button up
|
|
|
|
|
fakeKeyDown(kKeyEscape, 8192, 1, AppUtil::instance().getCurrentLanguageCode());
|
|
|
|
|
fakeKeyUp(1);
|
|
|
|
|
fakeMouseButton(kButtonLeft, false);
|
|
|
|
|
}
|
|
|
|
|
return m_draggingFilename;
|
2013-09-18 14:12:19 +00:00
|
|
|
}
|
2014-02-25 19:10:54 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void OSXScreen::waitForCarbonLoop() const
|
2014-04-17 10:56:25 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
if (*m_carbonLoopReady) {
|
|
|
|
|
LOG((CLOG_DEBUG "carbon loop already ready"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
2014-10-21 14:48:04 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
Lock lock(m_carbonLoopMutex);
|
2014-10-21 14:48:04 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
LOG((CLOG_DEBUG "waiting for carbon loop"));
|
2014-10-21 14:48:04 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
double timeout = ARCH->time() + kCarbonLoopWaitTimeout;
|
|
|
|
|
while (!m_carbonLoopReady->wait()) {
|
|
|
|
|
if (ARCH->time() > timeout) {
|
|
|
|
|
LOG((CLOG_DEBUG "carbon loop not ready, waiting again"));
|
|
|
|
|
timeout = ARCH->time() + kCarbonLoopWaitTimeout;
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-10-21 14:48:04 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
LOG((CLOG_DEBUG "carbon loop ready"));
|
2014-04-17 10:56:25 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-06 00:55:07 +00:00
|
|
|
std::string OSXScreen::getSecureInputApp() const
|
2021-06-29 10:20:56 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
if (IsSecureEventInputEnabled()) {
|
|
|
|
|
int secureInputProcessPID = getSecureInputEventPID();
|
|
|
|
|
if (secureInputProcessPID == 0)
|
|
|
|
|
return "unknown";
|
|
|
|
|
return getProcessName(secureInputProcessPID);
|
|
|
|
|
}
|
|
|
|
|
return "";
|
2021-06-29 10:20:56 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
int getSecureInputEventPID()
|
2021-06-29 10:20:56 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
io_service_t service = MACH_PORT_NULL, service_root = MACH_PORT_NULL;
|
|
|
|
|
mach_port_t masterPort;
|
2021-06-29 10:20:56 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
kern_return_t kr = IOMasterPort(MACH_PORT_NULL, &masterPort);
|
|
|
|
|
if (kr != KERN_SUCCESS)
|
|
|
|
|
return 0;
|
2021-06-29 10:20:56 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
// IO registry refuses to tap into the root level directly
|
|
|
|
|
// as a workaround access the parent of the top user level
|
|
|
|
|
service = IORegistryEntryFromPath(masterPort, kIOServicePlane ":/");
|
|
|
|
|
IORegistryEntryGetParentEntry(service, kIOServicePlane, &service_root);
|
2021-06-29 10:20:56 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
std::unique_ptr<std::remove_pointer<CFTypeRef>::type, decltype(&CFRelease)> consoleUsers(
|
|
|
|
|
IORegistryEntrySearchCFProperty(
|
|
|
|
|
service_root, kIOServicePlane, CFSTR("IOConsoleUsers"), NULL,
|
|
|
|
|
kIORegistryIterateParents | kIORegistryIterateRecursively
|
|
|
|
|
),
|
|
|
|
|
CFRelease
|
|
|
|
|
);
|
|
|
|
|
if (!consoleUsers)
|
|
|
|
|
return 0;
|
2021-06-29 10:20:56 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
CFTypeID type = CFGetTypeID(consoleUsers.get());
|
|
|
|
|
if (type != CFArrayGetTypeID())
|
|
|
|
|
return 0;
|
2021-06-29 10:20:56 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
CFTypeRef dict = CFArrayGetValueAtIndex((CFArrayRef)consoleUsers.get(), 0);
|
|
|
|
|
if (!dict)
|
|
|
|
|
return 0;
|
2021-06-29 10:20:56 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
type = CFGetTypeID(dict);
|
|
|
|
|
if (type != CFDictionaryGetTypeID())
|
|
|
|
|
return 0;
|
2021-06-29 10:20:56 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
CFTypeRef secureInputPID = nullptr;
|
|
|
|
|
CFDictionaryGetValueIfPresent((CFDictionaryRef)dict, CFSTR("kCGSSessionSecureInputPID"), &secureInputPID);
|
2021-06-29 10:20:56 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
if (secureInputPID == nullptr)
|
|
|
|
|
return 0;
|
2021-06-29 10:20:56 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
type = CFGetTypeID(secureInputPID);
|
|
|
|
|
if (type != CFNumberGetTypeID())
|
|
|
|
|
return 0;
|
2021-06-29 10:20:56 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
auto pidRef = (CFNumberRef)secureInputPID;
|
|
|
|
|
CFNumberType numberType = CFNumberGetType(pidRef);
|
|
|
|
|
if (numberType != kCFNumberSInt32Type)
|
|
|
|
|
return 0;
|
2021-06-29 10:20:56 +00:00
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
int pid;
|
|
|
|
|
CFNumberGetValue(pidRef, kCFNumberSInt32Type, &pid);
|
|
|
|
|
return pid;
|
2021-06-29 10:20:56 +00:00
|
|
|
}
|
|
|
|
|
|
2025-01-06 00:55:07 +00:00
|
|
|
std::string getProcessName(int pid)
|
2021-06-29 10:20:56 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
if (!pid)
|
|
|
|
|
return "";
|
|
|
|
|
char buf[128];
|
|
|
|
|
proc_name(pid, buf, sizeof(buf));
|
|
|
|
|
return buf;
|
2021-06-29 10:20:56 +00:00
|
|
|
}
|
|
|
|
|
|
2014-02-25 19:10:54 +00:00
|
|
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void setZeroSuppressionInterval()
|
2014-02-25 19:10:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
CGSetLocalEventsSuppressionInterval(0.0);
|
2014-02-25 19:10:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void avoidSupression()
|
2014-02-25 19:10:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
// avoid suppression of local hardware events
|
|
|
|
|
// stkamp@users.sourceforge.net
|
|
|
|
|
CGSetLocalEventsFilterDuringSupressionState(
|
|
|
|
|
kCGEventFilterMaskPermitAllEvents, kCGEventSupressionStateSupressionInterval
|
|
|
|
|
);
|
|
|
|
|
CGSetLocalEventsFilterDuringSupressionState(
|
|
|
|
|
(kCGEventFilterMaskPermitLocalKeyboardEvents | kCGEventFilterMaskPermitSystemDefinedEvents),
|
|
|
|
|
kCGEventSupressionStateRemoteMouseDrag
|
|
|
|
|
);
|
2014-02-25 19:10:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void logCursorVisibility()
|
2014-02-25 19:10:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
// CGCursorIsVisible is probably deprecated because its unreliable.
|
|
|
|
|
if (!CGCursorIsVisible()) {
|
|
|
|
|
LOG((CLOG_WARN "cursor may not be visible"));
|
|
|
|
|
}
|
2014-02-25 19:10:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-18 01:42:03 +00:00
|
|
|
void avoidHesitatingCursor()
|
2014-02-25 19:10:54 +00:00
|
|
|
{
|
2024-12-18 01:42:03 +00:00
|
|
|
// This used to be necessary to get smooth mouse motion on other screens,
|
|
|
|
|
// but now is just to avoid a hesitating cursor when transitioning to
|
|
|
|
|
// the primary (this) screen.
|
|
|
|
|
CGSetLocalEventsSuppressionInterval(0.0001);
|
2014-02-25 19:10:54 +00:00
|
|
|
}
|
|
|
|
|
|
2014-02-26 15:53:28 +00:00
|
|
|
#pragma GCC diagnostic error "-Wdeprecated-declarations"
|