This commit is contained in:
ch1p 2015-08-14 01:04:22 +03:00
commit 8c1a7423a0
329 changed files with 14424 additions and 0 deletions

43
README.md Normal file
View File

@ -0,0 +1,43 @@
# VK Player Controller
VK Player Controller - приложение для OS X, позволяющее контролировать аудиоплеер vk.com медиа-клавишами (F7-F9) и иметь доступ к плейлисту из статусбара.
#### Поддерживаемые браузеры:
* Chrome (+Opera, +Яндекс.Браузер) - *без расширения или с ним*
* Safari - *только без расширения*
* Firefox - *с расширением*
#### Поддерживаемые системы
Должно работать без проблем на OS X 10.8 и выше.
# Скриншоты
![](https://ch1p.com/vkpc/screenshots/dark_p.png) ![](https://ch1p.com/vkpc/screenshots/light_p.png)
# Некоторые технические подробности
#### Внедрение JavaScript
Для того, чтобы получить доступ к плееру в браузере, нужно внедрить свой JavaScript-код во вкладку. Существует два способа сделать это:
* через AppleScript *(работает в Chrome, Яндекс.Браузере и Safari)*
* через расширение *(работает в Firefox, Opera + опционально в Chrome и Яндекс.Браузере)*
Поскольку у Firefox и Opera нет AppleScript API для выполнения JavaScript-кода в контексте вкладки, им нужно расширение.
Судя по тому, что сообщали некоторые пользователи, метод AppleScript иногда не работает. Не очень понятно, от чего это зависит и как это воспроизвести, но на этот случай есть возможность включить режим расширения - для Chrome и Яндекс.Браузера.
Теоретически такой workaround возможен и в Safari, но под него расширение еще не портировано (предлагаю желающим заняться этим :), поэтому там альтернативы нет.
#### Как приложение общается с брауером
Для того, чтобы внедренный во вкладку скрипт имел возможность отправлять данные приложению и принимать их от него, приложение поднимает локальный WebSocket-сервер (с поддержкой SSL) на 127.0.0.1:56130 (в данный момент для SSL используется сертификат, выданный для домена vkpc-local.ch1p.com и истекающий 16 августа 2017 года. О том, зачем это нужно ниже).
Когда в плеере на сайте происходит какое-то событие (например, вы переключаете трек), скрипт через WS отправляет его приложению. Когда происходит событие в самом приложении (например, вы нажали F7 или кликнули на трек в плейлисте из статусбара), сообщение отправляется скрипту, который уже переключает трек на сайте.
Если vk.com открыт по https, браузер запрещает открывать WebSocket-соединение по http. Поэтому в качестве хака скрипт подключается не к 127.0.0.1, а к vkpc-local.ch1p.com, который указывает на 127.0.0.1.
#### libwebsockets
Для реализации WebSocket используется библиотека libwebsockets (https://github.com/warmcat/libwebsockets).
Нужно собрать ее с поддержкой SSL, установить заголовочные файлы и подключить libwebsockets.dylib к проекту.
# По вопросам
... можно писать сюда: https://vk.com/ez
P.S. Исторически сложилось, что однажды написанный говнокод в vkpc.js больше не менялся (нет желания туда лезть), его бы переписать...

1
Sparkle.framework/Headers Symbolic link
View File

@ -0,0 +1 @@
Versions/Current/Headers

1
Sparkle.framework/Resources Symbolic link
View File

@ -0,0 +1 @@
Versions/Current/Resources

1
Sparkle.framework/Sparkle Symbolic link
View File

@ -0,0 +1 @@
Versions/Current/Sparkle

View File

@ -0,0 +1,30 @@
//
// SUAppcast.h
// Sparkle
//
// Created by Andy Matuschak on 3/12/06.
// Copyright 2006 Andy Matuschak. All rights reserved.
//
#ifndef SUAPPCAST_H
#define SUAPPCAST_H
@protocol SUAppcastDelegate;
@class SUAppcastItem;
@interface SUAppcast : NSObject <NSURLDownloadDelegate>
@property (weak) id<SUAppcastDelegate> delegate;
@property (copy) NSString *userAgentString;
- (void)fetchAppcastFromURL:(NSURL *)url;
@property (readonly, copy) NSArray *items;
@end
@protocol SUAppcastDelegate <NSObject>
- (void)appcastDidFinishLoading:(SUAppcast *)appcast;
- (void)appcast:(SUAppcast *)appcast failedToLoadWithError:(NSError *)error;
@end
#endif

View File

@ -0,0 +1,40 @@
//
// SUAppcastItem.h
// Sparkle
//
// Created by Andy Matuschak on 3/12/06.
// Copyright 2006 Andy Matuschak. All rights reserved.
//
#ifndef SUAPPCASTITEM_H
#define SUAPPCASTITEM_H
@interface SUAppcastItem : NSObject
@property (copy, readonly) NSString *title;
@property (copy, readonly) NSDate *date;
@property (copy, readonly) NSString *itemDescription;
@property (strong, readonly) NSURL *releaseNotesURL;
@property (copy, readonly) NSString *DSASignature;
@property (copy, readonly) NSString *minimumSystemVersion;
@property (copy, readonly) NSString *maximumSystemVersion;
@property (strong, readonly) NSURL *fileURL;
@property (copy, readonly) NSString *versionString;
@property (copy, readonly) NSString *displayVersionString;
@property (copy, readonly) NSDictionary *deltaUpdates;
@property (strong, readonly) NSURL *infoURL;
// Initializes with data from a dictionary provided by the RSS class.
- (instancetype)initWithDictionary:(NSDictionary *)dict;
- (instancetype)initWithDictionary:(NSDictionary *)dict failureReason:(NSString **)error;
@property (getter=isDeltaUpdate, readonly) BOOL deltaUpdate;
@property (getter=isCriticalUpdate, readonly) BOOL criticalUpdate;
// Returns the dictionary provided in initWithDictionary; this might be useful later for extensions.
@property (readonly, copy) NSDictionary *propertiesDictionary;
- (NSURL *)infoURL;
@end
#endif

View File

@ -0,0 +1,37 @@
//
// SUStandardVersionComparator.h
// Sparkle
//
// Created by Andy Matuschak on 12/21/07.
// Copyright 2007 Andy Matuschak. All rights reserved.
//
#ifndef SUSTANDARDVERSIONCOMPARATOR_H
#define SUSTANDARDVERSIONCOMPARATOR_H
#import "SUVersionComparisonProtocol.h"
/*!
Sparkle's default version comparator.
This comparator is adapted from MacPAD, by Kevin Ballard.
It's "dumb" in that it does essentially string comparison,
in components split by character type.
*/
@interface SUStandardVersionComparator : NSObject <SUVersionComparison>
/*!
Returns a singleton instance of the comparator.
*/
+ (SUStandardVersionComparator *)defaultComparator;
/*!
Compares version strings through textual analysis.
See the implementation for more details.
*/
- (NSComparisonResult)compareVersion:(NSString *)versionA toVersion:(NSString *)versionB;
@end
#endif

View File

@ -0,0 +1,334 @@
//
// SUUpdater.h
// Sparkle
//
// Created by Andy Matuschak on 1/4/06.
// Copyright 2006 Andy Matuschak. All rights reserved.
//
#ifndef SUUPDATER_H
#define SUUPDATER_H
#import "SUVersionComparisonProtocol.h"
#import "SUVersionDisplayProtocol.h"
@class SUUpdateDriver, SUAppcastItem, SUHost, SUAppcast;
@protocol SUUpdaterDelegate;
/*!
The main API in Sparkle for controlling the update mechanism.
This class is used to configure the update paramters as well as manually
and automatically schedule and control checks for updates.
*/
@interface SUUpdater : NSObject
@property (weak) IBOutlet id<SUUpdaterDelegate> delegate;
+ (SUUpdater *)sharedUpdater;
+ (SUUpdater *)updaterForBundle:(NSBundle *)bundle;
- (instancetype)initForBundle:(NSBundle *)bundle;
@property (readonly, strong) NSBundle *hostBundle;
@property BOOL automaticallyChecksForUpdates;
@property NSTimeInterval updateCheckInterval;
/*!
* The URL of the appcast used to download update information.
*
* This property must be called on the main thread.
*/
@property (copy) NSURL *feedURL;
@property (nonatomic, copy) NSString *userAgentString;
@property BOOL sendsSystemProfile;
@property BOOL automaticallyDownloadsUpdates;
/*!
Explicitly checks for updates and displays a progress dialog while doing so.
This method is meant for a main menu item.
Connect any menu item to this action in Interface Builder,
and Sparkle will check for updates and report back its findings verbosely
when it is invoked.
*/
- (IBAction)checkForUpdates:(id)sender;
/*!
Checks for updates, but does not display any UI unless an update is found.
This is meant for programmatically initating a check for updates. That is,
it will display no UI unless it actually finds an update, in which case it
proceeds as usual.
If the fully automated updating is turned on, however, this will invoke that
behavior, and if an update is found, it will be downloaded and prepped for
installation.
*/
- (void)checkForUpdatesInBackground;
/*!
Returns the date of last update check.
\returns \c nil if no check has been performed.
*/
@property (readonly, copy) NSDate *lastUpdateCheckDate;
/*!
Begins a "probing" check for updates which will not actually offer to
update to that version.
However, the delegate methods
SUUpdaterDelegate::updater:didFindValidUpdate: and
SUUpdaterDelegate::updaterDidNotFindUpdate: will be called,
so you can use that information in your UI.
*/
- (void)checkForUpdateInformation;
/*!
Appropriately schedules or cancels the update checking timer according to
the preferences for time interval and automatic checks.
This call does not change the date of the next check,
but only the internal NSTimer.
*/
- (void)resetUpdateCycle;
@property (readonly) BOOL updateInProgress;
@end
// -----------------------------------------------------------------------------
// SUUpdater Notifications for events that might be interesting to more than just the delegate
// The updater will be the notification object
// -----------------------------------------------------------------------------
extern NSString *const SUUpdaterDidFinishLoadingAppCastNotification;
extern NSString *const SUUpdaterDidFindValidUpdateNotification;
extern NSString *const SUUpdaterDidNotFindUpdateNotification;
extern NSString *const SUUpdaterWillRestartNotification;
#define SUUpdaterWillRelaunchApplicationNotification SUUpdaterWillRestartNotification;
#define SUUpdaterWillInstallUpdateNotification SUUpdaterWillRestartNotification;
// Key for the SUAppcastItem object in the SUUpdaterDidFindValidUpdateNotification userInfo
extern NSString *const SUUpdaterAppcastItemNotificationKey;
// Key for the SUAppcast object in the SUUpdaterDidFinishLoadingAppCastNotification userInfo
extern NSString *const SUUpdaterAppcastNotificationKey;
// -----------------------------------------------------------------------------
// SUUpdater Delegate:
// -----------------------------------------------------------------------------
/*!
Provides methods to control the behavior of an SUUpdater object.
*/
@protocol SUUpdaterDelegate <NSObject>
@optional
/*!
Returns whether to allow Sparkle to pop up.
For example, this may be used to prevent Sparkle from interrupting a setup assistant.
\param updater The SUUpdater instance.
*/
- (BOOL)updaterMayCheckForUpdates:(SUUpdater *)updater;
/*!
Returns additional parameters to append to the appcast URL's query string.
This is potentially based on whether or not Sparkle will also be sending along the system profile.
\param updater The SUUpdater instance.
\param sendingProfile Whether the system profile will also be sent.
\return An array of dictionaries with keys: "key", "value", "displayKey", "displayValue", the latter two being specifically for display to the user.
*/
- (NSArray *)feedParametersForUpdater:(SUUpdater *)updater sendingSystemProfile:(BOOL)sendingProfile;
/*!
Returns a custom appcast URL.
Override this to dynamically specify the entire URL.
\param updater The SUUpdater instance.
*/
- (NSString *)feedURLStringForUpdater:(SUUpdater *)updater;
/*!
Returns whether Sparkle should prompt the user about automatic update checks.
Use this to override the default behavior.
\param updater The SUUpdater instance.
*/
- (BOOL)updaterShouldPromptForPermissionToCheckForUpdates:(SUUpdater *)updater;
/*!
Called after Sparkle has downloaded the appcast from the remote server.
Implement this if you want to do some special handling with the appcast once it finishes loading.
\param updater The SUUpdater instance.
\param appcast The appcast that was downloaded from the remote server.
*/
- (void)updater:(SUUpdater *)updater didFinishLoadingAppcast:(SUAppcast *)appcast;
/*!
Returns the item in the appcast corresponding to the update that should be installed.
If you're using special logic or extensions in your appcast,
implement this to use your own logic for finding a valid update, if any,
in the given appcast.
\param appcast The appcast that was downloaded from the remote server.
\param updater The SUUpdater instance.
*/
- (SUAppcastItem *)bestValidUpdateInAppcast:(SUAppcast *)appcast forUpdater:(SUUpdater *)updater;
/*!
Called when a valid update is found by the update driver.
\param updater The SUUpdater instance.
\param item The appcast item corresponding to the update that is proposed to be installed.
*/
- (void)updater:(SUUpdater *)updater didFindValidUpdate:(SUAppcastItem *)item;
/*!
Called when a valid update is not found.
\param updater The SUUpdater instance.
*/
- (void)updaterDidNotFindUpdate:(SUUpdater *)updater;
/*!
Called immediately before installing the specified update.
\param updater The SUUpdater instance.
\param item The appcast item corresponding to the update that is proposed to be installed.
*/
- (void)updater:(SUUpdater *)updater willInstallUpdate:(SUAppcastItem *)item;
/*!
Returns whether the relaunch should be delayed in order to perform other tasks.
This is not called if the user didn't relaunch on the previous update,
in that case it will immediately restart.
\param updater The SUUpdater instance.
\param item The appcast item corresponding to the update that is proposed to be installed.
\param invocation The invocation that must be completed before continuing with the relaunch.
\return \c YES to delay the relaunch until \p invocation is invoked.
*/
- (BOOL)updater:(SUUpdater *)updater shouldPostponeRelaunchForUpdate:(SUAppcastItem *)item untilInvoking:(NSInvocation *)invocation;
/*!
Returns whether the application should be relaunched at all.
Some apps \b cannot be relaunched under certain circumstances.
This method can be used to explicitly prevent a relaunch.
\param updater The SUUpdater instance.
*/
- (BOOL)updaterShouldRelaunchApplication:(SUUpdater *)updater;
/*!
Called immediately before relaunching.
\param updater The SUUpdater instance.
*/
- (void)updaterWillRelaunchApplication:(SUUpdater *)updater;
/*!
Returns an object that compares version numbers to determine their arithmetic relation to each other.
This method allows you to provide a custom version comparator.
If you don't implement this method or return \c nil,
the standard version comparator will be used.
\sa SUStandardVersionComparator
\param updater The SUUpdater instance.
*/
- (id<SUVersionComparison>)versionComparatorForUpdater:(SUUpdater *)updater;
/*!
Returns an object that formats version numbers for display to the user.
If you don't implement this method or return \c nil,
the standard version formatter will be used.
\sa SUUpdateAlert
\param updater The SUUpdater instance.
*/
- (id<SUVersionDisplay>)versionDisplayerForUpdater:(SUUpdater *)updater;
/*!
Returns the path which is used to relaunch the client after the update is installed.
The default is the path of the host bundle.
\param updater The SUUpdater instance.
*/
- (NSString *)pathToRelaunchForUpdater:(SUUpdater *)updater;
/*!
Called before an updater shows a modal alert window,
to give the host the opportunity to hide attached windows that may get in the way.
\param updater The SUUpdater instance.
*/
- (void)updaterWillShowModalAlert:(SUUpdater *)updater;
/*!
Called after an updater shows a modal alert window,
to give the host the opportunity to hide attached windows that may get in the way.
\param updater The SUUpdater instance.
*/
- (void)updaterDidShowModalAlert:(SUUpdater *)updater;
/*!
Called when an update is scheduled to be silently installed on quit.
\param updater The SUUpdater instance.
\param item The appcast item corresponding to the update that is proposed to be installed.
\param invocation Can be used to trigger an immediate silent install and relaunch.
*/
- (void)updater:(SUUpdater *)updater willInstallUpdateOnQuit:(SUAppcastItem *)item immediateInstallationInvocation:(NSInvocation *)invocation;
/*!
Calls after an update that was scheduled to be silently installed on quit has been canceled.
\param updater The SUUpdater instance.
\param item The appcast item corresponding to the update that was proposed to be installed.
*/
- (void)updater:(SUUpdater *)updater didCancelInstallUpdateOnQuit:(SUAppcastItem *)item;
@end
// -----------------------------------------------------------------------------
// Constants:
// -----------------------------------------------------------------------------
// Define some minimum intervals to avoid DOS-like checking attacks. These are in seconds.
#if defined(DEBUG) && DEBUG && 0
#define SU_MIN_CHECK_INTERVAL 60
#else
#define SU_MIN_CHECK_INTERVAL 60 * 60
#endif
#if defined(DEBUG) && DEBUG && 0
#define SU_DEFAULT_CHECK_INTERVAL 60
#else
#define SU_DEFAULT_CHECK_INTERVAL 60 * 60 * 24
#endif
#endif

View File

@ -0,0 +1,29 @@
//
// SUVersionComparisonProtocol.h
// Sparkle
//
// Created by Andy Matuschak on 12/21/07.
// Copyright 2007 Andy Matuschak. All rights reserved.
//
#ifndef SUVERSIONCOMPARISONPROTOCOL_H
#define SUVERSIONCOMPARISONPROTOCOL_H
#import <Cocoa/Cocoa.h>
/*!
Provides version comparison facilities for Sparkle.
*/
@protocol SUVersionComparison
/*!
An abstract method to compare two version strings.
Should return NSOrderedAscending if b > a, NSOrderedDescending if b < a,
and NSOrderedSame if they are equivalent.
*/
- (NSComparisonResult)compareVersion:(NSString *)versionA toVersion:(NSString *)versionB; // *** MAY BE CALLED ON NON-MAIN THREAD!
@end
#endif

View File

@ -0,0 +1,25 @@
//
// SUVersionDisplayProtocol.h
// EyeTV
//
// Created by Uli Kusterer on 08.12.09.
// Copyright 2009 Elgato Systems GmbH. All rights reserved.
//
#import <Cocoa/Cocoa.h>
/*!
Applies special display formatting to version numbers.
*/
@protocol SUVersionDisplay
/*!
Formats two version strings.
Both versions are provided so that important distinguishing information
can be displayed while also leaving out unnecessary/confusing parts.
*/
- (void)formatVersion:(NSString **)inOutVersionA andVersion:(NSString **)inOutVersionB;
@end

View File

@ -0,0 +1,22 @@
//
// Sparkle.h
// Sparkle
//
// Created by Andy Matuschak on 3/16/06. (Modified by CDHW on 23/12/07)
// Copyright 2006 Andy Matuschak. All rights reserved.
//
#ifndef SPARKLE_H
#define SPARKLE_H
// This list should include the shared headers. It doesn't matter if some of them aren't shared (unless
// there are name-space collisions) so we can list all of them to start with:
#import <Sparkle/SUUpdater.h>
#import <Sparkle/SUAppcast.h>
#import <Sparkle/SUAppcastItem.h>
#import <Sparkle/SUVersionComparisonProtocol.h>
#import <Sparkle/SUStandardVersionComparator.h>
#endif

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>14A298i</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>Autoupdate</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundleIdentifier</key>
<string>org.sparkle-project.Sparkle.Autoupdate</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.8.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.8.0</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>6A254o</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>14A283h</string>
<key>DTSDKName</key>
<string>macosx10.10</string>
<key>DTXcode</key>
<string>0600</string>
<key>DTXcodeBuild</key>
<string>6A254o</string>
<key>LSBackgroundOnly</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>10.7</string>
<key>LSUIElement</key>
<string>1</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@ -0,0 +1 @@
APPL????

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>14A298i</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>Sparkle</string>
<key>CFBundleIdentifier</key>
<string>org.andymatuschak.Sparkle</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Sparkle</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.8.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>0b186dc</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>6A254o</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>14A283h</string>
<key>DTSDKName</key>
<string>macosx10.10</string>
<key>DTXcode</key>
<string>0600</string>
<key>DTXcodeBuild</key>
<string>6A254o</string>
</dict>
</plist>

View File

@ -0,0 +1,228 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ADP2,1</key>
<string>Developer Transition Kit</string>
<key>iMac1,1</key>
<string>iMac G3 (Rev A-D)</string>
<key>iMac4,1</key>
<string>iMac (Core Duo)</string>
<key>iMac4,2</key>
<string>iMac for Education (17 inch, Core Duo)</string>
<key>iMac5,1</key>
<string>iMac (Core 2 Duo, 17 or 20 inch, SuperDrive)</string>
<key>iMac5,2</key>
<string>iMac (Core 2 Duo, 17 inch, Combo Drive)</string>
<key>iMac6,1</key>
<string>iMac (Core 2 Duo, 24 inch, SuperDrive)</string>
<key>iMac8,1</key>
<string>iMac (Core 2 Duo, 20 or 24 inch, Early 2008 )</string>
<key>iMac9,1</key>
<string>iMac (Core 2 Duo, 20 or 24 inch, Early or Mid 2009 )</string>
<key>iMac10,1</key>
<string>iMac (Core 2 Duo, 21.5 or 27 inch, Late 2009 )</string>
<key>iMac11,1</key>
<string>iMac (Core i5 or i7, 27 inch Late 2009)</string>
<key>iMac11,2</key>
<string>iMac (Core i3 or i5, 27 inch Mid 2010)</string>
<key>iMac11,3</key>
<string>iMac (Core i5 or i7, 27 inch Mid 2010)</string>
<key>iMac12,1</key>
<string>iMac (Core i3 or i5 or i7, 21.5 inch Mid 2010 or Late 2011)</string>
<key>iMac12,2</key>
<string>iMac (Core i5 or i7, 27 inch Mid 2011)</string>
<key>iMac13,1</key>
<string>iMac (Core i3 or i5 or i7, 21.5 inch Late 2012 or Early 2013)</string>
<key>iMac13,2</key>
<string>iMac (Core i5 or i7, 27 inch Late 2012)</string>
<key>iMac14,1</key>
<string>iMac (Core i5, 21.5 inch Late 2013)</string>
<key>iMac14,2</key>
<string>iMac (Core i5 or i7, 27 inch Late 2013)</string>
<key>iMac14,3</key>
<string>iMac (Core i5 or i7, 21.5 inch Late 2013)</string>
<key>MacBook1,1</key>
<string>MacBook (Core Duo)</string>
<key>MacBook2,1</key>
<string>MacBook (Core 2 Duo)</string>
<key>MacBook4,1</key>
<string>MacBook (Core 2 Duo Feb 2008)</string>
<key>MacBook5,1</key>
<string>MacBook (Core 2 Duo, Late 2008, Unibody)</string>
<key>MacBook5,2</key>
<string>MacBook (Core 2 Duo, Early 2009, White)</string>
<key>MacBook6,1</key>
<string>MacBook (Core 2 Duo, Late 2009, Unibody)</string>
<key>MacBook7,1</key>
<string>MacBook (Core 2 Duo, Mid 2010, White)</string>
<key>MacBookAir1,1</key>
<string>MacBook Air (Core 2 Duo, 13 inch, Early 2008)</string>
<key>MacBookAir2,1</key>
<string>MacBook Air (Core 2 Duo, 13 inch, Mid 2009)</string>
<key>MacBookAir3,1</key>
<string>MacBook Air (Core 2 Duo, 11 inch, Late 2010)</string>
<key>MacBookAir3,2</key>
<string>MacBook Air (Core 2 Duo, 13 inch, Late 2010)</string>
<key>MacBookAir4,1</key>
<string>MacBook Air (Core i5 or i7, 11 inch, Mid 2011)</string>
<key>MacBookAir4,2</key>
<string>MacBook Air (Core i5 or i7, 13 inch, Mid 2011)</string>
<key>MacBookAir5,1</key>
<string>MacBook Air (Core i5 or i7, 11 inch, Mid 2012)</string>
<key>MacBookAir5,2</key>
<string>MacBook Air (Core i5 or i7, 13 inch, Mid 2012)</string>
<key>MacBookAir6,1</key>
<string>MacBook Air (Core i5 or i7, 11 inch, Mid 2013 or Early 2014)</string>
<key>MacBookAir6,2</key>
<string>MacBook Air (Core i5 or i7, 13 inch, Mid 2013 or Early 2014)</string>
<key>MacBookPro1,1</key>
<string>MacBook Pro Core Duo (15-inch)</string>
<key>MacBookPro1,2</key>
<string>MacBook Pro Core Duo (17-inch)</string>
<key>MacBookPro2,1</key>
<string>MacBook Pro Core 2 Duo (17-inch)</string>
<key>MacBookPro2,2</key>
<string>MacBook Pro Core 2 Duo (15-inch)</string>
<key>MacBookPro3,1</key>
<string>MacBook Pro Core 2 Duo (15-inch LED, Core 2 Duo)</string>
<key>MacBookPro3,2</key>
<string>MacBook Pro Core 2 Duo (17-inch HD, Core 2 Duo)</string>
<key>MacBookPro4,1</key>
<string>MacBook Pro (Core 2 Duo Feb 2008)</string>
<key>Macmini1,1</key>
<string>Mac Mini (Core Solo/Duo)</string>
<key>MacPro1,1</key>
<string>Mac Pro (four-core)</string>
<key>MacPro2,1</key>
<string>Mac Pro (eight-core)</string>
<key>MacPro3,1</key>
<string>Mac Pro (January 2008 4- or 8- core &quot;Harpertown&quot;)</string>
<key>MacPro4,1</key>
<string>Mac Pro (March 2009)</string>
<key>MacPro5,1</key>
<string>Mac Pro (August 2010)</string>
<key>PowerBook1,1</key>
<string>PowerBook G3</string>
<key>PowerBook2,1</key>
<string>iBook G3</string>
<key>PowerBook2,2</key>
<string>iBook G3 (FireWire)</string>
<key>PowerBook2,3</key>
<string>iBook G3</string>
<key>PowerBook2,4</key>
<string>iBook G3</string>
<key>PowerBook3,1</key>
<string>PowerBook G3 (FireWire)</string>
<key>PowerBook3,2</key>
<string>PowerBook G4</string>
<key>PowerBook3,3</key>
<string>PowerBook G4 (Gigabit Ethernet)</string>
<key>PowerBook3,4</key>
<string>PowerBook G4 (DVI)</string>
<key>PowerBook3,5</key>
<string>PowerBook G4 (1GHz / 867MHz)</string>
<key>PowerBook4,1</key>
<string>iBook G3 (Dual USB, Late 2001)</string>
<key>PowerBook4,2</key>
<string>iBook G3 (16MB VRAM)</string>
<key>PowerBook4,3</key>
<string>iBook G3 Opaque 16MB VRAM, 32MB VRAM, Early 2003)</string>
<key>PowerBook5,1</key>
<string>PowerBook G4 (17 inch)</string>
<key>PowerBook5,2</key>
<string>PowerBook G4 (15 inch FW 800)</string>
<key>PowerBook5,3</key>
<string>PowerBook G4 (17-inch 1.33GHz)</string>
<key>PowerBook5,4</key>
<string>PowerBook G4 (15 inch 1.5/1.33GHz)</string>
<key>PowerBook5,5</key>
<string>PowerBook G4 (17-inch 1.5GHz)</string>
<key>PowerBook5,6</key>
<string>PowerBook G4 (15 inch 1.67GHz/1.5GHz)</string>
<key>PowerBook5,7</key>
<string>PowerBook G4 (17-inch 1.67GHz)</string>
<key>PowerBook5,8</key>
<string>PowerBook G4 (Double layer SD, 15 inch)</string>
<key>PowerBook5,9</key>
<string>PowerBook G4 (Double layer SD, 17 inch)</string>
<key>PowerBook6,1</key>
<string>PowerBook G4 (12 inch)</string>
<key>PowerBook6,2</key>
<string>PowerBook G4 (12 inch, DVI)</string>
<key>PowerBook6,3</key>
<string>iBook G4</string>
<key>PowerBook6,4</key>
<string>PowerBook G4 (12 inch 1.33GHz)</string>
<key>PowerBook6,5</key>
<string>iBook G4 (Early-Late 2004)</string>
<key>PowerBook6,7</key>
<string>iBook G4 (Mid 2005)</string>
<key>PowerBook6,8</key>
<string>PowerBook G4 (12 inch 1.5GHz)</string>
<key>PowerMac1,1</key>
<string>Power Macintosh G3 (Blue &amp; White)</string>
<key>PowerMac1,2</key>
<string>Power Macintosh G4 (PCI Graphics)</string>
<key>PowerMac10,1</key>
<string>Mac Mini G4</string>
<key>PowerMac10,2</key>
<string>Mac Mini (Late 2005)</string>
<key>PowerMac11,2</key>
<string>Power Macintosh G5 (Late 2005)</string>
<key>PowerMac12,1</key>
<string>iMac G5 (iSight)</string>
<key>PowerMac2,1</key>
<string>iMac G3 (Slot-loading CD-ROM)</string>
<key>PowerMac2,2</key>
<string>iMac G3 (Summer 2000)</string>
<key>PowerMac3,1</key>
<string>Power Macintosh G4 (AGP Graphics)</string>
<key>PowerMac3,2</key>
<string>Power Macintosh G4 (AGP Graphics)</string>
<key>PowerMac3,3</key>
<string>Power Macintosh G4 (Gigabit Ethernet)</string>
<key>PowerMac3,4</key>
<string>Power Macintosh G4 (Digital Audio)</string>
<key>PowerMac3,5</key>
<string>Power Macintosh G4 (Quick Silver)</string>
<key>PowerMac3,6</key>
<string>Power Macintosh G4 (Mirrored Drive Door)</string>
<key>PowerMac4,1</key>
<string>iMac G3 (Early/Summer 2001)</string>
<key>PowerMac4,2</key>
<string>iMac G4 (Flat Panel)</string>
<key>PowerMac4,4</key>
<string>eMac</string>
<key>PowerMac4,5</key>
<string>iMac G4 (17-inch Flat Panel)</string>
<key>PowerMac5,1</key>
<string>Power Macintosh G4 Cube</string>
<key>PowerMac6,1</key>
<string>iMac G4 (USB 2.0)</string>
<key>PowerMac6,3</key>
<string>iMac G4 (20-inch Flat Panel)</string>
<key>PowerMac6,4</key>
<string>eMac (USB 2.0, 2005)</string>
<key>PowerMac7,2</key>
<string>Power Macintosh G5</string>
<key>PowerMac7,3</key>
<string>Power Macintosh G5</string>
<key>PowerMac8,1</key>
<string>iMac G5</string>
<key>PowerMac8,2</key>
<string>iMac G5 (Ambient Light Sensor)</string>
<key>PowerMac9,1</key>
<string>Power Macintosh G5 (Late 2005)</string>
<key>RackMac1,1</key>
<string>Xserve G4</string>
<key>RackMac1,2</key>
<string>Xserve G4 (slot-loading, cluster node)</string>
<key>RackMac3,1</key>
<string>Xserve G5</string>
<key>Xserve1,1</key>
<string>Xserve (Intel Xeon)</string>
<key>Xserve2,1</key>
<string>Xserve (January 2008 quad-core)</string>
</dict>
</plist>

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
A

View File

@ -0,0 +1,849 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
5C09B68E1A12B21600F970E8 /* Statistics.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C09B68D1A12B21600F970E8 /* Statistics.m */; };
5C0B60271A191C86009595C5 /* Application.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C0B60261A191C86009595C5 /* Application.m */; };
5C0B602A1A191CFD009595C5 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C0B60281A191CFD009595C5 /* MainMenu.xib */; };
5C14D8051A07EC4F007E6D59 /* VKPC1.icns in Resources */ = {isa = PBXBuildFile; fileRef = 5C14D8041A07EC4F007E6D59 /* VKPC1.icns */; };
5C14D8081A07ED84007E6D59 /* PopoverClipView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C14D8071A07ED84007E6D59 /* PopoverClipView.m */; };
5C14D80F1A07EECB007E6D59 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C14D80B1A07EE18007E6D59 /* QuartzCore.framework */; };
5C14D8121A07EF31007E6D59 /* PopoverScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C14D8111A07EF31007E6D59 /* PopoverScrollView.m */; };
5C2EF5421A0FCE80005442E0 /* Autostart.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C2EF5411A0FCE80005442E0 /* Autostart.m */; };
5C2F3A8C1A0FD58500C4ADB7 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C2F3A8B1A0FD58500C4ADB7 /* Sparkle.framework */; };
5C2F3A8E1A0FD5DE00C4ADB7 /* libwebsockets.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5CA5468119F94ADE0038F869 /* libwebsockets.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
5C2F3A8F1A0FD5E400C4ADB7 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5C2F3A8B1A0FD58500C4ADB7 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
5C2FFD0D19FE6DC900CB8FA3 /* vkpc-local.ch1p.com.key in Resources */ = {isa = PBXBuildFile; fileRef = 5C2FFD0B19FE6DC900CB8FA3 /* vkpc-local.ch1p.com.key */; };
5C2FFD0E19FEA25400CB8FA3 /* libwebsockets.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CA5468119F94ADE0038F869 /* libwebsockets.dylib */; };
5C2FFD1219FEA61B00CB8FA3 /* ssl_bundle.crt in Resources */ = {isa = PBXBuildFile; fileRef = 5C2FFD1119FEA61B00CB8FA3 /* ssl_bundle.crt */; };
5C2FFD9219FFCF7D00CB8FA3 /* ImagesLegacy.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 5C2FFD8F19FFCF7D00CB8FA3 /* ImagesLegacy.bundle */; };
5C2FFD9319FFCF7D00CB8FA3 /* ImagesYosemite.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 5C2FFD9019FFCF7D00CB8FA3 /* ImagesYosemite.bundle */; };
5C2FFD9419FFCF7D00CB8FA3 /* ImagesYosemiteDark.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 5C2FFD9119FFCF7D00CB8FA3 /* ImagesYosemiteDark.bundle */; };
5C2FFD9719FFFC3500CB8FA3 /* VibrantTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C2FFD9619FFFC3500CB8FA3 /* VibrantTextField.m */; };
5C2FFD9D1A00048100CB8FA3 /* VibrantButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C2FFD9C1A00048100CB8FA3 /* VibrantButton.m */; };
5C2FFDA01A001EF700CB8FA3 /* VibrantImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C2FFD9F1A001EF700CB8FA3 /* VibrantImageView.m */; };
5C2FFDA51A00FED500CB8FA3 /* PopoverImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C2FFDA41A00FED500CB8FA3 /* PopoverImageView.m */; };
5C4F7DD91844E65700394A5A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5C4F7DD71844E65700394A5A /* InfoPlist.strings */; };
5C4F7DDB1844E65700394A5A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C4F7DDA1844E65700394A5A /* main.m */; };
5C4F7DE21844E65700394A5A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C4F7DE11844E65700394A5A /* AppDelegate.m */; };
5C4F7DE71844E65700394A5A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5C4F7DE61844E65700394A5A /* Images.xcassets */; };
5C4F7E0B1844F8FF00394A5A /* SPMediaKeyTap.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C4F7E0A1844F8FF00394A5A /* SPMediaKeyTap.m */; };
5C4F7E151844F98900394A5A /* NSObject+SPInvocationGrabbing.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C4F7E111844F98900394A5A /* NSObject+SPInvocationGrabbing.m */; };
5C4FE5FA1848F07E0023CB77 /* inject.js in Resources */ = {isa = PBXBuildFile; fileRef = 5C67ADB01848E25F005B541C /* inject.js */; };
5C4FE6001849072A0023CB77 /* Server.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FE5FF1849072A0023CB77 /* Server.m */; };
5C5D418919F6D60500DEE14A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4F7DD31844E65700394A5A /* Foundation.framework */; };
5C5D418A19F6D60D00DEE14A /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4F7E161844FB2700394A5A /* Carbon.framework */; };
5C5D418D19F6D61700DEE14A /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4F7DCE1844E65700394A5A /* Cocoa.framework */; };
5C5D418E19F6D61D00DEE14A /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CC25BD11861AC4E00A69C93 /* IOKit.framework */; };
5C5D419719F7BDAF00DEE14A /* CatchMediaButtons.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C5D419619F7BDAF00DEE14A /* CatchMediaButtons.m */; };
5C5F831B18718A3A00E67F59 /* AppleRemote.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C5F831418718A3A00E67F59 /* AppleRemote.m */; };
5C5F831C18718A3A00E67F59 /* HIDRemoteControlDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C5F831618718A3A00E67F59 /* HIDRemoteControlDevice.m */; };
5C5F831D18718A3A00E67F59 /* MultiClickRemoteBehavior.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C5F831818718A3A00E67F59 /* MultiClickRemoteBehavior.m */; };
5C5F831E18718A3A00E67F59 /* RemoteControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C5F831A18718A3A00E67F59 /* RemoteControl.m */; };
5C67ADAC1848CB40005B541C /* Global.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C67ADAB1848CB40005B541C /* Global.m */; };
5C67ADB21848E25F005B541C /* inject.js in Sources */ = {isa = PBXBuildFile; fileRef = 5C67ADB01848E25F005B541C /* inject.js */; };
5C67ADB31848E25F005B541C /* inject.as in Resources */ = {isa = PBXBuildFile; fileRef = 5C67ADB11848E25F005B541C /* inject.as */; };
5C9D6D861A1BA5D100494738 /* NonVibrantButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C9D6D851A1BA5D100494738 /* NonVibrantButton.m */; };
5CA5463B19F8F10C0038F869 /* Controller.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CA5463A19F8F10C0038F869 /* Controller.m */; };
5CBCAC6C1A025C6400C8E803 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0E001A1847DACE00AA2D44 /* Security.framework */; };
5CD13449184B8919003295B0 /* FlippedView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CD13448184B8919003295B0 /* FlippedView.m */; };
5CD1345E184BAB48003295B0 /* PlaylistTableController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CD1345D184BAB48003295B0 /* PlaylistTableController.m */; };
5CD13471184BC185003295B0 /* PlaylistTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CD13470184BC185003295B0 /* PlaylistTableView.m */; };
5CD13474184BE8E2003295B0 /* PlaylistTableCellView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CD13473184BE8E2003295B0 /* PlaylistTableCellView.m */; };
5CD1347F184BF17E003295B0 /* PlaylistTableRowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CD1347E184BF17E003295B0 /* PlaylistTableRowView.m */; };
5CD13482184CFD5C003295B0 /* ShadowTextFieldCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CD13481184CFD5C003295B0 /* ShadowTextFieldCell.m */; };
5CD13486184E4A59003295B0 /* Queue.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CD13485184E4A59003295B0 /* Queue.m */; };
5CD1348C184E4C96003295B0 /* NSMutableArray+QueueAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CD1348B184E4C96003295B0 /* NSMutableArray+QueueAdditions.m */; };
5CEBA880184FC3C800EEB81E /* Playlist.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBA87F184FC3C800EEB81E /* Playlist.m */; };
5CF166A6184A164800FB9495 /* Popover.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CF166A5184A164800FB9495 /* Popover.m */; };
5CF166A9184A16EC00FB9495 /* PopoverController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CF166A8184A16EC00FB9495 /* PopoverController.m */; };
5CF166AB184A194D00FB9495 /* PopoverView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5CF166AA184A194D00FB9495 /* PopoverView.xib */; };
5CF166CB184A6B4D00FB9495 /* AboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5CF166CA184A6B4D00FB9495 /* AboutWindow.xib */; };
5CF166D0184AA01A00FB9495 /* AboutWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CF166CF184AA01A00FB9495 /* AboutWindowController.m */; };
5CF166D3184AA94800FB9495 /* WindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CF166D2184AA94800FB9495 /* WindowController.m */; };
5CFD57041A08228700891DA7 /* NSTimer+Blocks.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CFD57031A08228700891DA7 /* NSTimer+Blocks.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
5C2F3A8D1A0FD5C300C4ADB7 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
5C2F3A8E1A0FD5DE00C4ADB7 /* libwebsockets.dylib in CopyFiles */,
5C2F3A8F1A0FD5E400C4ADB7 /* Sparkle.framework in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
5C09B68C1A12B21600F970E8 /* Statistics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Statistics.h; sourceTree = "<group>"; };
5C09B68D1A12B21600F970E8 /* Statistics.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Statistics.m; sourceTree = "<group>"; };
5C0B60251A191C86009595C5 /* Application.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Application.h; sourceTree = "<group>"; };
5C0B60261A191C86009595C5 /* Application.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Application.m; sourceTree = "<group>"; };
5C0B60291A191CFD009595C5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
5C0E001A1847DACE00AA2D44 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
5C14D8041A07EC4F007E6D59 /* VKPC1.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = VKPC1.icns; sourceTree = "<group>"; };
5C14D8061A07ED84007E6D59 /* PopoverClipView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PopoverClipView.h; sourceTree = "<group>"; };
5C14D8071A07ED84007E6D59 /* PopoverClipView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PopoverClipView.m; sourceTree = "<group>"; };
5C14D8091A07EDC3007E6D59 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
5C14D80B1A07EE18007E6D59 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
5C14D80D1A07EE67007E6D59 /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = System/Library/Frameworks/Quartz.framework; sourceTree = SDKROOT; };
5C14D8101A07EF31007E6D59 /* PopoverScrollView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PopoverScrollView.h; sourceTree = "<group>"; };
5C14D8111A07EF31007E6D59 /* PopoverScrollView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PopoverScrollView.m; sourceTree = "<group>"; };
5C2EF5401A0FCE80005442E0 /* Autostart.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Autostart.h; sourceTree = "<group>"; };
5C2EF5411A0FCE80005442E0 /* Autostart.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Autostart.m; sourceTree = "<group>"; };
5C2F3A8B1A0FD58500C4ADB7 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Sparkle.framework; sourceTree = "<group>"; };
5C2FFD0B19FE6DC900CB8FA3 /* vkpc-local.ch1p.com.key */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "vkpc-local.ch1p.com.key"; sourceTree = "<group>"; };
5C2FFD1119FEA61B00CB8FA3 /* ssl_bundle.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ssl_bundle.crt; sourceTree = "<group>"; };
5C2FFD8F19FFCF7D00CB8FA3 /* ImagesLegacy.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = ImagesLegacy.bundle; sourceTree = "<group>"; };
5C2FFD9019FFCF7D00CB8FA3 /* ImagesYosemite.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = ImagesYosemite.bundle; sourceTree = "<group>"; };
5C2FFD9119FFCF7D00CB8FA3 /* ImagesYosemiteDark.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = ImagesYosemiteDark.bundle; sourceTree = "<group>"; };
5C2FFD9519FFFC3500CB8FA3 /* VibrantTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VibrantTextField.h; sourceTree = "<group>"; };
5C2FFD9619FFFC3500CB8FA3 /* VibrantTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VibrantTextField.m; sourceTree = "<group>"; };
5C2FFD9B1A00048100CB8FA3 /* VibrantButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VibrantButton.h; sourceTree = "<group>"; };
5C2FFD9C1A00048100CB8FA3 /* VibrantButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VibrantButton.m; sourceTree = "<group>"; };
5C2FFD9E1A001EF700CB8FA3 /* VibrantImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VibrantImageView.h; sourceTree = "<group>"; };
5C2FFD9F1A001EF700CB8FA3 /* VibrantImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VibrantImageView.m; sourceTree = "<group>"; };
5C2FFDA31A00FED500CB8FA3 /* PopoverImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PopoverImageView.h; sourceTree = "<group>"; };
5C2FFDA41A00FED500CB8FA3 /* PopoverImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PopoverImageView.m; sourceTree = "<group>"; };
5C441BDE18479493004175A0 /* Global.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Global.h; sourceTree = "<group>"; };
5C4F7DCB1844E65700394A5A /* VKPC.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VKPC.app; sourceTree = BUILT_PRODUCTS_DIR; };
5C4F7DCE1844E65700394A5A /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
5C4F7DD11844E65700394A5A /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
5C4F7DD21844E65700394A5A /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
5C4F7DD31844E65700394A5A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
5C4F7DD61844E65700394A5A /* VKPC-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "VKPC-Info.plist"; sourceTree = "<group>"; };
5C4F7DD81844E65700394A5A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
5C4F7DDA1844E65700394A5A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
5C4F7DDC1844E65700394A5A /* VKPC-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "VKPC-Prefix.pch"; sourceTree = "<group>"; };
5C4F7DE01844E65700394A5A /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
5C4F7DE11844E65700394A5A /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
5C4F7DE61844E65700394A5A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
5C4F7DF41844E65700394A5A /* VKPCTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "VKPCTests-Info.plist"; sourceTree = "<group>"; };
5C4F7DF61844E65700394A5A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
5C4F7DF81844E65700394A5A /* VKPCTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VKPCTests.m; sourceTree = "<group>"; };
5C4F7E091844F8FF00394A5A /* SPMediaKeyTap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPMediaKeyTap.h; sourceTree = "<group>"; };
5C4F7E0A1844F8FF00394A5A /* SPMediaKeyTap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMediaKeyTap.m; sourceTree = "<group>"; };
5C4F7E101844F98900394A5A /* NSObject+SPInvocationGrabbing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+SPInvocationGrabbing.h"; sourceTree = "<group>"; };
5C4F7E111844F98900394A5A /* NSObject+SPInvocationGrabbing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+SPInvocationGrabbing.m"; sourceTree = "<group>"; };
5C4F7E161844FB2700394A5A /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; };
5C4FE5FE1849072A0023CB77 /* Server.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Server.h; sourceTree = "<group>"; };
5C4FE5FF1849072A0023CB77 /* Server.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Server.m; sourceTree = "<group>"; };
5C5D418B19F6D61000DEE14A /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
5C5D418F19F6F7BE00DEE14A /* Types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Types.h; sourceTree = "<group>"; };
5C5D419519F7BDAF00DEE14A /* CatchMediaButtons.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CatchMediaButtons.h; sourceTree = "<group>"; };
5C5D419619F7BDAF00DEE14A /* CatchMediaButtons.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CatchMediaButtons.m; sourceTree = "<group>"; };
5C5F831318718A3A00E67F59 /* AppleRemote.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppleRemote.h; sourceTree = "<group>"; };
5C5F831418718A3A00E67F59 /* AppleRemote.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppleRemote.m; sourceTree = "<group>"; };
5C5F831518718A3A00E67F59 /* HIDRemoteControlDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HIDRemoteControlDevice.h; sourceTree = "<group>"; };
5C5F831618718A3A00E67F59 /* HIDRemoteControlDevice.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HIDRemoteControlDevice.m; sourceTree = "<group>"; };
5C5F831718718A3A00E67F59 /* MultiClickRemoteBehavior.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultiClickRemoteBehavior.h; sourceTree = "<group>"; };
5C5F831818718A3A00E67F59 /* MultiClickRemoteBehavior.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MultiClickRemoteBehavior.m; sourceTree = "<group>"; };
5C5F831918718A3A00E67F59 /* RemoteControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RemoteControl.h; sourceTree = "<group>"; };
5C5F831A18718A3A00E67F59 /* RemoteControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RemoteControl.m; sourceTree = "<group>"; };
5C67ADAB1848CB40005B541C /* Global.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Global.m; sourceTree = "<group>"; };
5C67ADB01848E25F005B541C /* inject.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = inject.js; path = scripts/inject.js; sourceTree = "<group>"; };
5C67ADB11848E25F005B541C /* inject.as */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = inject.as; path = scripts/inject.as; sourceTree = "<group>"; };
5C9D6D841A1BA5D100494738 /* NonVibrantButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NonVibrantButton.h; sourceTree = "<group>"; };
5C9D6D851A1BA5D100494738 /* NonVibrantButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NonVibrantButton.m; sourceTree = "<group>"; };
5C9D6D871A1BAA6100494738 /* NSUserNotificationCenter+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSUserNotificationCenter+Private.h"; sourceTree = "<group>"; };
5CA5463919F8F10C0038F869 /* Controller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Controller.h; sourceTree = "<group>"; };
5CA5463A19F8F10C0038F869 /* Controller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Controller.m; sourceTree = "<group>"; };
5CA5468119F94ADE0038F869 /* libwebsockets.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libwebsockets.dylib; sourceTree = "<group>"; };
5CBCAC6A1A025C3400C8E803 /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = System/Library/Frameworks/LocalAuthentication.framework; sourceTree = SDKROOT; };
5CC25BD11861AC4E00A69C93 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; };
5CD13447184B8919003295B0 /* FlippedView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FlippedView.h; sourceTree = "<group>"; };
5CD13448184B8919003295B0 /* FlippedView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FlippedView.m; sourceTree = "<group>"; };
5CD1345C184BAB48003295B0 /* PlaylistTableController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlaylistTableController.h; sourceTree = "<group>"; };
5CD1345D184BAB48003295B0 /* PlaylistTableController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlaylistTableController.m; sourceTree = "<group>"; };
5CD1346F184BC185003295B0 /* PlaylistTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlaylistTableView.h; sourceTree = "<group>"; };
5CD13470184BC185003295B0 /* PlaylistTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlaylistTableView.m; sourceTree = "<group>"; };
5CD13472184BE8E2003295B0 /* PlaylistTableCellView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlaylistTableCellView.h; sourceTree = "<group>"; };
5CD13473184BE8E2003295B0 /* PlaylistTableCellView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlaylistTableCellView.m; sourceTree = "<group>"; };
5CD1347D184BF17E003295B0 /* PlaylistTableRowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlaylistTableRowView.h; sourceTree = "<group>"; };
5CD1347E184BF17E003295B0 /* PlaylistTableRowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlaylistTableRowView.m; sourceTree = "<group>"; };
5CD13480184CFD5C003295B0 /* ShadowTextFieldCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShadowTextFieldCell.h; sourceTree = "<group>"; };
5CD13481184CFD5C003295B0 /* ShadowTextFieldCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShadowTextFieldCell.m; sourceTree = "<group>"; };
5CD13484184E4A59003295B0 /* Queue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Queue.h; sourceTree = "<group>"; };
5CD13485184E4A59003295B0 /* Queue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Queue.m; sourceTree = "<group>"; };
5CD13487184E4B95003295B0 /* QueueControllerProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QueueControllerProtocol.h; sourceTree = "<group>"; };
5CD1348A184E4C96003295B0 /* NSMutableArray+QueueAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableArray+QueueAdditions.h"; sourceTree = "<group>"; };
5CD1348B184E4C96003295B0 /* NSMutableArray+QueueAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableArray+QueueAdditions.m"; sourceTree = "<group>"; };
5CEBA87E184FC3C800EEB81E /* Playlist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Playlist.h; sourceTree = "<group>"; };
5CEBA87F184FC3C800EEB81E /* Playlist.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Playlist.m; sourceTree = "<group>"; };
5CF166A4184A164800FB9495 /* Popover.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Popover.h; path = AXStatusItemPopup/Popover.h; sourceTree = "<group>"; };
5CF166A5184A164800FB9495 /* Popover.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Popover.m; path = AXStatusItemPopup/Popover.m; sourceTree = "<group>"; };
5CF166A7184A16EC00FB9495 /* PopoverController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PopoverController.h; sourceTree = "<group>"; };
5CF166A8184A16EC00FB9495 /* PopoverController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PopoverController.m; sourceTree = "<group>"; };
5CF166AA184A194D00FB9495 /* PopoverView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PopoverView.xib; sourceTree = "<group>"; };
5CF166CA184A6B4D00FB9495 /* AboutWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AboutWindow.xib; sourceTree = "<group>"; };
5CF166CE184AA01A00FB9495 /* AboutWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AboutWindowController.h; sourceTree = "<group>"; };
5CF166CF184AA01A00FB9495 /* AboutWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AboutWindowController.m; sourceTree = "<group>"; };
5CF166D1184AA94800FB9495 /* WindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowController.h; sourceTree = "<group>"; };
5CF166D2184AA94800FB9495 /* WindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WindowController.m; sourceTree = "<group>"; };
5CFD57021A08228700891DA7 /* NSTimer+Blocks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSTimer+Blocks.h"; sourceTree = "<group>"; };
5CFD57031A08228700891DA7 /* NSTimer+Blocks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSTimer+Blocks.m"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
5C4F7DC81844E65700394A5A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5C14D80F1A07EECB007E6D59 /* QuartzCore.framework in Frameworks */,
5CBCAC6C1A025C6400C8E803 /* Security.framework in Frameworks */,
5C2FFD0E19FEA25400CB8FA3 /* libwebsockets.dylib in Frameworks */,
5C5D418E19F6D61D00DEE14A /* IOKit.framework in Frameworks */,
5C5D418D19F6D61700DEE14A /* Cocoa.framework in Frameworks */,
5C5D418A19F6D60D00DEE14A /* Carbon.framework in Frameworks */,
5C5D418919F6D60500DEE14A /* Foundation.framework in Frameworks */,
5C2F3A8C1A0FD58500C4ADB7 /* Sparkle.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
5C21778619F6A0CD0038495C /* Libs */ = {
isa = PBXGroup;
children = (
5C5F831218718A3A00E67F59 /* AppleRemote */,
);
name = Libs;
sourceTree = "<group>";
};
5C21778719F6A0DD0038495C /* Source */ = {
isa = PBXGroup;
children = (
5CBCAC701A02780D00C8E803 /* Catch Media Buttons */,
5CBCAC6F1A0277AF00C8E803 /* About Window */,
5CBCAC6D1A02776000C8E803 /* Popover */,
5CBCAC711A02782900C8E803 /* Playlist */,
5C21778619F6A0CD0038495C /* Libs */,
5C2FFD1319FEC4F300CB8FA3 /* Views */,
5C2FFDA21A004D3500CB8FA3 /* Main */,
5CBCAC721A02786600C8E803 /* Player Controller */,
5CF166D1184AA94800FB9495 /* WindowController.h */,
5CF166D2184AA94800FB9495 /* WindowController.m */,
5CD1348A184E4C96003295B0 /* NSMutableArray+QueueAdditions.h */,
5C9D6D871A1BAA6100494738 /* NSUserNotificationCenter+Private.h */,
5CD1348B184E4C96003295B0 /* NSMutableArray+QueueAdditions.m */,
5CFD57031A08228700891DA7 /* NSTimer+Blocks.m */,
5CFD57021A08228700891DA7 /* NSTimer+Blocks.h */,
);
name = Source;
sourceTree = "<group>";
};
5C2FFD0919FE6DC900CB8FA3 /* SSL */ = {
isa = PBXGroup;
children = (
5C2FFD1119FEA61B00CB8FA3 /* ssl_bundle.crt */,
5C2FFD0B19FE6DC900CB8FA3 /* vkpc-local.ch1p.com.key */,
);
path = SSL;
sourceTree = "<group>";
};
5C2FFD1319FEC4F300CB8FA3 /* Views */ = {
isa = PBXGroup;
children = (
5CD13447184B8919003295B0 /* FlippedView.h */,
5CD13448184B8919003295B0 /* FlippedView.m */,
5C9D6D841A1BA5D100494738 /* NonVibrantButton.h */,
5C9D6D851A1BA5D100494738 /* NonVibrantButton.m */,
5C2FFD9B1A00048100CB8FA3 /* VibrantButton.h */,
5C2FFD9C1A00048100CB8FA3 /* VibrantButton.m */,
5C2FFD9519FFFC3500CB8FA3 /* VibrantTextField.h */,
5C2FFD9619FFFC3500CB8FA3 /* VibrantTextField.m */,
5C2FFD9E1A001EF700CB8FA3 /* VibrantImageView.h */,
5C2FFD9F1A001EF700CB8FA3 /* VibrantImageView.m */,
5CD13480184CFD5C003295B0 /* ShadowTextFieldCell.h */,
5CD13481184CFD5C003295B0 /* ShadowTextFieldCell.m */,
);
name = Views;
sourceTree = "<group>";
};
5C2FFDA21A004D3500CB8FA3 /* Main */ = {
isa = PBXGroup;
children = (
5C0B60281A191CFD009595C5 /* MainMenu.xib */,
5C0B60251A191C86009595C5 /* Application.h */,
5C0B60261A191C86009595C5 /* Application.m */,
5C4F7DDA1844E65700394A5A /* main.m */,
5C441BDE18479493004175A0 /* Global.h */,
5C67ADAB1848CB40005B541C /* Global.m */,
5C5D418F19F6F7BE00DEE14A /* Types.h */,
5C4F7DE01844E65700394A5A /* AppDelegate.h */,
5C4F7DE11844E65700394A5A /* AppDelegate.m */,
5C2EF5401A0FCE80005442E0 /* Autostart.h */,
5C2EF5411A0FCE80005442E0 /* Autostart.m */,
5C09B68C1A12B21600F970E8 /* Statistics.h */,
5C09B68D1A12B21600F970E8 /* Statistics.m */,
);
name = Main;
sourceTree = "<group>";
};
5C4F7DC21844E65700394A5A = {
isa = PBXGroup;
children = (
5C4F7DD41844E65700394A5A /* VKPC */,
5C4F7DF21844E65700394A5A /* VKPCTests */,
5C4F7DCD1844E65700394A5A /* Frameworks */,
5C4F7DCC1844E65700394A5A /* Products */,
);
sourceTree = "<group>";
};
5C4F7DCC1844E65700394A5A /* Products */ = {
isa = PBXGroup;
children = (
5C4F7DCB1844E65700394A5A /* VKPC.app */,
);
name = Products;
sourceTree = "<group>";
};
5C4F7DCD1844E65700394A5A /* Frameworks */ = {
isa = PBXGroup;
children = (
5C2F3A8B1A0FD58500C4ADB7 /* Sparkle.framework */,
5C14D80D1A07EE67007E6D59 /* Quartz.framework */,
5C14D80B1A07EE18007E6D59 /* QuartzCore.framework */,
5C14D8091A07EDC3007E6D59 /* CoreGraphics.framework */,
5CBCAC6A1A025C3400C8E803 /* LocalAuthentication.framework */,
5CA5468119F94ADE0038F869 /* libwebsockets.dylib */,
5C5D418B19F6D61000DEE14A /* AudioToolbox.framework */,
5CC25BD11861AC4E00A69C93 /* IOKit.framework */,
5C0E001A1847DACE00AA2D44 /* Security.framework */,
5C4F7E161844FB2700394A5A /* Carbon.framework */,
5C4F7DCE1844E65700394A5A /* Cocoa.framework */,
5C4F7DD01844E65700394A5A /* Other Frameworks */,
);
name = Frameworks;
sourceTree = "<group>";
};
5C4F7DD01844E65700394A5A /* Other Frameworks */ = {
isa = PBXGroup;
children = (
5C4F7DD11844E65700394A5A /* AppKit.framework */,
5C4F7DD21844E65700394A5A /* CoreData.framework */,
5C4F7DD31844E65700394A5A /* Foundation.framework */,
);
name = "Other Frameworks";
sourceTree = "<group>";
};
5C4F7DD41844E65700394A5A /* VKPC */ = {
isa = PBXGroup;
children = (
5C14D8041A07EC4F007E6D59 /* VKPC1.icns */,
5C2FFD8F19FFCF7D00CB8FA3 /* ImagesLegacy.bundle */,
5C2FFD9019FFCF7D00CB8FA3 /* ImagesYosemite.bundle */,
5C2FFD9119FFCF7D00CB8FA3 /* ImagesYosemiteDark.bundle */,
5C2FFD0919FE6DC900CB8FA3 /* SSL */,
5C4F7E18184509CC00394A5A /* Scripts */,
5C21778719F6A0DD0038495C /* Source */,
5C4F7DE61844E65700394A5A /* Images.xcassets */,
5C4F7DD51844E65700394A5A /* Supporting Files */,
);
path = VKPC;
sourceTree = "<group>";
};
5C4F7DD51844E65700394A5A /* Supporting Files */ = {
isa = PBXGroup;
children = (
5C4F7DD61844E65700394A5A /* VKPC-Info.plist */,
5C4F7DD71844E65700394A5A /* InfoPlist.strings */,
5C4F7DDC1844E65700394A5A /* VKPC-Prefix.pch */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
5C4F7DF21844E65700394A5A /* VKPCTests */ = {
isa = PBXGroup;
children = (
5C4F7DF81844E65700394A5A /* VKPCTests.m */,
5C4F7DF31844E65700394A5A /* Supporting Files */,
);
path = VKPCTests;
sourceTree = "<group>";
};
5C4F7DF31844E65700394A5A /* Supporting Files */ = {
isa = PBXGroup;
children = (
5C4F7DF41844E65700394A5A /* VKPCTests-Info.plist */,
5C4F7DF51844E65700394A5A /* InfoPlist.strings */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
5C4F7E0C1844F98900394A5A /* SPInvocationGrabbing */ = {
isa = PBXGroup;
children = (
5C4F7E101844F98900394A5A /* NSObject+SPInvocationGrabbing.h */,
5C4F7E111844F98900394A5A /* NSObject+SPInvocationGrabbing.m */,
);
path = SPInvocationGrabbing;
sourceTree = "<group>";
};
5C4F7E18184509CC00394A5A /* Scripts */ = {
isa = PBXGroup;
children = (
5C67ADB01848E25F005B541C /* inject.js */,
5C67ADB11848E25F005B541C /* inject.as */,
);
name = Scripts;
sourceTree = "<group>";
};
5C5F831218718A3A00E67F59 /* AppleRemote */ = {
isa = PBXGroup;
children = (
5C5F831318718A3A00E67F59 /* AppleRemote.h */,
5C5F831418718A3A00E67F59 /* AppleRemote.m */,
5C5F831518718A3A00E67F59 /* HIDRemoteControlDevice.h */,
5C5F831618718A3A00E67F59 /* HIDRemoteControlDevice.m */,
5C5F831718718A3A00E67F59 /* MultiClickRemoteBehavior.h */,
5C5F831818718A3A00E67F59 /* MultiClickRemoteBehavior.m */,
5C5F831918718A3A00E67F59 /* RemoteControl.h */,
5C5F831A18718A3A00E67F59 /* RemoteControl.m */,
);
path = AppleRemote;
sourceTree = "<group>";
};
5CBCAC6D1A02776000C8E803 /* Popover */ = {
isa = PBXGroup;
children = (
5CF166AA184A194D00FB9495 /* PopoverView.xib */,
5CF166A4184A164800FB9495 /* Popover.h */,
5CF166A5184A164800FB9495 /* Popover.m */,
5CF166A7184A16EC00FB9495 /* PopoverController.h */,
5CF166A8184A16EC00FB9495 /* PopoverController.m */,
5C2FFDA31A00FED500CB8FA3 /* PopoverImageView.h */,
5C2FFDA41A00FED500CB8FA3 /* PopoverImageView.m */,
5C14D8061A07ED84007E6D59 /* PopoverClipView.h */,
5C14D8071A07ED84007E6D59 /* PopoverClipView.m */,
5C14D8101A07EF31007E6D59 /* PopoverScrollView.h */,
5C14D8111A07EF31007E6D59 /* PopoverScrollView.m */,
);
name = Popover;
sourceTree = "<group>";
};
5CBCAC6F1A0277AF00C8E803 /* About Window */ = {
isa = PBXGroup;
children = (
5CF166CA184A6B4D00FB9495 /* AboutWindow.xib */,
5CF166CE184AA01A00FB9495 /* AboutWindowController.h */,
5CF166CF184AA01A00FB9495 /* AboutWindowController.m */,
);
name = "About Window";
sourceTree = "<group>";
};
5CBCAC701A02780D00C8E803 /* Catch Media Buttons */ = {
isa = PBXGroup;
children = (
5CF166D9184AABB300FB9495 /* SPMediaKeyTap */,
5C5D419519F7BDAF00DEE14A /* CatchMediaButtons.h */,
5C5D419619F7BDAF00DEE14A /* CatchMediaButtons.m */,
);
name = "Catch Media Buttons";
sourceTree = "<group>";
};
5CBCAC711A02782900C8E803 /* Playlist */ = {
isa = PBXGroup;
children = (
5CEBA87E184FC3C800EEB81E /* Playlist.h */,
5CEBA87F184FC3C800EEB81E /* Playlist.m */,
5CD1345C184BAB48003295B0 /* PlaylistTableController.h */,
5CD1345D184BAB48003295B0 /* PlaylistTableController.m */,
5CD13472184BE8E2003295B0 /* PlaylistTableCellView.h */,
5CD13473184BE8E2003295B0 /* PlaylistTableCellView.m */,
5CD1347D184BF17E003295B0 /* PlaylistTableRowView.h */,
5CD1347E184BF17E003295B0 /* PlaylistTableRowView.m */,
5CD1346F184BC185003295B0 /* PlaylistTableView.h */,
5CD13470184BC185003295B0 /* PlaylistTableView.m */,
5CD13484184E4A59003295B0 /* Queue.h */,
5CD13485184E4A59003295B0 /* Queue.m */,
5CD13487184E4B95003295B0 /* QueueControllerProtocol.h */,
);
name = Playlist;
sourceTree = "<group>";
};
5CBCAC721A02786600C8E803 /* Player Controller */ = {
isa = PBXGroup;
children = (
5CA5463919F8F10C0038F869 /* Controller.h */,
5CA5463A19F8F10C0038F869 /* Controller.m */,
5C4FE5FE1849072A0023CB77 /* Server.h */,
5C4FE5FF1849072A0023CB77 /* Server.m */,
);
name = "Player Controller";
sourceTree = "<group>";
};
5CF166D9184AABB300FB9495 /* SPMediaKeyTap */ = {
isa = PBXGroup;
children = (
5C4F7E0C1844F98900394A5A /* SPInvocationGrabbing */,
5C4F7E091844F8FF00394A5A /* SPMediaKeyTap.h */,
5C4F7E0A1844F8FF00394A5A /* SPMediaKeyTap.m */,
);
name = SPMediaKeyTap;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
5C4F7DCA1844E65700394A5A /* VKPC */ = {
isa = PBXNativeTarget;
buildConfigurationList = 5C4F7DFC1844E65700394A5A /* Build configuration list for PBXNativeTarget "VKPC" */;
buildPhases = (
5C4F7DC71844E65700394A5A /* Sources */,
5C4F7DC81844E65700394A5A /* Frameworks */,
5C4F7DC91844E65700394A5A /* Resources */,
5CEEDC641A07A0D400DC2114 /* ShellScript */,
5C2F3A8D1A0FD5C300C4ADB7 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = VKPC;
productName = VKPC;
productReference = 5C4F7DCB1844E65700394A5A /* VKPC.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
5C4F7DC31844E65700394A5A /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0600;
ORGANIZATIONNAME = "Eugene Z";
};
buildConfigurationList = 5C4F7DC61844E65700394A5A /* Build configuration list for PBXProject "VKPC" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 5C4F7DC21844E65700394A5A;
productRefGroup = 5C4F7DCC1844E65700394A5A /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
5C4F7DCA1844E65700394A5A /* VKPC */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
5C4F7DC91844E65700394A5A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5C2FFD0D19FE6DC900CB8FA3 /* vkpc-local.ch1p.com.key in Resources */,
5C2FFD9319FFCF7D00CB8FA3 /* ImagesYosemite.bundle in Resources */,
5C2FFD9219FFCF7D00CB8FA3 /* ImagesLegacy.bundle in Resources */,
5C4FE5FA1848F07E0023CB77 /* inject.js in Resources */,
5C0B602A1A191CFD009595C5 /* MainMenu.xib in Resources */,
5C4F7DD91844E65700394A5A /* InfoPlist.strings in Resources */,
5CF166AB184A194D00FB9495 /* PopoverView.xib in Resources */,
5C2FFD1219FEA61B00CB8FA3 /* ssl_bundle.crt in Resources */,
5C2FFD9419FFCF7D00CB8FA3 /* ImagesYosemiteDark.bundle in Resources */,
5C14D8051A07EC4F007E6D59 /* VKPC1.icns in Resources */,
5CF166CB184A6B4D00FB9495 /* AboutWindow.xib in Resources */,
5C4F7DE71844E65700394A5A /* Images.xcassets in Resources */,
5C67ADB31848E25F005B541C /* inject.as in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
5CEEDC641A07A0D400DC2114 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 8;
files = (
);
inputPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 1;
shellPath = /bin/sh;
shellScript = "codesign --deep -f -s \"Self-signed Applications\" \"${BUILT_PRODUCTS_DIR}/${WRAPPER_NAME}/\"\ninstall_name_tool -id @loader_path/../Frameworks/libwebsockets.dylib libwebsockets.dylib";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
5C4F7DC71844E65700394A5A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5CD1348C184E4C96003295B0 /* NSMutableArray+QueueAdditions.m in Sources */,
5CF166D3184AA94800FB9495 /* WindowController.m in Sources */,
5CF166D0184AA01A00FB9495 /* AboutWindowController.m in Sources */,
5C67ADB21848E25F005B541C /* inject.js in Sources */,
5CD13449184B8919003295B0 /* FlippedView.m in Sources */,
5CF166A9184A16EC00FB9495 /* PopoverController.m in Sources */,
5CD13486184E4A59003295B0 /* Queue.m in Sources */,
5C4FE6001849072A0023CB77 /* Server.m in Sources */,
5CA5463B19F8F10C0038F869 /* Controller.m in Sources */,
5C5F831D18718A3A00E67F59 /* MultiClickRemoteBehavior.m in Sources */,
5C5F831C18718A3A00E67F59 /* HIDRemoteControlDevice.m in Sources */,
5C4F7DE21844E65700394A5A /* AppDelegate.m in Sources */,
5CD1345E184BAB48003295B0 /* PlaylistTableController.m in Sources */,
5CD13471184BC185003295B0 /* PlaylistTableView.m in Sources */,
5C2FFD9D1A00048100CB8FA3 /* VibrantButton.m in Sources */,
5CD1347F184BF17E003295B0 /* PlaylistTableRowView.m in Sources */,
5CD13482184CFD5C003295B0 /* ShadowTextFieldCell.m in Sources */,
5C2FFDA01A001EF700CB8FA3 /* VibrantImageView.m in Sources */,
5C4F7DDB1844E65700394A5A /* main.m in Sources */,
5C14D8121A07EF31007E6D59 /* PopoverScrollView.m in Sources */,
5C5F831E18718A3A00E67F59 /* RemoteControl.m in Sources */,
5C2FFDA51A00FED500CB8FA3 /* PopoverImageView.m in Sources */,
5CEBA880184FC3C800EEB81E /* Playlist.m in Sources */,
5C4F7E151844F98900394A5A /* NSObject+SPInvocationGrabbing.m in Sources */,
5C14D8081A07ED84007E6D59 /* PopoverClipView.m in Sources */,
5C09B68E1A12B21600F970E8 /* Statistics.m in Sources */,
5C0B60271A191C86009595C5 /* Application.m in Sources */,
5C9D6D861A1BA5D100494738 /* NonVibrantButton.m in Sources */,
5C2FFD9719FFFC3500CB8FA3 /* VibrantTextField.m in Sources */,
5C67ADAC1848CB40005B541C /* Global.m in Sources */,
5CFD57041A08228700891DA7 /* NSTimer+Blocks.m in Sources */,
5C5F831B18718A3A00E67F59 /* AppleRemote.m in Sources */,
5CF166A6184A164800FB9495 /* Popover.m in Sources */,
5C4F7E0B1844F8FF00394A5A /* SPMediaKeyTap.m in Sources */,
5CD13474184BE8E2003295B0 /* PlaylistTableCellView.m in Sources */,
5C5D419719F7BDAF00DEE14A /* CatchMediaButtons.m in Sources */,
5C2EF5421A0FCE80005442E0 /* Autostart.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
5C0B60281A191CFD009595C5 /* MainMenu.xib */ = {
isa = PBXVariantGroup;
children = (
5C0B60291A191CFD009595C5 /* Base */,
);
name = MainMenu.xib;
sourceTree = "<group>";
};
5C4F7DD71844E65700394A5A /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
5C4F7DD81844E65700394A5A /* en */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
};
5C4F7DF51844E65700394A5A /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
5C4F7DF61844E65700394A5A /* en */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
5C4F7DFA1844E65700394A5A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.8;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "";
SDKROOT = macosx;
};
name = Debug;
};
5C4F7DFB1844E65700394A5A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.8;
ONLY_ACTIVE_ARCH = NO;
OTHER_CFLAGS = "";
SDKROOT = macosx;
};
name = Release;
};
5C4F7DFD1844E65700394A5A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LIBRARY = "compiler-default";
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_IDENTITY = "Self-signed Applications";
COMBINE_HIDPI_IMAGES = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(DEVELOPER_DIR)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/System/Library/Frameworks",
"$(PROJECT_DIR)",
);
GCC_LINK_WITH_DYNAMIC_LIBRARIES = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "VKPC/VKPC-Prefix.pch";
GCC_PREPROCESSOR_DEFINITIONS = (
"_DEBUG=1",
"DEBUG=1",
"$(inherited)",
);
"GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = (
"DEBUG=1",
"_DEBUG=1",
"$(inherited)",
);
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
/usr/local/include,
/Users/evgeny/Development/Mac/libs/CocoaHTTPServer/Core,
);
INFOPLIST_FILE = "VKPC/VKPC-Info.plist";
LD_DYLIB_INSTALL_NAME = "";
LD_RUNPATH_SEARCH_PATHS = "";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(DEVELOPER_DIR)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/lib",
/Users/evgeny/dev/Mac,
"$(PROJECT_DIR)",
);
MACOSX_DEPLOYMENT_TARGET = 10.7;
OTHER_LDFLAGS = "-Wl,-rpath,@loader_path/../Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
USER_HEADER_SEARCH_PATHS = "";
WRAPPER_EXTENSION = app;
};
name = Debug;
};
5C4F7DFE1844E65700394A5A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LIBRARY = "compiler-default";
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_IDENTITY = "Self-signed Applications";
COMBINE_HIDPI_IMAGES = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(DEVELOPER_DIR)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/System/Library/Frameworks",
"$(PROJECT_DIR)",
);
GCC_LINK_WITH_DYNAMIC_LIBRARIES = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "VKPC/VKPC-Prefix.pch";
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
/usr/local/include,
/Users/evgeny/Development/Mac/libs/CocoaHTTPServer/Core,
);
INFOPLIST_FILE = "VKPC/VKPC-Info.plist";
LD_DYLIB_INSTALL_NAME = "";
LD_RUNPATH_SEARCH_PATHS = "";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(DEVELOPER_DIR)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/lib",
/Users/evgeny/dev/Mac,
"$(PROJECT_DIR)",
);
MACOSX_DEPLOYMENT_TARGET = 10.7;
OTHER_LDFLAGS = "-Wl,-rpath,@loader_path/../Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
USER_HEADER_SEARCH_PATHS = "";
WRAPPER_EXTENSION = app;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
5C4F7DC61844E65700394A5A /* Build configuration list for PBXProject "VKPC" */ = {
isa = XCConfigurationList;
buildConfigurations = (
5C4F7DFA1844E65700394A5A /* Debug */,
5C4F7DFB1844E65700394A5A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
5C4F7DFC1844E65700394A5A /* Build configuration list for PBXNativeTarget "VKPC" */ = {
isa = XCConfigurationList;
buildConfigurations = (
5C4F7DFD1844E65700394A5A /* Debug */,
5C4F7DFE1844E65700394A5A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 5C4F7DC31844E65700394A5A /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:VKPC.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildLocationStyle</key>
<string>UseAppPreferences</string>
<key>CustomBuildLocationType</key>
<string>RelativeToDerivedData</string>
<key>DerivedDataLocationStyle</key>
<string>Default</string>
<key>HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges</key>
<true/>
<key>IssueFilterStyle</key>
<string>ShowActiveSchemeOnly</string>
<key>LiveSourceIssuesEnabled</key>
<true/>
<key>SnapshotAutomaticallyBeforeSignificantChanges</key>
<true/>
<key>SnapshotLocationStyle</key>
<string>Default</string>
</dict>
</plist>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
type = "4"
version = "2.0">
</Bucket>

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
type = "1"
version = "2.0">
<Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.SymbolicBreakpoint">
<BreakpointContent
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "malloc_error_break"
moduleName = "">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "VKPC/Statistics.m"
timestampString = "437673144.95233"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "134"
endingLineNumber = "134"
landmarkName = "getOSXVersion()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "VKPC/PlaylistTableController.m"
timestampString = "437590603.092032"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "37"
endingLineNumber = "37"
landmarkName = "-init"
landmarkType = "5">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "VKPC/PopoverController.m"
timestampString = "446058171.654243"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "347"
endingLineNumber = "347"
landmarkName = "-menuItemBrowserAction:"
landmarkType = "5">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>

View File

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0600"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "5C4F7DCA1844E65700394A5A"
BuildableName = "VKPC.app"
BlueprintName = "VKPC"
ReferencedContainer = "container:VKPC.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "5C4F7DCA1844E65700394A5A"
BuildableName = "VKPC.app"
BlueprintName = "VKPC"
ReferencedContainer = "container:VKPC.xcodeproj">
</BuildableReference>
</MacroExpansion>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
allowLocationSimulation = "YES">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "5C4F7DCA1844E65700394A5A"
BuildableName = "VKPC.app"
BlueprintName = "VKPC"
ReferencedContainer = "container:VKPC.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "5C4F7DCA1844E65700394A5A"
BuildableName = "VKPC.app"
BlueprintName = "VKPC"
ReferencedContainer = "container:VKPC.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>VKPC.xcscheme</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict>
<key>5C4F7DCA1844E65700394A5A</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>5C4F7DEB1844E65700394A5A</key>
<dict>
<key>primary</key>
<true/>
</dict>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,30 @@
//
// Created by Alexander Schuch on 06/03/13.
// Modified by Eugene Zinoviev on 12/03/13.
// Copyright (c) 2013 Alexander Schuch. All rights reserved.
// Copyright (c) 2013-2014 Eugene Zinoviev. All rights reserved.
//
#import <Cocoa/Cocoa.h>
//#import "PopupControllerProtocol.h"
#import "PopoverController.h"
@interface Popover : NSView
@property(assign, nonatomic, getter=isActive) BOOL active;
@property(strong, nonatomic) NSImage *defaultImage;
@property(strong, nonatomic) NSImage *altImage;
@property(strong, nonatomic) NSImage *flatImage;
@property(strong, nonatomic) NSStatusItem *statusItem;
@property(strong) NSPopover *popover;
+ (id)shared;
- (id)init;
- (void)showPopover;
- (void)hidePopover;
- (NSSize)getSize;
- (void)setSize:(NSSize)size animate:(BOOL)animate;
@end

View File

@ -0,0 +1,163 @@
//
// Created by Alexander Schuch on 06/03/13.
// Modified by Eugene Zinoviev on 12/03/13.
// Copyright (c) 2013 Alexander Schuch. All rights reserved.
// Copyright (c) 2013-2014 Eugene Zinoviev. All rights reserved.
//
#import "Popover.h"
#import "PopoverImageView.h"
static const int kMinViewWidth = 28;
@implementation Popover {
PopoverImageView *imageView;
id popoverTransiencyMonitor;
BOOL popoverTransiencyMonitorEnabled;
// BOOL firstDrawed;
// id eventMonitor;
// BOOL escMonitorSet;
}
+ (id)shared {
static Popover *shared = nil;
@synchronized (self) {
if (shared == nil){
shared = [[self alloc] init];
}
}
return shared;
}
- (id)init {
popoverTransiencyMonitorEnabled = NO;
// firstDrawed = NO;
CGFloat height = [NSStatusBar systemStatusBar].thickness;
_active = NO;
// escMonitorSet = NO;
if (self = [super initWithFrame:NSMakeRect(0, 0, kMinViewWidth, height)]) {
imageView = [[PopoverImageView alloc] initWithFrame:NSMakeRect(0, 0, kMinViewWidth, height)];
[self addSubview:imageView];
_statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
_statusItem.view = self;
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
// [self showPopover];
// });
}
return self;
}
- (BOOL)allowsVibrancy {
return YES;
}
- (void)drawRect:(NSRect)dirtyRect {
// if (!firstDrawed) {
// firstDrawed = YES;
// [self showPopover];
// }
if (_active) {
[[NSColor selectedMenuItemColor] setFill];
} else {
[[NSColor clearColor] setFill];
}
NSRectFill(dirtyRect);
NSDictionary *images = VKPCGetImagesDictionary();
NSImage *imgDefault = images[VKPCImageStatus], *imgActive = images[VKPCImageStatusPressed];
imageView.image = _active ? imgActive : imgDefault;
}
- (void)mouseDown:(NSEvent *)theEvent {
if (_popover.isShown) {
[self hidePopover];
} else {
[self showPopover];
}
}
- (void)setActive:(BOOL)active {
_active = active;
[self setNeedsDisplay:YES];
}
- (void)updateViewFrame {
CGFloat width = MAX(MAX(kMinViewWidth, _altImage.size.width), _defaultImage.size.width);
CGFloat height = [NSStatusBar systemStatusBar].thickness;
NSRect frame = NSMakeRect(0, 0, width, height);
self.frame = frame;
imageView.frame = frame;
[self setNeedsDisplay:YES];
}
- (void)showPopover {
self.active = YES;
if (!_popover) {
_popover = [[NSPopover alloc] init];
_popover.contentViewController = [PopoverController shared];
// NSEvent *(^handler)(NSEvent *) = ^NSEvent *(NSEvent *theEvent) {
// if (theEvent.keyCode == 53) {
// NSLog(@"[Popover] catch ESC");
// return nil;
// }
//
// return theEvent;
// };
//
// eventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:handler];
};
if (!_popover.isShown) {
_popover.animates = NO;
[_popover showRelativeToRect:self.frame ofView:self preferredEdge:NSMinYEdge];
if (!popoverTransiencyMonitorEnabled) {
popoverTransiencyMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:NSLeftMouseDownMask|NSRightMouseDownMask handler:^(NSEvent* event) {
[self hidePopover];
}];
popoverTransiencyMonitorEnabled = YES;
}
}
[[PopoverController shared] popoverDidShow];
}
- (void)hidePopover {
self.active = NO;
if (_popover && _popover.isShown) {
[_popover close];
[[PopoverController shared] popoverDidHide];
if (popoverTransiencyMonitorEnabled) {
[NSEvent removeMonitor:popoverTransiencyMonitor];
popoverTransiencyMonitorEnabled = NO;
}
}
}
- (NSSize)getSize {
return [_popover contentSize];
}
- (void)setSize:(NSSize)size animate:(BOOL)animate {
BOOL bkAnimates = _popover.animates;
_popover.animates = animate;
[_popover setContentSize:size];
_popover.animates = bkAnimates;
}
@end

74
VKPC/AboutWindow.xib Normal file
View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6254" systemVersion="14D72i" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6254"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="AboutWindowController">
<connections>
<outlet property="ch1pTextField" destination="RKx-9V-CuI" id="wrk-64-JVE"/>
<outlet property="copyrightTextField" destination="ERc-8w-PEv" id="Oqv-42-sHP"/>
<outlet property="ezTextField" destination="ptd-Ue-h4k" id="Xqt-aa-z4S"/>
<outlet property="titleTextField" destination="9iH-bO-wdX" id="6Tm-zx-l1p"/>
<outlet property="window" destination="G5N-nr-30p" id="wNb-Cf-WGv"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="About" allowsToolTipsWhenApplicationIsInactive="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="G5N-nr-30p">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<rect key="contentRect" x="131" y="159" width="336" height="152"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
<view key="contentView" id="NwM-Tg-Ru9">
<rect key="frame" x="0.0" y="0.0" width="336" height="152"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ERc-8w-PEv">
<rect key="frame" x="31" y="52" width="274" height="22"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="center" title="Eugene Z © 2013-2015" id="uv2-0i-619">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ptd-Ue-h4k">
<rect key="frame" x="31" y="33" width="274" height="23"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="center" title="vk.com/ez" id="yTi-Tb-TPm">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9iH-bO-wdX">
<rect key="frame" x="14" y="121" width="309" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Title" id="sen-Gb-PyL">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1Eu-bN-jqb">
<rect key="frame" x="-9" y="84" width="354" height="34"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="center" id="Ar7-Dg-5Ft">
<font key="font" metaFont="system"/>
<string key="title">Use media buttons (F7-F9) to switch
between tracks</string>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="RKx-9V-CuI">
<rect key="frame" x="31" y="14" width="274" height="23"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="center" title="ch1p.com/vkpc/" id="lw6-hX-ors">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
</view>
<point key="canvasLocation" x="280" y="254"/>
</window>
</objects>
</document>

View File

@ -0,0 +1,22 @@
//
// AboutWindowController.h
// VKPC
//
// Created by Eugene on 12/1/13.
// Copyright (c) 2013 Eugene Z. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import "WindowController.h"
@interface AboutWindowController : WindowController<NSWindowDelegate>
@property (strong) IBOutlet NSWindow *window;
@property (weak) IBOutlet NSTextField *titleTextField;
@property (weak) IBOutlet NSTextField *copyrightTextField;
@property (weak) IBOutlet NSTextField *ezTextField;
@property (weak) IBOutlet NSTextField *ch1pTextField;
//- (IBAction)sendEmailAction:(id)sender;
@end

View File

@ -0,0 +1,90 @@
//
// AboutWindowController.m
// VKPC
//
// Created by Eugene on 12/1/13.
// Copyright (c) 2013 Eugene Z. All rights reserved.
//
#import "AboutWindowController.h"
static NSString * const ezURL = @"<a href=\"http://vk.com/ez\">vk.com/ez</a>";
static NSString * const ch1pURL = @"<a href=\"http://ch1p.com/vkpc/\">ch1p.com/vkpc/</a>";
@implementation AboutWindowController
- (BOOL)allowsClosingWithShortcut {
return YES;
}
static void setStyleForAttributedString(NSMutableAttributedString *string) {
NSRange range = NSMakeRange(0, string.length);
NSFont *font = [NSFont fontWithName:GetSystemFontName() size:13.0];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setAlignment:NSCenterTextAlignment];
[paragraphStyle setLineSpacing:3];
[string addAttributes:[NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName] range:range];
[string addAttributes:[NSDictionary dictionaryWithObject:paragraphStyle forKey:NSParagraphStyleAttributeName] range:range];
}
- (void)windowDidLoad {
[super windowDidLoad];
// Title
NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:kCFBundleShortVersionString];
if (VKPCIsDebug)
version = [NSString stringWithFormat:@"%@ %@", version, @"dev"];
NSString *title = [NSString stringWithFormat:@"%@ %@ (build %@)",
[[[NSBundle mainBundle] infoDictionary] objectForKey:kCFBundleDisplayName],
version,
[[[NSBundle mainBundle] infoDictionary] objectForKey:kCFBundleVersion]];
[_titleTextField setStringValue:title];
NSDictionary *stringOptions = @{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)};
// Copyright
// NSMutableAttributedString *copyright = [[NSMutableAttributedString alloc] initWithHTML:[copyrightHTML dataUsingEncoding:NSUTF8StringEncoding]
// options:stringOptions
// documentAttributes:nil];
// setStyleForAttributedString(copyright);
// EZ Link
NSMutableAttributedString *ez = [[NSMutableAttributedString alloc] initWithHTML:[ezURL dataUsingEncoding:NSUTF8StringEncoding]
options:stringOptions
documentAttributes:nil];
setStyleForAttributedString(ez);
[_ezTextField setAllowsEditingTextAttributes:YES];
[_ezTextField setSelectable:YES];
[_ezTextField setAttributedStringValue:ez];
// CH1P Link
NSMutableAttributedString *ch1p = [[NSMutableAttributedString alloc] initWithHTML:[ch1pURL dataUsingEncoding:NSUTF8StringEncoding]
options:stringOptions
documentAttributes:nil];
setStyleForAttributedString(ch1p);
[_ch1pTextField setAllowsEditingTextAttributes:YES];
[_ch1pTextField setSelectable:YES];
[_ch1pTextField setAttributedStringValue:ch1p];
// [_copyrightTextField setAllowsEditingTextAttributes:YES];
// [_copyrightTextField setSelectable:YES];
// [_copyrightTextFie/ld setAttributedStringValue:copyright];
}
//- (IBAction)sendEmailAction:(id)sender {
// NSString *encodedSubject = [NSString stringWithFormat:@"SUBJECT=%@", [@"VK Player Controller" stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
// NSString *encodedTo = [CH1PEmail stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
//
// NSString *encodedURLString = [NSString stringWithFormat:@"mailto:%@?%@&%@", encodedTo, encodedSubject, @""];
// NSURL *mailtoURL = [NSURL URLWithString:encodedURLString];
//
// [[NSWorkspace sharedWorkspace] openURL:mailtoURL];
//}
@end

