aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiguel Borges de Freitas <92enen@gmail.com>2024-02-26 18:57:59 +0000
committerMiguel Borges de Freitas <92enen@gmail.com>2024-02-29 21:42:55 +0000
commitb5840d1c6dc88b6d87a2a59b4313800d83e30838 (patch)
tree18bf9bde6a4535b6de0f76338955775766618c4a
parent87904eebe6c8da8594a73848a4350ebf56bfa0df (diff)
MacOS: Implement hotkeycontroller to fix exclusive mediakey usage
-rw-r--r--xbmc/platform/darwin/osx/CMakeLists.txt6
-rw-r--r--xbmc/platform/darwin/osx/HotKeyController.h30
-rw-r--r--xbmc/platform/darwin/osx/HotKeyController.mm74
-rw-r--r--xbmc/platform/darwin/osx/Info.plist.in2
-rw-r--r--xbmc/platform/darwin/osx/MediaKeys.h19
-rw-r--r--xbmc/platform/darwin/osx/MediaKeys.mm158
-rw-r--r--xbmc/platform/darwin/osx/PlatformDarwinOSX.h6
-rw-r--r--xbmc/platform/darwin/osx/PlatformDarwinOSX.mm (renamed from xbmc/platform/darwin/osx/PlatformDarwinOSX.cpp)2
-rw-r--r--xbmc/windowing/osx/OpenGL/WindowControllerMacOS.mm3
-rw-r--r--xbmc/windowing/osx/WinEventsOSXImpl.mm68
10 files changed, 297 insertions, 71 deletions
diff --git a/xbmc/platform/darwin/osx/CMakeLists.txt b/xbmc/platform/darwin/osx/CMakeLists.txt
index b1af5622b3..ec063e74e0 100644
--- a/xbmc/platform/darwin/osx/CMakeLists.txt
+++ b/xbmc/platform/darwin/osx/CMakeLists.txt
@@ -1,11 +1,15 @@
set(SOURCES CocoaInterface.mm
CPUInfoOsx.cpp
GPUInfoMacOS.cpp
- PlatformDarwinOSX.cpp)
+ HotKeyController.mm
+ MediaKeys.mm
+ PlatformDarwinOSX.mm)
set(HEADERS CocoaInterface.h
CPUInfoOsx.h
GPUInfoMacOS.h
+ HotKeyController.h
+ MediaKeys.h
PlatformDarwinOSX.h)
if(ENABLE_XBMCHELPER)
diff --git a/xbmc/platform/darwin/osx/HotKeyController.h b/xbmc/platform/darwin/osx/HotKeyController.h
new file mode 100644
index 0000000000..94d25dd839
--- /dev/null
+++ b/xbmc/platform/darwin/osx/HotKeyController.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "interfaces/IAnnouncer.h"
+
+@class CMediaKeyTap;
+
+class CHotKeyController : public ANNOUNCEMENT::IAnnouncer
+{
+public:
+ CHotKeyController();
+ ~CHotKeyController() override;
+
+ void Announce(ANNOUNCEMENT::AnnouncementFlag flag,
+ const std::string& sender,
+ const std::string& message,
+ const CVariant& data) override;
+
+private:
+ CMediaKeyTap* m_mediaKeytap;
+ bool m_appHasFocus{false};
+ bool m_appIsPlaying{false};
+};
diff --git a/xbmc/platform/darwin/osx/HotKeyController.mm b/xbmc/platform/darwin/osx/HotKeyController.mm
new file mode 100644
index 0000000000..bc5e0a21f9
--- /dev/null
+++ b/xbmc/platform/darwin/osx/HotKeyController.mm
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "HotKeyController.h"
+
+#include "ServiceBroker.h"
+#include "interfaces/AnnouncementManager.h"
+
+#include "platform/darwin/osx/MediaKeys.h"
+
+CHotKeyController::CHotKeyController()
+{
+ m_mediaKeytap = [CMediaKeyTap new];
+ CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this);
+}
+
+CHotKeyController::~CHotKeyController()
+{
+ CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this);
+}
+
+void CHotKeyController::Announce(ANNOUNCEMENT::AnnouncementFlag flag,
+ const std::string& sender,
+ const std::string& message,
+ const CVariant& data)
+{
+ if (sender != ANNOUNCEMENT::CAnnouncementManager::ANNOUNCEMENT_SENDER)
+ return;
+
+ switch (flag)
+ {
+ case ANNOUNCEMENT::GUI:
+ {
+ if (message == "WindowFocused")
+ {
+ m_appHasFocus = true;
+ [m_mediaKeytap enableMediaKeyTap];
+ }
+ else if (message == "WindowUnfocused")
+ {
+ m_appHasFocus = false;
+ if (!m_appIsPlaying)
+ {
+ [m_mediaKeytap disableMediaKeyTap];
+ }
+ }
+ break;
+ }
+ case ANNOUNCEMENT::Player:
+ {
+ if (message == "OnPlay" || message == "OnResume")
+ {
+ m_appIsPlaying = true;
+ [m_mediaKeytap enableMediaKeyTap];
+ }
+ else if (message == "OnStop")
+ {
+ m_appIsPlaying = false;
+ if (!m_appHasFocus)
+ {
+ [m_mediaKeytap disableMediaKeyTap];
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
diff --git a/xbmc/platform/darwin/osx/Info.plist.in b/xbmc/platform/darwin/osx/Info.plist.in
index bddd2ea3ac..50246a2edb 100644
--- a/xbmc/platform/darwin/osx/Info.plist.in
+++ b/xbmc/platform/darwin/osx/Info.plist.in
@@ -38,5 +38,7 @@
<string>Translate speech to text in virtual keyboard dialog</string>
<key>NSMicrophoneUsageDescription</key>
<string>Used for speech recognition</string>
+ <key>NSAppleEventsUsageDescription</key>
+ <string>Used for intercepting media keys</string>
</dict>
</plist>
diff --git a/xbmc/platform/darwin/osx/MediaKeys.h b/xbmc/platform/darwin/osx/MediaKeys.h
new file mode 100644
index 0000000000..618c299553
--- /dev/null
+++ b/xbmc/platform/darwin/osx/MediaKeys.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#import <Foundation/Foundation.h>
+
+@interface CMediaKeyTap : NSObject
+
+- (void)enableMediaKeyTap;
+- (void)disableMediaKeyTap;
+- (bool)HandleMediaKey:(int)keyCode;
+
+@end
diff --git a/xbmc/platform/darwin/osx/MediaKeys.mm b/xbmc/platform/darwin/osx/MediaKeys.mm
new file mode 100644
index 0000000000..1b939da0eb
--- /dev/null
+++ b/xbmc/platform/darwin/osx/MediaKeys.mm
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "MediaKeys.h"
+
+#include "ServiceBroker.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "interfaces/AnnouncementManager.h"
+#include "messaging/ApplicationMessenger.h"
+#include "utils/log.h"
+
+#import <AppKit/AppKit.h>
+#import <IOKit/hidsystem/ev_keymap.h>
+
+namespace
+{
+CGEventRef MediaKeyCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void* refcon)
+{
+ auto tap = (__bridge CMediaKeyTap*)refcon;
+ NSEvent* nsEvent = [NSEvent eventWithCGEvent:event];
+ if (nsEvent.type == NSEventTypeSystemDefined && nsEvent.subtype == NX_SUBTYPE_AUX_CONTROL_BUTTONS)
+ {
+ const int keyCode = (([nsEvent data1] & 0xFFFF0000) >> 16);
+ const int keyFlags = ([nsEvent data1] & 0x0000FFFF);
+ const int keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA;
+ if (keyState == 1) // if pressed
+ {
+ if ([tap HandleMediaKey:keyCode])
+ return nullptr;
+ }
+ }
+ return event;
+}
+} // namespace
+
+@implementation CMediaKeyTap
+{
+ CFMachPortRef m_portRef;
+ CFRunLoopSourceRef m_sourceRef;
+ CFRunLoopRef m_tapThreadURL;
+ NSThread* m_mediaKeyTapThread;
+}
+
+- (void)dealloc
+{
+ [self disableMediaKeyTap];
+}
+
+- (void)enableMediaKeyTap
+{
+ if (m_portRef)
+ return;
+
+ m_portRef = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault,
+ CGEventMaskBit(NX_SYSDEFINED), MediaKeyCallback,
+ (__bridge void* __nullable)(self));
+ if (!m_portRef)
+ {
+ CLog::LogF(LOGERROR, "Failed to create media key tap. Check app accessibility permissions.");
+ return;
+ }
+
+ m_sourceRef = CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault, m_portRef, 0);
+ if (!m_sourceRef)
+ {
+ CLog::LogF(LOGERROR, "Failed to create media key tap. Check app accessibility permissions.");
+ return;
+ }
+
+ m_mediaKeyTapThread = [[NSThread alloc] initWithTarget:self
+ selector:@selector(eventTapThread)
+ object:nil];
+ [m_mediaKeyTapThread start];
+}
+
+- (void)disableMediaKeyTap
+{
+ if (m_tapThreadURL)
+ {
+ CFRunLoopStop(m_tapThreadURL);
+ m_tapThreadURL = nullptr;
+ }
+
+ if (m_portRef)
+ {
+ CFMachPortInvalidate(m_portRef);
+ CFRelease(m_portRef);
+ m_portRef = nullptr;
+ }
+
+ if (m_sourceRef)
+ {
+ CFRelease(m_sourceRef);
+ m_sourceRef = nullptr;
+ }
+}
+
+- (void)eventTapThread
+{
+ m_tapThreadURL = CFRunLoopGetCurrent();
+ CFRunLoopAddSource(m_tapThreadURL, m_sourceRef, kCFRunLoopCommonModes);
+ CFRunLoopRun();
+}
+
+- (bool)HandleMediaKey:(int)keyCode
+{
+ bool intercepted = true;
+ switch (keyCode)
+ {
+ case NX_KEYTYPE_PLAY:
+ {
+ [self SendPlayerAction:ACTION_PLAYER_PLAYPAUSE];
+ break;
+ }
+ case NX_KEYTYPE_NEXT:
+ {
+ [self SendPlayerAction:ACTION_NEXT_ITEM];
+ break;
+ }
+ case NX_KEYTYPE_PREVIOUS:
+ {
+ [self SendPlayerAction:ACTION_PREV_ITEM];
+ break;
+ }
+ case NX_KEYTYPE_FAST:
+ {
+ [self SendPlayerAction:ACTION_PLAYER_FORWARD];
+ break;
+ }
+ case NX_KEYTYPE_REWIND:
+ {
+ [self SendPlayerAction:ACTION_PLAYER_REWIND];
+ break;
+ }
+ default:
+ {
+ intercepted = false;
+ break;
+ }
+ }
+ return intercepted;
+}
+
+- (void)SendPlayerAction:(int)actionId
+{
+ //! @TODO: This shouldn't depend on GUI/Actions (e.g. headless music player can also use mediakeys)
+ CAction* action = new CAction(actionId);
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1,
+ static_cast<void*>(action));
+}
+
+@end
diff --git a/xbmc/platform/darwin/osx/PlatformDarwinOSX.h b/xbmc/platform/darwin/osx/PlatformDarwinOSX.h
index 7ff2a0a63b..f7be5edeb5 100644
--- a/xbmc/platform/darwin/osx/PlatformDarwinOSX.h
+++ b/xbmc/platform/darwin/osx/PlatformDarwinOSX.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2020 Team Kodi
+ * Copyright (C) 2005-2024 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
@@ -9,6 +9,7 @@
#pragma once
#include "platform/darwin/PlatformDarwin.h"
+#include "platform/darwin/osx/HotKeyController.h"
class CPlatformDarwinOSX : public CPlatformDarwin
{
@@ -19,4 +20,7 @@ public:
bool InitStageOne() override;
bool InitStageTwo() override;
+
+private:
+ CHotKeyController m_hotkeyController;
};
diff --git a/xbmc/platform/darwin/osx/PlatformDarwinOSX.cpp b/xbmc/platform/darwin/osx/PlatformDarwinOSX.mm
index f1b24a964d..93c729e4b6 100644
--- a/xbmc/platform/darwin/osx/PlatformDarwinOSX.cpp
+++ b/xbmc/platform/darwin/osx/PlatformDarwinOSX.mm
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016-2018 Team Kodi
+ * Copyright (C) 2016-2024 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
diff --git a/xbmc/windowing/osx/OpenGL/WindowControllerMacOS.mm b/xbmc/windowing/osx/OpenGL/WindowControllerMacOS.mm
index 06eea773e3..5bfb6b0d22 100644
--- a/xbmc/windowing/osx/OpenGL/WindowControllerMacOS.mm
+++ b/xbmc/windowing/osx/OpenGL/WindowControllerMacOS.mm
@@ -11,6 +11,7 @@
#include "ServiceBroker.h"
#include "application/AppInboundProtocol.h"
#include "application/Application.h"
+#include "interfaces/AnnouncementManager.h"
#include "messaging/ApplicationMessenger.h"
#include "settings/DisplaySettings.h"
#include "settings/Settings.h"
@@ -89,6 +90,7 @@
- (void)windowDidBecomeKey:(NSNotification*)aNotification
{
g_application.m_AppFocused = true;
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::GUI, "WindowFocused");
auto winSystem = dynamic_cast<CWinSystemOSX*>(CServiceBroker::GetWinSystem());
if (winSystem)
@@ -100,6 +102,7 @@
- (void)windowDidResignKey:(NSNotification*)aNotification
{
g_application.m_AppFocused = false;
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::GUI, "WindowUnfocused");
auto winSystem = dynamic_cast<CWinSystemOSX*>(CServiceBroker::GetWinSystem());
if (winSystem)
diff --git a/xbmc/windowing/osx/WinEventsOSXImpl.mm b/xbmc/windowing/osx/WinEventsOSXImpl.mm
index fd8a2bb397..e22c9219e9 100644
--- a/xbmc/windowing/osx/WinEventsOSXImpl.mm
+++ b/xbmc/windowing/osx/WinEventsOSXImpl.mm
@@ -22,7 +22,6 @@
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
-#import <IOKit/hidsystem/ev_keymap.h>
#pragma mark - objc implementation
@@ -30,7 +29,6 @@
{
std::queue<XBMC_Event> events;
CCriticalSection m_inputlock;
- id m_mediaKeysTap;
bool m_inputEnabled;
//! macOS requires the calls the NSCursor hide/unhide to be balanced
@@ -201,16 +199,11 @@
- (void)enableInputEvents
{
- m_mediaKeysTap = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskSystemDefined
- handler:^(NSEvent* event) {
- return [self InputEventHandler:event];
- }];
m_inputEnabled = true;
}
- (void)disableInputEvents
{
- m_mediaKeysTap = nil;
m_inputEnabled = false;
}
@@ -355,21 +348,6 @@
break;
}
- // media keys
- case NSEventTypeSystemDefined:
- {
- if (nsEvent.subtype == NX_SUBTYPE_AUX_CONTROL_BUTTONS)
- {
- const int keyCode = (([nsEvent data1] & 0xFFFF0000) >> 16);
- const int keyFlags = ([nsEvent data1] & 0x0000FFFF);
- const int keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA;
- if (keyState == 1) // if pressed
- {
- passEvent = [self HandleMediaKey:keyCode];
- }
- }
- break;
- }
default:
return nsEvent;
}
@@ -452,50 +430,4 @@
return location;
}
-- (void)SendPlayerAction:(int)actionId
-{
- CAction* action = new CAction(actionId);
- CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1,
- static_cast<void*>(action));
-}
-
-- (bool)HandleMediaKey:(int)keyCode
-{
- bool passEvent = false;
- switch (keyCode)
- {
- case NX_KEYTYPE_PLAY:
- {
- [self SendPlayerAction:ACTION_PLAYER_PLAYPAUSE];
- break;
- }
- case NX_KEYTYPE_NEXT:
- {
- [self SendPlayerAction:ACTION_NEXT_ITEM];
- break;
- }
- case NX_KEYTYPE_PREVIOUS:
- {
- [self SendPlayerAction:ACTION_PREV_ITEM];
- break;
- }
- case NX_KEYTYPE_FAST:
- {
- [self SendPlayerAction:ACTION_PLAYER_FORWARD];
- break;
- }
- case NX_KEYTYPE_REWIND:
- {
- [self SendPlayerAction:ACTION_PLAYER_REWIND];
- break;
- }
- default:
- {
- passEvent = true;
- break;
- }
- }
- return passEvent;
-}
-
@end