diff options
author | Miguel Borges de Freitas <92enen@gmail.com> | 2024-02-26 18:57:59 +0000 |
---|---|---|
committer | Miguel Borges de Freitas <92enen@gmail.com> | 2024-02-29 21:42:55 +0000 |
commit | b5840d1c6dc88b6d87a2a59b4313800d83e30838 (patch) | |
tree | 18bf9bde6a4535b6de0f76338955775766618c4a | |
parent | 87904eebe6c8da8594a73848a4350ebf56bfa0df (diff) |
MacOS: Implement hotkeycontroller to fix exclusive mediakey usage
-rw-r--r-- | xbmc/platform/darwin/osx/CMakeLists.txt | 6 | ||||
-rw-r--r-- | xbmc/platform/darwin/osx/HotKeyController.h | 30 | ||||
-rw-r--r-- | xbmc/platform/darwin/osx/HotKeyController.mm | 74 | ||||
-rw-r--r-- | xbmc/platform/darwin/osx/Info.plist.in | 2 | ||||
-rw-r--r-- | xbmc/platform/darwin/osx/MediaKeys.h | 19 | ||||
-rw-r--r-- | xbmc/platform/darwin/osx/MediaKeys.mm | 158 | ||||
-rw-r--r-- | xbmc/platform/darwin/osx/PlatformDarwinOSX.h | 6 | ||||
-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.mm | 3 | ||||
-rw-r--r-- | xbmc/windowing/osx/WinEventsOSXImpl.mm | 68 |
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 |