14
VKPC/AppDelegate.h Normal file
View File

@ -0,0 +1,14 @@
//
// AppDelegate.h
// VKPC
//
// Created by Eugene on 11/26/13.
// Copyright (c) 2013-2014 Eugene Z. All rights reserved.
//
@interface AppDelegate : NSObject
+ (AppDelegate *)shared;
- (void)continueRunning;
@end

76
VKPC/AppDelegate.m Normal file
View File

@ -0,0 +1,76 @@
//
// AppDelegate.m
// VKPC
//
// Created by Eugene on 11/26/13.
// Copyright (c) 2013-2014 Eugene Z. All rights reserved.
//
#import "AppDelegate.h"
#import "Server.h"
#import "CatchMediaButtons.h"
#import "Controller.h"
//#import "HostsHack.h"
#import "Statistics.h"
#import "PopoverController.h"
#import "Popover.h"
static AppDelegate *shared;
@implementation AppDelegate
+ (AppDelegate *)shared {
return shared;
}
- (void)awakeFromNib {
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
shared = self;
if (IsAnotherProcessRunning()) {
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:@"Error"];
[alert setInformativeText:@"Another VKPC process is already running."];
[alert setAlertStyle:NSWarningAlertStyle];
[alert runModal];
[[NSApplication sharedApplication] terminate:nil];
return;
}
// Init UI stuff
[Popover shared];
[PopoverController shared];
// Preferences
[[NSUserDefaults standardUserDefaults] registerDefaults:@{
VKPCPreferencesShowNotifications: [NSNumber numberWithBool:YES],
VKPCPreferencesInvertPlaylistIcons: [NSNumber numberWithBool:YES],
VKPCPreferencesCatchMediaButtons: [NSNumber numberWithBool:YES],
VKPCPreferencesBrowser: [NSNumber numberWithInt:0],
VKPCPreferencesStatisticReportedTimestamp: [NSNumber numberWithInt:0],
VKPCPreferencesUUID: @"",
VKPCPreferencesUseExtensionMode: [NSNumber numberWithBool:NO],
}];
VKPCInitUUID();
// Start catching (or not catching) media buttons
[CatchMediaButtons initialize];
// Usage reporting
[Statistics initialize];
[[PopoverController shared] setState:PopoverStatePlaylistNotLoaded];
// Start server in a background thread
[Server start];
// Controller
[Controller initialize];
}
@end

