mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 22:27:20 +02:00
Removed SPMediaKeyTap solution for macOS media keys.
Related commit: fd6a312abc
.
This commit is contained in:
parent
4f8bab6a5f
commit
3ffbb94fdb
9 changed files with 2 additions and 699 deletions
|
@ -1246,8 +1246,6 @@ if (WIN32)
|
|||
# $<IF:${release},"Appending compatibility manifest.","Finalizing build.">
|
||||
# )
|
||||
elseif (APPLE)
|
||||
target_link_libraries(Telegram PRIVATE desktop-app::external_sp_media_key_tap)
|
||||
|
||||
if (NOT DESKTOP_APP_USE_PACKAGED)
|
||||
target_link_libraries(Telegram PRIVATE desktop-app::external_iconv)
|
||||
endif()
|
||||
|
|
|
@ -45,7 +45,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include <CoreFoundation/CFURL.h>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <IOKit/hidsystem/ev_keymap.h>
|
||||
#include <SPMediaKeyTap.h>
|
||||
|
||||
@interface MainWindowObserver : NSObject {
|
||||
}
|
||||
|
@ -106,25 +105,6 @@ private:
|
|||
|
||||
#endif // OS_MAC_OLD
|
||||
|
||||
class EventFilter : public QAbstractNativeEventFilter {
|
||||
public:
|
||||
EventFilter(not_null<MainWindow*> window) : _window(window) {
|
||||
}
|
||||
|
||||
bool nativeEventFilter(
|
||||
const QByteArray &eventType,
|
||||
void *message,
|
||||
long *result) {
|
||||
return Core::Sandbox::Instance().customEnterFromEventLoop([&] {
|
||||
return _window->psFilterNativeEvent(message);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
not_null<MainWindow*> _window;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] QImage TrayIconBack(bool darkMode) {
|
||||
static const auto WithColor = [](QColor color) {
|
||||
return st::macTrayIcon.instance(color, 100);
|
||||
|
@ -153,8 +133,6 @@ public:
|
|||
|
||||
void enableShadow(WId winId);
|
||||
|
||||
bool filterNativeEvent(void *event);
|
||||
|
||||
void willEnterFullScreen();
|
||||
void willExitFullScreen();
|
||||
void didExitFullScreen();
|
||||
|
@ -188,8 +166,6 @@ private:
|
|||
int _generalPasteboardChangeCount = -1;
|
||||
bool _generalPasteboardHasText = false;
|
||||
|
||||
EventFilter _nativeEventFilter;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Platform
|
||||
|
@ -260,8 +236,7 @@ void ForceDisabled(QAction *action, bool disabled) {
|
|||
|
||||
MainWindow::Private::Private(not_null<MainWindow*> window)
|
||||
: _public(window)
|
||||
, _observer([[MainWindowObserver alloc] init:this])
|
||||
, _nativeEventFilter(window) {
|
||||
, _observer([[MainWindowObserver alloc] init:this]) {
|
||||
_generalPasteboard = [NSPasteboard generalPasteboard];
|
||||
|
||||
@autoreleasepool {
|
||||
|
@ -271,11 +246,6 @@ MainWindow::Private::Private(not_null<MainWindow*> window)
|
|||
[[NSDistributedNotificationCenter defaultCenter] addObserver:_observer selector:@selector(screenIsLocked:) name:Q2NSString(strNotificationAboutScreenLocked()) object:nil];
|
||||
[[NSDistributedNotificationCenter defaultCenter] addObserver:_observer selector:@selector(screenIsUnlocked:) name:Q2NSString(strNotificationAboutScreenUnlocked()) object:nil];
|
||||
|
||||
#ifndef OS_MAC_STORE
|
||||
// Register defaults for the whitelist of apps that want to use media keys
|
||||
[[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:[SPMediaKeyTap defaultMediaKeyUserBundleIdentifiers], kMediaKeyUsingBundleIdentifiersDefaultsKey, nil]];
|
||||
#endif // !OS_MAC_STORE
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -467,21 +437,6 @@ void MainWindow::Private::enableShadow(WId winId) {
|
|||
// [[(NSView*)winId window] setHasShadow:YES];
|
||||
}
|
||||
|
||||
bool MainWindow::Private::filterNativeEvent(void *event) {
|
||||
NSEvent *e = static_cast<NSEvent*>(event);
|
||||
if (e && [e type] == NSSystemDefined && [e subtype] == SPSystemDefinedEventMediaKeys) {
|
||||
#ifndef OS_MAC_STORE
|
||||
// If event tap is not installed, handle events that reach the app instead
|
||||
if (![SPMediaKeyTap usesGlobalMediaKeyTap]) {
|
||||
return objc_handleMediaKeyEvent(e);
|
||||
}
|
||||
#else // !OS_MAC_STORE
|
||||
return objc_handleMediaKeyEvent(e);
|
||||
#endif // else for !OS_MAC_STORE
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
MainWindow::Private::~Private() {
|
||||
[_observer release];
|
||||
}
|
||||
|
@ -489,8 +444,6 @@ MainWindow::Private::~Private() {
|
|||
MainWindow::MainWindow(not_null<Window::Controller*> controller)
|
||||
: Window::MainWindow(controller)
|
||||
, _private(std::make_unique<Private>(this)) {
|
||||
QCoreApplication::instance()->installNativeEventFilter(
|
||||
&_private->_nativeEventFilter);
|
||||
|
||||
#ifndef OS_MAC_OLD
|
||||
auto forceOpenGL = std::make_unique<QOpenGLWidget>(this);
|
||||
|
@ -931,10 +884,6 @@ void MainWindow::updateGlobalMenuHook() {
|
|||
ForceDisabled(psClearFormat, !canApplyMarkdown);
|
||||
}
|
||||
|
||||
bool MainWindow::psFilterNativeEvent(void *event) {
|
||||
return _private->filterNativeEvent(event);
|
||||
}
|
||||
|
||||
bool MainWindow::eventFilter(QObject *obj, QEvent *evt) {
|
||||
QEvent::Type t = evt->type();
|
||||
if (t == QEvent::FocusIn || t == QEvent::FocusOut) {
|
||||
|
|
|
@ -33,7 +33,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include <CoreFoundation/CFURL.h>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <IOKit/hidsystem/ev_keymap.h>
|
||||
#include <SPMediaKeyTap.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <AVFoundation/AVFoundation.h>
|
||||
|
||||
|
|
|
@ -33,7 +33,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include <CoreFoundation/CFURL.h>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <IOKit/hidsystem/ev_keymap.h>
|
||||
#include <SPMediaKeyTap.h>
|
||||
|
||||
using Platform::Q2NSString;
|
||||
using Platform::NS2QString;
|
||||
|
@ -131,15 +130,10 @@ NSMenuItem *CreateMenuItem(
|
|||
}
|
||||
|
||||
- (BOOL) applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag;
|
||||
- (void) applicationDidFinishLaunching:(NSNotification *)aNotification;
|
||||
- (void) applicationDidBecomeActive:(NSNotification *)aNotification;
|
||||
- (void) applicationDidResignActive:(NSNotification *)aNotification;
|
||||
- (void) receiveWakeNote:(NSNotification*)note;
|
||||
|
||||
- (void) setWatchingMediaKeys:(bool)watching;
|
||||
- (bool) isWatchingMediaKeys;
|
||||
- (void) mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event;
|
||||
|
||||
- (void) ignoreApplicationActivationRightNow;
|
||||
|
||||
- (NSMenu *) applicationDockMenu:(NSApplication *)sender;
|
||||
|
@ -149,8 +143,6 @@ NSMenuItem *CreateMenuItem(
|
|||
ApplicationDelegate *_sharedDelegate = nil;
|
||||
|
||||
@implementation ApplicationDelegate {
|
||||
SPMediaKeyTap *_keyTap;
|
||||
bool _watchingMediaKeys;
|
||||
bool _ignoreActivation;
|
||||
base::Timer _ignoreActivationStop;
|
||||
}
|
||||
|
@ -165,24 +157,10 @@ ApplicationDelegate *_sharedDelegate = nil;
|
|||
}
|
||||
|
||||
- (void) applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
_keyTap = nullptr;
|
||||
_watchingMediaKeys = false;
|
||||
_ignoreActivation = false;
|
||||
_ignoreActivationStop.setCallback([self] {
|
||||
_ignoreActivation = false;
|
||||
});
|
||||
#ifndef OS_MAC_STORE
|
||||
if ([SPMediaKeyTap usesGlobalMediaKeyTap]) {
|
||||
if (!Platform::IsMac10_14OrGreater()) {
|
||||
_keyTap = [[SPMediaKeyTap alloc] initWithDelegate:self];
|
||||
} else {
|
||||
// In macOS Mojave it requires accessibility features.
|
||||
LOG(("Media key monitoring disabled starting with Mojave."));
|
||||
}
|
||||
} else {
|
||||
LOG(("Media key monitoring disabled"));
|
||||
}
|
||||
#endif // else for !OS_MAC_STORE
|
||||
}
|
||||
|
||||
- (void) applicationDidBecomeActive:(NSNotification *)aNotification {
|
||||
|
@ -216,33 +194,6 @@ ApplicationDelegate *_sharedDelegate = nil;
|
|||
});
|
||||
}
|
||||
|
||||
- (void) setWatchingMediaKeys:(bool)watching {
|
||||
if (_watchingMediaKeys != watching) {
|
||||
_watchingMediaKeys = watching;
|
||||
if (_keyTap) {
|
||||
#ifndef OS_MAC_STORE
|
||||
if (_watchingMediaKeys) {
|
||||
[_keyTap startWatchingMediaKeys];
|
||||
} else {
|
||||
[_keyTap stopWatchingMediaKeys];
|
||||
}
|
||||
#endif // else for !OS_MAC_STORE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (bool) isWatchingMediaKeys {
|
||||
return _watchingMediaKeys;
|
||||
}
|
||||
|
||||
- (void) mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)e {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
|
||||
if (e && [e type] == NSSystemDefined && [e subtype] == SPSystemDefinedEventMediaKeys) {
|
||||
objc_handleMediaKeyEvent(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void) ignoreApplicationActivationRightNow {
|
||||
_ignoreActivation = true;
|
||||
_ignoreActivationStop.callOnce(kIgnoreActivationTimeoutMs);
|
||||
|
@ -293,9 +244,6 @@ ApplicationDelegate *_sharedDelegate = nil;
|
|||
namespace Platform {
|
||||
|
||||
void SetWatchingMediaKeys(bool watching) {
|
||||
if (_sharedDelegate) {
|
||||
[_sharedDelegate setWatchingMediaKeys:watching];
|
||||
}
|
||||
}
|
||||
|
||||
void SetApplicationIcon(const QIcon &icon) {
|
||||
|
@ -310,43 +258,6 @@ void SetApplicationIcon(const QIcon &icon) {
|
|||
|
||||
} // namespace Platform
|
||||
|
||||
bool objc_handleMediaKeyEvent(void *ev) {
|
||||
auto e = reinterpret_cast<NSEvent*>(ev);
|
||||
|
||||
int keyCode = (([e data1] & 0xFFFF0000) >> 16);
|
||||
int keyFlags = ([e data1] & 0x0000FFFF);
|
||||
int keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA;
|
||||
int keyRepeat = (keyFlags & 0x1);
|
||||
|
||||
if (!_sharedDelegate || ![_sharedDelegate isWatchingMediaKeys]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (keyCode) {
|
||||
case NX_KEYTYPE_PLAY:
|
||||
if (keyState == 0) { // Play pressed and released
|
||||
Media::Player::instance()->playPause();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case NX_KEYTYPE_FAST:
|
||||
if (keyState == 0) { // Next pressed and released
|
||||
Media::Player::instance()->next();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case NX_KEYTYPE_REWIND:
|
||||
if (keyState == 0) { // Previous pressed and released
|
||||
Media::Player::instance()->previous();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void objc_debugShowAlert(const QString &str) {
|
||||
@autoreleasepool {
|
||||
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface SPInvocationGrabber : NSObject {
|
||||
id _object;
|
||||
NSInvocation *_invocation;
|
||||
int frameCount;
|
||||
char **frameStrings;
|
||||
BOOL backgroundAfterForward;
|
||||
BOOL onMainAfterForward;
|
||||
BOOL waitUntilDone;
|
||||
}
|
||||
-(id)initWithObject:(id)obj;
|
||||
-(id)initWithObject:(id)obj stacktraceSaving:(BOOL)saveStack;
|
||||
@property (readonly, retain, nonatomic) id object;
|
||||
@property (readonly, retain, nonatomic) NSInvocation *invocation;
|
||||
@property BOOL backgroundAfterForward;
|
||||
@property BOOL onMainAfterForward;
|
||||
@property BOOL waitUntilDone;
|
||||
-(void)invoke; // will release object and invocation
|
||||
-(void)printBacktrace;
|
||||
-(void)saveBacktrace;
|
||||
@end
|
||||
|
||||
@interface NSObject (SPInvocationGrabbing)
|
||||
-(id)grab;
|
||||
-(id)invokeAfter:(NSTimeInterval)delta;
|
||||
-(id)nextRunloop;
|
||||
-(id)inBackground;
|
||||
-(id)onMainAsync:(BOOL)async;
|
||||
@end
|
|
@ -1,127 +0,0 @@
|
|||
#import "NSObject+SPInvocationGrabbing.h"
|
||||
#import <execinfo.h>
|
||||
|
||||
#pragma mark Invocation grabbing
|
||||
@interface SPInvocationGrabber ()
|
||||
@property (readwrite, retain, nonatomic) id object;
|
||||
@property (readwrite, retain, nonatomic) NSInvocation *invocation;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SPInvocationGrabber
|
||||
- (id)initWithObject:(id)obj
|
||||
{
|
||||
return [self initWithObject:obj stacktraceSaving:YES];
|
||||
}
|
||||
|
||||
-(id)initWithObject:(id)obj stacktraceSaving:(BOOL)saveStack
|
||||
{
|
||||
self.object = obj;
|
||||
|
||||
if(saveStack)
|
||||
[self saveBacktrace];
|
||||
|
||||
return self;
|
||||
}
|
||||
-(void)dealloc
|
||||
{
|
||||
free(frameStrings);
|
||||
self.object = nil;
|
||||
self.invocation = nil;
|
||||
[super dealloc];
|
||||
}
|
||||
@synthesize invocation = _invocation, object = _object;
|
||||
|
||||
@synthesize backgroundAfterForward, onMainAfterForward, waitUntilDone;
|
||||
- (void)runInBackground
|
||||
{
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
@try {
|
||||
[self invoke];
|
||||
}
|
||||
@finally {
|
||||
[pool drain];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)forwardInvocation:(NSInvocation *)anInvocation {
|
||||
[anInvocation retainArguments];
|
||||
anInvocation.target = _object;
|
||||
self.invocation = anInvocation;
|
||||
|
||||
if(backgroundAfterForward)
|
||||
[NSThread detachNewThreadSelector:@selector(runInBackground) toTarget:self withObject:nil];
|
||||
else if(onMainAfterForward)
|
||||
[self performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:waitUntilDone];
|
||||
}
|
||||
- (NSMethodSignature *)methodSignatureForSelector:(SEL)inSelector {
|
||||
NSMethodSignature *signature = [super methodSignatureForSelector:inSelector];
|
||||
if (signature == NULL)
|
||||
signature = [_object methodSignatureForSelector:inSelector];
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
- (void)invoke
|
||||
{
|
||||
|
||||
@try {
|
||||
[_invocation invoke];
|
||||
}
|
||||
@catch (NSException * e) {
|
||||
NSLog(@"SPInvocationGrabber's target raised %@:\n\t%@\nInvocation was originally scheduled at:", e.name, e);
|
||||
[self printBacktrace];
|
||||
printf("\n");
|
||||
[e raise];
|
||||
}
|
||||
|
||||
self.invocation = nil;
|
||||
self.object = nil;
|
||||
}
|
||||
|
||||
-(void)saveBacktrace
|
||||
{
|
||||
void *backtraceFrames[128];
|
||||
frameCount = backtrace(&backtraceFrames[0], 128);
|
||||
frameStrings = backtrace_symbols(&backtraceFrames[0], frameCount);
|
||||
}
|
||||
-(void)printBacktrace
|
||||
{
|
||||
for(int x = 3; x < frameCount; x++) {
|
||||
if(frameStrings[x] == NULL) { break; }
|
||||
printf("%s\n", frameStrings[x]);
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation NSObject (SPInvocationGrabbing)
|
||||
-(id)grab
|
||||
{
|
||||
return [[[SPInvocationGrabber alloc] initWithObject:self] autorelease];
|
||||
}
|
||||
-(id)invokeAfter:(NSTimeInterval)delta
|
||||
{
|
||||
id grabber = [self grab];
|
||||
[NSTimer scheduledTimerWithTimeInterval:delta target:grabber selector:@selector(invoke) userInfo:nil repeats:NO];
|
||||
return grabber;
|
||||
}
|
||||
- (id)nextRunloop
|
||||
{
|
||||
return [self invokeAfter:0];
|
||||
}
|
||||
-(id)inBackground
|
||||
{
|
||||
SPInvocationGrabber *grabber = [self grab];
|
||||
grabber.backgroundAfterForward = YES;
|
||||
return grabber;
|
||||
}
|
||||
-(id)onMainAsync:(BOOL)async
|
||||
{
|
||||
SPInvocationGrabber *grabber = [self grab];
|
||||
grabber.onMainAfterForward = YES;
|
||||
grabber.waitUntilDone = !async;
|
||||
return grabber;
|
||||
}
|
||||
|
||||
@end
|
|
@ -1,43 +0,0 @@
|
|||
#include <Cocoa/Cocoa.h>
|
||||
#import <IOKit/hidsystem/ev_keymap.h>
|
||||
#import <Carbon/Carbon.h>
|
||||
|
||||
// http://overooped.com/post/2593597587/mediakeys
|
||||
|
||||
#define SPSystemDefinedEventMediaKeys 8
|
||||
|
||||
@interface SPMediaKeyTap : NSObject {
|
||||
EventHandlerRef _app_switching_ref;
|
||||
EventHandlerRef _app_terminating_ref;
|
||||
CFMachPortRef _eventPort;
|
||||
CFRunLoopSourceRef _eventPortSource;
|
||||
CFRunLoopRef _tapThreadRL;
|
||||
BOOL _shouldInterceptMediaKeyEvents;
|
||||
id _delegate;
|
||||
// The app that is frontmost in this list owns media keys
|
||||
NSMutableArray *_mediaKeyAppList;
|
||||
}
|
||||
+ (NSArray*)defaultMediaKeyUserBundleIdentifiers;
|
||||
|
||||
-(id)initWithDelegate:(id)delegate;
|
||||
|
||||
+(BOOL)usesGlobalMediaKeyTap;
|
||||
-(void)startWatchingMediaKeys;
|
||||
-(void)stopWatchingMediaKeys;
|
||||
-(void)handleAndReleaseMediaKeyEvent:(NSEvent *)event;
|
||||
@end
|
||||
|
||||
@interface NSObject (SPMediaKeyTapDelegate)
|
||||
-(void)mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event;
|
||||
@end
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey;
|
||||
extern NSString *kIgnoreMediaKeysDefaultsKey;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
354
Telegram/ThirdParty/SPMediaKeyTap/SPMediaKeyTap.m
vendored
354
Telegram/ThirdParty/SPMediaKeyTap/SPMediaKeyTap.m
vendored
|
@ -1,354 +0,0 @@
|
|||
// Copyright (c) 2010 Spotify AB
|
||||
#import "SPMediaKeyTap.h"
|
||||
#import "SPInvocationGrabbing/NSObject+SPInvocationGrabbing.h" // https://gist.github.com/511181, in submodule
|
||||
|
||||
@interface SPMediaKeyTap ()
|
||||
-(BOOL)shouldInterceptMediaKeyEvents;
|
||||
-(void)setShouldInterceptMediaKeyEvents:(BOOL)newSetting;
|
||||
-(void)startWatchingAppSwitching;
|
||||
-(void)stopWatchingAppSwitching;
|
||||
-(void)eventTapThread;
|
||||
@end
|
||||
static SPMediaKeyTap *singleton = nil;
|
||||
|
||||
static pascal OSStatus appSwitched (EventHandlerCallRef nextHandler, EventRef evt, void* userData);
|
||||
static pascal OSStatus appTerminated (EventHandlerCallRef nextHandler, EventRef evt, void* userData);
|
||||
static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon);
|
||||
|
||||
// Inspired by http://gist.github.com/546311
|
||||
|
||||
@implementation SPMediaKeyTap
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Setup and teardown
|
||||
-(id)initWithDelegate:(id)delegate
|
||||
{
|
||||
_delegate = delegate;
|
||||
[self startWatchingAppSwitching];
|
||||
singleton = self;
|
||||
_mediaKeyAppList = [NSMutableArray new];
|
||||
_tapThreadRL=nil;
|
||||
_eventPort=nil;
|
||||
_eventPortSource=nil;
|
||||
return self;
|
||||
}
|
||||
-(void)dealloc
|
||||
{
|
||||
[self stopWatchingMediaKeys];
|
||||
[self stopWatchingAppSwitching];
|
||||
[_mediaKeyAppList release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
-(void)startWatchingAppSwitching
|
||||
{
|
||||
// Listen to "app switched" event, so that we don't intercept media keys if we
|
||||
// weren't the last "media key listening" app to be active
|
||||
EventTypeSpec eventType = { kEventClassApplication, kEventAppFrontSwitched };
|
||||
OSStatus err = InstallApplicationEventHandler(NewEventHandlerUPP(appSwitched), 1, &eventType, self, &_app_switching_ref);
|
||||
assert(err == noErr);
|
||||
|
||||
eventType.eventKind = kEventAppTerminated;
|
||||
err = InstallApplicationEventHandler(NewEventHandlerUPP(appTerminated), 1, &eventType, self, &_app_terminating_ref);
|
||||
assert(err == noErr);
|
||||
}
|
||||
-(void)stopWatchingAppSwitching
|
||||
{
|
||||
if(!_app_switching_ref) return;
|
||||
RemoveEventHandler(_app_switching_ref);
|
||||
_app_switching_ref = NULL;
|
||||
|
||||
if(!_app_terminating_ref) return;
|
||||
RemoveEventHandler(_app_terminating_ref);
|
||||
_app_terminating_ref = NULL;
|
||||
}
|
||||
|
||||
-(void)startWatchingMediaKeys
|
||||
{
|
||||
// Prevent having multiple mediaKeys threads
|
||||
[self stopWatchingMediaKeys];
|
||||
|
||||
[self setShouldInterceptMediaKeyEvents:YES];
|
||||
|
||||
// Add an event tap to intercept the system defined media key events
|
||||
_eventPort = CGEventTapCreate(kCGSessionEventTap,
|
||||
kCGHeadInsertEventTap,
|
||||
kCGEventTapOptionDefault,
|
||||
CGEventMaskBit(NX_SYSDEFINED),
|
||||
tapEventCallback,
|
||||
self);
|
||||
if (!_eventPort) {
|
||||
[self stopWatchingMediaKeys];
|
||||
return;
|
||||
}
|
||||
|
||||
_eventPortSource = CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault, _eventPort, 0);
|
||||
assert(_eventPortSource != NULL);
|
||||
|
||||
// Let's do this in a separate thread so that a slow app doesn't lag the event tap
|
||||
[NSThread detachNewThreadSelector:@selector(eventTapThread) toTarget:self withObject:nil];
|
||||
}
|
||||
-(void)stopWatchingMediaKeys
|
||||
{
|
||||
// TODO<nevyn>: Shut down thread, remove event tap port and source
|
||||
|
||||
if(_tapThreadRL){
|
||||
CFRunLoopStop(_tapThreadRL);
|
||||
_tapThreadRL=nil;
|
||||
}
|
||||
|
||||
if(_eventPort){
|
||||
CFMachPortInvalidate(_eventPort);
|
||||
CFRelease(_eventPort);
|
||||
_eventPort=nil;
|
||||
}
|
||||
|
||||
if(_eventPortSource){
|
||||
CFRelease(_eventPortSource);
|
||||
_eventPortSource=nil;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Accessors
|
||||
|
||||
+(BOOL)usesGlobalMediaKeyTap
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
// breaking in gdb with a key tap inserted sometimes locks up all mouse and keyboard input forever, forcing reboot
|
||||
return NO;
|
||||
#else
|
||||
// XXX(nevyn): MediaKey event tap doesn't work on 10.4, feel free to figure out why if you have the energy.
|
||||
return
|
||||
![[NSUserDefaults standardUserDefaults] boolForKey:kIgnoreMediaKeysDefaultsKey]
|
||||
&& floor(NSAppKitVersionNumber) >= 949/*NSAppKitVersionNumber10_5*/;
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (NSArray*)defaultMediaKeyUserBundleIdentifiers
|
||||
{
|
||||
return [NSArray arrayWithObjects:
|
||||
[[NSBundle mainBundle] bundleIdentifier], // your app
|
||||
@"com.spotify.client",
|
||||
@"com.apple.iTunes",
|
||||
@"com.apple.QuickTimePlayerX",
|
||||
@"com.apple.quicktimeplayer",
|
||||
@"com.apple.iWork.Keynote",
|
||||
@"com.apple.iPhoto",
|
||||
@"org.videolan.vlc",
|
||||
@"com.apple.Aperture",
|
||||
@"com.plexsquared.Plex",
|
||||
@"com.soundcloud.desktop",
|
||||
@"org.niltsh.MPlayerX",
|
||||
@"com.ilabs.PandorasHelper",
|
||||
@"com.mahasoftware.pandabar",
|
||||
@"com.bitcartel.pandorajam",
|
||||
@"org.clementine-player.clementine",
|
||||
@"fm.last.Last.fm",
|
||||
@"fm.last.Scrobbler",
|
||||
@"com.beatport.BeatportPro",
|
||||
@"com.Timenut.SongKey",
|
||||
@"com.macromedia.fireworks", // the tap messes up their mouse input
|
||||
@"at.justp.Theremin",
|
||||
@"ru.ya.themblsha.YandexMusic",
|
||||
@"com.jriver.MediaCenter18",
|
||||
@"com.jriver.MediaCenter19",
|
||||
@"com.jriver.MediaCenter20",
|
||||
@"co.rackit.mate",
|
||||
@"com.ttitt.b-music",
|
||||
@"com.beardedspice.BeardedSpice",
|
||||
@"com.plug.Plug",
|
||||
@"com.plug.Plug2",
|
||||
@"com.netease.163music",
|
||||
@"org.quodlibet.quodlibet",
|
||||
nil
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
-(BOOL)shouldInterceptMediaKeyEvents
|
||||
{
|
||||
BOOL shouldIntercept = NO;
|
||||
@synchronized(self) {
|
||||
shouldIntercept = _shouldInterceptMediaKeyEvents;
|
||||
}
|
||||
return shouldIntercept;
|
||||
}
|
||||
|
||||
-(void)pauseTapOnTapThread:(BOOL)yeahno
|
||||
{
|
||||
CGEventTapEnable(self->_eventPort, yeahno);
|
||||
}
|
||||
-(void)setShouldInterceptMediaKeyEvents:(BOOL)newSetting
|
||||
{
|
||||
BOOL oldSetting;
|
||||
@synchronized(self) {
|
||||
oldSetting = _shouldInterceptMediaKeyEvents;
|
||||
_shouldInterceptMediaKeyEvents = newSetting;
|
||||
}
|
||||
if(_tapThreadRL && oldSetting != newSetting) {
|
||||
id grab = [self grab];
|
||||
[grab pauseTapOnTapThread:newSetting];
|
||||
NSTimer *timer = [NSTimer timerWithTimeInterval:0 invocation:[grab invocation] repeats:NO];
|
||||
CFRunLoopAddTimer(_tapThreadRL, (CFRunLoopTimerRef)timer, kCFRunLoopCommonModes);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark
|
||||
#pragma mark -
|
||||
#pragma mark Event tap callbacks
|
||||
|
||||
// Note: method called on background thread
|
||||
|
||||
static CGEventRef tapEventCallback2(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
|
||||
{
|
||||
SPMediaKeyTap *self = refcon;
|
||||
|
||||
if(type == kCGEventTapDisabledByTimeout) {
|
||||
NSLog(@"Media key event tap was disabled by timeout");
|
||||
CGEventTapEnable(self->_eventPort, TRUE);
|
||||
return event;
|
||||
} else if(type == kCGEventTapDisabledByUserInput) {
|
||||
// Was disabled manually by -[pauseTapOnTapThread]
|
||||
return event;
|
||||
}
|
||||
NSEvent *nsEvent = nil;
|
||||
@try {
|
||||
nsEvent = [NSEvent eventWithCGEvent:event];
|
||||
}
|
||||
@catch (NSException * e) {
|
||||
NSLog(@"Strange CGEventType: %d: %@", type, e);
|
||||
assert(0);
|
||||
return event;
|
||||
}
|
||||
|
||||
if (type != NX_SYSDEFINED || [nsEvent subtype] != SPSystemDefinedEventMediaKeys)
|
||||
return event;
|
||||
|
||||
int keyCode = (([nsEvent data1] & 0xFFFF0000) >> 16);
|
||||
if (keyCode != NX_KEYTYPE_PLAY && keyCode != NX_KEYTYPE_FAST && keyCode != NX_KEYTYPE_REWIND && keyCode != NX_KEYTYPE_PREVIOUS && keyCode != NX_KEYTYPE_NEXT)
|
||||
return event;
|
||||
|
||||
if (![self shouldInterceptMediaKeyEvents])
|
||||
return event;
|
||||
|
||||
[nsEvent retain]; // matched in handleAndReleaseMediaKeyEvent:
|
||||
[self performSelectorOnMainThread:@selector(handleAndReleaseMediaKeyEvent:) withObject:nsEvent waitUntilDone:NO];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
|
||||
{
|
||||
NSAutoreleasePool *pool = [NSAutoreleasePool new];
|
||||
CGEventRef ret = tapEventCallback2(proxy, type, event, refcon);
|
||||
[pool drain];
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
// event will have been retained in the other thread
|
||||
-(void)handleAndReleaseMediaKeyEvent:(NSEvent *)event {
|
||||
[event autorelease];
|
||||
|
||||
[_delegate mediaKeyTap:self receivedMediaKeyEvent:event];
|
||||
}
|
||||
|
||||
|
||||
-(void)eventTapThread
|
||||
{
|
||||
_tapThreadRL = CFRunLoopGetCurrent();
|
||||
CFRunLoopAddSource(_tapThreadRL, _eventPortSource, kCFRunLoopCommonModes);
|
||||
CFRunLoopRun();
|
||||
}
|
||||
|
||||
#pragma mark Task switching callbacks
|
||||
|
||||
NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey = @"SPApplicationsNeedingMediaKeys";
|
||||
NSString *kIgnoreMediaKeysDefaultsKey = @"SPIgnoreMediaKeys";
|
||||
|
||||
|
||||
|
||||
-(void)mediaKeyAppListChanged
|
||||
{
|
||||
if([_mediaKeyAppList count] == 0) return;
|
||||
|
||||
/*NSLog(@"--");
|
||||
int i = 0;
|
||||
for (NSValue *psnv in _mediaKeyAppList) {
|
||||
ProcessSerialNumber psn; [psnv getValue:&psn];
|
||||
NSDictionary *processInfo = [(id)ProcessInformationCopyDictionary(
|
||||
&psn,
|
||||
kProcessDictionaryIncludeAllInformationMask
|
||||
) autorelease];
|
||||
NSString *bundleIdentifier = [processInfo objectForKey:(id)kCFBundleIdentifierKey];
|
||||
NSLog(@"%d: %@", i++, bundleIdentifier);
|
||||
}*/
|
||||
|
||||
ProcessSerialNumber mySerial, topSerial;
|
||||
GetCurrentProcess(&mySerial);
|
||||
[[_mediaKeyAppList objectAtIndex:0] getValue:&topSerial];
|
||||
|
||||
Boolean same;
|
||||
OSErr err = SameProcess(&mySerial, &topSerial, &same);
|
||||
[self setShouldInterceptMediaKeyEvents:(err == noErr && same)];
|
||||
|
||||
}
|
||||
-(void)appIsNowFrontmost:(ProcessSerialNumber)psn
|
||||
{
|
||||
NSValue *psnv = [NSValue valueWithBytes:&psn objCType:@encode(ProcessSerialNumber)];
|
||||
|
||||
NSDictionary *processInfo = [(id)ProcessInformationCopyDictionary(
|
||||
&psn,
|
||||
kProcessDictionaryIncludeAllInformationMask
|
||||
) autorelease];
|
||||
NSString *bundleIdentifier = [processInfo objectForKey:(id)kCFBundleIdentifierKey];
|
||||
|
||||
NSArray *whitelistIdentifiers = [[NSUserDefaults standardUserDefaults] arrayForKey:kMediaKeyUsingBundleIdentifiersDefaultsKey];
|
||||
if(![whitelistIdentifiers containsObject:bundleIdentifier]) return;
|
||||
|
||||
[_mediaKeyAppList removeObject:psnv];
|
||||
[_mediaKeyAppList insertObject:psnv atIndex:0];
|
||||
[self mediaKeyAppListChanged];
|
||||
}
|
||||
-(void)appTerminated:(ProcessSerialNumber)psn
|
||||
{
|
||||
NSValue *psnv = [NSValue valueWithBytes:&psn objCType:@encode(ProcessSerialNumber)];
|
||||
[_mediaKeyAppList removeObject:psnv];
|
||||
[self mediaKeyAppListChanged];
|
||||
}
|
||||
|
||||
static pascal OSStatus appSwitched (EventHandlerCallRef nextHandler, EventRef evt, void* userData)
|
||||
{
|
||||
SPMediaKeyTap *self = (id)userData;
|
||||
|
||||
ProcessSerialNumber newSerial;
|
||||
GetFrontProcess(&newSerial);
|
||||
|
||||
[self appIsNowFrontmost:newSerial];
|
||||
|
||||
return CallNextEventHandler(nextHandler, evt);
|
||||
}
|
||||
|
||||
static pascal OSStatus appTerminated (EventHandlerCallRef nextHandler, EventRef evt, void* userData)
|
||||
{
|
||||
SPMediaKeyTap *self = (id)userData;
|
||||
|
||||
ProcessSerialNumber deadPSN;
|
||||
|
||||
GetEventParameter(
|
||||
evt,
|
||||
kEventParamProcessID,
|
||||
typeProcessSerialNumber,
|
||||
NULL,
|
||||
sizeof(deadPSN),
|
||||
NULL,
|
||||
&deadPSN
|
||||
);
|
||||
|
||||
|
||||
[self appTerminated:deadPSN];
|
||||
return CallNextEventHandler(nextHandler, evt);
|
||||
}
|
||||
|
||||
@end
|
2
cmake
2
cmake
|
@ -1 +1 @@
|
|||
Subproject commit 03efe01ff978b84745414e046e4766225ffffb7f
|
||||
Subproject commit 97b9c3f6d899b8a24137b9e6c076f5752d13a037
|
Loading…
Add table
Reference in a new issue