View File

@ -0,0 +1,42 @@
/*****************************************************************************
* RemoteControlWrapper.h
* RemoteControlWrapper
*
* Created by Martin Kahr on 11.03.06 under a MIT-style license.
* Copyright (c) 2006 martinkahr.com. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED ÄúAS ISÄù, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
#import <Cocoa/Cocoa.h>
#import "HIDRemoteControlDevice.h"
/* Interacts with the Apple Remote Control HID device
The class is not thread safe
*/
@interface AppleRemote : HIDRemoteControlDevice {
BOOL lastSecureEventInputState;
io_object_t eventSecureInputNotification;
IONotificationPortRef notifyPort;
}
- (BOOL) retrieveSecureEventInputState;
@end

View File

@ -0,0 +1,319 @@
/*****************************************************************************
* RemoteControlWrapper.m
* RemoteControlWrapper
*
* Created by Martin Kahr on 11.03.06 under a MIT-style license.
* Copyright (c) 2006 martinkahr.com. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
#import "AppleRemote.h"
#import <IOKit/IOKitLib.h>
#import <IOKit/IOCFPlugIn.h>
#import <IOKit/hid/IOHIDKeys.h>
#import <IOKit/IOKitLib.h>
static void IOREInterestCallback(
void * refcon,
io_service_t service,
uint32_t messageType,
void * messageArgument );
#ifndef NSAppKitVersionNumber10_4
#define NSAppKitVersionNumber10_4 824
#endif
#ifndef NSAppKitVersionNumber10_5
#define NSAppKitVersionNumber10_5 949
#endif
const char* AppleRemoteDeviceName = "AppleIRController";
@implementation AppleRemote
+ (const char*) remoteControlDeviceName {
return AppleRemoteDeviceName;
}
- (id) initWithDelegate: (id) _remoteControlDelegate {
if ((self = [super initWithDelegate: _remoteControlDelegate])) {
// A security update in february of 2007 introduced an odd behavior.
// Whenever SecureEventInput is activated or deactivated the exclusive access
// to the apple remote control device is lost. This leads to very strange behavior where
// a press on the Menu button activates FrontRow while your app still gets the event.
// A great number of people have complained about this.
//
// Finally I found a way to get the state of the SecureEventInput
// With that information I regain access to the device each time when the SecureEventInput state
// is changing.
io_registry_entry_t root = IORegistryGetRootEntry( kIOMasterPortDefault );
if (root != MACH_PORT_NULL) {
notifyPort = IONotificationPortCreate( kIOMasterPortDefault );
if (notifyPort) {
CFRunLoopSourceRef runLoopSource = IONotificationPortGetRunLoopSource(notifyPort);
CFRunLoopRef gRunLoop = CFRunLoopGetCurrent();
CFRunLoopAddSource(gRunLoop, runLoopSource, kCFRunLoopDefaultMode);
io_registry_entry_t entry = IORegistryEntryFromPath( kIOMasterPortDefault, kIOServicePlane ":/");
if (entry != MACH_PORT_NULL) {
kern_return_t kr;
kr = IOServiceAddInterestNotification(notifyPort,
entry,
kIOBusyInterest,
&IOREInterestCallback, (__bridge void *)self, &eventSecureInputNotification );
if (kr != KERN_SUCCESS) {
NSLog(@"Error when installing EventSecureInput Notification");
IONotificationPortDestroy(notifyPort);
notifyPort = NULL;
}
IOObjectRelease(entry);
}
}
IOObjectRelease(root);
}
lastSecureEventInputState = [self retrieveSecureEventInputState];
}
return self;
}
- (void)dealloc
{
if (notifyPort) {
IONotificationPortDestroy(notifyPort);
notifyPort = NULL;
}
IOObjectRelease (eventSecureInputNotification);
eventSecureInputNotification = MACH_PORT_NULL;
// [super dealloc];
}
- (void)finalize
{
IONotificationPortDestroy(notifyPort);
notifyPort = NULL;
// Although IOObjectRelease is not documented as thread safe, I was assured at WWDC09 that it is.
IOObjectRelease (eventSecureInputNotification);
eventSecureInputNotification = MACH_PORT_NULL;
[super finalize];
}
- (void) setCookieMappingInDictionary: (NSMutableDictionary*) _cookieToButtonMapping {
// check if we are using the rb device driver instead of the one from Apple
io_object_t foundRemoteDevice = [[self class] findRemoteDevice];
BOOL leopardEmulation = NO;
if (foundRemoteDevice != 0) {
CFTypeRef leoEmuAttr = IORegistryEntryCreateCFProperty(foundRemoteDevice, CFSTR("RemoteBuddyEmulationV2"), kCFAllocatorDefault, 0);
if (leoEmuAttr) {
leopardEmulation = CFEqual(leoEmuAttr, kCFBooleanTrue);
CFRelease(leoEmuAttr);
}
IOObjectRelease(foundRemoteDevice);
}
if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_4) {
// 10.4.x Tiger
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlus] forKey:@"14_12_11_6_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMinus] forKey:@"14_13_11_6_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu] forKey:@"14_7_6_14_7_6_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"14_8_6_14_8_6_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight] forKey:@"14_9_6_14_9_6_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft] forKey:@"14_10_6_14_10_6_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold] forKey:@"14_6_4_2_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold] forKey:@"14_6_3_2_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold] forKey:@"14_6_14_6_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Hold] forKey:@"18_14_6_18_14_6_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"19_"];
} else if ((floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_5) || (leopardEmulation)) {
// 10.5.x Leopard
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlus] forKey:@"31_29_28_19_18_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMinus] forKey:@"31_30_28_19_18_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu] forKey:@"31_20_19_18_31_20_19_18_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"31_21_19_18_31_21_19_18_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight] forKey:@"31_22_19_18_31_22_19_18_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft] forKey:@"31_23_19_18_31_23_19_18_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold] forKey:@"31_19_18_4_2_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold] forKey:@"31_19_18_3_2_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold] forKey:@"31_19_18_31_19_18_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Hold] forKey:@"35_31_19_18_35_31_19_18_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"19_"];
} else {
// 10.6.2 Snow Leopard
// Note: does not work on 10.6.0 and 10.6.1
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlus] forKey:@"33_31_30_21_20_2_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMinus] forKey:@"33_32_30_21_20_2_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu] forKey:@"33_22_21_20_2_33_22_21_20_2_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"33_23_21_20_2_33_23_21_20_2_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight] forKey:@"33_24_21_20_2_33_24_21_20_2_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft] forKey:@"33_25_21_20_2_33_25_21_20_2_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold] forKey:@"33_21_20_14_12_2_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold] forKey:@"33_21_20_13_12_2_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold] forKey:@"33_21_20_2_33_21_20_2_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Hold] forKey:@"37_33_21_20_2_37_33_21_20_2_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"19_"];
// new Aluminum model
// Mappings changed due to addition of a 7th center button
// Treat the new center button and play/pause button the same
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"33_21_20_8_2_33_21_20_8_2_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"33_21_20_3_2_33_21_20_3_2_"];
[_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Hold] forKey:@"33_21_20_11_2_33_21_20_11_2_"];
}
}
- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown {
if (pressedDown == NO && event == kRemoteButtonMenu_Hold) {
// There is no seperate event for pressed down on menu hold. We are simulating that event here
[super sendRemoteButtonEvent:event pressedDown:YES];
}
[super sendRemoteButtonEvent:event pressedDown:pressedDown];
if (pressedDown && (event == kRemoteButtonRight || event == kRemoteButtonLeft || event == kRemoteButtonPlay || event == kRemoteButtonMenu || event == kRemoteButtonPlay_Hold)) {
// There is no seperate event when the button is being released. We are simulating that event here
[super sendRemoteButtonEvent:event pressedDown:NO];
}
}
// overwritten to handle a special case with old versions of the rb driver
+ (io_object_t) findRemoteDevice
{
CFMutableDictionaryRef hidMatchDictionary = NULL;
IOReturn ioReturnValue = kIOReturnSuccess;
io_iterator_t hidObjectIterator = 0;
io_object_t hidDevice = 0;
// Set up a matching dictionary to search the I/O Registry by class
// name for all HID class devices
hidMatchDictionary = IOServiceMatching([self remoteControlDeviceName]);
// Now search I/O Registry for matching devices.
ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, hidMatchDictionary, &hidObjectIterator);
if ((ioReturnValue == kIOReturnSuccess) && (hidObjectIterator != 0))
{
io_object_t matchingService = 0, foundService = 0;
BOOL finalMatch = NO;
while ((matchingService = IOIteratorNext(hidObjectIterator)))
{
if (!finalMatch)
{
CFTypeRef className;
if (!foundService)
{
if (IOObjectRetain(matchingService) == kIOReturnSuccess)
{
foundService = matchingService;
}
}
if ((className = IORegistryEntryCreateCFProperty((io_registry_entry_t)matchingService, CFSTR("IOClass"), kCFAllocatorDefault, 0)))
{
if ([(__bridge NSString *)className isEqual:[NSString stringWithUTF8String:[self remoteControlDeviceName]]])
{
if (foundService)
{
IOObjectRelease(foundService);
foundService = 0;
}
if (IOObjectRetain(matchingService) == kIOReturnSuccess)
{
foundService = matchingService;
finalMatch = YES;
}
}
CFRelease(className);
}
}
IOObjectRelease(matchingService);
}
hidDevice = foundService;
// release the iterator
IOObjectRelease(hidObjectIterator);
}
return hidDevice;
}
- (BOOL) retrieveSecureEventInputState {
BOOL returnValue = NO;
io_registry_entry_t root = IORegistryGetRootEntry( kIOMasterPortDefault );
if (root != MACH_PORT_NULL) {
CFArrayRef arrayRef = IORegistryEntrySearchCFProperty(root, kIOServicePlane, CFSTR("IOConsoleUsers"), NULL, kIORegistryIterateRecursively);
if (arrayRef != NULL) {
NSArray* array = (__bridge NSArray*)arrayRef;
unsigned int i;
for(i=0; i < [array count]; i++) {
NSDictionary* dict = [array objectAtIndex:i];
if ([[dict objectForKey: @"kCGSSessionUserNameKey"] isEqual: NSUserName()]) {
returnValue = ([dict objectForKey:@"kCGSSessionSecureInputPID"] != nil);
}
}
CFRelease(arrayRef);
}
IOObjectRelease(root);
}
return returnValue;
}
- (void) dealWithSecureEventInputChange {
if ([self isListeningToRemote] == NO || [self isOpenInExclusiveMode] == NO) return;
BOOL newState = [self retrieveSecureEventInputState];
if (lastSecureEventInputState == newState) return;
// close and open the device again
[self closeRemoteControlDevice: NO];
[self openRemoteControlDevice];
lastSecureEventInputState = newState;
}
static void IOREInterestCallback(void * refcon,
io_service_t service,
uint32_t messageType,
void * messageArgument )
{
(void)service;
(void)messageType;
(void)messageArgument;
// With garbage collection, such a cast is dangerous because the refcon parameter is not strong. That means that, unless someone has a strong reference somewhere, the AppleRemote may have already been finalized. But it should be pretty safe in this case, since if the AppleRemote is finalized, the callback is cancelled and should never be invoked.
AppleRemote* remote = (__bridge AppleRemote*)refcon;
[remote dealWithSecureEventInputChange];
}
@end

View File

@ -0,0 +1,72 @@
/*****************************************************************************
* HIDRemoteControlDevice.h
* RemoteControlWrapper
*
* Created by Martin Kahr on 11.03.06 under a MIT-style license.
* Copyright (c) 2006 martinkahr.com. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED ÄúAS ISÄù, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
#import <Cocoa/Cocoa.h>
#import <IOKit/hid/IOHIDLib.h>
#import "RemoteControl.h"
/*
Base class for HID based remote control devices
*/
@interface HIDRemoteControlDevice : RemoteControl {
IOHIDDeviceInterface** hidDeviceInterface;
IOHIDQueueInterface** queue;
NSMutableArray* allCookies;
NSMutableDictionary* cookieToButtonMapping;
// __strong CFRunLoopSourceRef eventSource;
CFRunLoopSourceRef eventSource;
BOOL openInExclusiveMode;
BOOL processesBacklog;
int supportedButtonEvents;
}
// When your application needs to much time on the main thread when processing an event other events
// may already be received which are put on a backlog. As soon as your main thread
// has some spare time this backlog is processed and may flood your delegate with calls.
// Backlog processing is turned off by default.
- (BOOL) processesBacklog;
- (void) setProcessesBacklog: (BOOL) value;
// methods that should be overridden by subclasses
- (void) setCookieMappingInDictionary: (NSMutableDictionary*) cookieToButtonMapping;
- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown;
+ (const char*) remoteControlDeviceName;
// protected methods
- (void) openRemoteControlDevice;
- (void) closeRemoteControlDevice: (BOOL) shallSendNotifications;
+ (io_object_t) findRemoteDevice;
+ (BOOL) isRemoteAvailable;
@end

View File

@ -0,0 +1,535 @@
/*****************************************************************************
* HIDRemoteControlDevice.m
* RemoteControlWrapper
*
* Created by Martin Kahr on 11.03.06 under a MIT-style license.
* Copyright (c) 2006 martinkahr.com. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
#import "HIDRemoteControlDevice.h"
#import <IOKit/IOKitLib.h>
#import <IOKit/IOCFPlugIn.h>
#import <IOKit/hid/IOHIDKeys.h>
@interface HIDRemoteControlDevice (PrivateMethods)
- (NSDictionary*) cookieToButtonMapping;
- (IOHIDQueueInterface**) queue;
- (IOHIDDeviceInterface**) hidDeviceInterface;
- (void) handleEventWithCookieString: (NSString*) cookieString sumOfValues: (SInt32) sumOfValues;
- (void) removeNotifcationObserver;
- (void) remoteControlAvailable:(NSNotification *)notification;
@end
@interface HIDRemoteControlDevice (IOKitMethods)
- (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice;
- (BOOL) initializeCookies;
- (BOOL) openDevice;
@end
@implementation HIDRemoteControlDevice
// This class acts as an abstract base class - therefore subclasses have to override this method
+ (const char*) remoteControlDeviceName {
return "";
}
+ (BOOL) isRemoteAvailable {
io_object_t hidDevice = [self findRemoteDevice];
if (hidDevice != 0) {
IOObjectRelease(hidDevice);
return YES;
} else {
return NO;
}
}
+ (io_object_t) findRemoteDevice {
CFMutableDictionaryRef hidMatchDictionary = NULL;
IOReturn ioReturnValue = kIOReturnSuccess;
io_iterator_t hidObjectIterator = 0;
io_object_t hidDevice = 0;
// Set up a matching dictionary to search the I/O Registry by class
// name for all HID class devices
hidMatchDictionary = IOServiceMatching([self remoteControlDeviceName]);
// Now search I/O Registry for matching devices.
ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, hidMatchDictionary, &hidObjectIterator);
if (hidObjectIterator != 0) {
if (ioReturnValue == kIOReturnSuccess) {
hidDevice = IOIteratorNext(hidObjectIterator);
}
// release the iterator
IOObjectRelease(hidObjectIterator);
}
// Returned value must be released by the caller when it is finished
return hidDevice;
}
- (id) initWithDelegate: (id) _remoteControlDelegate {
if ([[self class] isRemoteAvailable] == NO) {
// [super dealloc];
self = nil;
} else if ( (self = [super initWithDelegate: _remoteControlDelegate]) ) {
openInExclusiveMode = YES;
queue = NULL;
hidDeviceInterface = NULL;
cookieToButtonMapping = [[NSMutableDictionary alloc] init];
[self setCookieMappingInDictionary: cookieToButtonMapping];
NSEnumerator* enumerator = [cookieToButtonMapping objectEnumerator];
NSNumber* identifier;
supportedButtonEvents = 0;
while( (identifier = [enumerator nextObject]) ) {
supportedButtonEvents |= [identifier intValue];
}
}
return self;
}
- (void) dealloc {
[self removeNotifcationObserver];
[self stopListening:self];
// [cookieToButtonMapping release];
cookieToButtonMapping = nil;
// [super dealloc];
}
- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown {
[delegate sendRemoteButtonEvent: event pressedDown: pressedDown remoteControl:self];
}
- (void) setCookieMappingInDictionary: (NSMutableDictionary*) aCookieToButtonMapping {
(void)aCookieToButtonMapping;
}
- (int) remoteIdSwitchCookie {
return 0;
}
- (BOOL) sendsEventForButtonIdentifier: (RemoteControlEventIdentifier) identifier {
return (supportedButtonEvents & identifier) == identifier;
}
- (BOOL) isListeningToRemote {
return (hidDeviceInterface != NULL && allCookies != NULL && queue != NULL);
}
- (void) setListeningToRemote: (BOOL) value {
if (value == NO) {
[self stopListening:self];
} else {
[self startListening:self];
}
}
- (BOOL) isOpenInExclusiveMode {
return openInExclusiveMode;
}
- (void) setOpenInExclusiveMode: (BOOL) value {
openInExclusiveMode = value;
}
- (BOOL) processesBacklog {
return processesBacklog;
}
- (void) setProcessesBacklog: (BOOL) value {
processesBacklog = value;
}
- (void) openRemoteControlDevice {
io_object_t hidDevice = [[self class] findRemoteDevice];
if (hidDevice == 0) return;
if ([self createInterfaceForDevice:hidDevice] == NULL) {
goto error;
}
if ([self initializeCookies]==NO) {
goto error;
}
if ([self openDevice]==NO) {
goto error;
}
goto cleanup;
error:
[self stopListening:self];
cleanup:
IOObjectRelease(hidDevice);
}
- (void) closeRemoteControlDevice: (BOOL) shallSendNotifications {
BOOL sendNotification = NO;
if (eventSource != NULL) {
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode);
CFRelease(eventSource);
eventSource = NULL;
}
if (queue != NULL) {
(*queue)->stop(queue);
//dispose of queue
(*queue)->dispose(queue);
//release the queue we allocated
(*queue)->Release(queue);
queue = NULL;
sendNotification = YES;
}
if (allCookies != nil) {
// [allCookies autorelease];
allCookies = nil;
}
if (hidDeviceInterface != NULL) {
//close the device
(*hidDeviceInterface)->close(hidDeviceInterface);
//release the interface
(*hidDeviceInterface)->Release(hidDeviceInterface);
hidDeviceInterface = NULL;
}
if (shallSendNotifications && [self isOpenInExclusiveMode] && sendNotification) {
[[self class] sendFinishedNotifcationForAppIdentifier: nil];
}
}
- (IBAction) startListening: (id) sender {
(void)sender;
if ([self isListeningToRemote]) return;
[self willChangeValueForKey:@"listeningToRemote"];
[self openRemoteControlDevice];
[self didChangeValueForKey:@"listeningToRemote"];
}
- (IBAction) stopListening: (id) sender {
(void)sender;
if ([self isListeningToRemote]==NO) return;
[self willChangeValueForKey:@"listeningToRemote"];
[self closeRemoteControlDevice: YES];
[self didChangeValueForKey:@"listeningToRemote"];
}
@end
@implementation HIDRemoteControlDevice (PrivateMethods)
- (IOHIDQueueInterface**) queue {
return queue;
}
- (IOHIDDeviceInterface**) hidDeviceInterface {
return hidDeviceInterface;
}
- (NSDictionary*) cookieToButtonMapping {
return cookieToButtonMapping;
}
- (NSString*) validCookieSubstring: (NSString*) cookieString {
if (cookieString == nil || [cookieString length] == 0) return nil;
NSEnumerator* keyEnum = [[self cookieToButtonMapping] keyEnumerator];
NSString* key;
// find the best match
while( (key = [keyEnum nextObject]) ) {
NSRange range = [cookieString rangeOfString:key];
if (range.location == 0) return key;
}
return nil;
}
- (void) handleEventWithCookieString: (NSString*) cookieString sumOfValues: (SInt32) sumOfValues {
/*
if (previousRemainingCookieString) {
cookieString = [previousRemainingCookieString stringByAppendingString: cookieString];
NSLog(@"New cookie string is %@", cookieString);
[previousRemainingCookieString release], previousRemainingCookieString=nil;
}*/
if (cookieString == nil || [cookieString length] == 0) return;
NSNumber* buttonId = [[self cookieToButtonMapping] objectForKey: cookieString];
if (buttonId != nil) {
[self sendRemoteButtonEvent: [buttonId intValue] pressedDown: (sumOfValues>0)];
} else {
// let's see if this is the first event after a restart of the OS.
// In this case the event has a prefix that we can ignore and we just get the down event but no up event
NSEnumerator* keyEnum = [[self cookieToButtonMapping] keyEnumerator];
NSString* key;
while( (key = [keyEnum nextObject]) ) {
NSRange range = [cookieString rangeOfString:key];
if (range.location != NSNotFound && range.location > 0) {
buttonId = [[self cookieToButtonMapping] objectForKey: key];
if (buttonId != nil) {
[self sendRemoteButtonEvent: [buttonId intValue] pressedDown: YES];
[self sendRemoteButtonEvent: [buttonId intValue] pressedDown: NO];
return;
}
return;
}
}
// let's see if a number of events are stored in the cookie string. this does
// happen when the main thread is too busy to handle all incoming events in time.
NSString* subCookieString;
NSString* lastSubCookieString=nil;
while( (subCookieString = [self validCookieSubstring: cookieString]) ) {
cookieString = [cookieString substringFromIndex: [subCookieString length]];
lastSubCookieString = subCookieString;
if (processesBacklog) [self handleEventWithCookieString: subCookieString sumOfValues:sumOfValues];
}
if (processesBacklog == NO && lastSubCookieString != nil) {
// process the last event of the backlog and assume that the button is not pressed down any longer.
// The events in the backlog do not seem to be in order and therefore (in rare cases) the last event might be
// a button pressed down event while in reality the user has released it.
// NSLog(@"processing last event of backlog");
[self handleEventWithCookieString: lastSubCookieString sumOfValues:0];
}
if ([cookieString length] > 0) {
NSLog(@"Unknown button for cookiestring %@", cookieString);
}
}
}
- (void) removeNotifcationObserver {
NSDistributedNotificationCenter* defaultCenter = [NSDistributedNotificationCenter defaultCenter];
[defaultCenter removeObserver:self name:FINISHED_USING_REMOTE_CONTROL_NOTIFICATION object:nil];
}
- (void) remoteControlAvailable:(NSNotification *)notification {
(void)notification;
[self removeNotifcationObserver];
[self startListening: self];
}
@end
/* Callback method for the device queue
Will be called for any event of any type (cookie) to which we subscribe
*/
static void QueueCallbackFunction(void* target, IOReturn result, void* refcon, void* sender) {
(void)refcon;
(void)sender;
if (target == NULL) {
NSLog(@"QueueCallbackFunction called with invalid target!");
return;
}
// NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
HIDRemoteControlDevice* remote = (__bridge HIDRemoteControlDevice*)target;
IOHIDEventStruct event;
AbsoluteTime zeroTime = {0,0};
NSMutableString* cookieString = [NSMutableString string];
SInt32 sumOfValues = 0;
while (result == kIOReturnSuccess)
{
result = (*[remote queue])->getNextEvent([remote queue], &event, zeroTime, 0);
if ( result != kIOReturnSuccess )
continue;
//printf("%u %d %p\n", event.elementCookie, event.value, event.longValue);
if (((int)event.elementCookie)!=5) {
sumOfValues+=event.value;
[cookieString appendString:[NSString stringWithFormat:@"%u_", event.elementCookie]];
}
}
[remote handleEventWithCookieString: cookieString sumOfValues: sumOfValues];
// [pool release];
}
@implementation HIDRemoteControlDevice (IOKitMethods)
- (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice {
io_name_t className;
IOCFPlugInInterface** plugInInterface = NULL;
HRESULT plugInResult = S_OK;
SInt32 score = 0;
IOReturn ioReturnValue = kIOReturnSuccess;
hidDeviceInterface = NULL;
ioReturnValue = IOObjectGetClass(hidDevice, className);
if (ioReturnValue != kIOReturnSuccess) {
NSLog(@"Error: Failed to get class name.");
return NULL;
}
ioReturnValue = IOCreatePlugInInterfaceForService(hidDevice,
kIOHIDDeviceUserClientTypeID,
kIOCFPlugInInterfaceID,
&plugInInterface,
&score);
if (ioReturnValue == kIOReturnSuccess)
{
//Call a method of the intermediate plug-in to create the device interface
plugInResult = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (LPVOID) &hidDeviceInterface);
if (plugInResult != S_OK) {
NSLog(@"Error: Couldn't create HID class device interface");
}
// Release
if (plugInInterface) (*plugInInterface)->Release(plugInInterface);
}
return hidDeviceInterface;
}
- (BOOL) initializeCookies {
IOHIDDeviceInterface122** handle = (IOHIDDeviceInterface122**)hidDeviceInterface;
IOHIDElementCookie cookie;
//long usage;
//long usagePage;
id object;
CFArrayRef elements = nil;
NSDictionary* element;
IOReturn success;
if (!handle || !(*handle)) return NO;
// Copy all elements, since we're grabbing most of the elements
// for this device anyway, and thus, it's faster to iterate them
// ourselves. When grabbing only one or two elements, a matching
// dictionary should be passed in here instead of NULL.
success = (*handle)->copyMatchingElements(handle, NULL, &elements);
if ( (success == kIOReturnSuccess) && elements ) {
/*
cookies = calloc(NUMBER_OF_APPLE_REMOTE_ACTIONS, sizeof(IOHIDElementCookie));
memset(cookies, 0, sizeof(IOHIDElementCookie) * NUMBER_OF_APPLE_REMOTE_ACTIONS);
*/
allCookies = [[NSMutableArray alloc] init];
NSEnumerator *elementsEnumerator = [(__bridge NSArray*)elements objectEnumerator];
while ( (element = [elementsEnumerator nextObject]) ) {
//Get cookie
object = [element valueForKey:@kIOHIDElementCookieKey ];
if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue;
if (object == 0 || CFGetTypeID((__bridge const void *)object) != CFNumberGetTypeID()) continue;
cookie = (IOHIDElementCookie) [object longValue];
//Get usage
object = [element valueForKey: @kIOHIDElementUsageKey ];
if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue;
//usage = [object longValue];
//Get usage page
object = [element valueForKey: @kIOHIDElementUsagePageKey ];
if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue;
//usagePage = [object longValue];
//It seems wrong to cast a cookie to a 32 bit integer since it is a void*, but in 64 bit it's actually a uint32_t! So in both 32 and 64 bit it is 32 bit in size.
[allCookies addObject: [NSNumber numberWithUnsignedInt:(uint32_t)cookie]];
}
CFRelease(elements);
} else {
return NO;
}
return YES;
}
- (BOOL)openDevice {
HRESULT result;
IOHIDOptionsType openMode = kIOHIDOptionsTypeNone;
if ([self isOpenInExclusiveMode]) openMode = kIOHIDOptionsTypeSeizeDevice;
IOReturn ioReturnValue = (*hidDeviceInterface)->open(hidDeviceInterface, openMode);
if (ioReturnValue == KERN_SUCCESS) {
queue = (*hidDeviceInterface)->allocQueue(hidDeviceInterface);
if (queue) {
result = (*queue)->create(queue, 0, 12); //depth: maximum number of elements in queue before oldest elements in queue begin to be lost.
if (result == kIOReturnSuccess) {
IOHIDElementCookie cookie;
NSEnumerator *allCookiesEnumerator = [allCookies objectEnumerator];
while ( (cookie = (IOHIDElementCookie)[[allCookiesEnumerator nextObject] unsignedIntValue]) ) {
(*queue)->addElement(queue, cookie, 0);
}
// add callback for async events
ioReturnValue = (*queue)->createAsyncEventSource(queue, &eventSource);
if (ioReturnValue == KERN_SUCCESS) {
ioReturnValue = (*queue)->setEventCallout(queue,QueueCallbackFunction, (__bridge void *)self, NULL);
if (ioReturnValue == KERN_SUCCESS) {
CFRunLoopAddSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode);
//start data delivery to queue
(*queue)->start(queue);
return YES;
} else {
NSLog(@"Error when setting event callback");
}
} else {
NSLog(@"Error when creating async event source");
}
} else {
NSLog(@"Error when creating queue");
}
} else {
NSLog(@"Error when opening device");
}
} else if (ioReturnValue == kIOReturnExclusiveAccess) {
// the device is used exclusive by another application
// 1. we register for the FINISHED_USING_REMOTE_CONTROL_NOTIFICATION notification
NSDistributedNotificationCenter* defaultCenter = [NSDistributedNotificationCenter defaultCenter];
[defaultCenter addObserver:self selector:@selector(remoteControlAvailable:) name:FINISHED_USING_REMOTE_CONTROL_NOTIFICATION object:nil];
// 2. send a distributed notification that we wanted to use the remote control
[[self class] sendRequestForRemoteControlNotification];
}
return NO;
}
@end

View File

@ -0,0 +1,90 @@
/*****************************************************************************
* MultiClickRemoteBehavior.h
* RemoteControlWrapper
*
* Created by Martin Kahr on 11.03.06 under a MIT-style license.
* Copyright (c) 2006 martinkahr.com. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
#import <Cocoa/Cocoa.h>
#import "RemoteControl.h"
/**
A behavior that adds multiclick and hold events on top of a device.
Events are generated and send to a delegate
*/
@interface MultiClickRemoteBehavior : NSObject {
id delegate;
// state for simulating plus/minus hold
BOOL simulateHoldEvents;
BOOL lastEventSimulatedHold;
RemoteControlEventIdentifier lastHoldEvent;
NSTimeInterval lastHoldEventTime;
// state for multi click
unsigned int clickCountEnabledButtons;
NSTimeInterval maxClickTimeDifference;
NSTimeInterval lastClickCountEventTime;
RemoteControlEventIdentifier lastClickCountEvent;
unsigned int eventClickCount;
}
- (id) init;
// Delegates are not retained
- (void) setDelegate: (id) delegate;
- (id) delegate;
// Simulating hold events does deactivate sending of individual requests for pressed down/released.
// Instead special hold events are being triggered when the user is pressing and holding a button for a small period.
// Simulation is activated only for those buttons and remote control that do not have a seperate event already
- (BOOL) simulateHoldEvent;
- (void) setSimulateHoldEvent: (BOOL) value;
// click counting makes it possible to recognize if the user has pressed a button repeatedly
// click counting does delay each event as it has to wait if there is another event (second click)
// therefore there is a slight time difference (maximumClickCountTimeDifference) between a single click
// of the user and the call of your delegate method
// click counting can be enabled individually for specific buttons. Use the property clickCountEnableButtons to
// set the buttons for which click counting shall be enabled
- (BOOL) clickCountingEnabled;
- (void) setClickCountingEnabled: (BOOL) value;
- (unsigned int) clickCountEnabledButtons;
- (void) setClickCountEnabledButtons: (unsigned int)value;
// the maximum time difference till which clicks are recognized as multi clicks
- (NSTimeInterval) maximumClickCountTimeDifference;
- (void) setMaximumClickCountTimeDifference: (NSTimeInterval) timeDiff;
@end
/*
* Method definitions for the delegate of the MultiClickRemoteBehavior class
*/
@interface NSObject(MultiClickRemoteBehaviorDelegate)
- (void) remoteButton: (RemoteControlEventIdentifier)buttonIdentifier pressedDown: (BOOL) pressedDown clickCount: (unsigned int) count;
@end

View File

@ -0,0 +1,214 @@
/*****************************************************************************
* MultiClickRemoteBehavior.m
* RemoteControlWrapper
*
* Created by Martin Kahr on 11.03.06 under a MIT-style license.
* Copyright (c) 2006 martinkahr.com. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
#import "MultiClickRemoteBehavior.h"
const NSTimeInterval DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE=0.35;
const NSTimeInterval HOLD_RECOGNITION_TIME_INTERVAL=0.4;
@implementation MultiClickRemoteBehavior
- (id) init {
if (self = [super init]) {
maxClickTimeDifference = DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE;
}
return self;
}
// Delegates are not retained!
// http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CommunicatingWithObjects/chapter_6_section_4.html
// Delegating objects do not (and should not) retain their delegates.
// However, clients of delegating objects (applications, usually) are responsible for ensuring that their delegates are around
// to receive delegation messages. To do this, they may have to retain the delegate.
- (void) setDelegate: (id) _delegate {
if (_delegate && [_delegate respondsToSelector:@selector(remoteButton:pressedDown:clickCount:)]==NO) return;
delegate = _delegate;
}
- (id) delegate {
return delegate;
}
- (BOOL) simulateHoldEvent {
return simulateHoldEvents;
}
- (void) setSimulateHoldEvent: (BOOL) value {
simulateHoldEvents = value;
}
- (BOOL) simulatesHoldForButtonIdentifier: (RemoteControlEventIdentifier) identifier remoteControl: (RemoteControl*) remoteControl {
// we do that check only for the normal button identifiers as we would check for hold support for hold events instead
if (identifier > (1 << EVENT_TO_HOLD_EVENT_OFFSET)) return NO;
return [self simulateHoldEvent] && [remoteControl sendsEventForButtonIdentifier: (identifier << EVENT_TO_HOLD_EVENT_OFFSET)]==NO;
}
- (BOOL) clickCountingEnabled {
return clickCountEnabledButtons != 0;
}
- (void) setClickCountingEnabled: (BOOL) value {
if (value) {
[self setClickCountEnabledButtons: kRemoteButtonPlus | kRemoteButtonMinus | kRemoteButtonPlay | kRemoteButtonLeft | kRemoteButtonRight | kRemoteButtonMenu];
} else {
[self setClickCountEnabledButtons: 0];
}
}
- (unsigned int) clickCountEnabledButtons {
return clickCountEnabledButtons;
}
- (void) setClickCountEnabledButtons: (unsigned int)value {
clickCountEnabledButtons = value;
}
- (NSTimeInterval) maximumClickCountTimeDifference {
return maxClickTimeDifference;
}
- (void) setMaximumClickCountTimeDifference: (NSTimeInterval) timeDiff {
maxClickTimeDifference = timeDiff;
}
- (void) sendPressedDownEventToMainThread: (NSNumber*) event {
[delegate remoteButton:[event intValue] pressedDown:YES clickCount:1];
}
- (void) sendSimulatedHoldEvent: (id) time {
BOOL startSimulateHold = NO;
RemoteControlEventIdentifier event = lastHoldEvent;
@synchronized(self) {
startSimulateHold = (lastHoldEvent>0 && lastHoldEventTime == [time doubleValue]);
}
if (startSimulateHold) {
lastEventSimulatedHold = YES;
event = (event << EVENT_TO_HOLD_EVENT_OFFSET);
[self performSelectorOnMainThread:@selector(sendPressedDownEventToMainThread:) withObject:[NSNumber numberWithInt:event] waitUntilDone:NO];
}
}
- (void) executeClickCountEvent: (NSArray*) values {
RemoteControlEventIdentifier event = [[values objectAtIndex: 0] unsignedIntValue];
NSTimeInterval eventTimePoint = [[values objectAtIndex: 1] doubleValue];
BOOL finishedClicking = NO;
int finalClickCount = eventClickCount;
@synchronized(self) {
finishedClicking = (event != lastClickCountEvent || eventTimePoint == lastClickCountEventTime);
if (finishedClicking) {
eventClickCount = 0;
lastClickCountEvent = 0;
lastClickCountEventTime = 0;
}
}
if (finishedClicking) {
[delegate remoteButton:event pressedDown: YES clickCount:finalClickCount];
// trigger a button release event, too
[NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow:0.1]];
[delegate remoteButton:event pressedDown: NO clickCount:finalClickCount];
}
}
- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown remoteControl: (RemoteControl*) remoteControl {
if (!delegate) return;
BOOL clickCountingForEvent = ([self clickCountEnabledButtons] & event) == event;
if ([self simulatesHoldForButtonIdentifier: event remoteControl: remoteControl] && lastClickCountEvent==0) {
if (pressedDown) {
// wait to see if it is a hold
lastHoldEvent = event;
lastHoldEventTime = [NSDate timeIntervalSinceReferenceDate];
[self performSelector:@selector(sendSimulatedHoldEvent:)
withObject:[NSNumber numberWithDouble:lastHoldEventTime]
afterDelay:HOLD_RECOGNITION_TIME_INTERVAL];
return;
} else {
if (lastEventSimulatedHold) {
// it was a hold
// send an event for "hold release"
event = (event << EVENT_TO_HOLD_EVENT_OFFSET);
lastHoldEvent = 0;
lastEventSimulatedHold = NO;
[delegate remoteButton:event pressedDown: pressedDown clickCount:1];
return;
} else {
RemoteControlEventIdentifier previousEvent = lastHoldEvent;
@synchronized(self) {
lastHoldEvent = 0;
}
// in case click counting is enabled we have to setup the state for that, too
if (clickCountingForEvent) {
lastClickCountEvent = previousEvent;
lastClickCountEventTime = lastHoldEventTime;
NSNumber* eventNumber;
NSNumber* timeNumber;
eventClickCount = 1;
timeNumber = [NSNumber numberWithDouble:lastClickCountEventTime];
eventNumber= [NSNumber numberWithUnsignedInt:previousEvent];
NSTimeInterval diffTime = maxClickTimeDifference-([NSDate timeIntervalSinceReferenceDate]-lastHoldEventTime);
[self performSelector: @selector(executeClickCountEvent:)
withObject: [NSArray arrayWithObjects:eventNumber, timeNumber, nil]
afterDelay: diffTime];
// we do not return here because we are still in the press-release event
// that will be consumed below
} else {
// trigger the pressed down event that we consumed first
[delegate remoteButton:event pressedDown: YES clickCount:1];
}
}
}
}
if (clickCountingForEvent) {
if (pressedDown == NO) return;
NSNumber* eventNumber;
NSNumber* timeNumber;
@synchronized(self) {
lastClickCountEventTime = [NSDate timeIntervalSinceReferenceDate];
if (lastClickCountEvent == event) {
eventClickCount = eventClickCount + 1;
} else {
eventClickCount = 1;
}
lastClickCountEvent = event;
timeNumber = [NSNumber numberWithDouble:lastClickCountEventTime];
eventNumber= [NSNumber numberWithUnsignedInt:event];
}
[self performSelector: @selector(executeClickCountEvent:)
withObject: [NSArray arrayWithObjects:eventNumber, timeNumber, nil]
afterDelay: maxClickTimeDifference];
} else {
[delegate remoteButton:event pressedDown: pressedDown clickCount:1];
}
}
@end

View File

@ -0,0 +1,105 @@
/*****************************************************************************
* RemoteControl.h
* RemoteControlWrapper
*
* Created by Martin Kahr on 11.03.06 under a MIT-style license.
* Copyright (c) 2006 martinkahr.com. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
#import <Cocoa/Cocoa.h>
// notifaction names that are being used to signal that an application wants to
// have access to the remote control device or if the application has finished
// using the remote control device
extern NSString* const REQUEST_FOR_REMOTE_CONTROL_NOTIFCATION;
extern NSString* const FINISHED_USING_REMOTE_CONTROL_NOTIFICATION;
// keys used in user objects for distributed notifications
extern NSString* const kRemoteControlDeviceName;
extern NSString* const kApplicationIdentifier;
extern NSString* const kTargetApplicationIdentifier;
// we have a 6 bit offset to make a hold event out of a normal event
#define EVENT_TO_HOLD_EVENT_OFFSET 6
@class RemoteControl;
typedef enum _RemoteControlEventIdentifier {
// normal events
kRemoteButtonPlus =1<<1,
kRemoteButtonMinus =1<<2,
kRemoteButtonMenu =1<<3,
kRemoteButtonPlay =1<<4,
kRemoteButtonRight =1<<5,
kRemoteButtonLeft =1<<6,
// hold events
kRemoteButtonPlus_Hold =1<<7,
kRemoteButtonMinus_Hold =1<<8,
kRemoteButtonMenu_Hold =1<<9,
kRemoteButtonPlay_Hold =1<<10,
kRemoteButtonRight_Hold =1<<11,
kRemoteButtonLeft_Hold =1<<12,
// special events (not supported by all devices)
kRemoteControl_Switched =1<<13,
} RemoteControlEventIdentifier;
@interface NSObject(RemoteControlDelegate)
- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown remoteControl: (RemoteControl*) remoteControl;
@end
/*
Base Interface for Remote Control devices
*/
@interface RemoteControl : NSObject {
id delegate;
}
// returns nil if the remote control device is not available
- (id) initWithDelegate: (id) remoteControlDelegate;
- (void) setDelegate: (id) value;
- (id) delegate;
- (void) setListeningToRemote: (BOOL) value;
- (BOOL) isListeningToRemote;
- (BOOL) isOpenInExclusiveMode;
- (void) setOpenInExclusiveMode: (BOOL) value;
- (IBAction) startListening: (id) sender;
- (IBAction) stopListening: (id) sender;
// is this remote control sending the given event?
- (BOOL) sendsEventForButtonIdentifier: (RemoteControlEventIdentifier) identifier;
// sending of notifications between applications
+ (void) sendFinishedNotifcationForAppIdentifier: (NSString*) identifier;
+ (void) sendRequestForRemoteControlNotification;
// name of the device
+ (const char*) remoteControlDeviceName;
@end

View File

@ -0,0 +1,120 @@
/*****************************************************************************
* RemoteControl.m
* RemoteControlWrapper
*
* Created by Martin Kahr on 11.03.06 under a MIT-style license.
* Copyright (c) 2006 martinkahr.com. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
#import "RemoteControl.h"
// notifaction names that are being used to signal that an application wants to
// have access to the remote control device or if the application has finished
// using the remote control device
NSString* const REQUEST_FOR_REMOTE_CONTROL_NOTIFCATION = @"mac.remotecontrols.RequestForRemoteControl";
NSString* const FINISHED_USING_REMOTE_CONTROL_NOTIFICATION = @"mac.remotecontrols.FinishedUsingRemoteControl";
// keys used in user objects for distributed notifications
NSString* const kRemoteControlDeviceName = @"RemoteControlDeviceName";
NSString* const kApplicationIdentifier = @"CFBundleIdentifier";
// bundle identifier of the application that should get access to the remote control
// this key is being used in the FINISHED notification only
NSString* const kTargetApplicationIdentifier = @"TargetBundleIdentifier";
@implementation RemoteControl
// returns nil if the remote control device is not available
- (id) initWithDelegate: (id) _remoteControlDelegate {
if ( (self = [super init]) ) {
// delegate = [_remoteControlDelegate retain];
}
return self;
}
//- (void) dealloc {
// [delegate release];
// delegate = nil;
// [super dealloc];
//}
- (void)setDelegate:(id)value {
delegate = value;
// if (delegate != value) {
// [delegate release];
// delegate = [value retain];
// }
}
- (id) delegate {
return delegate;
}
- (void) setListeningToRemote: (BOOL) value {
(void)value;
}
- (BOOL) isListeningToRemote {
return NO;
}
- (IBAction) startListening: (id) sender {
(void)sender;
}
- (IBAction) stopListening: (id) sender {
(void)sender;
}
- (BOOL) isOpenInExclusiveMode {
return YES;
}
- (void) setOpenInExclusiveMode: (BOOL) value {
(void)value;
}
- (BOOL) sendsEventForButtonIdentifier: (RemoteControlEventIdentifier) identifier {
(void)identifier;
return YES;
}
+ (void) sendDistributedNotification: (NSString*) notificationName targetBundleIdentifier: (NSString*) targetIdentifier {
NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys: [NSString stringWithCString:[self remoteControlDeviceName] encoding:NSASCIIStringEncoding],
kRemoteControlDeviceName, [[NSBundle mainBundle] bundleIdentifier], kApplicationIdentifier,
targetIdentifier, kTargetApplicationIdentifier, nil];
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:notificationName
object:nil
userInfo:userInfo
deliverImmediately:YES];
}
+ (void) sendFinishedNotifcationForAppIdentifier: (NSString*) identifier {
[self sendDistributedNotification:FINISHED_USING_REMOTE_CONTROL_NOTIFICATION targetBundleIdentifier:identifier];
}
+ (void) sendRequestForRemoteControlNotification {
[self sendDistributedNotification:REQUEST_FOR_REMOTE_CONTROL_NOTIFCATION targetBundleIdentifier:nil];
}
+ (const char*) remoteControlDeviceName {
return NULL;
}
@end

12
VKPC/Application.h Normal file
View File

@ -0,0 +1,12 @@
//
// Application.h
// VKPC
//
// Created by Eugene on 11/29/13.
// Copyright (c) 2013-2014 Eugene Z. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface Application : NSApplication
@end

27
VKPC/Application.m Normal file
View File

@ -0,0 +1,27 @@
//
// Application.m
// VKPC
//
// Created by Eugene on 11/29/13.
// Copyright (c) 2013-2014 Eugene Z. All rights reserved.
//
//#import <IOKit/hidsystem/ev_keymap.h>
#import "Application.h"
//#import "SPMediaKeyTap.h"
@implementation Application {
}
//- (void)sendEvent:(NSEvent *)event {
// [super sendEvent:event];
// If event tap is not installed, handle events that reach the app instead
// BOOL shouldHandleMediaKeyEventLocally = ![SPMediaKeyTap usesGlobalMediaKeyTap];
// if (shouldHandleMediaKeyEventLocally && event.type == NSSystemDefined && event.subtype == SPSystemDefinedEventMediaKeys) {
// [(id)[self delegate] mediaKeyTap:nil receivedMediaKeyEvent:event];
// }
//}
@end

16
VKPC/Autostart.h Normal file
View File

@ -0,0 +1,16 @@
//
// Autostart.h
// VKPC
//
// Created by Eugene on 11/9/14.
// Copyright (c) 2014 Eugene Z. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface Autostart : NSObject
+ (BOOL)isLaunchAtStartup;
+ (void)toggleLaunchAtStartup;
@end

77
VKPC/Autostart.m Normal file
View File

@ -0,0 +1,77 @@
//
// Autostart.m
// VKPC
//
// Created by Eugene on 11/9/14.
// Copyright (c) 2014 Eugene Z. All rights reserved.
//
// Based on https://stackoverflow.com/questions/608963/register-as-login-item-with-cocoa
#import "Autostart.h"
@implementation Autostart
static LSSharedFileListItemRef itemRefInLoginItems() {
LSSharedFileListItemRef res = nil;
// Get the app's URL.
NSURL *bundleURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
// Get the LoginItems list.
LSSharedFileListRef loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItemsRef == nil) return nil;
// Iterate over the LoginItems.
NSArray *loginItems = (__bridge NSArray *)LSSharedFileListCopySnapshot(loginItemsRef, nil);
for (id item in loginItems) {
LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)(item);
CFURLRef itemURLRef;
if (LSSharedFileListItemResolve(itemRef, 0, &itemURLRef, NULL) == noErr) {
// Again, use toll-free bridging.
NSURL *itemURL = (__bridge NSURL *)itemURLRef;
if ([itemURL isEqual:bundleURL]) {
res = itemRef;
break;
}
}
}
// Retain the LoginItem reference.
if (res != nil) CFRetain(res);
CFRelease(loginItemsRef);
CFRelease((__bridge CFTypeRef)(loginItems));
return res;
}
+ (BOOL)isLaunchAtStartup {
// See if the app is currently in LoginItems.
LSSharedFileListItemRef itemRef = itemRefInLoginItems();
// Store away that boolean.
BOOL isInList = itemRef != nil;
// Release the reference if it exists.
if (itemRef != nil) {
CFRelease(itemRef);
}
return isInList;
}
+ (void)toggleLaunchAtStartup {
// Toggle the state.
BOOL shouldBeToggled = ![self isLaunchAtStartup];
// Get the LoginItems list.
LSSharedFileListRef loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItemsRef == nil) return;
if (shouldBeToggled) {
// Add the app to the LoginItems list.
CFURLRef appUrl = (__bridge CFURLRef)[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
LSSharedFileListItemRef itemRef = LSSharedFileListInsertItemURL(loginItemsRef, kLSSharedFileListItemLast, NULL, NULL, appUrl, NULL, NULL);
if (itemRef) CFRelease(itemRef);
} else {
// Remove the app from the LoginItems list.
LSSharedFileListItemRef itemRef = itemRefInLoginItems();
LSSharedFileListItemRemove(loginItemsRef,itemRef);
if (itemRef != nil) CFRelease(itemRef);
}
CFRelease(loginItemsRef);
}
@end

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6250" systemVersion="14A388a" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment version="1070" identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6250"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="Application">
<connections>
<outlet property="delegate" destination="494" id="495"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="494" customClass="AppDelegate"/>
</objects>
</document>

18
VKPC/CatchMediaButtons.h Normal file
View File

@ -0,0 +1,18 @@
//
// CatchMediaButtons.h
// VKPC
//
// Created by Eugene on 10/22/14.
// Copyright (c) 2014 Eugene Z. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "AppleRemote.h"
@interface CatchMediaButtons : NSObject
//+ (id)shared;
+ (void)start;
+ (void)stop;
@end

146
VKPC/CatchMediaButtons.m Normal file
View File

@ -0,0 +1,146 @@
//
// CatchMediaButtons.m
// VKPC
//
// Created by Eugene on 10/22/14.
// Copyright (c) 2014 Eugene Z. All rights reserved.
#import "CatchMediaButtons.h"
#import "MultiClickRemoteBehavior.h"
#import "SPMediaKeyTap.h"
#import "Controller.h"
static SPMediaKeyTap *keyTap;
static MultiClickRemoteBehavior *remoteBehavior;
static RemoteControl *remoteControl;
static BOOL started = NO;
static BOOL initialized = NO;
@implementation CatchMediaButtons
+ (void)initialize {
if (initialized) {
return;
}
keyTap = nil;
[[NSUserDefaults standardUserDefaults] addObserver:(id)[self class]
forKeyPath:VKPCPreferencesCatchMediaButtons
options:NSKeyValueObservingOptionNew
context:NULL];
if ([[NSUserDefaults standardUserDefaults] boolForKey:VKPCPreferencesCatchMediaButtons]) {
[CatchMediaButtons start];
}
initialized = YES;
}
+ (void)start {
NSLog(@"[CatchMediaButtons] start");
if (started) {
NSLog(@"[CatchMediaButtons] start: already started, calling stop first");
[self stop];
}
if (keyTap == nil) {
NSLog(@"[CatchMediaButtons] start: keyTap == nil, creating instance");
keyTap = [[SPMediaKeyTap alloc] initWithDelegate:self];
remoteControl = [[AppleRemote alloc] initWithDelegate:self];
[remoteControl setDelegate:self];
remoteBehavior = [MultiClickRemoteBehavior new];
[remoteBehavior setDelegate:self];
[remoteControl setDelegate:remoteBehavior];
}
[keyTap startWatchingMediaKeys];
[remoteControl startListening:self];
NSLog(@"[CatchMediaButtons] started");
started = YES;
}
+ (void)stop {
NSLog(@"[CatchMediaButtons] stop");
if (!started) {
NSLog(@"[CatchMediaButtons] stop: not started");
return;
}
[keyTap stopWatchingMediaKeys];
[remoteControl stopListening:self];
NSLog(@"[CatchMediaButtons] stopped");
started = NO;
}
+ (void)remoteButton:(RemoteControlEventIdentifier)buttonIdentifier pressedDown:(BOOL)pressedDown clickCount:(unsigned int)clickCount {
if (!pressedDown) {
return;
}
switch(buttonIdentifier) {
case kRemoteButtonPlay:
// [self forAllPlay];
break;
case kRemoteButtonRight:
// [self forAllNext];
break;
case kRemoteButtonLeft:
// [self forAllPrev];
break;
default:
break;
}
}
+ (void)mediaKeyTap:(SPMediaKeyTap *)keyTap receivedMediaKeyEvent:(NSEvent *)event; {
NSAssert(event.type == NSSystemDefined && [event subtype] == SPSystemDefinedEventMediaKeys, @"Unexpected NSEvent in mediaKeyTap:receivedMediaKeyEvent:");
int keyCode = (([event data1] & 0xFFFF0000) >> 16);
int keyFlags = ([event data1] & 0x0000FFFF);
BOOL keyIsPressed = (((keyFlags & 0xFF00) >> 8)) == 0xA;
if (keyIsPressed) {
switch (keyCode) {
case NX_KEYTYPE_PLAY:
[Controller playpause];
break;
case NX_KEYTYPE_FAST:
[Controller next];
break;
case NX_KEYTYPE_REWIND:
[Controller prev];
break;
default:
// More cases defined in hidsystem/ev_keymap.h
break;
}
}
}
// KVO
+ (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:VKPCPreferencesCatchMediaButtons]) {
NSNumber *new = change[NSKeyValueChangeKindKey];
if ([new integerValue] == NSKeyValueChangeSetting) {
BOOL value = [(NSNumber *)change[NSKeyValueChangeNewKey] boolValue];
if (!value) {
[CatchMediaButtons stop];
} else {
[CatchMediaButtons start];
}
}
}
}
@end

33
VKPC/Controller.h Normal file
View File

@ -0,0 +1,33 @@
//
// Controller.h
// VKPC
//
// Created by Eugene on 10/23/14.
// Copyright (c) 2014 Eugene Z. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Playlist.h"
@interface Controller : NSObject <PlaylistDelegate>
+ (void)prev;
+ (void)next;
+ (void)playpause;
+ (void)operateTrack:(NSString *)trackID;
+ (NSString *)findRunningAppAndPrepareASForCommand:(NSString *)command;
+ (void)sendCommand:(NSString *)command;
+ (void)handleClient:(NSDictionary *)json;
+ (BOOL)isASBrowser:(NSInteger)browser;
+ (NSString *)JSONForCommand:(NSString *)command data:(NSObject *)data;
#ifdef DEBUG
+ (void)debugInject;
+ (void)debugSendPlay;
+ (void)debugCopyJS;
+ (void)debugCopyAS;
#endif
@end

345
VKPC/Controller.m Normal file
View File

@ -0,0 +1,345 @@
//
// Controller.m
// VKPC
//
// Created by Eugene on 10/23/14.
// Copyright (c) 2014 Eugene Z. All rights reserved.
//
#import "Controller.h"
#import "PopoverController.h"
#import "Server.h"
static NSString * const kASExecuteJSChrome = @"execute javascript";
static NSString * const kASExecuteJSSafari = @"do JavaScript";
static NSString * const kASCurrentTabChrome = @"active tab";
static NSString * const kASCurrentTabSafari = @"current tab";
static NSString * const kASTabTitleChrome = @"title of";
static NSString * const kASTabTitleSafari = @"name of";
static NSString * const kCommandAfterInjection = @"afterInjection";
static NSString * const kCommandPlayPause = @"playpause";
static NSString * const kCommandPrev = @"prev";
static NSString * const kCommandNext = @"next";
static NSString * const kCommandOperateTrack = @"operateTrack:{id}";
static NSArray *browsers = nil;
static NSString *scriptJS;
#ifdef DEBUG
static NSString *scriptJSUnescaped;
#endif
static NSString *scriptAS;
static NSMutableDictionary *cache = nil;
static NSTimer *timer = nil;
static NSInteger browser;
static BOOL initialized = NO;
@implementation Controller
+ (void)initialize {
if (initialized) {
return;
}
browsers = @[
@[@{@"id": @"com.google.Chrome", @"name": @"Google Chrome", @"key": @"chrome"}, @{@"id": @"com.google.Chrome.canary", @"name": @"Google Chrome Canary", @"key": @"chromecanary"}],
@[@{@"id": @"org.mozilla.firefox", @"name": @"Firefox", @"key": @"firefox"}],
@[@{@"id": @"com.apple.Safari", @"name": @"Safari", @"key": @"safari"}],
@[@{@"id": @"com.operasoftware.Opera", @"name": @"Opera", @"key": @"opera"}, @{@"id": @"com.operasoftware.OperaNext", @"name": @"Opera Next", @"key": @"operanext"}],
@[@{@"id": @"ru.yandex.desktop.yandex-browser", @"name": @"Yandex", @"key": @"yandex"}]
];
NSError *error = nil;
scriptJS = GetFileFromResourceAsString(@"inject.js", &error);
scriptAS = GetFileFromResourceAsString(@"inject.as", &error);
if (error) {
NSLog(@"Error while reading from resources: %@", error);
// TODO something
return;
}
scriptJS = [scriptJS stringByReplacingOccurrencesOfString:@"{sid}" withString:[NSString stringWithFormat:@"%d", VKPCSessionID]];
// scriptJS = [scriptJS stringByReplacingOccurrencesOfString:@"{debug}" withString:(VKPCIsDebug ? @"true" : @"false")];
#ifdef DEBUG
scriptJSUnescaped = [NSString stringWithString:scriptJS];
#endif
scriptJS = [scriptJS stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
scriptJS = [scriptJS stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
cache = [[NSMutableDictionary alloc] init];
// if (NO)
[self setupTimer];
browser = [[NSUserDefaults standardUserDefaults] integerForKey:VKPCPreferencesBrowser];
[[NSUserDefaults standardUserDefaults] addObserver:(id)[Controller class]
forKeyPath:VKPCPreferencesBrowser
options:NSKeyValueObservingOptionNew
context:NULL];
initialized = YES;
}
+ (void)setupTimer {
if (timer != nil) {
[timer invalidate];
timer = nil;
}
[self timerCallback:nil];
timer = [NSTimer scheduledTimerWithTimeInterval:2.0
target:[Controller class]
selector:@selector(timerCallback:)
userInfo:nil
repeats:YES];
}
#ifdef DEBUG
+ (void)debugSendPlay {
[Controller playpause];
}
+ (void)debugInject {
[Controller sendCommand:kCommandAfterInjection];
}
+ (void)debugCopyJS {
NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
[pasteBoard declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil] owner:nil];
[pasteBoard setString:scriptJSUnescaped forType:NSStringPboardType];
}
+ (void)debugCopyAS {
NSString *code = [self findRunningAppAndPrepareASForCommand:kCommandAfterInjection];
if (code != nil) {
NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
[pasteBoard declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil] owner:nil];
[pasteBoard setString:code forType:NSStringPboardType];
} else {
NSLog(@"[Controller debugCopyAS] code == nil");
}
}
#endif
//static BOOL playlistDelegateSet = NO;
+ (void)timerCallback:(NSTimer *)timer {
if ([[PopoverController shared] playlistTableController] != nil && [[PopoverController shared] playlistTableController].playlist.delegate == nil) {
[[PopoverController shared].playlistTableController.playlist setDelegate:(id)[self class]];
NSLog(@"[Controller timerCallback] playlist delegate set");
// playlistDelegateSet = YES;
}
if ([self isASBrowser:browser]) {
[Controller sendCommand:kCommandAfterInjection];
} else if ([Server connectedCount:browser] <= 0) {
[[PopoverController shared].playlistTableController clearPlaylist];
}
}
+ (void)prev {
[Controller sendCommand:kCommandPrev];
}
+ (void)next {
[Controller sendCommand:kCommandNext];
}
+ (void)playpause {
[Controller sendCommand:kCommandPlayPause];
}
+ (void)operateTrack:(NSString *)trackID {
[Controller sendCommand:[kCommandOperateTrack stringByReplacingOccurrencesOfString:@"{id}" withString:trackID]];
}
+ (void)sendCommand:(NSString *)command {
if ([self isASBrowser:browser]) {
NSString *code = [self findRunningAppAndPrepareASForCommand:command];
if (code == nil) {
// NSLog(@"[Controller sendCommand:] code == nil, returning");
// Clear playlist?
[[PopoverController shared].playlistTableController clearPlaylist];
return;
}
NSAppleScript *as = [[NSAppleScript alloc] initWithSource:code];
NSDictionary *error = nil;
NSAppleEventDescriptor *result = [as executeAndReturnError:&error];
if (error) {
NSLog(@"[Controller sendCommand:] error: %@", error);
} else if ([command isEqualToString:kCommandAfterInjection]) {
int returnValue = 0;
[result.data getBytes:&returnValue length:result.data.length];
// NSLog(@"[Controller sendCommand:] returnValue = %d", returnValue);
if (returnValue == 1) {
[[PopoverController shared].playlistTableController clearPlaylist];
}
}
} else {
if ([Server connectedCount:browser] <= 0) {
[[PopoverController shared].playlistTableController clearPlaylist];
return;
}
// Send to extensions
[Server send:[self JSONForCommand:@"vkpc" data:command] forBrowser:browser];
}
}
+ (NSString *)findRunningAppAndPrepareASForCommand:(NSString *)command {
NSArray *list = browsers[browser];
NSArray *apps = [[NSWorkspace sharedWorkspace] runningApplications];
NSDictionary *app;
BOOL found = NO;
for (int i = 0; i < apps.count; i++) {
NSRunningApplication *currentApp = apps[i];
for (NSDictionary *dict in list) {
if ([currentApp.bundleIdentifier isEqualToString:dict[@"id"]]) {
app = dict;
found = YES;
break;
}
}
if (found)
break;
}
if (!found) {
// NSLog(@"[Controller findRunningAppAndPrepareASForCommand:] %@ not found in running applications, nil will returned", (NSString *)browsers[browser][0][@"name"]);
return nil;
}
NSString *as = scriptAS;
NSInteger playlistID = [PopoverController shared].playlistTableController.playlist.playlistID;
NSString *ASExecuteJS, *ASCurrentTab, *ASTabTitle;
if (browser == BrowserSafari) {
ASExecuteJS = kASExecuteJSSafari;
ASCurrentTab = kASCurrentTabSafari;
ASTabTitle = kASTabTitleSafari;
} else {
ASExecuteJS = kASExecuteJSChrome;
ASCurrentTab = kASCurrentTabChrome;
ASTabTitle = kASTabTitleChrome;
}
as = [as stringByReplacingOccurrencesOfString:@"{appName}" withString:app[@"name"]];
as = [as stringByReplacingOccurrencesOfString:@"{js}" withString:scriptJS];
as = [as stringByReplacingOccurrencesOfString:@"{ASExecuteJS}" withString:ASExecuteJS];
as = [as stringByReplacingOccurrencesOfString:@"{ASCurrentTab}" withString:ASCurrentTab];
as = [as stringByReplacingOccurrencesOfString:@"{ASTabTitle}" withString:ASTabTitle];
as = [as stringByReplacingOccurrencesOfString:@"{playlistID}" withString:[NSString stringWithFormat:@"%ld", playlistID]];
as = [as stringByReplacingOccurrencesOfString:@"{command}" withString:command];
return as;
}
+ (void)handleClient:(NSDictionary *)json {
// NSLog(@"[Controller handleClient] json: %@", json);
NSInteger fromBrowser = [(NSNumber *)json[@"_browser"] integerValue];
if (fromBrowser != browser) {
// NSLog(@"[Controller handleClient] received message from browser=%zd, but current browser=%zd, skipping", fromBrowser, browser);
return;
}
NSString *command = json[@"command"];
NSDictionary *data = json[@"data"];
if (!command || [command isEqual:[NSNull null]]) {
NSLog(@"[Controller handleCommand] !json");
return;
}
if ([command isEqualToString:@"updatePlaylist"]) {
NSArray *tracks = data[@"tracks"];
NSInteger playlistId = [(NSNumber *)data[@"id"] intValue];
NSString *title = data[@"title"];
NSDictionary *active = data[@"active"];
NSString *browser = data[@"browser"];
NSString *activeStatus = active[@"status"];
NSString *activeId = active[@"id"];
BOOL playingStatus = ( activeStatus && ![activeStatus isEqual:[NSNull null]] && [activeStatus isEqualToString:@"play"] ) ? YES : NO;
NSLog(@"[server] got updatePlaylist; id=%ld, activeId=%@, activeStatus=%@, browser=%@, title=%@",
playlistId, (NSString *)active[@"id"], active[@"status"], browser, title);
if ([[PopoverController shared].playlistTableController inited]) {
NSLog(@"[Controller handleClient] call setPlaylist..");
[[PopoverController shared].playlistTableController setPlaylistDataWithTracks:tracks title:title id:playlistId activeId:activeId activePlaying:playingStatus browser:browser];
} else {
NSLog(@"[Controller handleClient] call preSetPlaylist..");
[PlaylistTableController preSetPlaylistDataWithTracks:tracks title:title id:playlistId activeId:activeId activePlaying:playingStatus browser:browser];
}
} else if ([command isEqualToString:@"operateTrack"]) {
NSString *trackId = data[@"id"];
NSString *status = data[@"status"];
NSInteger playlistId = [(NSNumber *)data[@"playlistId"] intValue];
NSLog(@"[server] got operateTrack; trackId=%@, status=%@, plId=%ld",
trackId, status, playlistId);
PlayingStatus playingStatus = (status && ![status isEqual:[NSNull null]] && [status isEqualToString:@"play"]) ? PlayingStatusPlaying : PlayingStatusPaused;
[[PopoverController shared].playlistTableController setPlayingTrackById:trackId withStatus:playingStatus forPlaylist:playlistId];
} else if ([command isEqualToString:@"clearPlaylist"]) {
[[PopoverController shared].playlistTableController clearPlaylist];
}
}
// PlaylistDeletage
+ (void)playlistIDChanged:(NSInteger)playlistID {
// NSLog(@"playlist id changed! new id: %zd", playlistID);
if (initialized) {
// NSLog(@"now send new playlist id to clients");
[Server send:[self JSONForCommand:@"set_playlist_id" data:[NSNumber numberWithInteger:playlistID]] forBrowser:-1];
}
}
// KVO
+ (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:VKPCPreferencesBrowser]) {
NSNumber *new = change[NSKeyValueChangeKindKey];
if ([new integerValue] == NSKeyValueChangeSetting) {
NSInteger value = [(NSNumber *)change[NSKeyValueChangeNewKey] integerValue];
if (browser != value) {
NSLog(@"[Controller KVO] new browser is %@", browsers[value][0][@"name"]);
browser = value;
[cache removeAllObjects];
[[PopoverController shared].playlistTableController clearPlaylist];
[self setupTimer];
}
}
}
}
// Other
+ (BOOL)isASBrowser:(NSInteger)browser {
return ![[NSUserDefaults standardUserDefaults] boolForKey:VKPCPreferencesUseExtensionMode] && (
browser == BrowserChrome
|| browser == BrowserYandex
|| browser == BrowserSafari );
}
+ (NSString *)JSONForCommand:(NSString *)command data:(NSObject *)data {
NSDictionary *dict = @{@"command": command, @"data": data};
NSError *error;
NSData *json = [NSJSONSerialization dataWithJSONObject:dict options:(NSJSONWritingOptions)0 error:&error];
if (!json) {
NSLog(@"[Controller JSONForCommand] error: %@", error.localizedDescription);
return @"{}";
} else {
return [[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding];
}
}
@end

15
VKPC/FlippedView.h Normal file
View File

@ -0,0 +1,15 @@
//
// PopoverView.h
// VKPC
//
// Created by Eugene on 12/1/13.
// Copyright (c) 2013 Eugene Z. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface FlippedView : NSView
- (BOOL)isFlipped;
@end

31
VKPC/FlippedView.m Normal file
View File

@ -0,0 +1,31 @@
//
// PopoverView.m
// VKPC
//
// Created by Eugene on 12/1/13.
// Copyright (c) 2013 Eugene Z. All rights reserved.
//
#import "FlippedView.h"
@implementation FlippedView
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
}
- (BOOL)isFlipped {
return YES;
}
@end

72
VKPC/Global.h Normal file
View File

@ -0,0 +1,72 @@
//
// global.h
// VKPC
//
// Created by Eugene on 11/28/13.
// Copyright (c) 2013-2014 Eugene Z. All rights reserved.
//
#import "PlaylistTableController.h"
// Variables
extern int const VKPCHTTPServerPort;
extern NSString * const VKPCHTTPServerHost;
extern int const VKPCWSServerPort;
extern char * const VKPCWSServerHost;
extern char * const VKPCWSClientHost;
//extern char * const VKPCHostsFile;
extern NSString * const VKPCAppHomeURL;
extern NSString * const CH1PEmail;
extern BOOL const VKPCIsDebug;
extern BOOL const VKPCIsServerLogsEnabled;
extern BOOL VKPCIsYosemite;
extern NSString * const VKPCEZCopyright;
extern NSString * const VKPCEZCopyrightYears;
extern NSString * const VKPCEZURL;
extern NSString * const VKPCPreferencesShowNotifications;
extern NSString * const VKPCPreferencesInvertPlaylistIcons;
extern NSString * const VKPCPreferencesCatchMediaButtons;
extern NSString * const VKPCPreferencesBrowser;
extern NSString * const VKPCPreferencesStatisticReportedTimestamp;
extern NSString * const VKPCPreferencesUUID;
extern NSString * const VKPCPreferencesUseExtensionMode;
extern int VKPCSessionID;
//extern PlaylistTableController *VKPCPlaylistTableController;
extern pid_t VKPCPID;
extern NSString * const VKPCImageEmpty;
extern NSString * const VKPCImageCellBg;
extern NSString * const VKPCImageCellPressedBg;
extern NSString * const VKPCImagePause;
extern NSString * const VKPCImagePlay;
extern NSString * const VKPCImageTitleSeparator;
extern NSString * const VKPCImageSettings;
extern NSString * const VKPCImageSettingsPressed;
extern NSString * const VKPCImageStatus;
extern NSString * const VKPCImageStatusPressed;
extern NSString * const kAppleInterfaceStyle;
extern NSString * const kAppleInterfaceStyleDark;
extern NSString * const kAppleInterfaceThemeChangedNotification;
extern NSString * const kCFBundleDisplayName;
extern NSString * const kCFBundleShortVersionString;
extern NSString * const kCFBundleVersion;
// Functions
void VKPCInitGlobals();
void VKPCInitUUID();
void ShowNotification();
NSString * GetFileFromResourceAsString(NSString *fileName, NSError * __autoreleasing *error);
NSString *GetSystemFontName();
//BOOL IsDarkMode();
InterfaceStyle GetInterfaceStyle();
NSDictionary * VKPCGetImagesDictionary();
void DebugLog(const char *str);
long GetTimestamp();
BOOL IsAnotherProcessRunning();

234
VKPC/Global.m Normal file
View File

@ -0,0 +1,234 @@
//
// global.m
// VKPC
//
// Created by Eugene on 11/28/13.
// Copyright (c) 2013-2014 Eugene Z. All rights reserved.
//
#import "Global.h"
#import <CoreServices/CoreServices.h>
#import "NSTimer+Blocks.h"
#import "NSUserNotificationCenter+Private.h"
#include <stdlib.h>
#include <math.h>
int const VKPCWSServerPort = 56130;
char * const VKPCWSServerHost = "127.0.0.1";
char * const VKPCWSClientHost = "vkpc-local.ch1p.com";
//char * const VKPCHostsFile = "/private/etc/hosts";
NSString * const VKPCAppHomeURL = @"https://ch1p.com/vkpc/?v={version}";
NSString * const CH1PEmail = @"ch1p@ch1p.com";
#ifdef DEBUG
BOOL const VKPCIsDebug = YES;
#else
BOOL const VKPCIsDebug = NO;
#endif
BOOL const VKPCIsServerLogsEnabled = NO;
BOOL VKPCIsYosemite = NO;
NSString * const VKPCEZCopyright = @"Eugene Z";
NSString * const VKPCEZCopyrightYears = @" © 2013-2015";
NSString * const VKPCEZURL = @"https://vk.com/ez";
NSString * const VKPCPreferencesShowNotifications = @"VKPCShowNotifications";
NSString * const VKPCPreferencesInvertPlaylistIcons = @"VKPCInvertPlaylistIcons";
NSString * const VKPCPreferencesCatchMediaButtons = @"VKPCCatchMediaButtons";
NSString * const VKPCPreferencesBrowser = @"VKPCBrowser";
NSString * const VKPCPreferencesStatisticReportedTimestamp = @"VKPCStatisticReportedTimestamp";
NSString * const VKPCPreferencesUUID = @"VKPCUUID";
NSString * const VKPCPreferencesUseExtensionMode = @"VKPCUseExtensionMode";
NSString * const kAppleInterfaceStyle = @"AppleInterfaceStyle";
NSString * const kAppleInterfaceThemeChangedNotification = @"AppleInterfaceThemeChangedNotification";
NSString * const kAppleInterfaceStyleDark = @"Dark";
NSString * const kCFBundleDisplayName = @"CFBundleDisplayName";
NSString * const kCFBundleShortVersionString = @"CFBundleShortVersionString";
NSString * const kCFBundleVersion = @"CFBundleVersion";
int VKPCSessionID;
pid_t VKPCPID;
void VKPCInitGlobals() {
SInt32 major, minor;
Gestalt(gestaltSystemVersionMajor, &major);
Gestalt(gestaltSystemVersionMinor, &minor);
VKPCIsYosemite = major >= 10 && minor >= 10;
VKPCSessionID = arc4random() % 65536;
VKPCPID = [[NSProcessInfo processInfo] processIdentifier];
}
void VKPCInitUUID() {
NSString *currentUUID = [[NSUserDefaults standardUserDefaults] stringForKey:VKPCPreferencesUUID];
if ([currentUUID isEqualToString:@""]) {
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
NSString *uuidString = (__bridge_transfer NSString *)CFUUIDCreateString(kCFAllocatorDefault, uuid);
CFRelease(uuid);
[[NSUserDefaults standardUserDefaults] setObject:uuidString forKey:VKPCPreferencesUUID];
}
}
static NSUserNotification *lastNotification;
static BOOL isLowerThan10_9() {
SInt32 major, minor;
Gestalt(gestaltSystemVersionMajor, &major);
Gestalt(gestaltSystemVersionMinor, &minor);
return major == 10 && minor < 9;
}
static void removeNotification(NSUserNotification *notification) {
// if (isLowerThan10_9()) {
[[NSUserNotificationCenter defaultUserNotificationCenter] _removeDisplayedNotification:notification];
// } else {
// [[NSUserNotificationCenter defaultUserNotificationCenter] removeDeliveredNotification:notification];
// }
}
void ShowNotification(NSString *title, NSString *text) {
NSUserNotification *notification = [[NSUserNotification alloc] init];
[notification setTitle:title];
[notification setInformativeText:text];
[notification setHasActionButton:NO];
if (lastNotification != nil) {
removeNotification(lastNotification);
}
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
[NSTimer scheduledTimerWithTimeInterval:4.0 block:^{
removeNotification(notification);
} repeats:NO];
lastNotification = notification;
}
NSString * GetFileFromResourceAsString(NSString *fileName, NSError * __autoreleasing * error) {
NSString *path = [[fileName lastPathComponent] stringByDeletingPathExtension];
NSString *type = [fileName pathExtension];
NSError *localError = nil;
NSString *resPath = [[NSBundle mainBundle] pathForResource:path ofType:type];
NSURL *url = [NSURL fileURLWithPath:resPath];
NSString *content = nil;
content = [[NSString alloc]
initWithContentsOfURL:url
encoding:NSUTF8StringEncoding
error:&localError];
if (localError || content == nil) {
*error = localError;
NSLog(@"Error reading file %@\n%@", url, [localError localizedFailureReason]);
}
return content;
}
NSString * GetSystemFontName() {
return VKPCIsYosemite ? @"Helvetica Neue" : @"Lucida Grande";
}
InterfaceStyle GetInterfaceStyle() {
if (VKPCIsYosemite) {
NSString *theme = [[NSUserDefaults standardUserDefaults] stringForKey:kAppleInterfaceStyle];
if (theme != nil && [theme isKindOfClass:[NSString class]] && [theme isEqualToString:kAppleInterfaceStyleDark]) {
return InterfaceStyleYosemiteDark;
}
return InterfaceStyleYosemite;
}
return InterfaceStyleLegacy;
}
long GetTimestamp() {
return (long)floor([[NSDate date] timeIntervalSince1970]);
}
BOOL IsAnotherProcessRunning() {
NSArray *list = [[NSWorkspace sharedWorkspace] runningApplications];
for (int i = 0; i < list.count; i++) {
NSRunningApplication *app = list[i];
if ([[app bundleIdentifier] isEqualToString:[[NSBundle mainBundle] bundleIdentifier]]
&& [app processIdentifier] != VKPCPID) {
return YES;
}
}
return NO;
}
void DebugLog(const char *str) {
// printf("><> %s\n", str);
}
//////////////////////////////////// Images ////////////////////////////////////
NSString * const VKPCImageEmpty = @"empty";
NSString * const VKPCImageCellBg = @"pl_cell_bg";
NSString * const VKPCImageCellPressedBg = @"pl_cell_pressed_bg";
NSString * const VKPCImagePause = @"pl_pause";
NSString * const VKPCImagePlay = @"pl_play";
NSString * const VKPCImageTitleSeparator = @"pl_title_separator";
NSString * const VKPCImageSettings = @"settings";
NSString * const VKPCImageSettingsPressed = @"settings_pressed";
NSString * const VKPCImageStatus = @"status";
NSString * const VKPCImageStatusPressed = @"status_pressed";
static NSString * const kImagesBundleLegacy = @"ImagesLegacy";
static NSString * const kImagesBundleYosemite = @"ImagesYosemite";
static NSString * const kImagesBundleYosemiteDark = @"ImagesYosemiteDark";
static BOOL imagesInited = NO;
static NSArray *imageNames;
static NSMutableDictionary *imageBundles; // @{<bundleName>: NSBundle}
static NSMutableDictionary *allImages; // @{<bundleName>: @{<imageName>: NSImage, ...}}
NSDictionary * VKPCGetImagesDictionary() {
if (!imagesInited) {
allImages = [[NSMutableDictionary alloc] init];
imageBundles = [[NSMutableDictionary alloc] init];
imageNames = @[VKPCImageEmpty, VKPCImageCellBg, VKPCImageCellPressedBg,
VKPCImagePause, VKPCImagePlay, VKPCImageTitleSeparator, VKPCImageSettings,
VKPCImageSettingsPressed, VKPCImageStatus, VKPCImageStatusPressed];
// Loading bundles
NSArray *bundlePaths = [[NSBundle mainBundle] pathsForResourcesOfType:@"bundle" inDirectory:@""];
for (NSString *bundlePath in bundlePaths) {
NSString *bundleKey = [[bundlePath lastPathComponent] stringByDeletingPathExtension];
if ([bundleKey hasPrefix:@"Images"]) {
imageBundles[bundleKey] = [NSBundle bundleWithPath:bundlePath];
}
}
imagesInited = YES;
}
NSString *bundleKey;
switch (GetInterfaceStyle()) {
case InterfaceStyleYosemite:
bundleKey = kImagesBundleYosemite;
break;
case InterfaceStyleLegacy:
bundleKey = kImagesBundleLegacy;
break;
case InterfaceStyleYosemiteDark:
bundleKey = kImagesBundleYosemiteDark;
break;
}
if (allImages[bundleKey] != nil) {
// NSLog(@"[VKPCGetImagesDictionary] returning from cache, bundleKey = %@", bundleKey);
return (NSDictionary *)allImages[bundleKey];
}
allImages[bundleKey] = [[NSMutableDictionary alloc] init];
for (NSString *named in imageNames) {
NSImage *img = [(NSBundle *)imageBundles[bundleKey] imageForResource:named];
allImages[bundleKey][named] = img;
}
return (NSDictionary *)allImages[bundleKey];
}

21
VKPC/HostsHack.h Normal file
View File

@ -0,0 +1,21 @@
//
// HostsHack.h
// VKPC
//
// Created by Eugene on 10/30/14.
// Copyright (c) 2014 Eugene Z. All rights reserved.
//
#import <Foundation/Foundation.h>
extern NSString * const VKPCHostsHackTaskFinished;
@interface HostsHack : NSObject
+ (void)check;
+ (void)hack;
+ (BOOL)found;
+ (void)showWindow;
+ (int)doHack;
@end

163
VKPC/HostsHack.m Normal file
View File

@ -0,0 +1,163 @@
//
// HostsHack.m
// VKPC
//
// Created by Eugene on 10/30/14.
// Copyright (c) 2014 Eugene Z. All rights reserved.
//
#import "HostsHack.h"
#import "HostsHackWindowController.h"
#import "AppDelegate.h"
#include <stdio.h>
#ifdef DEBUG
#include <time.h>
#endif
NSString * const VKPCHostsHackTaskFinished = @"VKPCHostsHackTaskFinished";
static BOOL hackFound = NO;
static HostsHackWindowController *windowController = nil;
#ifdef DEBUG
static char *testPath = "/tmp/vkpc_test";
#endif
@implementation HostsHack
static NSString *readLineASNSString(FILE *file) {
char *line = NULL;
size_t len = 0;
getline(&line, &len, file);
return [NSString stringWithUTF8String:line];
}
+ (void)check {
hackFound = NO;
#ifdef DEBUG
clock_t begin = clock();
#endif
FILE *file = fopen(VKPCHostsFile, "r");
if (file == NULL) {
NSLog(@"[HostsHack check] !file, returning");
return;
}
while (!feof(file)) {
NSString *line = readLineASNSString(file);
// NSLog(@"[hostshack] line: %@", line);
NSRange rng = [line rangeOfString:[NSString stringWithUTF8String:VKPCWSClientHost]];
if (rng.location != NSNotFound) {
// if ([line containsString:[NSString stringWithUTF8String:VKPCWSClientHost]]) {
line = [line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if ([line hasPrefix:@"127.0.0.1"]) {
hackFound = YES;
break;
}
}
}
fclose(file);
#ifdef DEBUG
NSLog(@"[HostsHack check] file reading time: %lf", (double)(clock() - begin) / CLOCKS_PER_SEC);
NSLog(@"[HostsHack check] found: %s", hackFound ? "YES" : "NO");
#endif
}
static void showAlert(NSString *text, NSString *informativeText) {
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:text];
[alert setInformativeText:informativeText];
[alert setAlertStyle:NSWarningAlertStyle];
[alert runModal];
}
+ (void)hack {
// return;
AuthorizationRef auth = NULL;
OSStatus err;
err = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagInteractionAllowed, &auth);
if (err != errAuthorizationSuccess) {
showAlert(@"VKPC Error", [NSString stringWithFormat:@"Failed to obtain authorization. Code = %d", err]);
[windowController setButtonRetry];
return;
}
const char *path = [[NSProcessInfo processInfo].arguments[0] UTF8String];
char * const args[] = {"--hostshack", NULL};
[windowController setButtonWait];
err = AuthorizationExecuteWithPrivileges(auth, path, kAuthorizationFlagDefaults, args, NULL);
if (err != errAuthorizationSuccess) {
showAlert(@"VKPC Error", [NSString stringWithFormat:@"Failed to run command with adminstrative privileges. Code = %d", err]);
[windowController setButtonRetry];
return;
}
[[NSDistributedNotificationCenter defaultCenter] addObserver:(id)[self class]
selector:@selector(hackTaskFinished:)
name:VKPCHostsHackTaskFinished
object:nil];
}
+ (void)hackTaskFinished:(id)notification {
[[NSDistributedNotificationCenter defaultCenter] removeObserver:(id)[self class]];
[self check];
if (hackFound) {
[windowController close];
[[AppDelegate shared] continueRunning];
} else {
[self showUnableAlert];
}
}
+ (void)showUnableAlert {
showAlert(@"VKPC Error", [NSString stringWithFormat:
@"Unfortunately, VKPC failed to automatically edit the file %@. Now you have to make it manually.\n\n"
"Please open the file %@ with root privileges and add following line at the end:\n\n"
"127.0.0.1\t%@\n\n"
"Then save the file and relaunch the app.",
[NSString stringWithUTF8String:VKPCHostsFile],
[NSString stringWithUTF8String:VKPCHostsFile],
[NSString stringWithUTF8String:VKPCWSClientHost]]);
}
+ (int)doHack {
// sleep(2);
char *path = VKPCHostsFile;
FILE *file = fopen(path, "a");
if (!file) {
NSLog(@"[HostsHack doHack] error opening file %s, returning error", path);
return -1;
}
fputs("\n#VK Player Controller", file);
fputs([[NSString stringWithFormat:@"\n127.0.0.1\t%@", [NSString stringWithUTF8String:VKPCWSClientHost]] UTF8String], file);
fclose(file);
return 0;
}
+ (BOOL)found {
return hackFound;
}
+ (void)showWindow {
if (!windowController) {
windowController = [[HostsHackWindowController alloc] initWithWindowNibName:@"HostsHackWindow"];
}
[windowController showWindow:nil];
[windowController.window makeKeyAndOrderFront:nil];
[windowController.window setLevel:kCGFloatingWindowLevel];
}
@end

50
VKPC/HostsHackWindow.xib Normal file
View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6250" systemVersion="14A388a" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6250"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="HostsHackWindowController">
<connections>
<outlet property="button" destination="DkI-Wh-sfI" id="nZQ-ER-OSv"/>
<outlet property="configurationRequiredTextField" destination="LPj-KT-VLz" id="697-Qc-LGh"/>
<outlet property="window" destination="QvC-M9-y7g" id="Ip1-1G-tLg"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window allowsToolTipsWhenApplicationIsInactive="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
<rect key="contentRect" x="480" y="296" width="480" height="270"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
<view key="contentView" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" id="LPj-KT-VLz">
<rect key="frame" x="18" y="175" width="444" height="80"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="justified" title="bla bla" allowsEditingTextAttributes="YES" id="hi1-HP-gi7">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" id="DkI-Wh-sfI">
<rect key="frame" x="169" y="51" width="142" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Continue" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="S5R-Td-Ee1">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="buttonPressed:" target="-2" id="ynn-3H-jCh"/>
</connections>
</button>
</subviews>
</view>
<point key="canvasLocation" x="347" y="88"/>
</window>
</objects>
</document>

View File

@ -0,0 +1,26 @@
//
// HostsHackWindowController.h
// VKPC
//
// Created by Eugene on 10/30/14.
// Copyright (c) 2014 Eugene Z. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import "WindowController.h"
#import "FlippedView.h"
//@class FlippedView;
@interface HostsHackWindowController : WindowController<NSWindowDelegate>
//@property (strong) IBOutlet NSWindow *window;
@property (weak) IBOutlet NSTextField *configurationRequiredTextField;
@property (weak) IBOutlet NSButton *button;
- (IBAction)buttonPressed:(id)sender;
- (void)setButtonRetry;
- (void)setButtonContinue;
- (void)setButtonWait;
@end

View File

@ -0,0 +1,105 @@
//
// HostsHackWindowController.m
// VKPC
//
// Created by Eugene on 10/30/14.
// Copyright (c) 2014 Eugene Z. All rights reserved.
//
#import "HostsHackWindowController.h"
#import "HostsHack.h"
// TODO rewrite using normal coords
@implementation HostsHackWindowController {
NSMutableAttributedString *configurationRequired;
NSView *contentView;
}
- (BOOL)allowsClosingWithShortcut {
return YES;
}
- (void)windowDidLoad {
[super windowDidLoad];
contentView = self.window.contentView;
NSTextFieldCell *cell = (NSTextFieldCell *)_configurationRequiredTextField.cell;
NSString *configurationTextHTML = [NSString stringWithFormat:
@"<html><span style=\"font-family: %@;\">"
"<span style=\"line-height: 10px; font-size: 14px\"><b>Welcome to VK Player Controller!</b></span>"
"<span style=\"font-size: 6px\"><br/><br/></span>"
"<span style=\"font-size: 13px\">"
"Let's make one magic trick with system DNS settings, it's necessary for a proper work of VK Player Controller. Don't worry, <b>it's absolutely safe</b>. Please press <b>Continue</b> button below."
// "For VK Player Controller to work it is necessary to do some hacking with DNS resolution configuration. Press <b>Continue</b> to continue."
"</span>"
"<span style=\"font-size: 7px\"><br/><br/></span>"
"<span style=\"font-size: 12px; color: #707070;\">"
"The app modifies the file <b>%@</b>. If you don't trust us, open that file manually with admin privileges, add this line: <b>127.0.0.1\t%@</b>, save it and relaunch the app."
"</span>"
"</span></html>",
//GetSystemFontName(),
@"Helvetica Neue",
[NSString stringWithUTF8String:VKPCHostsFile],
[NSString stringWithUTF8String:VKPCWSClientHost]];
configurationRequired = [[NSMutableAttributedString alloc] initWithHTML:[configurationTextHTML dataUsingEncoding:NSUTF8StringEncoding]
options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)}
documentAttributes:nil];
[_configurationRequiredTextField setAttributedStringValue:configurationRequired];
[cell setWraps:YES];
// Position text
NSRect textFrame = _configurationRequiredTextField.frame;
float textHeight = [configurationRequired boundingRectWithSize:CGSizeMake(textFrame.size.width, FLT_MAX) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading].size.height;
textFrame.origin.y += textFrame.size.height - textHeight;
textFrame.size.height = textHeight;
[_configurationRequiredTextField setFrame:textFrame];
// Position button
NSRect buttonFrame = _button.frame;
float padding = contentView.frame.size.height - (textFrame.origin.y + textFrame.size.height);
float buttonPadding = 20;
float buttonY = [self.window.contentView frame].size.height - textHeight - padding * 2 - buttonFrame.size.height;
buttonFrame.origin.y = buttonY;
[_button setFrame:buttonFrame];
float windowHeight = contentView.frame.size.height - buttonFrame.origin.y + padding + buttonPadding;
NSRect windowFrame = self.window.frame;
windowFrame.origin.y += windowFrame.size.height;
windowFrame.origin.y -= windowHeight;
windowFrame.size.height = windowHeight;
[self.window setFrame:windowFrame display:YES];
}
- (void)setButtonContinue {
[_button setTitle:@"Continue"];
[_button setEnabled:YES];
}
- (void)setButtonRetry {
[_button setTitle:@"Retry"];
[_button setEnabled:YES];
}
- (void)setButtonWait {
[_button setTitle:@"Please wait.."];
[_button setEnabled:NO];
}
- (IBAction)buttonPressed:(id)sender {
[HostsHack hack];
}
- (void)showWindow:(id)sender {
[self setButtonContinue];
[super showWindow:sender];
[self.window setDefaultButtonCell:_button.cell];
}
@end

View File

@ -0,0 +1,58 @@
{
"images" : [
{
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Some files were not shown because too many files have changed in this diff Show More