├── docs ├── _config.yml └── index.md ├── grid.gif ├── sshot.png ├── AppGrid.acorn ├── statusitem.sketch ├── AppGrid ├── defaults.plist ├── sd_intro.png ├── statusitem.png ├── statusitem.acorn ├── statusitem@2x.png ├── SDGrid.h ├── SDAppDelegate.h ├── Images.xcassets │ └── AppIcon.appiconset │ │ ├── icon_16x16.png │ │ ├── icon_32x32.png │ │ ├── icon_128x128.png │ │ ├── icon_256x256.png │ │ ├── icon_512x512.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256@2x.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512@2x.png │ │ └── Contents.json ├── SDOpenAtLogin.h ├── MASShortcutView+UserDefaults.h ├── SDWelcomeWindowController.h ├── MASShortcut+Monitoring.h ├── SDAccessibility.h ├── SDPreferences.h ├── MASShortcut+UserDefaults.h ├── SDValueTransformers.m ├── SDWindow.h ├── MASShortcutView.h ├── SDPreferences.m ├── AppGrid-Info.plist ├── SDAccessibility.m ├── SDPreferencesWindowController.h ├── Credits.rtf ├── SDWelcomeWindowController.m ├── MASShortcut.h ├── SDOpenAtLogin.m ├── SDAppDelegate.m ├── MASShortcut+UserDefaults.m ├── WelcomeWindow.xib ├── MASShortcutView+UserDefaults.m ├── MASShortcut+Monitoring.m ├── SDPreferencesWindowController.m ├── SDGrid.m ├── SDWindow.m ├── MASShortcut.m ├── MASShortcutView.m └── PreferencesWindow.xib ├── .github └── workflows │ ├── build.yml │ └── pre-release.yml ├── .gitignore ├── Makefile ├── README.md ├── version.sh ├── AppGrid.xcodeproj └── project.pbxproj └── LICENSE /docs/_config.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ~~~ 2 | ~~~ 3 | Testing. 4 | -------------------------------------------------------------------------------- /grid.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjolnirapp/AppGrid/HEAD/grid.gif -------------------------------------------------------------------------------- /sshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjolnirapp/AppGrid/HEAD/sshot.png -------------------------------------------------------------------------------- /AppGrid.acorn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjolnirapp/AppGrid/HEAD/AppGrid.acorn -------------------------------------------------------------------------------- /statusitem.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjolnirapp/AppGrid/HEAD/statusitem.sketch -------------------------------------------------------------------------------- /AppGrid/defaults.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjolnirapp/AppGrid/HEAD/AppGrid/defaults.plist -------------------------------------------------------------------------------- /AppGrid/sd_intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjolnirapp/AppGrid/HEAD/AppGrid/sd_intro.png -------------------------------------------------------------------------------- /AppGrid/statusitem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjolnirapp/AppGrid/HEAD/AppGrid/statusitem.png -------------------------------------------------------------------------------- /AppGrid/statusitem.acorn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjolnirapp/AppGrid/HEAD/AppGrid/statusitem.acorn -------------------------------------------------------------------------------- /AppGrid/statusitem@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjolnirapp/AppGrid/HEAD/AppGrid/statusitem@2x.png -------------------------------------------------------------------------------- /AppGrid/SDGrid.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface SDGrid : NSObject 4 | + (void) setup; 5 | @end 6 | -------------------------------------------------------------------------------- /AppGrid/SDAppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface SDAppDelegate : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /AppGrid/Images.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjolnirapp/AppGrid/HEAD/AppGrid/Images.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /AppGrid/Images.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjolnirapp/AppGrid/HEAD/AppGrid/Images.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /AppGrid/Images.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjolnirapp/AppGrid/HEAD/AppGrid/Images.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /AppGrid/Images.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjolnirapp/AppGrid/HEAD/AppGrid/Images.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /AppGrid/Images.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjolnirapp/AppGrid/HEAD/AppGrid/Images.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /AppGrid/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjolnirapp/AppGrid/HEAD/AppGrid/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /AppGrid/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjolnirapp/AppGrid/HEAD/AppGrid/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /AppGrid/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjolnirapp/AppGrid/HEAD/AppGrid/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /AppGrid/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjolnirapp/AppGrid/HEAD/AppGrid/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /AppGrid/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjolnirapp/AppGrid/HEAD/AppGrid/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /AppGrid/SDOpenAtLogin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface SDOpenAtLogin : NSObject 4 | 5 | + (BOOL) opensAtLogin; 6 | + (void) setOpensAtLogin:(BOOL)opensAtLogin; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /AppGrid/MASShortcutView+UserDefaults.h: -------------------------------------------------------------------------------- 1 | #import "MASShortcutView.h" 2 | 3 | @interface MASShortcutView (UserDefaults) 4 | 5 | @property (nonatomic, copy) NSString *associatedUserDefaultsKey; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /AppGrid/SDWelcomeWindowController.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface SDWelcomeWindowController : NSWindowController 4 | 5 | + (void) showInstructionsWindow; 6 | + (void) showInstructionsWindowFirstTimeOnly; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | - push 4 | jobs: 5 | macos: 6 | runs-on: macOS-latest 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v2 10 | - name: Build 11 | run: make release 12 | -------------------------------------------------------------------------------- /AppGrid/MASShortcut+Monitoring.h: -------------------------------------------------------------------------------- 1 | #import "MASShortcut.h" 2 | 3 | @interface MASShortcut (Monitoring) 4 | 5 | + (id)addGlobalHotkeyMonitorWithShortcut:(MASShortcut *)shortcut handler:(void (^)(void))handler; 6 | + (void)removeGlobalHotkeyMonitor:(id)monitor; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /AppGrid/SDAccessibility.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface SDAccessibility : NSObject 4 | 5 | + (void) openPanel; 6 | + (instancetype) singleton; 7 | 8 | // yes, this is intentionally a @property, because we using bindings with it 9 | @property BOOL isEnabled; 10 | 11 | + (void) setup; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /AppGrid/SDPreferences.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface SDPreferences : NSObject 4 | 5 | + (NSInteger)width; 6 | + (void)setWidth:(NSInteger)newWidth; 7 | 8 | + (BOOL)usesWindowMargins; 9 | + (void)setUsesWindowMargins:(BOOL)usesWindowMargins; 10 | 11 | + (NSInteger)windowMargins; 12 | + (void)setWindowMargins:(NSInteger)newWindowMargins; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | *.xcworkspace 13 | !default.xcworkspace 14 | xcuserdata 15 | profile 16 | *.moved-aside 17 | DerivedData 18 | .idea/ 19 | 20 | /AppGrid.app 21 | /AppGrid-*.tgz 22 | /AppGrid-*.zip 23 | /Index 24 | Index/* 25 | -------------------------------------------------------------------------------- /AppGrid/MASShortcut+UserDefaults.h: -------------------------------------------------------------------------------- 1 | #import "MASShortcut.h" 2 | 3 | @interface MASShortcut (UserDefaults) 4 | 5 | + (void)registerGlobalShortcutWithUserDefaultsKey:(NSString *)userDefaultsKey handler:(void (^)(void))handler; 6 | + (void)unregisterGlobalShortcutWithUserDefaultsKey:(NSString *)userDefaultsKey; 7 | + (void)setGlobalShortcut:(MASShortcut *)shortcut forUserDefaultsKey:(NSString *)userDefaultsKey; 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /AppGrid/SDValueTransformers.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface SDStringNotEmptyValueTransformer : NSValueTransformer 4 | @end 5 | @implementation SDStringNotEmptyValueTransformer 6 | + (Class)transformedValueClass { return [NSString class]; } 7 | + (BOOL)allowsReverseTransformation { return NO; } 8 | - (id)transformedValue:(NSString*)value { 9 | return @([[value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length] > 0); 10 | } 11 | @end 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION = $(shell defaults read `pwd`/AppGrid/AppGrid-Info CFBundleVersion) 2 | APPFILE = AppGrid.app 3 | TGZFILE = AppGrid-$(VERSION).tgz 4 | ZIPFILE = AppGrid-$(VERSION).zip 5 | 6 | release: $(TGZFILE) $(ZIPFILE) 7 | 8 | $(APPFILE): AppGrid AppGrid.xcodeproj 9 | rm -rf $@ 10 | xcodebuild clean build 11 | cp -R build/Release/AppGrid.app $@ 12 | 13 | $(TGZFILE): $(APPFILE) 14 | tar -czf $@ $< 15 | 16 | $(ZIPFILE): $(APPFILE) 17 | zip -qr $@ $< 18 | 19 | clean: 20 | rm -rf $(APPFILE) $(TGZFILE) $(ZIPFILE) 21 | 22 | .PHONY: release clean 23 | -------------------------------------------------------------------------------- /AppGrid/SDWindow.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface SDWindow : NSObject 4 | 5 | + (NSArray*) allWindows; 6 | + (NSArray*) visibleWindows; 7 | + (SDWindow*) focusedWindow; 8 | 9 | - (CGRect) frame; 10 | - (void) setFrame:(CGRect)frame; 11 | 12 | - (CGRect) gridProps; 13 | - (void) moveToGridProps:(CGRect)gridProps; 14 | 15 | - (void) moveToNextScreen; 16 | - (void) moveToPreviousScreen; 17 | 18 | - (void) maximize; 19 | 20 | - (BOOL) focusWindow; 21 | 22 | - (NSArray*) otherWindowsOnSameScreen; 23 | 24 | - (NSString *) title; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AppGrid 2 | ======= 3 | 4 | *macOS window manager with Vim–like hotkeys* 5 | 6 | [Download the app (.zip)](https://github.com/sdegutis/AppGrid/releases/download/1.0.4/AppGrid-1.0.4.zip) 7 | 8 | - Move and resize windows along an invisible "grid" of your screen. 9 | - Keys default to Mash (cmd + ctrl + opt) and Vim keys, but are customizable. 10 | - AppGrid is 🍺ware: if you find it useful, feel free to [buy me a 🍺](https://www.paypal.com/cgi-bin/webscr?business=sbdegutis@gmail.com&cmd=_donations&item_name=AppGrid%20donation&no_shipping=1). 11 | 12 | Animated Screenshot 13 | ------------------- 14 | 15 | ![Animated screenshot](grid.gif) 16 | 17 | Configuration 18 | ------------- 19 | 20 | ![Configuration panel](sshot.png) 21 | 22 | License 23 | ------- 24 | 25 | See [LICENSE](LICENSE) 26 | -------------------------------------------------------------------------------- /AppGrid/MASShortcutView.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class MASShortcut; 4 | 5 | typedef enum { 6 | MASShortcutViewAppearanceDefault = 0, // Height = 19 px 7 | MASShortcutViewAppearanceTexturedRect, // Height = 25 px 8 | MASShortcutViewAppearanceRounded // Height = 43 px 9 | } MASShortcutViewAppearance; 10 | 11 | @interface MASShortcutView : NSView 12 | 13 | @property (nonatomic, strong) MASShortcut *shortcutValue; 14 | @property (nonatomic, getter = isRecording) BOOL recording; 15 | @property (nonatomic, getter = isEnabled) BOOL enabled; 16 | @property (nonatomic, copy) void (^shortcutValueChange)(MASShortcutView *sender); 17 | @property (nonatomic) MASShortcutViewAppearance appearance; 18 | 19 | /// Returns custom class for drawing control. 20 | + (Class)shortcutCellClass; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CURVER=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" AppGrid/AppGrid-Info.plist) 4 | 5 | MAJOR=$(echo "$CURVER" | awk -F "." '{print $1}') 6 | MINOR=$(echo "$CURVER" | awk -F "." '{print $2}') 7 | PATCH=$(echo "$CURVER" | awk -F "." '{print $3}') 8 | 9 | MSG=$(git log -1 --format=%s) 10 | 11 | # Follow conventaional commits? 12 | # https://www.conventionalcommits.org/en/v1.0.0/ 13 | case $MSG in 14 | "fix"*) 15 | PATCH=$(($PATCH + 1)) 16 | ;; 17 | "feat"*) 18 | MINOR=$(($MINOR + 1)) 19 | PATCH="0" 20 | ;; 21 | "BREAKING CHANGE"*) 22 | MAJOR=$(($MAJOR + 1)) 23 | MINOR="0" 24 | PATCH="0" 25 | ;; 26 | esac 27 | 28 | NEW_VERSION="$MAJOR.$MINOR.$PATCH" 29 | /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $NEW_VERSION" AppGrid/AppGrid-Info.plist 30 | /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $NEW_VERSION" AppGrid/AppGrid-Info.plist 31 | -------------------------------------------------------------------------------- /.github/workflows/pre-release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "pre-release" 3 | 4 | on: 5 | push: 6 | branches: 7 | - "master" 8 | 9 | jobs: 10 | macos: 11 | runs-on: macOS-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | - name: Increment Version 16 | run: ./version.sh 17 | - uses: EndBug/add-and-commit@v7 18 | with: 19 | default_author: github_actions 20 | - name: Build 21 | run: make release 22 | - name: Upload artifacts 23 | uses: actions/upload-artifact@v2 24 | with: 25 | name: build 26 | path: . 27 | 28 | pre-release: 29 | name: "Pre Release" 30 | runs-on: "ubuntu-latest" 31 | needs: macos 32 | steps: 33 | - name: Download artifacts 34 | uses: actions/download-artifact@v4.1.7 35 | with: 36 | name: build 37 | - uses: "marvinpinto/action-automatic-releases@latest" 38 | with: 39 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 40 | automatic_release_tag: "latest" 41 | prerelease: true 42 | title: "Development Build" 43 | files: | 44 | *.tgz 45 | *.zip 46 | -------------------------------------------------------------------------------- /AppGrid/SDPreferences.m: -------------------------------------------------------------------------------- 1 | #import "SDPreferences.h" 2 | 3 | #define MyGridWidthDefaultsKey @"MyGridWidthDefaultsKey" 4 | #define MyUseWindowMarginsDefaultsKey @"MyUseWindowMarginsDefaultsKey" 5 | #define MyWindowMarginsDefaultKey @"MyWindowMarginsDefaultKey" 6 | 7 | @implementation SDPreferences 8 | 9 | + (NSInteger) width { 10 | return [[NSUserDefaults standardUserDefaults] integerForKey:MyGridWidthDefaultsKey]; 11 | } 12 | 13 | + (void) setWidth:(NSInteger)newWidth { 14 | [[NSUserDefaults standardUserDefaults] setInteger:newWidth forKey:MyGridWidthDefaultsKey]; 15 | } 16 | 17 | + (BOOL) usesWindowMargins { 18 | return [[NSUserDefaults standardUserDefaults] boolForKey:MyUseWindowMarginsDefaultsKey]; 19 | } 20 | 21 | + (void) setUsesWindowMargins:(BOOL)usesWindowMargins { 22 | [[NSUserDefaults standardUserDefaults] setBool:usesWindowMargins forKey:MyUseWindowMarginsDefaultsKey]; 23 | } 24 | 25 | + (NSInteger) windowMargins { 26 | return [[NSUserDefaults standardUserDefaults] integerForKey:MyWindowMarginsDefaultKey]; 27 | } 28 | 29 | + (void) setWindowMargins:(NSInteger)newWindowMargins { 30 | [[NSUserDefaults standardUserDefaults] setInteger:newWindowMargins forKey:MyWindowMarginsDefaultKey]; 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /AppGrid/AppGrid-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0.1 23 | LSApplicationCategoryType 24 | public.app-category.productivity 25 | LSMinimumSystemVersion 26 | ${MACOSX_DEPLOYMENT_TARGET} 27 | LSUIElement 28 | 29 | NSAppleScriptEnabled 30 | 31 | NSHumanReadableCopyright 32 | Copyright © 2018 Steven Degutis. All rights reserved. 33 | NSMainNibFile 34 | MainMenu 35 | NSPrincipalClass 36 | NSApplication 37 | 38 | 39 | -------------------------------------------------------------------------------- /AppGrid/SDAccessibility.m: -------------------------------------------------------------------------------- 1 | #import "SDAccessibility.h" 2 | 3 | extern Boolean AXIsProcessTrustedWithOptions(CFDictionaryRef options); 4 | extern CFStringRef kAXTrustedCheckOptionPrompt __attribute__((weak_import)); 5 | 6 | @implementation SDAccessibility 7 | 8 | + (void) setup { 9 | [[NSDistributedNotificationCenter defaultCenter] addObserver:[self singleton] 10 | selector:@selector(accessibilityChanged:) 11 | name:@"com.apple.accessibility.api" 12 | object:nil]; 13 | [[self singleton] recache]; 14 | } 15 | 16 | - (void) recache { 17 | self.isEnabled = [SDAccessibility isEnabled]; 18 | } 19 | 20 | + (BOOL) isEnabled { 21 | return AXIsProcessTrustedWithOptions(NULL); 22 | } 23 | 24 | + (void) openPanel { 25 | AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)@{(__bridge id)kAXTrustedCheckOptionPrompt: @YES}); 26 | } 27 | 28 | + (instancetype) singleton { 29 | static id s; 30 | static dispatch_once_t onceToken; 31 | dispatch_once(&onceToken, ^{ 32 | s = [[self alloc] init]; 33 | }); 34 | return s; 35 | } 36 | 37 | - (void) accessibilityChanged:(NSNotification*)note { 38 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.15 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 39 | [self recache]; 40 | }); 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /AppGrid/SDPreferencesWindowController.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "MASShortcutView.h" 3 | 4 | #define MyAlignAllToGridShortcutKey @"MyAlignAllToGridShortcutKey" 5 | #define MyAlignThisToGridShortcutKey @"MyAlignThisToGridShortcutKey" 6 | 7 | #define MyMoveNextScreenShortcutKey @"MyMoveNextScreenShortcutKey" 8 | #define MyMovePrevScreenShortcutKey @"MyMovePrevScreenShortcutKey" 9 | 10 | #define MyMoveLeftShortcutKey @"MyMoveLeftShortcutKey" 11 | #define MyMoveRightShortcutKey @"MyMoveRightShortcutKey" 12 | 13 | #define MyGrowRightShortcutKey @"MyGrowRightShortcutKey" 14 | #define MyShrinkRightShortcutKey @"MyShrinkRightShortcutKey" 15 | 16 | #define MyIncreaseGridWidthShortcutKey @"MyIncreaseGridWidthShortcutKey" 17 | #define MyDecreaseGridWidthShortcutKey @"MyDecreaseGridWidthShortcutKey" 18 | 19 | #define MyMaximizeShortcutKey @"MyMaximizeShortcutKey" 20 | 21 | #define MyFocusWindowLeftShortcutKey @"MyFocusWindowLeftShortcutKey" 22 | #define MyFocusWindowRightShortcutKey @"MyFocusWindowRightShortcutKey" 23 | #define MyFocusWindowUpShortcutKey @"MyFocusWindowUpShortcutKey" 24 | #define MyFocusWindowDownShortcutKey @"MyFocusWindowDownShortcutKey" 25 | 26 | #define MyShrinkToUpperRowShortcutKey @"MyShrinkToUpperRowShortcutKey" 27 | #define MyShrinkToLowerRowShortcutKey @"MyShrinkToLowerRowShortcutKey" 28 | #define MyFillEntireColumnShortcutKey @"MyFillEntireColumnShortcutKey" 29 | 30 | @interface SDPreferencesWindowController : NSWindowController 31 | 32 | + (instancetype) singleton; 33 | 34 | - (BOOL) showAccessibilityWarningIfNeeded; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /AppGrid/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "icon_16x16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "icon_16x16@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "icon_32x32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "icon_32x32@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "icon_128x128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "icon_128x128@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "icon_256x256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "icon_256x256@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "icon_512x512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "icon_512x512@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /AppGrid/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1561\cocoasubrtf200 2 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fnil\fcharset0 AppleColorEmoji;} 3 | {\colortbl;\red255\green255\blue255;} 4 | {\*\expandedcolortbl;;} 5 | \margl1440\margr1440\vieww10800\viewh8400\viewkind0 6 | \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\qc\partightenfactor0 7 | 8 | \f0\fs26 \cf0 \ 9 | AppGrid is made with 10 | \f1 \uc0\u10084 \u65039 11 | \f0 by {\field{\*\fldinst{HYPERLINK "http://sdegutis.com/"}}{\fldrslt Steven Degutis}}\ 12 | \ 13 | Enjoying the app? {\field{\*\fldinst{HYPERLINK "https://www.paypal.com/cgi-bin/webscr?business=sbdegutis@gmail.com&cmd=_donations&item_name=AppGrid%20donation&no_shipping=1"}}{\fldrslt Buy me a 14 | \f1 \uc0\u55356 \u57210 }}\ 15 | \ 16 | \ 17 | \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 18 | \cf0 Other software licenses:\ 19 | \ 20 | 21 | \b MASShortcut 22 | \b0 \ 23 | \ 24 | Copyright (c) 2012-2013, Vadim Shpakovski\ 25 | All rights reserved.\ 26 | \ 27 | Redistribution and use in source and binary forms, with or without\ 28 | modification, are permitted provided that the following conditions are met:\ 29 | \ 30 | 1. Redistributions of source code must retain the above copyright notice, this\ 31 | list of conditions and the following disclaimer.\ 32 | 2. Redistributions in binary form must reproduce the above copyright notice,\ 33 | this list of conditions and the following disclaimer in the documentation\ 34 | and/or other materials provided with the distribution.\ 35 | \ 36 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND\ 37 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\ 38 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\ 39 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\ 40 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\ 41 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\ 42 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\ 43 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\ 44 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\ 45 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\ 46 | } -------------------------------------------------------------------------------- /AppGrid/SDWelcomeWindowController.m: -------------------------------------------------------------------------------- 1 | #import "SDWelcomeWindowController.h" 2 | 3 | #define SDInstructionsWindowFirstTimePastKey @"SDInstructionsWindowFirstTimePassed" // never change this line, ever. 4 | 5 | @implementation SDWelcomeWindowController 6 | 7 | - (NSString*) windowNibName { 8 | return @"WelcomeWindow"; 9 | } 10 | 11 | + (SDWelcomeWindowController*) sharedWelcomeWindowController { 12 | static SDWelcomeWindowController* sharedWelcomeWindowController; 13 | static dispatch_once_t onceToken; 14 | dispatch_once(&onceToken, ^{ 15 | sharedWelcomeWindowController = [[SDWelcomeWindowController alloc] init]; 16 | }); 17 | return sharedWelcomeWindowController; 18 | } 19 | 20 | + (void) showInstructionsWindowFirstTimeOnly { 21 | BOOL firstTimePast = [[NSUserDefaults standardUserDefaults] boolForKey:SDInstructionsWindowFirstTimePastKey]; 22 | 23 | if (!firstTimePast) { 24 | [[NSUserDefaults standardUserDefaults] setBool:YES forKey:SDInstructionsWindowFirstTimePastKey]; 25 | [self showInstructionsWindow]; 26 | } 27 | } 28 | 29 | + (void) showInstructionsWindow { 30 | [NSApp activateIgnoringOtherApps:YES]; 31 | [[[self sharedWelcomeWindowController] window] center]; 32 | [[self sharedWelcomeWindowController] showWindow:self]; 33 | } 34 | 35 | - (NSString*) appName { 36 | return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"]; 37 | } 38 | 39 | - (void) windowDidLoad { 40 | [super windowDidLoad]; 41 | [[self window] setContentBorderThickness:34.0 forEdge:NSMinYEdge]; 42 | } 43 | 44 | @end 45 | 46 | 47 | 48 | 49 | @interface SDWelcomeWindowRoundedImageView : NSImageView 50 | @end 51 | 52 | @implementation SDWelcomeWindowRoundedImageView 53 | 54 | - (void) drawRect:(NSRect)dirtyRect { 55 | float r = 7.0; 56 | float r2 = r - 0.9; 57 | 58 | NSRect bounds = [self bounds]; 59 | NSRect imageClipBounds = NSInsetRect(bounds, 1.0, 1.0); 60 | 61 | NSBezierPath *borderFillPath = [NSBezierPath bezierPathWithRoundedRect:bounds xRadius:r yRadius:r]; 62 | NSBezierPath *imageClipPath = [NSBezierPath bezierPathWithRoundedRect:imageClipBounds xRadius:r2 yRadius:r2]; 63 | 64 | // draw border 65 | [[NSColor colorWithCalibratedWhite:0.20 alpha:0.60] setFill]; 66 | [borderFillPath fill]; 67 | 68 | // clip 69 | [imageClipPath addClip]; 70 | 71 | // draw image 72 | [super drawRect:dirtyRect]; 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /AppGrid/MASShortcut.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #define MASShortcutChar(char) [NSString stringWithFormat:@"%C", (unsigned short)(char)] 5 | #define MASShortcutClear(flags) (flags & (NSControlKeyMask | NSShiftKeyMask | NSAlternateKeyMask | NSCommandKeyMask)) 6 | #define MASShortcutCarbonFlags(cocoaFlags) (\ 7 | (cocoaFlags & NSCommandKeyMask ? cmdKey : 0) | \ 8 | (cocoaFlags & NSAlternateKeyMask ? optionKey : 0) | \ 9 | (cocoaFlags & NSControlKeyMask ? controlKey : 0) | \ 10 | (cocoaFlags & NSShiftKeyMask ? shiftKey : 0)) 11 | 12 | // These glyphs are missed in Carbon.h 13 | static enum { 14 | kMASShortcutGlyphEject = 0x23CF, 15 | kMASShortcutGlyphClear = 0x2715, 16 | kMASShortcutGlyphDeleteLeft = 0x232B, 17 | kMASShortcutGlyphDeleteRight = 0x2326, 18 | kMASShortcutGlyphLeftArrow = 0x2190, 19 | kMASShortcutGlyphRightArrow = 0x2192, 20 | kMASShortcutGlyphUpArrow = 0x2191, 21 | kMASShortcutGlyphDownArrow = 0x2193, 22 | kMASShortcutGlyphEscape = 0x238B, 23 | kMASShortcutGlyphHelp = 0x003F, 24 | kMASShortcutGlyphPageDown = 0x21DF, 25 | kMASShortcutGlyphPageUp = 0x21DE, 26 | kMASShortcutGlyphTabRight = 0x21E5, 27 | kMASShortcutGlyphReturn = 0x2305, 28 | kMASShortcutGlyphReturnR2L = 0x21A9, 29 | kMASShortcutGlyphPadClear = 0x2327, 30 | kMASShortcutGlyphNorthwestArrow = 0x2196, 31 | kMASShortcutGlyphSoutheastArrow = 0x2198, 32 | } MASShortcutGlyph; 33 | 34 | @interface MASShortcut : NSObject 35 | 36 | @property (nonatomic) NSUInteger keyCode; 37 | @property (nonatomic) NSUInteger modifierFlags; 38 | @property (nonatomic, readonly) UInt32 carbonKeyCode; 39 | @property (nonatomic, readonly) UInt32 carbonFlags; 40 | @property (nonatomic, readonly) NSString *keyCodeString; 41 | @property (nonatomic, readonly) NSString *keyCodeStringForKeyEquivalent; 42 | @property (nonatomic, readonly) NSString *modifierFlagsString; 43 | @property (nonatomic, readonly) NSData *data; 44 | @property (nonatomic, readonly) BOOL shouldBypass; 45 | @property (nonatomic, readonly, getter = isValid) BOOL valid; 46 | 47 | - (id)initWithKeyCode:(NSUInteger)code modifierFlags:(NSUInteger)flags; 48 | 49 | + (MASShortcut *)shortcutWithKeyCode:(NSUInteger)code modifierFlags:(NSUInteger)flags; 50 | + (MASShortcut *)shortcutWithEvent:(NSEvent *)anEvent; 51 | + (MASShortcut *)shortcutWithData:(NSData *)aData; 52 | 53 | - (BOOL)isTakenError:(NSError **)error; 54 | 55 | // The following API enable hotkeys with the Option key as the only modifier 56 | // For example, Option-G will not generate © and Option-R will not paste ® 57 | + (void)setAllowsAnyHotkeyWithOptionModifier:(BOOL)allow; 58 | + (BOOL)allowsAnyHotkeyWithOptionModifier; 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /AppGrid/SDOpenAtLogin.m: -------------------------------------------------------------------------------- 1 | #import "SDOpenAtLogin.h" 2 | 3 | @implementation SDOpenAtLogin 4 | 5 | + (LSSharedFileListRef) sharedFileList { 6 | static LSSharedFileListRef sharedFileList; 7 | static dispatch_once_t onceToken; 8 | dispatch_once(&onceToken, ^{ 9 | sharedFileList = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL); 10 | }); 11 | return sharedFileList; 12 | } 13 | 14 | + (void) setOpensAtLogin:(BOOL)opensAtLogin { 15 | NSURL *appURL = [[[NSBundle mainBundle] bundleURL] fileReferenceURL]; 16 | 17 | if (opensAtLogin) { 18 | LSSharedFileListItemRef result = LSSharedFileListInsertItemURL([self sharedFileList], 19 | kLSSharedFileListItemLast, 20 | NULL, 21 | NULL, 22 | (__bridge CFURLRef)appURL, 23 | NULL, 24 | NULL); 25 | CFRelease(result); 26 | } 27 | else { 28 | UInt32 seed; 29 | NSArray *sharedFileListArray = (__bridge_transfer NSArray*)LSSharedFileListCopySnapshot([self sharedFileList], &seed); 30 | for (id item in sharedFileListArray) { 31 | LSSharedFileListItemRef sharedFileItem = (__bridge LSSharedFileListItemRef)item; 32 | CFURLRef url = NULL; 33 | 34 | OSStatus result = LSSharedFileListItemResolve(sharedFileItem, 0, &url, NULL); 35 | if (result == noErr && url != nil) { 36 | if ([appURL isEqual: [(__bridge NSURL*)url fileReferenceURL]]) 37 | LSSharedFileListItemRemove([self sharedFileList], sharedFileItem); 38 | 39 | CFRelease(url); 40 | } 41 | } 42 | } 43 | } 44 | 45 | + (BOOL) opensAtLogin { 46 | NSURL *appURL = [[[NSBundle mainBundle] bundleURL] fileReferenceURL]; 47 | 48 | UInt32 seed; 49 | NSArray *sharedFileListArray = (__bridge_transfer NSArray*)LSSharedFileListCopySnapshot([self sharedFileList], &seed); 50 | for (id item in sharedFileListArray) { 51 | LSSharedFileListItemRef sharedFileItem = (__bridge LSSharedFileListItemRef)item; 52 | CFURLRef url = NULL; 53 | 54 | OSStatus result = LSSharedFileListItemResolve(sharedFileItem, 0, &url, NULL); 55 | if (result == noErr && url != NULL) { 56 | BOOL foundIt = [appURL isEqual: [(__bridge NSURL*)url fileReferenceURL]]; 57 | 58 | CFRelease(url); 59 | 60 | if (foundIt) 61 | return YES; 62 | } 63 | } 64 | 65 | return NO; 66 | } 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /AppGrid/SDAppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "SDAppDelegate.h" 2 | #import "SDWelcomeWindowController.h" 3 | #import "SDPreferencesWindowController.h" 4 | #import "SDAccessibility.h" 5 | #import "SDGrid.h" 6 | 7 | @interface SDAppDelegate () 8 | 9 | @property NSStatusItem* statusItem; 10 | @property IBOutlet NSMenu* statusBarMenu; 11 | 12 | @end 13 | 14 | @implementation SDAppDelegate 15 | 16 | - (void) loadStatusItem { 17 | NSImage* statusItemImage = [NSImage imageNamed:@"statusitem"]; 18 | [statusItemImage setTemplate:YES]; 19 | 20 | self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength]; 21 | [self.statusItem setImage:statusItemImage]; 22 | [self.statusItem setHighlightMode:YES]; 23 | [self.statusItem setMenu:self.statusBarMenu]; 24 | } 25 | 26 | - (void) awakeFromNib { 27 | [self loadStatusItem]; 28 | } 29 | 30 | - (IBAction) reallyShowAboutPanel:(id)sender { 31 | [NSApp activateIgnoringOtherApps:YES]; 32 | [NSApp orderFrontStandardAboutPanel:sender]; 33 | } 34 | 35 | - (IBAction) showHotKeysWindow:(id)sender { 36 | [NSApp activateIgnoringOtherApps:YES]; 37 | [[SDPreferencesWindowController singleton] showWindow: sender]; 38 | } 39 | 40 | - (void) setupDefaults { 41 | NSMutableDictionary* defaults = [[NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"defaults" withExtension:@"plist"]] mutableCopy]; 42 | [[NSUserDefaults standardUserDefaults] registerDefaults: defaults]; 43 | } 44 | 45 | - (void) applicationDidFinishLaunching:(NSNotification *)aNotification { 46 | [self setupDefaults]; 47 | [SDAccessibility setup]; 48 | [SDGrid setup]; 49 | [[SDPreferencesWindowController singleton] showAccessibilityWarningIfNeeded]; 50 | [SDWelcomeWindowController showInstructionsWindowFirstTimeOnly]; 51 | [self askForBeer]; 52 | } 53 | 54 | - (void) askForBeer { 55 | NSUserNotification* note = [[NSUserNotification alloc] init]; 56 | note.identifier = @"buyappgridbeer"; 57 | 58 | note.title = @"AppGrid was made with ❤️ by Steven Degutis."; 59 | note.subtitle = @"It's open source and free and always will be."; 60 | note.informativeText = @"Finding it useful? Buy me a 🍺 via PayPal."; 61 | 62 | note.deliveryDate = [NSDate dateWithTimeIntervalSinceNow: 60 * 60 * 24]; 63 | 64 | [NSUserNotificationCenter defaultUserNotificationCenter].delegate = self; 65 | [[NSUserNotificationCenter defaultUserNotificationCenter] scheduleNotification: note]; 66 | } 67 | 68 | - (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification { 69 | return YES; 70 | } 71 | 72 | - (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification { 73 | [center removeDeliveredNotification: notification]; 74 | 75 | static NSString* DONATE_URL = @"https://www.paypal.com/cgi-bin/webscr?business=sbdegutis@gmail.com&cmd=_donations&item_name=AppGrid%20donation&no_shipping=1"; 76 | [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: DONATE_URL]]; 77 | } 78 | 79 | @end 80 | 81 | int main(int argc, char *argv[]) { 82 | return NSApplicationMain(argc, (const char **)argv); 83 | } 84 | -------------------------------------------------------------------------------- /AppGrid/MASShortcut+UserDefaults.m: -------------------------------------------------------------------------------- 1 | #import "MASShortcut+UserDefaults.h" 2 | #import "MASShortcut+Monitoring.h" 3 | 4 | @interface MASShortcutUserDefaultsHotKey : NSObject 5 | 6 | @property (nonatomic, readonly) NSString *userDefaultsKey; 7 | @property (nonatomic, copy) void (^handler)(void); 8 | @property (nonatomic, weak) id monitor; 9 | 10 | - (id)initWithUserDefaultsKey:(NSString *)userDefaultsKey handler:(void (^)(void))handler; 11 | 12 | @end 13 | 14 | #pragma mark - 15 | 16 | @implementation MASShortcut (UserDefaults) 17 | 18 | + (NSMutableDictionary *)registeredUserDefaultsHotKeys 19 | { 20 | static NSMutableDictionary *shared = nil; 21 | static dispatch_once_t onceToken; 22 | dispatch_once(&onceToken, ^{ 23 | shared = [NSMutableDictionary dictionary]; 24 | }); 25 | return shared; 26 | } 27 | 28 | + (void)registerGlobalShortcutWithUserDefaultsKey:(NSString *)userDefaultsKey handler:(void (^)(void))handler; 29 | { 30 | MASShortcutUserDefaultsHotKey *hotKey = [[MASShortcutUserDefaultsHotKey alloc] initWithUserDefaultsKey:userDefaultsKey handler:handler]; 31 | [[self registeredUserDefaultsHotKeys] setObject:hotKey forKey:userDefaultsKey]; 32 | } 33 | 34 | + (void)unregisterGlobalShortcutWithUserDefaultsKey:(NSString *)userDefaultsKey 35 | { 36 | NSMutableDictionary *registeredHotKeys = [self registeredUserDefaultsHotKeys]; 37 | [registeredHotKeys removeObjectForKey:userDefaultsKey]; 38 | } 39 | 40 | + (void)setGlobalShortcut:(MASShortcut *)shortcut forUserDefaultsKey:(NSString *)userDefaultsKey 41 | { 42 | NSData *shortcutData = shortcut.data; 43 | if (shortcutData) 44 | [[NSUserDefaults standardUserDefaults] setObject:shortcutData forKey:userDefaultsKey]; 45 | else 46 | [[NSUserDefaults standardUserDefaults] removeObjectForKey:userDefaultsKey]; 47 | } 48 | 49 | @end 50 | 51 | #pragma mark - 52 | 53 | @implementation MASShortcutUserDefaultsHotKey { 54 | NSString *_observableKeyPath; 55 | } 56 | 57 | void *MASShortcutUserDefaultsContext = &MASShortcutUserDefaultsContext; 58 | 59 | - (id)initWithUserDefaultsKey:(NSString *)userDefaultsKey handler:(void (^)(void))handler 60 | { 61 | self = [super init]; 62 | if (self) { 63 | _userDefaultsKey = userDefaultsKey.copy; 64 | _handler = [handler copy]; 65 | _observableKeyPath = [@"values." stringByAppendingString:_userDefaultsKey]; 66 | [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:_observableKeyPath options:NSKeyValueObservingOptionInitial context:MASShortcutUserDefaultsContext]; 67 | } 68 | return self; 69 | } 70 | 71 | - (void)dealloc 72 | { 73 | [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:_observableKeyPath context:MASShortcutUserDefaultsContext]; 74 | [MASShortcut removeGlobalHotkeyMonitor:self.monitor]; 75 | } 76 | 77 | #pragma mark - 78 | 79 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 80 | { 81 | if (context == MASShortcutUserDefaultsContext) { 82 | [MASShortcut removeGlobalHotkeyMonitor:self.monitor]; 83 | [self installHotKeyFromUserDefaults]; 84 | } 85 | else { 86 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 87 | } 88 | } 89 | 90 | - (void)installHotKeyFromUserDefaults 91 | { 92 | NSData *data = [[NSUserDefaults standardUserDefaults] dataForKey:_userDefaultsKey]; 93 | MASShortcut *shortcut = [MASShortcut shortcutWithData:data]; 94 | if (shortcut == nil) return; 95 | self.monitor = [MASShortcut addGlobalHotkeyMonitorWithShortcut:shortcut handler:self.handler]; 96 | } 97 | 98 | @end 99 | -------------------------------------------------------------------------------- /AppGrid/WelcomeWindow.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | Welcome to %{title1}@ 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /AppGrid/MASShortcutView+UserDefaults.m: -------------------------------------------------------------------------------- 1 | #import "MASShortcutView+UserDefaults.h" 2 | #import "MASShortcut.h" 3 | #import 4 | 5 | @interface MASShortcutDefaultsObserver : NSObject 6 | 7 | @property (nonatomic, readonly) NSString *userDefaultsKey; 8 | @property (nonatomic, readonly, weak) MASShortcutView *shortcutView; 9 | 10 | - (id)initWithShortcutView:(MASShortcutView *)shortcutView userDefaultsKey:(NSString *)userDefaultsKey; 11 | 12 | @end 13 | 14 | #pragma mark - 15 | 16 | @implementation MASShortcutView (UserDefaults) 17 | 18 | void *MASAssociatedDefaultsObserver = &MASAssociatedDefaultsObserver; 19 | 20 | - (NSString *)associatedUserDefaultsKey 21 | { 22 | MASShortcutDefaultsObserver *defaultsObserver = objc_getAssociatedObject(self, MASAssociatedDefaultsObserver); 23 | return defaultsObserver.userDefaultsKey; 24 | } 25 | 26 | - (void)setAssociatedUserDefaultsKey:(NSString *)associatedUserDefaultsKey 27 | { 28 | // First, stop observing previous shortcut view 29 | objc_setAssociatedObject(self, MASAssociatedDefaultsObserver, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 30 | 31 | if (associatedUserDefaultsKey.length == 0) return; 32 | 33 | // Next, start observing current shortcut view 34 | MASShortcutDefaultsObserver *defaultsObserver = [[MASShortcutDefaultsObserver alloc] initWithShortcutView:self userDefaultsKey:associatedUserDefaultsKey]; 35 | objc_setAssociatedObject(self, MASAssociatedDefaultsObserver, defaultsObserver, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 36 | } 37 | 38 | @end 39 | 40 | #pragma mark - 41 | 42 | @implementation MASShortcutDefaultsObserver { 43 | MASShortcut *_originalShortcut; 44 | BOOL _internalPreferenceChange; 45 | BOOL _internalShortcutChange; 46 | } 47 | 48 | - (id)initWithShortcutView:(MASShortcutView *)shortcutView userDefaultsKey:(NSString *)userDefaultsKey 49 | { 50 | self = [super init]; 51 | if (self) { 52 | _originalShortcut = shortcutView.shortcutValue; 53 | _shortcutView = shortcutView; 54 | _userDefaultsKey = userDefaultsKey.copy; 55 | [self startObservingShortcutView]; 56 | } 57 | return self; 58 | } 59 | 60 | - (void)dealloc 61 | { 62 | // __weak _shortcutView is not yet deallocated because it refers MASShortcutDefaultsObserver 63 | [self stopObservingShortcutView]; 64 | } 65 | 66 | #pragma mark - 67 | 68 | void *kShortcutValueObserver = &kShortcutValueObserver; 69 | 70 | - (void)startObservingShortcutView 71 | { 72 | // Read initial shortcut value from user preferences 73 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 74 | NSData *data = [defaults dataForKey:_userDefaultsKey]; 75 | _shortcutView.shortcutValue = [MASShortcut shortcutWithData:data]; 76 | 77 | // Observe user preferences to update shortcut value when it changed 78 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userDefaultsDidChange:) name:NSUserDefaultsDidChangeNotification object:defaults]; 79 | 80 | // Observe the keyboard shortcut that user inputs by hand 81 | [_shortcutView addObserver:self forKeyPath:@"shortcutValue" options:0 context:kShortcutValueObserver]; 82 | } 83 | 84 | - (void)userDefaultsDidChange:(NSNotification *)note 85 | { 86 | // Ignore notifications posted from -[self observeValueForKeyPath:] 87 | if (_internalPreferenceChange) return; 88 | 89 | _internalShortcutChange = YES; 90 | NSData *data = [note.object dataForKey:_userDefaultsKey]; 91 | _shortcutView.shortcutValue = [MASShortcut shortcutWithData:data]; 92 | _internalShortcutChange = NO; 93 | } 94 | 95 | - (void)stopObservingShortcutView 96 | { 97 | // Stop observing keyboard hotkeys entered by user in the shortcut view 98 | [_shortcutView removeObserver:self forKeyPath:@"shortcutValue" context:kShortcutValueObserver]; 99 | 100 | // Stop observing user preferences 101 | [[NSNotificationCenter defaultCenter] removeObserver:self name:NSUserDefaultsDidChangeNotification object:[NSUserDefaults standardUserDefaults]]; 102 | 103 | // Restore original hotkey in the shortcut view 104 | _shortcutView.shortcutValue = _originalShortcut; 105 | } 106 | 107 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 108 | { 109 | if (context == kShortcutValueObserver) { 110 | if (_internalShortcutChange) return; 111 | MASShortcut *shortcut = [object valueForKey:keyPath]; 112 | _internalPreferenceChange = YES; 113 | 114 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 115 | [defaults setObject:(shortcut.data ?: [NSKeyedArchiver archivedDataWithRootObject:nil]) forKey:_userDefaultsKey]; 116 | [defaults synchronize]; 117 | 118 | _internalPreferenceChange = NO; 119 | } 120 | else { 121 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 122 | } 123 | } 124 | 125 | @end 126 | -------------------------------------------------------------------------------- /AppGrid/MASShortcut+Monitoring.m: -------------------------------------------------------------------------------- 1 | #import "MASShortcut+Monitoring.h" 2 | 3 | NSMutableDictionary *MASRegisteredHotKeys(void); 4 | BOOL InstallCommonEventHandler(void); 5 | BOOL InstallHotkeyWithShortcut(MASShortcut *shortcut, UInt32 *outCarbonHotKeyID, EventHotKeyRef *outCarbonHotKey); 6 | void UninstallEventHandler(void); 7 | 8 | #pragma mark - 9 | 10 | @interface MASShortcutHotKey : NSObject 11 | 12 | @property (nonatomic, readonly) MASShortcut *shortcut; 13 | @property (nonatomic, readonly, copy) void (^handler)(void); 14 | @property (nonatomic, readonly) EventHotKeyRef carbonHotKey; 15 | @property (nonatomic, readonly) UInt32 carbonHotKeyID; 16 | 17 | - (id)initWithShortcut:(MASShortcut *)shortcut handler:(void (^)(void))handler; 18 | 19 | @end 20 | 21 | #pragma mark - 22 | 23 | @implementation MASShortcut (Monitoring) 24 | 25 | + (id)addGlobalHotkeyMonitorWithShortcut:(MASShortcut *)shortcut handler:(void (^)(void))handler 26 | { 27 | NSString *monitor = [NSString stringWithFormat:@"%@", shortcut.description]; 28 | if ([MASRegisteredHotKeys() objectForKey:monitor]) return nil; 29 | 30 | MASShortcutHotKey *hotKey = [[MASShortcutHotKey alloc] initWithShortcut:shortcut handler:handler]; 31 | if (hotKey == nil) return nil; 32 | 33 | [MASRegisteredHotKeys() setObject:hotKey forKey:monitor]; 34 | return monitor; 35 | } 36 | 37 | + (void)removeGlobalHotkeyMonitor:(id)monitor 38 | { 39 | if (monitor == nil) return; 40 | NSMutableDictionary *registeredHotKeys = MASRegisteredHotKeys(); 41 | [registeredHotKeys removeObjectForKey:monitor]; 42 | if (registeredHotKeys.count == 0) { 43 | UninstallEventHandler(); 44 | } 45 | } 46 | 47 | @end 48 | 49 | #pragma mark - 50 | 51 | @implementation MASShortcutHotKey 52 | 53 | @synthesize carbonHotKeyID = _carbonHotKeyID; 54 | @synthesize handler = _handler; 55 | @synthesize shortcut = _shortcut; 56 | @synthesize carbonHotKey = _carbonHotKey; 57 | 58 | #pragma mark - 59 | 60 | - (id)initWithShortcut:(MASShortcut *)shortcut handler:(void (^)(void))handler 61 | { 62 | self = [super init]; 63 | if (self) { 64 | _shortcut = shortcut; 65 | _handler = [handler copy]; 66 | 67 | if (!InstallHotkeyWithShortcut(shortcut, &_carbonHotKeyID, &_carbonHotKey)) 68 | self = nil; 69 | } 70 | return self; 71 | } 72 | 73 | - (void)dealloc 74 | { 75 | [self uninstallExisitingHotKey]; 76 | } 77 | 78 | - (void)uninstallExisitingHotKey 79 | { 80 | if (_carbonHotKey) { 81 | UnregisterEventHotKey(_carbonHotKey); 82 | _carbonHotKey = NULL; 83 | } 84 | } 85 | 86 | @end 87 | 88 | #pragma mark - Carbon magic 89 | 90 | NSMutableDictionary *MASRegisteredHotKeys() 91 | { 92 | static NSMutableDictionary *shared = nil; 93 | static dispatch_once_t onceToken; 94 | dispatch_once(&onceToken, ^{ 95 | shared = [NSMutableDictionary dictionary]; 96 | }); 97 | return shared; 98 | } 99 | 100 | #pragma mark - 101 | 102 | FourCharCode const kMASShortcutSignature = 'MASS'; 103 | 104 | BOOL InstallHotkeyWithShortcut(MASShortcut *shortcut, UInt32 *outCarbonHotKeyID, EventHotKeyRef *outCarbonHotKey) 105 | { 106 | if ((shortcut == nil) || !InstallCommonEventHandler()) return NO; 107 | 108 | static UInt32 sCarbonHotKeyID = 0; 109 | EventHotKeyID hotKeyID = { .signature = kMASShortcutSignature, .id = ++ sCarbonHotKeyID }; 110 | EventHotKeyRef carbonHotKey = NULL; 111 | if (RegisterEventHotKey(shortcut.carbonKeyCode, shortcut.carbonFlags, hotKeyID, GetEventDispatcherTarget(), kEventHotKeyExclusive, &carbonHotKey) != noErr) { 112 | return NO; 113 | } 114 | 115 | if (outCarbonHotKeyID) *outCarbonHotKeyID = hotKeyID.id; 116 | if (outCarbonHotKey) *outCarbonHotKey = carbonHotKey; 117 | return YES; 118 | } 119 | 120 | static OSStatus CarbonCallback(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData) 121 | { 122 | if (GetEventClass(inEvent) != kEventClassKeyboard) return noErr; 123 | 124 | EventHotKeyID hotKeyID; 125 | OSStatus status = GetEventParameter(inEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(hotKeyID), NULL, &hotKeyID); 126 | if (status != noErr) return status; 127 | 128 | if (hotKeyID.signature != kMASShortcutSignature) return noErr; 129 | 130 | [MASRegisteredHotKeys() enumerateKeysAndObjectsUsingBlock:^(id key, MASShortcutHotKey *hotKey, BOOL *stop) { 131 | if (hotKeyID.id == hotKey.carbonHotKeyID) { 132 | if (hotKey.handler) { 133 | hotKey.handler(); 134 | } 135 | *stop = YES; 136 | } 137 | }]; 138 | 139 | return noErr; 140 | } 141 | 142 | #pragma mark - 143 | 144 | static EventHandlerRef sEventHandler = NULL; 145 | 146 | BOOL InstallCommonEventHandler() 147 | { 148 | if (sEventHandler == NULL) { 149 | EventTypeSpec hotKeyPressedSpec = { .eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed }; 150 | OSStatus status = InstallEventHandler(GetEventDispatcherTarget(), CarbonCallback, 1, &hotKeyPressedSpec, NULL, &sEventHandler); 151 | if (status != noErr) { 152 | sEventHandler = NULL; 153 | return NO; 154 | } 155 | } 156 | return YES; 157 | } 158 | 159 | void UninstallEventHandler() 160 | { 161 | if (sEventHandler) { 162 | RemoveEventHandler(sEventHandler); 163 | sEventHandler = NULL; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /AppGrid/SDPreferencesWindowController.m: -------------------------------------------------------------------------------- 1 | #import "SDPreferencesWindowController.h" 2 | #import "SDAccessibility.h" 3 | #import "MASShortcutView+UserDefaults.h" 4 | #import "SDOpenAtLogin.h" 5 | #import "SDPreferences.h" 6 | 7 | @interface SDPreferencesWindowController () 8 | 9 | @property (nonatomic, weak) IBOutlet MASShortcutView *alignAllToGridShortcutView; 10 | @property (nonatomic, weak) IBOutlet MASShortcutView *alignThisToGridShortcutView; 11 | 12 | @property (nonatomic, weak) IBOutlet MASShortcutView *moveNextScreenShortcutView; 13 | @property (nonatomic, weak) IBOutlet MASShortcutView *movePrevScreenShortcutView; 14 | 15 | @property (nonatomic, weak) IBOutlet MASShortcutView *moveLeftShortcutView; 16 | @property (nonatomic, weak) IBOutlet MASShortcutView *moveRightShortcutView; 17 | 18 | @property (nonatomic, weak) IBOutlet MASShortcutView *growRightShortcutView; 19 | @property (nonatomic, weak) IBOutlet MASShortcutView *shrinkRightShortcutView; 20 | 21 | @property (nonatomic, weak) IBOutlet MASShortcutView *increaseGridWidthShortcutView; 22 | @property (nonatomic, weak) IBOutlet MASShortcutView *decreaseGridWidthShortcutView; 23 | 24 | @property (nonatomic, weak) IBOutlet MASShortcutView *maximizeShortcutView; 25 | 26 | @property (nonatomic, weak) IBOutlet MASShortcutView *shrinkToUpperRowShortcutView; 27 | @property (nonatomic, weak) IBOutlet MASShortcutView *shrinkToLowerRowShortcutView; 28 | @property (nonatomic, weak) IBOutlet MASShortcutView *fillEntireColumnShortcutView; 29 | 30 | @property (nonatomic, weak) IBOutlet MASShortcutView *focusWindowLeftShortcutView; 31 | @property (nonatomic, weak) IBOutlet MASShortcutView *focusWindowRightShortcutView; 32 | @property (nonatomic, weak) IBOutlet MASShortcutView *focusWindowUpShortcutView; 33 | @property (nonatomic, weak) IBOutlet MASShortcutView *focusWindowDownShortcutView; 34 | 35 | @property IBOutlet NSButton* checkForUpdatesButton; 36 | @property IBOutlet NSButton* openAtLoginButton; 37 | @property IBOutlet NSButton* toggleMarginsButton; 38 | @property (weak) IBOutlet NSTextField *marginWidthLabel; 39 | @property (strong) IBOutlet NSStepper *marginStepper; 40 | 41 | @property (readonly) SDAccessibility* accessibility; 42 | 43 | @end 44 | 45 | @implementation SDPreferencesWindowController 46 | 47 | @dynamic accessibility; 48 | 49 | + (instancetype) singleton { 50 | static id s; 51 | static dispatch_once_t onceToken; 52 | dispatch_once(&onceToken, ^{ 53 | s = [[self alloc] init]; 54 | }); 55 | return s; 56 | } 57 | 58 | - (IBAction) openAccessibility:(id)sender { 59 | [SDAccessibility openPanel]; 60 | } 61 | 62 | - (IBAction) toggleUseWindowMargins:(id)sender { 63 | BOOL enabled = [sender state] == NSControlStateValueOn; 64 | [SDPreferences setUsesWindowMargins: enabled]; 65 | } 66 | 67 | - (IBAction) toggleOpenAtLogin:(id)sender { 68 | BOOL enabled = [sender state] == NSControlStateValueOn; 69 | [SDOpenAtLogin setOpensAtLogin: enabled]; 70 | } 71 | 72 | - (BOOL) showAccessibilityWarningIfNeeded { 73 | if (!self.accessibility.isEnabled) { 74 | [NSApp activateIgnoringOtherApps: YES]; 75 | [self showWindow: nil]; 76 | } 77 | 78 | return !self.accessibility.isEnabled; 79 | } 80 | 81 | - (IBAction)marginWidth:(NSStepper *)sender { 82 | _marginWidthLabel.integerValue = sender.integerValue; 83 | [SDPreferences setWindowMargins: sender.integerValue]; 84 | } 85 | 86 | - (SDAccessibility*) accessibility { 87 | return [SDAccessibility singleton]; 88 | } 89 | 90 | - (NSString*) maybeEnableAccessibilityString { 91 | if (self.accessibility.isEnabled) 92 | return @"Accessibility is enabled. You're all set!"; 93 | else 94 | return @"Enable Accessibility for AppGrid."; 95 | } 96 | 97 | - (NSImage*) isAccessibilityEnabledImage { 98 | if (self.accessibility.isEnabled) 99 | return [NSImage imageNamed:NSImageNameStatusAvailable]; 100 | else 101 | return [NSImage imageNamed:NSImageNameStatusPartiallyAvailable]; 102 | } 103 | 104 | + (NSSet*) keyPathsForValuesAffectingMaybeEnableAccessibilityString { 105 | return [NSSet setWithArray:@[@"accessibility.isEnabled"]]; 106 | } 107 | 108 | + (NSSet*) keyPathsForValuesAffectingIsAccessibilityEnabledImage { 109 | return [NSSet setWithArray:@[@"accessibility.isEnabled"]]; 110 | } 111 | 112 | - (NSString*) windowNibName { 113 | return @"PreferencesWindow"; 114 | } 115 | 116 | - (void)windowDidLoad { 117 | [super windowDidLoad]; 118 | 119 | self.alignAllToGridShortcutView.associatedUserDefaultsKey = MyAlignAllToGridShortcutKey; 120 | self.alignThisToGridShortcutView.associatedUserDefaultsKey = MyAlignThisToGridShortcutKey; 121 | 122 | self.moveNextScreenShortcutView.associatedUserDefaultsKey = MyMoveNextScreenShortcutKey; 123 | self.movePrevScreenShortcutView.associatedUserDefaultsKey = MyMovePrevScreenShortcutKey; 124 | 125 | self.moveLeftShortcutView.associatedUserDefaultsKey = MyMoveLeftShortcutKey; 126 | self.moveRightShortcutView.associatedUserDefaultsKey = MyMoveRightShortcutKey; 127 | 128 | self.growRightShortcutView.associatedUserDefaultsKey = MyGrowRightShortcutKey; 129 | self.shrinkRightShortcutView.associatedUserDefaultsKey = MyShrinkRightShortcutKey; 130 | 131 | self.increaseGridWidthShortcutView.associatedUserDefaultsKey = MyIncreaseGridWidthShortcutKey; 132 | self.decreaseGridWidthShortcutView.associatedUserDefaultsKey = MyDecreaseGridWidthShortcutKey; 133 | 134 | self.maximizeShortcutView.associatedUserDefaultsKey = MyMaximizeShortcutKey; 135 | 136 | self.focusWindowLeftShortcutView.associatedUserDefaultsKey = MyFocusWindowLeftShortcutKey; 137 | self.focusWindowRightShortcutView.associatedUserDefaultsKey = MyFocusWindowRightShortcutKey; 138 | self.focusWindowUpShortcutView.associatedUserDefaultsKey = MyFocusWindowUpShortcutKey; 139 | self.focusWindowDownShortcutView.associatedUserDefaultsKey = MyFocusWindowDownShortcutKey; 140 | 141 | self.shrinkToLowerRowShortcutView.associatedUserDefaultsKey = MyShrinkToLowerRowShortcutKey; 142 | self.shrinkToUpperRowShortcutView.associatedUserDefaultsKey = MyShrinkToUpperRowShortcutKey; 143 | self.fillEntireColumnShortcutView.associatedUserDefaultsKey = MyFillEntireColumnShortcutKey; 144 | 145 | [self.openAtLoginButton setState: [SDOpenAtLogin opensAtLogin] ? NSOnState : NSOffState]; 146 | [self.toggleMarginsButton setState: [SDPreferences usesWindowMargins] ? NSOnState : NSOffState]; 147 | 148 | self.marginWidthLabel.integerValue = [SDPreferences windowMargins]; 149 | self.marginStepper.integerValue = [SDPreferences windowMargins]; 150 | } 151 | 152 | - (void) showWindow:(id)sender { 153 | if (![[self window] isVisible]) 154 | [[self window] center]; 155 | 156 | [super showWindow: sender]; 157 | } 158 | 159 | - (void) resetKeysSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo; { 160 | if (returnCode == NSAlertAlternateReturn) { 161 | NSDictionary* defaults = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"defaults" withExtension:@"plist"]]; 162 | for (NSString* key in defaults) { 163 | NSData* val = [defaults objectForKey:key]; 164 | [[NSUserDefaults standardUserDefaults] setObject:val forKey:key]; 165 | } 166 | } 167 | } 168 | 169 | - (IBAction) resetToDefaults:(id)sender { 170 | NSBeginAlertSheet(@"Really reset to the default keys?", 171 | @"Do Nothing", 172 | @"Reset Keys", 173 | nil, 174 | [sender window], 175 | self, 176 | @selector(resetKeysSheetDidEnd:returnCode:contextInfo:), 177 | NULL, 178 | NULL, 179 | @"This will discard your custom AppGrid hot keys."); 180 | } 181 | 182 | @end 183 | -------------------------------------------------------------------------------- /AppGrid/SDGrid.m: -------------------------------------------------------------------------------- 1 | #import "SDGrid.h" 2 | 3 | #import "SDAccessibility.h" 4 | #import "SDPreferencesWindowController.h" 5 | #import "MASShortcut+UserDefaults.h" 6 | #import "SDWindow.h" 7 | #import "SDPreferences.h" 8 | 9 | @implementation SDGrid 10 | 11 | + (void) setup { 12 | [MASShortcut setAllowsAnyHotkeyWithOptionModifier:YES]; 13 | 14 | [self bindDefaultsKey:MyAlignAllToGridShortcutKey action:^{ [self alignAllWindows]; }]; 15 | [self bindDefaultsKey:MyAlignThisToGridShortcutKey action:^{ [self alignThisWindow]; }]; 16 | 17 | [self bindDefaultsKey:MyMovePrevScreenShortcutKey action:^{ [self moveToNextScreen]; }]; 18 | [self bindDefaultsKey:MyMoveNextScreenShortcutKey action:^{ [self moveToPreviousScreen]; }]; 19 | 20 | [self bindDefaultsKey:MyMoveLeftShortcutKey action:^{ [self moveLeft]; }]; 21 | [self bindDefaultsKey:MyMoveRightShortcutKey action:^{ [self moveRight]; }]; 22 | 23 | [self bindDefaultsKey:MyGrowRightShortcutKey action:^{ [self growRight]; }]; 24 | [self bindDefaultsKey:MyShrinkRightShortcutKey action:^{ [self shrinkRight]; }]; 25 | 26 | [self bindDefaultsKey:MyIncreaseGridWidthShortcutKey action:^{ [self increaseGridWidth]; }]; 27 | [self bindDefaultsKey:MyDecreaseGridWidthShortcutKey action:^{ [self decreaseGridWidth]; }]; 28 | 29 | [self bindDefaultsKey:MyMaximizeShortcutKey action:^{ [self maximize]; }]; 30 | 31 | [self bindDefaultsKey:MyFocusWindowLeftShortcutKey action:^{ [self focusWindowLeft]; }]; 32 | [self bindDefaultsKey:MyFocusWindowRightShortcutKey action:^{ [self focusWindowRight]; }]; 33 | [self bindDefaultsKey:MyFocusWindowUpShortcutKey action:^{ [self focusWindowUp]; }]; 34 | [self bindDefaultsKey:MyFocusWindowDownShortcutKey action:^{ [self focusWindowDown]; }]; 35 | 36 | [self bindDefaultsKey:MyShrinkToLowerRowShortcutKey action:^{ [self shrinkToLower]; }]; 37 | [self bindDefaultsKey:MyShrinkToUpperRowShortcutKey action:^{ [self shrinkToUpper]; }]; 38 | [self bindDefaultsKey:MyFillEntireColumnShortcutKey action:^{ [self fillEntireColumn]; }]; 39 | } 40 | 41 | + (void) bindDefaultsKey:(NSString*)key action:(dispatch_block_t)action { 42 | [MASShortcut registerGlobalShortcutWithUserDefaultsKey:key handler:^{ 43 | dispatch_async(dispatch_get_main_queue(), ^{ 44 | if ([[SDPreferencesWindowController singleton] showAccessibilityWarningIfNeeded]) 45 | return; 46 | 47 | action(); 48 | }); 49 | }]; 50 | } 51 | 52 | #pragma mark - 53 | 54 | NSPoint SDMidpoint(NSRect r) { 55 | return NSMakePoint(NSMidX(r), NSMidY(r)); 56 | } 57 | 58 | + (NSArray*) windowsInDirectionFn:(double(^)(double angle))whichDirectionFn 59 | shouldDisregardFn:(BOOL(^)(double deltaX, double deltaY))shouldDisregardFn 60 | { 61 | SDWindow* thisWindow = [SDWindow focusedWindow]; 62 | NSPoint startingPoint = SDMidpoint([thisWindow frame]); 63 | 64 | NSArray* otherWindows = [thisWindow otherWindowsOnSameScreen]; 65 | NSMutableArray* closestOtherWindows = [NSMutableArray arrayWithCapacity:[otherWindows count]]; 66 | 67 | for (SDWindow* win in otherWindows) { 68 | NSPoint otherPoint = SDMidpoint([win frame]); 69 | 70 | double deltaX = otherPoint.x - startingPoint.x; 71 | double deltaY = otherPoint.y - startingPoint.y; 72 | 73 | if (shouldDisregardFn(deltaX, deltaY)) 74 | continue; 75 | 76 | double angle = atan2(deltaY, deltaX); 77 | double distance = hypot(deltaX, deltaY); 78 | 79 | double angleDifference = whichDirectionFn(angle); 80 | 81 | double score = distance / cos(angleDifference / 2.0); 82 | 83 | [closestOtherWindows addObject:@{ 84 | @"score": @(score), 85 | @"win": win, 86 | }]; 87 | } 88 | 89 | NSArray* sortedOtherWindows = [closestOtherWindows sortedArrayUsingComparator:^NSComparisonResult(NSDictionary* pair1, NSDictionary* pair2) { 90 | return [[pair1 objectForKey:@"score"] compare: [pair2 objectForKey:@"score"]]; 91 | }]; 92 | 93 | return sortedOtherWindows; 94 | } 95 | 96 | + (void) focusFirstValidWindowIn:(NSArray*)closestWindows { 97 | for (SDWindow* win in [closestWindows valueForKeyPath:@"win"]) { 98 | if ([win focusWindow]) 99 | break; 100 | } 101 | } 102 | 103 | + (void) focusWindowLeft { 104 | NSArray* closestWindows = [self windowsInDirectionFn:^double(double angle) { return M_PI - fabs(angle); } 105 | shouldDisregardFn:^BOOL(double deltaX, double deltaY) { return (deltaX >= 0); }]; 106 | 107 | [self focusFirstValidWindowIn:closestWindows]; 108 | } 109 | 110 | + (void) focusWindowRight { 111 | NSArray* closestWindows = [self windowsInDirectionFn:^double(double angle) { return 0.0 - angle; } 112 | shouldDisregardFn:^BOOL(double deltaX, double deltaY) { return (deltaX <= 0); }]; 113 | 114 | [self focusFirstValidWindowIn:closestWindows]; 115 | } 116 | 117 | + (void) focusWindowUp { 118 | NSArray* closestWindows = [self windowsInDirectionFn:^double(double angle) { return -M_PI_2 - angle; } 119 | shouldDisregardFn:^BOOL(double deltaX, double deltaY) { return (deltaY >= 0); }]; 120 | 121 | [self focusFirstValidWindowIn:closestWindows]; 122 | } 123 | 124 | + (void) focusWindowDown { 125 | NSArray* closestWindows = [self windowsInDirectionFn:^double(double angle) { return M_PI_2 - angle; } 126 | shouldDisregardFn:^BOOL(double deltaX, double deltaY) { return (deltaY <= 0); }]; 127 | 128 | [self focusFirstValidWindowIn:closestWindows]; 129 | } 130 | 131 | + (void) maximize { 132 | [[SDWindow focusedWindow] maximize]; 133 | } 134 | 135 | + (void) increaseGridWidth { 136 | [SDPreferences setWidth:[SDPreferences width] + 1]; 137 | [self alignAllWindows]; 138 | } 139 | 140 | + (void) decreaseGridWidth { 141 | [SDPreferences setWidth:MAX(2, [SDPreferences width] - 1)]; 142 | [self alignAllWindows]; 143 | } 144 | 145 | + (void) increaseWindowMargins { 146 | [SDPreferences setWindowMargins:[SDPreferences windowMargins] + 1]; 147 | [self alignAllWindows]; 148 | } 149 | 150 | + (void) decreaseWindowMargins { 151 | [SDPreferences setWindowMargins:MAX(0, [SDPreferences windowMargins] - 1)]; 152 | [self alignAllWindows]; 153 | } 154 | 155 | + (void) alignAllWindows { 156 | for (SDWindow* win in [SDWindow visibleWindows]) { 157 | [win moveToGridProps:[win gridProps]]; 158 | } 159 | } 160 | 161 | + (void) alignThisWindow { 162 | SDWindow* win = [SDWindow focusedWindow]; 163 | [win moveToGridProps:[win gridProps]]; 164 | } 165 | 166 | + (void) moveLeft { 167 | SDWindow* win = [SDWindow focusedWindow]; 168 | CGRect r = [win gridProps]; 169 | r.origin.x = MAX(r.origin.x - 1, 0); 170 | [win moveToGridProps:r]; 171 | } 172 | 173 | + (void) moveRight { 174 | SDWindow* win = [SDWindow focusedWindow]; 175 | CGRect r = [win gridProps]; 176 | r.origin.x = MIN(r.origin.x + 1, [SDPreferences width] - r.size.width); 177 | [win moveToGridProps:r]; 178 | } 179 | 180 | + (void) growRight { 181 | SDWindow* win = [SDWindow focusedWindow]; 182 | CGRect r = [win gridProps]; 183 | r.size.width = MIN(r.size.width + 1, [SDPreferences width] - r.origin.x); 184 | [win moveToGridProps:r]; 185 | } 186 | 187 | + (void) shrinkRight { 188 | SDWindow* win = [SDWindow focusedWindow]; 189 | CGRect r = [win gridProps]; 190 | r.size.width = MAX(r.size.width - 1, 1); 191 | [win moveToGridProps:r]; 192 | } 193 | 194 | + (void) moveToNextScreen { 195 | SDWindow* win = [SDWindow focusedWindow]; 196 | [win moveToNextScreen]; 197 | } 198 | 199 | + (void) moveToPreviousScreen { 200 | SDWindow* win = [SDWindow focusedWindow]; 201 | [win moveToPreviousScreen]; 202 | } 203 | 204 | + (void) shrinkToLower { 205 | SDWindow* win = [SDWindow focusedWindow]; 206 | CGRect r = [win gridProps]; 207 | r.origin.y = 1; 208 | r.size.height = 1; 209 | [win moveToGridProps:r]; 210 | } 211 | 212 | + (void) shrinkToUpper { 213 | SDWindow* win = [SDWindow focusedWindow]; 214 | CGRect r = [win gridProps]; 215 | r.origin.y = 0; 216 | r.size.height = 1; 217 | [win moveToGridProps:r]; 218 | } 219 | 220 | + (void) fillEntireColumn { 221 | SDWindow* win = [SDWindow focusedWindow]; 222 | CGRect r = [win gridProps]; 223 | r.origin.y = 0; 224 | r.size.height = 2; 225 | [win moveToGridProps:r]; 226 | } 227 | 228 | @end 229 | -------------------------------------------------------------------------------- /AppGrid/SDWindow.m: -------------------------------------------------------------------------------- 1 | #import "SDWindow.h" 2 | 3 | #import "SDPreferences.h" 4 | 5 | @interface SDWindow () 6 | 7 | @property CFTypeRef window; 8 | 9 | @end 10 | 11 | @implementation SDWindow 12 | 13 | + (CGRect) realFrameForScreen:(NSScreen*)screen { 14 | NSScreen* primaryScreen = [[NSScreen screens] objectAtIndex:0]; 15 | CGRect f = [screen visibleFrame]; 16 | f.origin.y = NSHeight([primaryScreen frame]) - NSHeight(f) - f.origin.y; 17 | return f; 18 | } 19 | 20 | + (NSArray*) allWindows { 21 | NSMutableArray* windows = [NSMutableArray array]; 22 | 23 | for (NSRunningApplication* runningApp in [[NSWorkspace sharedWorkspace] runningApplications]) { 24 | // if ([runningApp activationPolicy] == NSApplicationActivationPolicyRegular) { 25 | AXUIElementRef app = AXUIElementCreateApplication([runningApp processIdentifier]); 26 | 27 | CFArrayRef _windows; 28 | AXError result = AXUIElementCopyAttributeValues(app, kAXWindowsAttribute, 0, 100, &_windows); 29 | if (result == kAXErrorSuccess) { 30 | for (NSInteger i = 0; i < CFArrayGetCount(_windows); i++) { 31 | AXUIElementRef win = CFArrayGetValueAtIndex(_windows, i); 32 | SDWindow* window = [[SDWindow alloc] init]; 33 | window.window = CFRetain(win); 34 | [windows addObject:window]; 35 | } 36 | CFRelease(_windows); 37 | } 38 | 39 | CFRelease(app); 40 | // } 41 | } 42 | 43 | return windows; 44 | } 45 | 46 | + (NSArray*) visibleWindows { 47 | return [[self allWindows] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(SDWindow* win, NSDictionary *bindings) { 48 | return ![win isAppHidden] 49 | && ![win isWindowMinimized] 50 | && [[win subrole] isEqualToString: (__bridge NSString*)kAXStandardWindowSubrole]; 51 | }]]; 52 | } 53 | 54 | - (NSArray*) otherWindowsOnSameScreen { 55 | return [[SDWindow visibleWindows] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(SDWindow* win, NSDictionary *bindings) { 56 | return !CFEqual(self.window, win.window) && [[self screen] isEqual: [win screen]]; 57 | }]]; 58 | } 59 | 60 | - (void) dealloc { 61 | if (self.window) 62 | CFRelease(self.window); 63 | } 64 | 65 | //unselectableApps = [NSDictionary dictionaryWithObjectsAndKeys:@"SystemUIServer", @"SystemUIServer", 66 | // @"Slate", @"Slate", 67 | // @"Dropbox", @"Dropbox", 68 | // @"loginwindow", @"loginwindow", nil]; 69 | 70 | + (AXUIElementRef) systemWideElement { 71 | static AXUIElementRef systemWideElement; 72 | static dispatch_once_t onceToken; 73 | dispatch_once(&onceToken, ^{ 74 | systemWideElement = AXUIElementCreateSystemWide(); 75 | }); 76 | return systemWideElement; 77 | } 78 | 79 | + (SDWindow*) focusedWindow { 80 | CFTypeRef app; 81 | AXUIElementCopyAttributeValue([self systemWideElement], kAXFocusedApplicationAttribute, &app); 82 | 83 | CFTypeRef win; 84 | AXError result = AXUIElementCopyAttributeValue(app, (CFStringRef)NSAccessibilityFocusedWindowAttribute, &win); 85 | CFRelease(app); 86 | 87 | if (result == kAXErrorSuccess) { 88 | SDWindow* window = [[SDWindow alloc] init]; 89 | window.window = win; 90 | return window; 91 | } 92 | 93 | return nil; 94 | } 95 | 96 | - (CGRect) gridProps { 97 | CGRect winFrame = [self frame]; 98 | 99 | CGRect screenRect = [SDWindow realFrameForScreen:[self screen]]; 100 | double thirdScrenWidth = screenRect.size.width / [SDPreferences width]; 101 | double halfScreenHeight = screenRect.size.height / 2.0; 102 | 103 | CGRect gridProps; 104 | 105 | gridProps.origin.x = round((winFrame.origin.x - NSMinX(screenRect)) / thirdScrenWidth); 106 | gridProps.origin.y = round((winFrame.origin.y - NSMinY(screenRect)) / halfScreenHeight); 107 | 108 | gridProps.size.width = MAX(round(winFrame.size.width / thirdScrenWidth), 1); 109 | gridProps.size.height = MAX(round(winFrame.size.height / halfScreenHeight), 1); 110 | 111 | return gridProps; 112 | } 113 | 114 | - (void) moveToGridProps:(CGRect)gridProps onScreen:(NSScreen*)screen { 115 | CGRect screenRect = [SDWindow realFrameForScreen:screen]; 116 | 117 | double thirdScrenWidth = screenRect.size.width / [SDPreferences width]; 118 | double halfScreenHeight = screenRect.size.height / 2.0; 119 | 120 | CGRect newFrame; 121 | 122 | newFrame.origin.x = (gridProps.origin.x * thirdScrenWidth) + NSMinX(screenRect); 123 | newFrame.origin.y = (gridProps.origin.y * halfScreenHeight) + NSMinY(screenRect); 124 | newFrame.size.width = gridProps.size.width * thirdScrenWidth; 125 | newFrame.size.height = gridProps.size.height * halfScreenHeight; 126 | 127 | if ([SDPreferences usesWindowMargins]) 128 | newFrame = NSInsetRect(newFrame, [SDPreferences windowMargins], [SDPreferences windowMargins]); 129 | 130 | newFrame = NSIntegralRect(newFrame); 131 | 132 | [self setFrame:newFrame]; 133 | } 134 | 135 | - (void) moveToGridProps:(CGRect)gridProps { 136 | [self moveToGridProps:gridProps onScreen:[self screen]]; 137 | } 138 | 139 | - (CGRect) frame { 140 | CGRect r; 141 | r.origin = [self topLeft]; 142 | r.size = [self size]; 143 | return r; 144 | } 145 | 146 | - (void) setFrame:(CGRect)frame { 147 | [self setSize:frame.size]; 148 | [self setTopLeft:frame.origin]; 149 | [self setSize:frame.size]; 150 | } 151 | 152 | - (CGPoint) topLeft { 153 | CFTypeRef positionStorage; 154 | AXError result = AXUIElementCopyAttributeValue(self.window, (CFStringRef)NSAccessibilityPositionAttribute, &positionStorage); 155 | 156 | CGPoint topLeft; 157 | if (result == kAXErrorSuccess) { 158 | if (!AXValueGetValue(positionStorage, kAXValueCGPointType, (void *)&topLeft)) { 159 | NSLog(@"could not decode topLeft"); 160 | topLeft = CGPointZero; 161 | } 162 | } 163 | else { 164 | NSLog(@"could not get window topLeft"); 165 | topLeft = CGPointZero; 166 | } 167 | 168 | if (positionStorage) 169 | CFRelease(positionStorage); 170 | 171 | return topLeft; 172 | } 173 | 174 | - (CGSize) size { 175 | CFTypeRef sizeStorage; 176 | AXError result = AXUIElementCopyAttributeValue(self.window, (CFStringRef)NSAccessibilitySizeAttribute, &sizeStorage); 177 | 178 | CGSize size; 179 | if (result == kAXErrorSuccess) { 180 | if (!AXValueGetValue(sizeStorage, kAXValueCGSizeType, (void *)&size)) { 181 | NSLog(@"could not decode topLeft"); 182 | size = CGSizeZero; 183 | } 184 | } 185 | else { 186 | NSLog(@"could not get window size"); 187 | size = CGSizeZero; 188 | } 189 | 190 | if (sizeStorage) 191 | CFRelease(sizeStorage); 192 | 193 | return size; 194 | } 195 | 196 | - (void) setTopLeft:(CGPoint)thePoint { 197 | CFTypeRef positionStorage = (CFTypeRef)(AXValueCreate(kAXValueCGPointType, (const void *)&thePoint)); 198 | AXUIElementSetAttributeValue(self.window, (CFStringRef)NSAccessibilityPositionAttribute, positionStorage); 199 | if (positionStorage) 200 | CFRelease(positionStorage); 201 | } 202 | 203 | - (void) setSize:(CGSize)theSize { 204 | CFTypeRef sizeStorage = (CFTypeRef)(AXValueCreate(kAXValueCGSizeType, (const void *)&theSize)); 205 | AXUIElementSetAttributeValue(self.window, (CFStringRef)NSAccessibilitySizeAttribute, sizeStorage); 206 | if (sizeStorage) 207 | CFRelease(sizeStorage); 208 | } 209 | 210 | - (NSScreen*) screen { 211 | CGRect windowFrame = [self frame]; 212 | 213 | CGFloat lastVolume = 0; 214 | NSScreen* lastScreen = nil; 215 | 216 | for (NSScreen* screen in [NSScreen screens]) { 217 | CGRect screenFrame = [SDWindow realFrameForScreen:screen]; 218 | CGRect intersection = CGRectIntersection(windowFrame, screenFrame); 219 | CGFloat volume = intersection.size.width * intersection.size.height; 220 | 221 | if (volume > lastVolume) { 222 | lastVolume = volume; 223 | lastScreen = screen; 224 | } 225 | } 226 | 227 | return lastScreen; 228 | } 229 | 230 | - (void) moveToNextScreen { 231 | NSArray* screens = [NSScreen screens]; 232 | NSScreen* currentScreen = [self screen]; 233 | 234 | NSUInteger idx = [screens indexOfObject:currentScreen]; 235 | 236 | idx += 1; 237 | if (idx == [screens count]) 238 | idx = 0; 239 | 240 | NSScreen* nextScreen = [screens objectAtIndex:idx]; 241 | [self moveToGridProps:[self gridProps] onScreen:nextScreen]; 242 | } 243 | 244 | - (void) moveToPreviousScreen { 245 | NSArray* screens = [NSScreen screens]; 246 | NSScreen* currentScreen = [self screen]; 247 | 248 | NSUInteger idx = [screens indexOfObject:currentScreen]; 249 | 250 | idx -= 1; 251 | if (idx == -1) 252 | idx = [screens count] - 1; 253 | 254 | NSScreen* nextScreen = [screens objectAtIndex:idx]; 255 | [self moveToGridProps:[self gridProps] onScreen:nextScreen]; 256 | } 257 | 258 | - (void) maximize { 259 | CGRect screenRect = [SDWindow realFrameForScreen:[self screen]]; 260 | [self setFrame:screenRect]; 261 | } 262 | 263 | - (BOOL) focusWindow { 264 | AXError changedMainWindowResult = AXUIElementSetAttributeValue(self.window, (CFStringRef)NSAccessibilityMainAttribute, kCFBooleanTrue); 265 | if (changedMainWindowResult != kAXErrorSuccess) { 266 | NSLog(@"ERROR: Could not change focus to window"); 267 | return NO; 268 | } 269 | 270 | ProcessSerialNumber psn; 271 | GetProcessForPID([self processIdentifier], &psn); 272 | OSStatus focusAppResult = SetFrontProcessWithOptions(&psn, kSetFrontProcessFrontWindowOnly); 273 | return focusAppResult == 0; 274 | } 275 | 276 | - (pid_t) processIdentifier { 277 | pid_t pid = 0; 278 | AXError result = AXUIElementGetPid(self.window, &pid); 279 | if (result == kAXErrorSuccess) 280 | return pid; 281 | else 282 | return 0; 283 | } 284 | 285 | - (BOOL) isAppHidden { 286 | AXUIElementRef app = AXUIElementCreateApplication([self processIdentifier]); 287 | if (app == NULL) 288 | return YES; 289 | 290 | CFTypeRef _isHidden; 291 | BOOL isHidden = NO; 292 | if (AXUIElementCopyAttributeValue(app, (CFStringRef)NSAccessibilityHiddenAttribute, (CFTypeRef *)&_isHidden) == kAXErrorSuccess) { 293 | NSNumber *isHiddenNum = CFBridgingRelease(_isHidden); 294 | isHidden = [isHiddenNum boolValue]; 295 | } 296 | 297 | CFRelease(app); 298 | 299 | return isHidden; 300 | } 301 | 302 | - (id) getWindowProperty:(NSString*)propType withDefaultValue:(id)defaultValue { 303 | CFTypeRef _someProperty; 304 | if (AXUIElementCopyAttributeValue(self.window, (__bridge CFStringRef)propType, &_someProperty) == kAXErrorSuccess) 305 | return CFBridgingRelease(_someProperty); 306 | 307 | return defaultValue; 308 | } 309 | 310 | - (NSString *) title { 311 | return [self getWindowProperty:NSAccessibilityTitleAttribute withDefaultValue:@""]; 312 | } 313 | 314 | - (NSString *) role { 315 | return [self getWindowProperty:NSAccessibilityRoleAttribute withDefaultValue:@""]; 316 | } 317 | 318 | - (NSString *) subrole { 319 | return [self getWindowProperty:NSAccessibilitySubroleAttribute withDefaultValue:@""]; 320 | } 321 | 322 | - (BOOL) isWindowMinimized { 323 | return [[self getWindowProperty:NSAccessibilityMinimizedAttribute withDefaultValue:@(NO)] boolValue]; 324 | } 325 | 326 | @end 327 | -------------------------------------------------------------------------------- /AppGrid/MASShortcut.m: -------------------------------------------------------------------------------- 1 | #import "MASShortcut.h" 2 | 3 | NSString *const MASShortcutKeyCode = @"KeyCode"; 4 | NSString *const MASShortcutModifierFlags = @"ModifierFlags"; 5 | 6 | @implementation MASShortcut { 7 | NSUInteger _keyCode; // NSNotFound if empty 8 | NSUInteger _modifierFlags; // 0 if empty 9 | } 10 | 11 | @synthesize modifierFlags = _modifierFlags; 12 | @synthesize keyCode = _keyCode; 13 | 14 | #pragma mark - 15 | 16 | + (BOOL)supportsSecureCoding 17 | { 18 | return YES; 19 | } 20 | 21 | - (void)encodeWithCoder:(NSCoder *)coder 22 | { 23 | [coder encodeInteger:(self.keyCode != NSNotFound ? (NSInteger)self.keyCode : - 1) forKey:MASShortcutKeyCode]; 24 | [coder encodeInteger:(NSInteger)self.modifierFlags forKey:MASShortcutModifierFlags]; 25 | } 26 | 27 | - (id)initWithCoder:(NSCoder *)decoder 28 | { 29 | self = [super init]; 30 | if (self) { 31 | NSInteger code = [decoder decodeIntegerForKey:MASShortcutKeyCode]; 32 | self.keyCode = (code < 0 ? NSNotFound : (NSUInteger)code); 33 | self.modifierFlags = [decoder decodeIntegerForKey:MASShortcutModifierFlags]; 34 | } 35 | return self; 36 | } 37 | 38 | - (id)initWithKeyCode:(NSUInteger)code modifierFlags:(NSUInteger)flags 39 | { 40 | self = [super init]; 41 | if (self) { 42 | _keyCode = code; 43 | _modifierFlags = MASShortcutClear(flags); 44 | } 45 | return self; 46 | } 47 | 48 | + (MASShortcut *)shortcutWithKeyCode:(NSUInteger)code modifierFlags:(NSUInteger)flags 49 | { 50 | return [[self alloc] initWithKeyCode:code modifierFlags:flags]; 51 | } 52 | 53 | + (MASShortcut *)shortcutWithEvent:(NSEvent *)event 54 | { 55 | return [[self alloc] initWithKeyCode:event.keyCode modifierFlags:event.modifierFlags]; 56 | } 57 | 58 | + (MASShortcut *)shortcutWithData:(NSData *)data 59 | { 60 | id shortcut = (data ? [NSKeyedUnarchiver unarchiveObjectWithData:data] : nil); 61 | return shortcut; 62 | } 63 | 64 | #pragma mark - Shortcut accessors 65 | 66 | - (NSData *)data 67 | { 68 | return [NSKeyedArchiver archivedDataWithRootObject:self]; 69 | } 70 | 71 | - (void)setModifierFlags:(NSUInteger)value 72 | { 73 | _modifierFlags = MASShortcutClear(value); 74 | } 75 | 76 | - (UInt32)carbonKeyCode 77 | { 78 | return (self.keyCode == NSNotFound ? 0 : (UInt32)self.keyCode); 79 | } 80 | 81 | - (UInt32)carbonFlags 82 | { 83 | return MASShortcutCarbonFlags(self.modifierFlags); 84 | } 85 | 86 | - (NSString *)description 87 | { 88 | return [NSString stringWithFormat:@"%@%@", self.modifierFlagsString, self.keyCodeString]; 89 | } 90 | 91 | - (NSString *)keyCodeStringForKeyEquivalent 92 | { 93 | NSString *keyCodeString = self.keyCodeString; 94 | if (keyCodeString.length > 1) { 95 | switch (self.keyCode) { 96 | case kVK_F1: return MASShortcutChar(0xF704); 97 | case kVK_F2: return MASShortcutChar(0xF705); 98 | case kVK_F3: return MASShortcutChar(0xF706); 99 | case kVK_F4: return MASShortcutChar(0xF707); 100 | case kVK_F5: return MASShortcutChar(0xF708); 101 | case kVK_F6: return MASShortcutChar(0xF709); 102 | case kVK_F7: return MASShortcutChar(0xF70a); 103 | case kVK_F8: return MASShortcutChar(0xF70b); 104 | case kVK_F9: return MASShortcutChar(0xF70c); 105 | case kVK_F10: return MASShortcutChar(0xF70d); 106 | case kVK_F11: return MASShortcutChar(0xF70e); 107 | case kVK_F12: return MASShortcutChar(0xF70f); 108 | // From this point down I am guessing F13 etc come sequentially, I don't have a keyboard to test. 109 | case kVK_F13: return MASShortcutChar(0xF710); 110 | case kVK_F14: return MASShortcutChar(0xF711); 111 | case kVK_F15: return MASShortcutChar(0xF712); 112 | case kVK_F16: return MASShortcutChar(0xF713); 113 | case kVK_F17: return MASShortcutChar(0xF714); 114 | case kVK_F18: return MASShortcutChar(0xF715); 115 | case kVK_F19: return MASShortcutChar(0xF716); 116 | case kVK_Space: return MASShortcutChar(0x20); 117 | default: return @""; 118 | } 119 | } 120 | return keyCodeString.lowercaseString; 121 | } 122 | 123 | - (NSString *)keyCodeString 124 | { 125 | // Some key codes don't have an equivalent 126 | switch (self.keyCode) { 127 | case NSNotFound: return @""; 128 | case kVK_F1: return @"F1"; 129 | case kVK_F2: return @"F2"; 130 | case kVK_F3: return @"F3"; 131 | case kVK_F4: return @"F4"; 132 | case kVK_F5: return @"F5"; 133 | case kVK_F6: return @"F6"; 134 | case kVK_F7: return @"F7"; 135 | case kVK_F8: return @"F8"; 136 | case kVK_F9: return @"F9"; 137 | case kVK_F10: return @"F10"; 138 | case kVK_F11: return @"F11"; 139 | case kVK_F12: return @"F12"; 140 | case kVK_F13: return @"F13"; 141 | case kVK_F14: return @"F14"; 142 | case kVK_F15: return @"F15"; 143 | case kVK_F16: return @"F16"; 144 | case kVK_F17: return @"F17"; 145 | case kVK_F18: return @"F18"; 146 | case kVK_F19: return @"F19"; 147 | case kVK_Space: return NSLocalizedString(@"Space", @"Shortcut glyph name for SPACE key"); 148 | case kVK_Escape: return MASShortcutChar(kMASShortcutGlyphEscape); 149 | case kVK_Delete: return MASShortcutChar(kMASShortcutGlyphDeleteLeft); 150 | case kVK_ForwardDelete: return MASShortcutChar(kMASShortcutGlyphDeleteRight); 151 | case kVK_LeftArrow: return MASShortcutChar(kMASShortcutGlyphLeftArrow); 152 | case kVK_RightArrow: return MASShortcutChar(kMASShortcutGlyphRightArrow); 153 | case kVK_UpArrow: return MASShortcutChar(kMASShortcutGlyphUpArrow); 154 | case kVK_DownArrow: return MASShortcutChar(kMASShortcutGlyphDownArrow); 155 | case kVK_Help: return MASShortcutChar(kMASShortcutGlyphHelp); 156 | case kVK_PageUp: return MASShortcutChar(kMASShortcutGlyphPageUp); 157 | case kVK_PageDown: return MASShortcutChar(kMASShortcutGlyphPageDown); 158 | case kVK_Tab: return MASShortcutChar(kMASShortcutGlyphTabRight); 159 | case kVK_Return: return MASShortcutChar(kMASShortcutGlyphReturnR2L); 160 | 161 | // Keypad 162 | case kVK_ANSI_Keypad0: return @"0"; 163 | case kVK_ANSI_Keypad1: return @"1"; 164 | case kVK_ANSI_Keypad2: return @"2"; 165 | case kVK_ANSI_Keypad3: return @"3"; 166 | case kVK_ANSI_Keypad4: return @"4"; 167 | case kVK_ANSI_Keypad5: return @"5"; 168 | case kVK_ANSI_Keypad6: return @"6"; 169 | case kVK_ANSI_Keypad7: return @"7"; 170 | case kVK_ANSI_Keypad8: return @"8"; 171 | case kVK_ANSI_Keypad9: return @"9"; 172 | case kVK_ANSI_KeypadDecimal: return @"."; 173 | case kVK_ANSI_KeypadMultiply: return @"*"; 174 | case kVK_ANSI_KeypadPlus: return @"+"; 175 | case kVK_ANSI_KeypadClear: return MASShortcutChar(kMASShortcutGlyphPadClear); 176 | case kVK_ANSI_KeypadDivide: return @"/"; 177 | case kVK_ANSI_KeypadEnter: return MASShortcutChar(kMASShortcutGlyphReturn); 178 | case kVK_ANSI_KeypadMinus: return @"–"; 179 | case kVK_ANSI_KeypadEquals: return @"="; 180 | 181 | // Hardcode 182 | case 119: return MASShortcutChar(kMASShortcutGlyphSoutheastArrow); 183 | case 115: return MASShortcutChar(kMASShortcutGlyphNorthwestArrow); 184 | } 185 | 186 | // Everything else should be printable so look it up in the current keyboard 187 | OSStatus error = noErr; 188 | NSString *keystroke = nil; 189 | TISInputSourceRef inputSource = TISCopyCurrentKeyboardLayoutInputSource(); 190 | if (inputSource) { 191 | CFDataRef layoutDataRef = TISGetInputSourceProperty(inputSource, kTISPropertyUnicodeKeyLayoutData); 192 | if (layoutDataRef) { 193 | UCKeyboardLayout *layoutData = (UCKeyboardLayout *)CFDataGetBytePtr(layoutDataRef); 194 | UniCharCount length = 0; 195 | UniChar chars[256] = { 0 }; 196 | UInt32 deadKeyState = 0; 197 | error = UCKeyTranslate(layoutData, (UInt16)self.keyCode, kUCKeyActionDisplay, 0, // No modifiers 198 | LMGetKbdType(), kUCKeyTranslateNoDeadKeysMask, &deadKeyState, 199 | sizeof(chars) / sizeof(UniChar), &length, chars); 200 | keystroke = ((error == noErr) && length ? [NSString stringWithCharacters:chars length:length] : @""); 201 | } 202 | CFRelease(inputSource); 203 | } 204 | 205 | // Validate keystroke 206 | if (keystroke.length) { 207 | static NSMutableCharacterSet *validChars = nil; 208 | if (validChars == nil) { 209 | validChars = [[NSMutableCharacterSet alloc] init]; 210 | [validChars formUnionWithCharacterSet:[NSCharacterSet alphanumericCharacterSet]]; 211 | [validChars formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]]; 212 | [validChars formUnionWithCharacterSet:[NSCharacterSet symbolCharacterSet]]; 213 | } 214 | for (NSUInteger i = 0, length = keystroke.length; i < length; i++) { 215 | if (![validChars characterIsMember:[keystroke characterAtIndex:i]]) { 216 | keystroke = @""; 217 | break; 218 | } 219 | } 220 | } 221 | 222 | // Finally, we've got a shortcut! 223 | return keystroke.uppercaseString; 224 | } 225 | 226 | - (NSString *)modifierFlagsString 227 | { 228 | unichar chars[4]; 229 | NSUInteger count = 0; 230 | // These are in the same order as the menu manager shows them 231 | if (self.modifierFlags & NSEventModifierFlagControl) chars[count++] = kControlUnicode; 232 | if (self.modifierFlags & NSEventModifierFlagOption) chars[count++] = kOptionUnicode; 233 | if (self.modifierFlags & NSEventModifierFlagShift) chars[count++] = kShiftUnicode; 234 | if (self.modifierFlags & NSEventModifierFlagCommand) chars[count++] = kCommandUnicode; 235 | return (count ? [NSString stringWithCharacters:chars length:count] : @""); 236 | } 237 | 238 | #pragma mark - Validation logic 239 | 240 | - (BOOL)shouldBypass 241 | { 242 | NSString *codeString = self.keyCodeString; 243 | return (self.modifierFlags == NSEventModifierFlagCommand) && ([codeString isEqualToString:@"W"] || [codeString isEqualToString:@"Q"]); 244 | } 245 | 246 | BOOL MASShortcutAllowsAnyHotkeyWithOptionModifier = NO; 247 | 248 | + (void)setAllowsAnyHotkeyWithOptionModifier:(BOOL)allow 249 | { 250 | MASShortcutAllowsAnyHotkeyWithOptionModifier = allow; 251 | } 252 | 253 | + (BOOL)allowsAnyHotkeyWithOptionModifier 254 | { 255 | return MASShortcutAllowsAnyHotkeyWithOptionModifier; 256 | } 257 | 258 | - (BOOL)isValid 259 | { 260 | // Allow any function key with any combination of modifiers 261 | BOOL includesFunctionKey = ((_keyCode == kVK_F1) || (_keyCode == kVK_F2) || (_keyCode == kVK_F3) || (_keyCode == kVK_F4) || 262 | (_keyCode == kVK_F5) || (_keyCode == kVK_F6) || (_keyCode == kVK_F7) || (_keyCode == kVK_F8) || 263 | (_keyCode == kVK_F9) || (_keyCode == kVK_F10) || (_keyCode == kVK_F11) || (_keyCode == kVK_F12) || 264 | (_keyCode == kVK_F13) || (_keyCode == kVK_F14) || (_keyCode == kVK_F15) || (_keyCode == kVK_F16) || 265 | (_keyCode == kVK_F17) || (_keyCode == kVK_F18) || (_keyCode == kVK_F19) || (_keyCode == kVK_F20)); 266 | if (includesFunctionKey) return YES; 267 | 268 | // Do not allow any other key without modifiers 269 | BOOL hasModifierFlags = (_modifierFlags > 0); 270 | if (!hasModifierFlags) return NO; 271 | 272 | // Allow any hotkey containing Control or Command modifier 273 | BOOL includesCommand = ((_modifierFlags & NSEventModifierFlagCommand) > 0); 274 | BOOL includesControl = ((_modifierFlags & NSEventModifierFlagControl) > 0); 275 | if (includesCommand || includesControl) return YES; 276 | 277 | // Allow Option key only in selected cases 278 | BOOL includesOption = ((_modifierFlags & NSEventModifierFlagOption) > 0); 279 | if (includesOption) { 280 | 281 | // Always allow Option-Space and Option-Escape because they do not have any bind system commands 282 | if ((_keyCode == kVK_Space) || (_keyCode == kVK_Escape)) return YES; 283 | 284 | // Allow Option modifier with any key even if it will break the system binding 285 | if ([[self class] allowsAnyHotkeyWithOptionModifier]) return YES; 286 | } 287 | 288 | // The hotkey does not have any modifiers or violates system bindings 289 | return NO; 290 | } 291 | 292 | - (BOOL)isKeyEquivalent:(NSString *)keyEquivalent flags:(NSUInteger)flags takenInMenu:(NSMenu *)menu error:(NSError **)outError 293 | { 294 | for (NSMenuItem *menuItem in menu.itemArray) { 295 | if (menuItem.hasSubmenu && [self isKeyEquivalent:keyEquivalent flags:flags takenInMenu:menuItem.submenu error:outError]) return YES; 296 | 297 | BOOL equalFlags = (MASShortcutClear(menuItem.keyEquivalentModifierMask) == flags); 298 | BOOL equalHotkeyLowercase = [menuItem.keyEquivalent.lowercaseString isEqualToString:keyEquivalent]; 299 | 300 | // Check if the cases are different, we know ours is lower and that shift is included in our modifiers 301 | // If theirs is capitol, we need to add shift to their modifiers 302 | if (equalHotkeyLowercase && ![menuItem.keyEquivalent isEqualToString:keyEquivalent]) { 303 | equalFlags = (MASShortcutClear(menuItem.keyEquivalentModifierMask | NSShiftKeyMask) == flags); 304 | } 305 | 306 | if (equalFlags && equalHotkeyLowercase) { 307 | if (outError) { 308 | NSString *format = NSLocalizedString(@"This shortcut cannot be used because it is already used by the menu item ‘%@’.", 309 | @"Message for alert when shortcut is already used"); 310 | NSDictionary *info = [NSDictionary dictionaryWithObject:[NSString stringWithFormat:format, menuItem.title] 311 | forKey:NSLocalizedDescriptionKey]; 312 | *outError = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:info]; 313 | } 314 | return YES; 315 | } 316 | } 317 | return NO; 318 | } 319 | 320 | - (BOOL)isTakenError:(NSError **)outError 321 | { 322 | CFArrayRef globalHotKeys; 323 | BOOL isTaken = NO; 324 | if (CopySymbolicHotKeys(&globalHotKeys) == noErr) { 325 | 326 | // Enumerate all global hotkeys and check if any of them matches current shortcut 327 | for (CFIndex i = 0, count = CFArrayGetCount(globalHotKeys); i < count; i++) { 328 | CFDictionaryRef hotKeyInfo = CFArrayGetValueAtIndex(globalHotKeys, i); 329 | CFNumberRef code = CFDictionaryGetValue(hotKeyInfo, kHISymbolicHotKeyCode); 330 | CFNumberRef flags = CFDictionaryGetValue(hotKeyInfo, kHISymbolicHotKeyModifiers); 331 | CFNumberRef enabled = CFDictionaryGetValue(hotKeyInfo, kHISymbolicHotKeyEnabled); 332 | 333 | if (([(__bridge NSNumber *)code unsignedIntegerValue] == self.keyCode) && 334 | ([(__bridge NSNumber *)flags unsignedIntegerValue] == self.carbonFlags) && 335 | ([(__bridge NSNumber *)enabled boolValue])) { 336 | 337 | if (outError) { 338 | NSString *description = NSLocalizedString(@"This combination cannot be used because it is already used by a system-wide " 339 | @"keyboard shortcut.\nIf you really want to use this key combination, most shortcuts " 340 | @"can be changed in the Keyboard & Mouse panel in System Preferences.", 341 | @"Message for alert when shortcut is already used by the system"); 342 | NSDictionary *info = [NSDictionary dictionaryWithObject:description forKey:NSLocalizedDescriptionKey]; 343 | *outError = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:info]; 344 | } 345 | isTaken = YES; 346 | break; 347 | } 348 | } 349 | CFRelease(globalHotKeys); 350 | } 351 | return (isTaken || [self isKeyEquivalent:self.keyCodeStringForKeyEquivalent flags:self.modifierFlags takenInMenu:[NSApp mainMenu] error:outError]); 352 | } 353 | 354 | @end 355 | -------------------------------------------------------------------------------- /AppGrid/MASShortcutView.m: -------------------------------------------------------------------------------- 1 | #import "MASShortcutView.h" 2 | #import "MASShortcut.h" 3 | 4 | #define HINT_BUTTON_WIDTH 23.0 5 | #define BUTTON_FONT_SIZE 11.0 6 | #define SEGMENT_CHROME_WIDTH 6.0 7 | 8 | #pragma mark - 9 | 10 | @interface MASShortcutView () // Private accessors 11 | 12 | @property (nonatomic, getter = isHinting) BOOL hinting; 13 | @property (nonatomic, copy) NSString *shortcutPlaceholder; 14 | 15 | @end 16 | 17 | #pragma mark - 18 | 19 | @implementation MASShortcutView { 20 | NSButtonCell *_shortcutCell; 21 | NSInteger _shortcutToolTipTag; 22 | NSInteger _hintToolTipTag; 23 | NSTrackingArea *_hintArea; 24 | } 25 | 26 | @synthesize enabled = _enabled; 27 | @synthesize hinting = _hinting; 28 | @synthesize shortcutValue = _shortcutValue; 29 | @synthesize shortcutPlaceholder = _shortcutPlaceholder; 30 | @synthesize shortcutValueChange = _shortcutValueChange; 31 | @synthesize recording = _recording; 32 | @synthesize appearance = _appearance; 33 | 34 | #pragma mark - 35 | 36 | + (Class)shortcutCellClass 37 | { 38 | return [NSButtonCell class]; 39 | } 40 | 41 | - (id)initWithFrame:(CGRect)frameRect 42 | { 43 | self = [super initWithFrame:frameRect]; 44 | if (self) { 45 | [self commonInit]; 46 | } 47 | return self; 48 | } 49 | 50 | - (id)initWithCoder:(NSCoder *)coder 51 | { 52 | self = [super initWithCoder:coder]; 53 | if (self) { 54 | [self commonInit]; 55 | } 56 | return self; 57 | } 58 | 59 | - (void)commonInit 60 | { 61 | _shortcutCell = [[[self.class shortcutCellClass] alloc] init]; 62 | _shortcutCell.buttonType = NSButtonTypePushOnPushOff; 63 | _shortcutCell.font = [[NSFontManager sharedFontManager] convertFont:_shortcutCell.font toSize:BUTTON_FONT_SIZE]; 64 | _enabled = YES; 65 | [self resetShortcutCellStyle]; 66 | } 67 | 68 | - (void)dealloc 69 | { 70 | [self activateEventMonitoring:NO]; 71 | [self activateResignObserver:NO]; 72 | } 73 | 74 | #pragma mark - Public accessors 75 | 76 | - (void)setEnabled:(BOOL)flag 77 | { 78 | if (_enabled != flag) { 79 | _enabled = flag; 80 | [self updateTrackingAreas]; 81 | self.recording = NO; 82 | [self setNeedsDisplay:YES]; 83 | } 84 | } 85 | 86 | - (void)setAppearance:(MASShortcutViewAppearance)appearance 87 | { 88 | if (_appearance != appearance) { 89 | _appearance = appearance; 90 | [self resetShortcutCellStyle]; 91 | [self setNeedsDisplay:YES]; 92 | } 93 | } 94 | 95 | - (void)resetShortcutCellStyle 96 | { 97 | switch (_appearance) { 98 | case MASShortcutViewAppearanceDefault: { 99 | _shortcutCell.bezelStyle = NSBezelStyleRoundRect; 100 | break; 101 | } 102 | case MASShortcutViewAppearanceTexturedRect: { 103 | _shortcutCell.bezelStyle = NSBezelStyleTexturedRounded; 104 | break; 105 | } 106 | case MASShortcutViewAppearanceRounded: { 107 | _shortcutCell.bezelStyle = NSBezelStyleRounded; 108 | break; 109 | } 110 | } 111 | } 112 | 113 | - (void)setRecording:(BOOL)flag 114 | { 115 | // Only one recorder can be active at the moment 116 | static MASShortcutView *currentRecorder = nil; 117 | if (flag && (currentRecorder != self)) { 118 | currentRecorder.recording = NO; 119 | currentRecorder = flag ? self : nil; 120 | } 121 | 122 | // Only enabled view supports recording 123 | if (flag && !self.enabled) return; 124 | 125 | if (_recording != flag) { 126 | _recording = flag; 127 | self.shortcutPlaceholder = nil; 128 | [self resetToolTips]; 129 | [self activateEventMonitoring:_recording]; 130 | [self activateResignObserver:_recording]; 131 | [self setNeedsDisplay:YES]; 132 | } 133 | } 134 | 135 | - (void)setShortcutValue:(MASShortcut *)shortcutValue 136 | { 137 | _shortcutValue = shortcutValue; 138 | [self resetToolTips]; 139 | [self setNeedsDisplay:YES]; 140 | 141 | if (self.shortcutValueChange) { 142 | self.shortcutValueChange(self); 143 | } 144 | } 145 | 146 | - (void)setShortcutPlaceholder:(NSString *)shortcutPlaceholder 147 | { 148 | _shortcutPlaceholder = shortcutPlaceholder.copy; 149 | [self setNeedsDisplay:YES]; 150 | } 151 | 152 | #pragma mark - Drawing 153 | 154 | - (BOOL)isFlipped 155 | { 156 | return YES; 157 | } 158 | 159 | - (void)drawInRect:(CGRect)frame withTitle:(NSString *)title alignment:(NSTextAlignment)alignment state:(NSInteger)state 160 | { 161 | _shortcutCell.title = title; 162 | _shortcutCell.alignment = alignment; 163 | _shortcutCell.state = state; 164 | _shortcutCell.enabled = self.enabled; 165 | 166 | switch (_appearance) { 167 | case MASShortcutViewAppearanceDefault: { 168 | [_shortcutCell drawWithFrame:frame inView:self]; 169 | break; 170 | } 171 | case MASShortcutViewAppearanceTexturedRect: { 172 | [_shortcutCell drawWithFrame:CGRectOffset(frame, 0.0, 1.0) inView:self]; 173 | break; 174 | } 175 | case MASShortcutViewAppearanceRounded: { 176 | [_shortcutCell drawWithFrame:CGRectOffset(frame, 0.0, 1.0) inView:self]; 177 | break; 178 | } 179 | } 180 | } 181 | 182 | - (void)drawRect:(CGRect)dirtyRect 183 | { 184 | if (self.shortcutValue) { 185 | [self drawInRect:self.bounds withTitle:MASShortcutChar(self.recording ? kMASShortcutGlyphEscape : kMASShortcutGlyphDeleteLeft) 186 | alignment:NSTextAlignmentRight state:NSControlStateValueOff]; 187 | 188 | CGRect shortcutRect; 189 | [self getShortcutRect:&shortcutRect hintRect:NULL]; 190 | NSString *title = (self.recording 191 | ? (_hinting 192 | ? NSLocalizedString(@"Use Old Shortcut", @"Cancel action button for non-empty shortcut in recording state") 193 | : (self.shortcutPlaceholder.length > 0 194 | ? self.shortcutPlaceholder 195 | : NSLocalizedString(@"Type New Shortcut", @"Non-empty shortcut button in recording state"))) 196 | : _shortcutValue ? _shortcutValue.description : @""); 197 | [self drawInRect:shortcutRect withTitle:title alignment:NSTextAlignmentCenter state:self.isRecording ? NSControlStateValueOn : NSControlStateValueOff]; 198 | } 199 | else { 200 | if (self.recording) 201 | { 202 | [self drawInRect:self.bounds withTitle:MASShortcutChar(kMASShortcutGlyphEscape) alignment:NSTextAlignmentRight state:NSControlStateValueOff]; 203 | 204 | CGRect shortcutRect; 205 | [self getShortcutRect:&shortcutRect hintRect:NULL]; 206 | NSString *title = (_hinting 207 | ? NSLocalizedString(@"Cancel", @"Cancel action button in recording state") 208 | : (self.shortcutPlaceholder.length > 0 209 | ? self.shortcutPlaceholder 210 | : NSLocalizedString(@"Type Shortcut", @"Empty shortcut button in recording state"))); 211 | [self drawInRect:shortcutRect withTitle:title alignment:NSTextAlignmentCenter state:NSControlStateValueOn]; 212 | } 213 | else 214 | { 215 | [self drawInRect:self.bounds withTitle:NSLocalizedString(@"Record Shortcut", @"Empty shortcut button in normal state") 216 | alignment:NSTextAlignmentCenter state:NSControlStateValueOff]; 217 | } 218 | } 219 | } 220 | 221 | #pragma mark - Mouse handling 222 | 223 | - (void)getShortcutRect:(CGRect *)shortcutRectRef hintRect:(CGRect *)hintRectRef 224 | { 225 | CGRect shortcutRect, hintRect; 226 | CGFloat hintButtonWidth = HINT_BUTTON_WIDTH; 227 | switch (self.appearance) { 228 | case MASShortcutViewAppearanceTexturedRect: hintButtonWidth += 2.0; break; 229 | case MASShortcutViewAppearanceRounded: hintButtonWidth += 3.0; break; 230 | default: break; 231 | } 232 | CGRectDivide(self.bounds, &hintRect, &shortcutRect, hintButtonWidth, CGRectMaxXEdge); 233 | if (shortcutRectRef) *shortcutRectRef = shortcutRect; 234 | if (hintRectRef) *hintRectRef = hintRect; 235 | } 236 | 237 | - (BOOL)locationInShortcutRect:(CGPoint)location 238 | { 239 | CGRect shortcutRect; 240 | [self getShortcutRect:&shortcutRect hintRect:NULL]; 241 | return CGRectContainsPoint(shortcutRect, [self convertPoint:location fromView:nil]); 242 | } 243 | 244 | - (BOOL)locationInHintRect:(CGPoint)location 245 | { 246 | CGRect hintRect; 247 | [self getShortcutRect:NULL hintRect:&hintRect]; 248 | return CGRectContainsPoint(hintRect, [self convertPoint:location fromView:nil]); 249 | } 250 | 251 | - (void)mouseDown:(NSEvent *)event 252 | { 253 | if (self.enabled) { 254 | if (self.shortcutValue) { 255 | if (self.recording) { 256 | if ([self locationInHintRect:event.locationInWindow]) { 257 | self.recording = NO; 258 | } 259 | } 260 | else { 261 | if ([self locationInShortcutRect:event.locationInWindow]) { 262 | self.recording = YES; 263 | } 264 | else { 265 | self.shortcutValue = nil; 266 | } 267 | } 268 | } 269 | else { 270 | if (self.recording) { 271 | if ([self locationInHintRect:event.locationInWindow]) { 272 | self.recording = NO; 273 | } 274 | } 275 | else { 276 | self.recording = YES; 277 | } 278 | } 279 | } 280 | else { 281 | [super mouseDown:event]; 282 | } 283 | } 284 | 285 | #pragma mark - Handling mouse over 286 | 287 | - (void)updateTrackingAreas 288 | { 289 | [super updateTrackingAreas]; 290 | 291 | if (_hintArea) { 292 | [self removeTrackingArea:_hintArea]; 293 | _hintArea = nil; 294 | } 295 | 296 | // Forbid hinting if view is disabled 297 | if (!self.enabled) return; 298 | 299 | CGRect hintRect; 300 | [self getShortcutRect:NULL hintRect:&hintRect]; 301 | NSTrackingAreaOptions options = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingAssumeInside); 302 | _hintArea = [[NSTrackingArea alloc] initWithRect:hintRect options:options owner:self userInfo:nil]; 303 | [self addTrackingArea:_hintArea]; 304 | } 305 | 306 | - (void)setHinting:(BOOL)flag 307 | { 308 | if (_hinting != flag) { 309 | _hinting = flag; 310 | [self setNeedsDisplay:YES]; 311 | } 312 | } 313 | 314 | - (void)mouseEntered:(NSEvent *)event 315 | { 316 | self.hinting = YES; 317 | } 318 | 319 | - (void)mouseExited:(NSEvent *)event 320 | { 321 | self.hinting = NO; 322 | } 323 | 324 | void *kUserDataShortcut = &kUserDataShortcut; 325 | void *kUserDataHint = &kUserDataHint; 326 | 327 | - (void)resetToolTips 328 | { 329 | if (_shortcutToolTipTag) { 330 | (void)([self removeToolTip:_shortcutToolTipTag]), _shortcutToolTipTag = 0; 331 | } 332 | if (_hintToolTipTag) { 333 | (void)([self removeToolTip:_hintToolTipTag]), _hintToolTipTag = 0; 334 | } 335 | 336 | if ((self.shortcutValue == nil) || self.recording || !self.enabled) return; 337 | 338 | CGRect shortcutRect, hintRect; 339 | [self getShortcutRect:&shortcutRect hintRect:&hintRect]; 340 | _shortcutToolTipTag = [self addToolTipRect:shortcutRect owner:self userData:kUserDataShortcut]; 341 | _hintToolTipTag = [self addToolTipRect:hintRect owner:self userData:kUserDataHint]; 342 | } 343 | 344 | - (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(CGPoint)point userData:(void *)data 345 | { 346 | if (data == kUserDataShortcut) { 347 | return NSLocalizedString(@"Click to record new shortcut", @"Tooltip for non-empty shortcut button"); 348 | } 349 | else if (data == kUserDataHint) { 350 | return NSLocalizedString(@"Delete shortcut", @"Tooltip for hint button near the non-empty shortcut"); 351 | } 352 | return nil; 353 | } 354 | 355 | #pragma mark - Event monitoring 356 | 357 | - (void)activateEventMonitoring:(BOOL)shouldActivate 358 | { 359 | static BOOL isActive = NO; 360 | if (isActive == shouldActivate) return; 361 | isActive = shouldActivate; 362 | 363 | static id eventMonitor = nil; 364 | if (shouldActivate) { 365 | __weak MASShortcutView *weakSelf = self; 366 | NSEventMask eventMask = (NSEventMaskKeyDown | NSEventMaskFlagsChanged); 367 | eventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:eventMask handler:^(NSEvent *event) { 368 | 369 | MASShortcut *shortcut = [MASShortcut shortcutWithEvent:event]; 370 | if ((shortcut.keyCode == kVK_Delete) || (shortcut.keyCode == kVK_ForwardDelete)) { 371 | // Delete shortcut 372 | weakSelf.shortcutValue = nil; 373 | weakSelf.recording = NO; 374 | event = nil; 375 | } 376 | else if (shortcut.keyCode == kVK_Escape && !shortcut.modifierFlags) { 377 | // Cancel recording 378 | weakSelf.recording = NO; 379 | event = nil; 380 | } 381 | else if (shortcut.shouldBypass) { 382 | // Command + W, Command + Q, ESC should deactivate recorder 383 | weakSelf.recording = NO; 384 | } 385 | else { 386 | // Verify possible shortcut 387 | if (shortcut.keyCodeString.length > 0) { 388 | if (shortcut.valid) { 389 | // Verify that shortcut is not used 390 | NSError *error = nil; 391 | if ([shortcut isTakenError:&error]) { 392 | // Prevent cancel of recording when Alert window is key 393 | [weakSelf activateResignObserver:NO]; 394 | [weakSelf activateEventMonitoring:NO]; 395 | NSString *format = NSLocalizedString(@"The key combination %@ cannot be used", 396 | @"Title for alert when shortcut is already used"); 397 | NSAlert *alert = [[NSAlert alloc] init]; 398 | alert.alertStyle = NSAlertStyleCritical; 399 | alert.informativeText = [NSString stringWithFormat:format, shortcut]; 400 | alert.messageText = error.localizedDescription; 401 | [alert addButtonWithTitle:NSLocalizedString(@"OK", @"Alert button when shortcut is already used")]; 402 | 403 | [alert runModal]; 404 | weakSelf.shortcutPlaceholder = nil; 405 | [weakSelf activateResignObserver:YES]; 406 | [weakSelf activateEventMonitoring:YES]; 407 | } 408 | else { 409 | weakSelf.shortcutValue = shortcut; 410 | weakSelf.recording = NO; 411 | } 412 | } 413 | else { 414 | // Key press with or without SHIFT is not valid input 415 | NSBeep(); 416 | } 417 | } 418 | else { 419 | // User is playing with modifier keys 420 | weakSelf.shortcutPlaceholder = shortcut.modifierFlagsString; 421 | } 422 | event = nil; 423 | } 424 | return event; 425 | }]; 426 | } 427 | else { 428 | [NSEvent removeMonitor:eventMonitor]; 429 | } 430 | } 431 | 432 | - (void)activateResignObserver:(BOOL)shouldActivate 433 | { 434 | static BOOL isActive = NO; 435 | if (isActive == shouldActivate) return; 436 | isActive = shouldActivate; 437 | 438 | static id observer = nil; 439 | NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; 440 | if (shouldActivate) { 441 | __weak MASShortcutView *weakSelf = self; 442 | observer = [notificationCenter addObserverForName:NSWindowDidResignKeyNotification object:self.window 443 | queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) { 444 | weakSelf.recording = NO; 445 | }]; 446 | } 447 | else { 448 | [notificationCenter removeObserver:observer]; 449 | } 450 | } 451 | 452 | @end 453 | -------------------------------------------------------------------------------- /AppGrid.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 942D394416F3898D006D869C /* WelcomeWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 942D393F16F3898D006D869C /* WelcomeWindow.xib */; }; 11 | 942D394516F3898D006D869C /* SDWelcomeWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 942D394116F3898D006D869C /* SDWelcomeWindowController.m */; }; 12 | 9449AA1419FADBB900095E6E /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9449AA1319FADBB900095E6E /* Images.xcassets */; }; 13 | 946CF3FA16E62ECF002DA707 /* SDValueTransformers.m in Sources */ = {isa = PBXBuildFile; fileRef = 946CF3F916E62ECF002DA707 /* SDValueTransformers.m */; }; 14 | 947C108616E7971E00B1D52E /* SDOpenAtLogin.m in Sources */ = {isa = PBXBuildFile; fileRef = 947C108516E7971E00B1D52E /* SDOpenAtLogin.m */; }; 15 | 949B57DF19F8A38600F412CF /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 949B57DE19F8A38600F412CF /* MainMenu.xib */; }; 16 | 949B57FA19F8C26C00F412CF /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 949B57F919F8C26C00F412CF /* Security.framework */; }; 17 | 94D6C0C216E0DDD700868523 /* SDPreferences.m in Sources */ = {isa = PBXBuildFile; fileRef = 94D6C0C116E0DDD700868523 /* SDPreferences.m */; }; 18 | 94D6C0C516E0DF4300868523 /* SDGrid.m in Sources */ = {isa = PBXBuildFile; fileRef = 94D6C0C416E0DF4300868523 /* SDGrid.m */; }; 19 | 94D6C0DF16E0EA8E00868523 /* sd_intro.png in Resources */ = {isa = PBXBuildFile; fileRef = 94D6C0DB16E0EA8E00868523 /* sd_intro.png */; }; 20 | 94ED279C16E01A440044ED33 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 94ED279B16E01A440044ED33 /* Cocoa.framework */; }; 21 | 94ED27AF16E01A440044ED33 /* SDAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 94ED27AE16E01A440044ED33 /* SDAppDelegate.m */; }; 22 | 94ED27EC16E024F20044ED33 /* MASShortcut.m in Sources */ = {isa = PBXBuildFile; fileRef = 94ED27E316E024F20044ED33 /* MASShortcut.m */; }; 23 | 94ED27ED16E024F20044ED33 /* MASShortcut+Monitoring.m in Sources */ = {isa = PBXBuildFile; fileRef = 94ED27E516E024F20044ED33 /* MASShortcut+Monitoring.m */; }; 24 | 94ED27EE16E024F20044ED33 /* MASShortcut+UserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 94ED27E716E024F20044ED33 /* MASShortcut+UserDefaults.m */; }; 25 | 94ED27EF16E024F20044ED33 /* MASShortcutView.m in Sources */ = {isa = PBXBuildFile; fileRef = 94ED27E916E024F20044ED33 /* MASShortcutView.m */; }; 26 | 94ED27F016E024F20044ED33 /* MASShortcutView+UserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 94ED27EB16E024F20044ED33 /* MASShortcutView+UserDefaults.m */; }; 27 | 94ED27F416E026370044ED33 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 94ED27F316E026360044ED33 /* Carbon.framework */; }; 28 | 94ED27F816E02A900044ED33 /* SDWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 94ED27F716E02A900044ED33 /* SDWindow.m */; }; 29 | 94ED27FE16E034820044ED33 /* SDPreferencesWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 94ED27FC16E034820044ED33 /* SDPreferencesWindowController.m */; }; 30 | 94ED27FF16E034820044ED33 /* PreferencesWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 94ED27FD16E034820044ED33 /* PreferencesWindow.xib */; }; 31 | 94ED280216E079C50044ED33 /* defaults.plist in Resources */ = {isa = PBXBuildFile; fileRef = 94ED280116E079C50044ED33 /* defaults.plist */; }; 32 | 94ED280916E08F880044ED33 /* statusitem.png in Resources */ = {isa = PBXBuildFile; fileRef = 94ED280816E08F880044ED33 /* statusitem.png */; }; 33 | 94F2E1D819F89C140089E4CA /* SDAccessibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 94F2E1D719F89C140089E4CA /* SDAccessibility.m */; }; 34 | 94F2E1DA19F8A27C0089E4CA /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 94F2E1D919F8A27C0089E4CA /* Credits.rtf */; }; 35 | 94FED95D1E8746CC003D5626 /* statusitem@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 94FED95C1E8746CC003D5626 /* statusitem@2x.png */; }; 36 | /* End PBXBuildFile section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | 942D393F16F3898D006D869C /* WelcomeWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = WelcomeWindow.xib; sourceTree = ""; }; 40 | 942D394016F3898D006D869C /* SDWelcomeWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDWelcomeWindowController.h; sourceTree = ""; }; 41 | 942D394116F3898D006D869C /* SDWelcomeWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWelcomeWindowController.m; sourceTree = ""; }; 42 | 9449AA1319FADBB900095E6E /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 43 | 946CF3F916E62ECF002DA707 /* SDValueTransformers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDValueTransformers.m; sourceTree = ""; }; 44 | 947C108416E7971D00B1D52E /* SDOpenAtLogin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDOpenAtLogin.h; sourceTree = ""; }; 45 | 947C108516E7971E00B1D52E /* SDOpenAtLogin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDOpenAtLogin.m; sourceTree = ""; }; 46 | 949B57DE19F8A38600F412CF /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; }; 47 | 949B57F919F8C26C00F412CF /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 48 | 94D6C0C016E0DDD700868523 /* SDPreferences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDPreferences.h; sourceTree = ""; }; 49 | 94D6C0C116E0DDD700868523 /* SDPreferences.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDPreferences.m; sourceTree = ""; }; 50 | 94D6C0C316E0DF4300868523 /* SDGrid.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDGrid.h; sourceTree = ""; }; 51 | 94D6C0C416E0DF4300868523 /* SDGrid.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDGrid.m; sourceTree = ""; }; 52 | 94D6C0DB16E0EA8E00868523 /* sd_intro.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = sd_intro.png; sourceTree = ""; }; 53 | 94ED279816E01A440044ED33 /* AppGrid.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppGrid.app; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 94ED279B16E01A440044ED33 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 55 | 94ED279E16E01A440044ED33 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 56 | 94ED279F16E01A440044ED33 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 57 | 94ED27A016E01A440044ED33 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 58 | 94ED27A316E01A440044ED33 /* AppGrid-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "AppGrid-Info.plist"; sourceTree = ""; }; 59 | 94ED27AD16E01A440044ED33 /* SDAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDAppDelegate.h; sourceTree = ""; }; 60 | 94ED27AE16E01A440044ED33 /* SDAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDAppDelegate.m; sourceTree = ""; }; 61 | 94ED27E216E024F20044ED33 /* MASShortcut.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MASShortcut.h; sourceTree = ""; }; 62 | 94ED27E316E024F20044ED33 /* MASShortcut.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MASShortcut.m; sourceTree = ""; }; 63 | 94ED27E416E024F20044ED33 /* MASShortcut+Monitoring.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MASShortcut+Monitoring.h"; sourceTree = ""; }; 64 | 94ED27E516E024F20044ED33 /* MASShortcut+Monitoring.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MASShortcut+Monitoring.m"; sourceTree = ""; }; 65 | 94ED27E616E024F20044ED33 /* MASShortcut+UserDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MASShortcut+UserDefaults.h"; sourceTree = ""; }; 66 | 94ED27E716E024F20044ED33 /* MASShortcut+UserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MASShortcut+UserDefaults.m"; sourceTree = ""; }; 67 | 94ED27E816E024F20044ED33 /* MASShortcutView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MASShortcutView.h; sourceTree = ""; }; 68 | 94ED27E916E024F20044ED33 /* MASShortcutView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MASShortcutView.m; sourceTree = ""; }; 69 | 94ED27EA16E024F20044ED33 /* MASShortcutView+UserDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MASShortcutView+UserDefaults.h"; sourceTree = ""; }; 70 | 94ED27EB16E024F20044ED33 /* MASShortcutView+UserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MASShortcutView+UserDefaults.m"; sourceTree = ""; }; 71 | 94ED27F316E026360044ED33 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; 72 | 94ED27F616E02A900044ED33 /* SDWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDWindow.h; sourceTree = ""; }; 73 | 94ED27F716E02A900044ED33 /* SDWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWindow.m; sourceTree = ""; }; 74 | 94ED27FB16E034820044ED33 /* SDPreferencesWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDPreferencesWindowController.h; sourceTree = ""; }; 75 | 94ED27FC16E034820044ED33 /* SDPreferencesWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDPreferencesWindowController.m; sourceTree = ""; }; 76 | 94ED27FD16E034820044ED33 /* PreferencesWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PreferencesWindow.xib; sourceTree = ""; }; 77 | 94ED280116E079C50044ED33 /* defaults.plist */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = defaults.plist; sourceTree = ""; }; 78 | 94ED280816E08F880044ED33 /* statusitem.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = statusitem.png; sourceTree = ""; }; 79 | 94F2E1D619F89C140089E4CA /* SDAccessibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDAccessibility.h; sourceTree = ""; }; 80 | 94F2E1D719F89C140089E4CA /* SDAccessibility.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDAccessibility.m; sourceTree = ""; }; 81 | 94F2E1D919F8A27C0089E4CA /* Credits.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = ""; }; 82 | 94FED95C1E8746CC003D5626 /* statusitem@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "statusitem@2x.png"; sourceTree = ""; }; 83 | /* End PBXFileReference section */ 84 | 85 | /* Begin PBXFrameworksBuildPhase section */ 86 | 94ED279516E01A440044ED33 /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | 949B57FA19F8C26C00F412CF /* Security.framework in Frameworks */, 91 | 94ED27F416E026370044ED33 /* Carbon.framework in Frameworks */, 92 | 94ED279C16E01A440044ED33 /* Cocoa.framework in Frameworks */, 93 | ); 94 | runOnlyForDeploymentPostprocessing = 0; 95 | }; 96 | /* End PBXFrameworksBuildPhase section */ 97 | 98 | /* Begin PBXGroup section */ 99 | 941E6B4E16E668D100B4FBA3 /* SDFramework */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 947C108416E7971D00B1D52E /* SDOpenAtLogin.h */, 103 | 947C108516E7971E00B1D52E /* SDOpenAtLogin.m */, 104 | 946CF3F916E62ECF002DA707 /* SDValueTransformers.m */, 105 | 94F2E1D619F89C140089E4CA /* SDAccessibility.h */, 106 | 94F2E1D719F89C140089E4CA /* SDAccessibility.m */, 107 | ); 108 | name = SDFramework; 109 | sourceTree = ""; 110 | }; 111 | 94D6C0C616E0E29C00868523 /* WelcomeWindow */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 942D393F16F3898D006D869C /* WelcomeWindow.xib */, 115 | 942D394016F3898D006D869C /* SDWelcomeWindowController.h */, 116 | 942D394116F3898D006D869C /* SDWelcomeWindowController.m */, 117 | 94D6C0DB16E0EA8E00868523 /* sd_intro.png */, 118 | ); 119 | name = WelcomeWindow; 120 | sourceTree = ""; 121 | }; 122 | 94ED278F16E01A440044ED33 = { 123 | isa = PBXGroup; 124 | children = ( 125 | 94ED27A116E01A440044ED33 /* AppGrid */, 126 | 94ED279A16E01A440044ED33 /* Frameworks */, 127 | 94ED279916E01A440044ED33 /* Products */, 128 | ); 129 | sourceTree = ""; 130 | }; 131 | 94ED279916E01A440044ED33 /* Products */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | 94ED279816E01A440044ED33 /* AppGrid.app */, 135 | ); 136 | name = Products; 137 | sourceTree = ""; 138 | }; 139 | 94ED279A16E01A440044ED33 /* Frameworks */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | 949B57F919F8C26C00F412CF /* Security.framework */, 143 | 94ED27F316E026360044ED33 /* Carbon.framework */, 144 | 94ED279B16E01A440044ED33 /* Cocoa.framework */, 145 | 94ED279D16E01A440044ED33 /* Other Frameworks */, 146 | ); 147 | name = Frameworks; 148 | sourceTree = ""; 149 | }; 150 | 94ED279D16E01A440044ED33 /* Other Frameworks */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 94ED279E16E01A440044ED33 /* AppKit.framework */, 154 | 94ED279F16E01A440044ED33 /* CoreData.framework */, 155 | 94ED27A016E01A440044ED33 /* Foundation.framework */, 156 | ); 157 | name = "Other Frameworks"; 158 | sourceTree = ""; 159 | }; 160 | 94ED27A116E01A440044ED33 /* AppGrid */ = { 161 | isa = PBXGroup; 162 | children = ( 163 | 94ED27F916E034510044ED33 /* Top Level */, 164 | 94D6C0C616E0E29C00868523 /* WelcomeWindow */, 165 | 94ED27F516E02A770044ED33 /* Window Manager */, 166 | 94F2E1DB19F8A2F00089E4CA /* Preferences */, 167 | 941E6B4E16E668D100B4FBA3 /* SDFramework */, 168 | 94ED27E116E024EA0044ED33 /* GlobalShortcuts */, 169 | 94ED27A216E01A440044ED33 /* Supporting Files */, 170 | ); 171 | path = AppGrid; 172 | sourceTree = ""; 173 | }; 174 | 94ED27A216E01A440044ED33 /* Supporting Files */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | 9449AA1319FADBB900095E6E /* Images.xcassets */, 178 | 94ED27A316E01A440044ED33 /* AppGrid-Info.plist */, 179 | 94ED280816E08F880044ED33 /* statusitem.png */, 180 | 94FED95C1E8746CC003D5626 /* statusitem@2x.png */, 181 | 94ED280116E079C50044ED33 /* defaults.plist */, 182 | 94F2E1D919F8A27C0089E4CA /* Credits.rtf */, 183 | ); 184 | name = "Supporting Files"; 185 | sourceTree = ""; 186 | }; 187 | 94ED27E116E024EA0044ED33 /* GlobalShortcuts */ = { 188 | isa = PBXGroup; 189 | children = ( 190 | 94ED27E216E024F20044ED33 /* MASShortcut.h */, 191 | 94ED27E316E024F20044ED33 /* MASShortcut.m */, 192 | 94ED27E416E024F20044ED33 /* MASShortcut+Monitoring.h */, 193 | 94ED27E516E024F20044ED33 /* MASShortcut+Monitoring.m */, 194 | 94ED27E616E024F20044ED33 /* MASShortcut+UserDefaults.h */, 195 | 94ED27E716E024F20044ED33 /* MASShortcut+UserDefaults.m */, 196 | 94ED27E816E024F20044ED33 /* MASShortcutView.h */, 197 | 94ED27E916E024F20044ED33 /* MASShortcutView.m */, 198 | 94ED27EA16E024F20044ED33 /* MASShortcutView+UserDefaults.h */, 199 | 94ED27EB16E024F20044ED33 /* MASShortcutView+UserDefaults.m */, 200 | ); 201 | name = GlobalShortcuts; 202 | sourceTree = ""; 203 | }; 204 | 94ED27F516E02A770044ED33 /* Window Manager */ = { 205 | isa = PBXGroup; 206 | children = ( 207 | 94ED27F616E02A900044ED33 /* SDWindow.h */, 208 | 94ED27F716E02A900044ED33 /* SDWindow.m */, 209 | 94D6C0C316E0DF4300868523 /* SDGrid.h */, 210 | 94D6C0C416E0DF4300868523 /* SDGrid.m */, 211 | ); 212 | name = "Window Manager"; 213 | sourceTree = ""; 214 | }; 215 | 94ED27F916E034510044ED33 /* Top Level */ = { 216 | isa = PBXGroup; 217 | children = ( 218 | 949B57DE19F8A38600F412CF /* MainMenu.xib */, 219 | 94ED27AD16E01A440044ED33 /* SDAppDelegate.h */, 220 | 94ED27AE16E01A440044ED33 /* SDAppDelegate.m */, 221 | ); 222 | name = "Top Level"; 223 | sourceTree = ""; 224 | }; 225 | 94F2E1DB19F8A2F00089E4CA /* Preferences */ = { 226 | isa = PBXGroup; 227 | children = ( 228 | 94ED27FD16E034820044ED33 /* PreferencesWindow.xib */, 229 | 94ED27FB16E034820044ED33 /* SDPreferencesWindowController.h */, 230 | 94ED27FC16E034820044ED33 /* SDPreferencesWindowController.m */, 231 | 94D6C0C016E0DDD700868523 /* SDPreferences.h */, 232 | 94D6C0C116E0DDD700868523 /* SDPreferences.m */, 233 | ); 234 | name = Preferences; 235 | sourceTree = ""; 236 | }; 237 | /* End PBXGroup section */ 238 | 239 | /* Begin PBXNativeTarget section */ 240 | 94ED279716E01A440044ED33 /* AppGrid */ = { 241 | isa = PBXNativeTarget; 242 | buildConfigurationList = 94ED27B516E01A440044ED33 /* Build configuration list for PBXNativeTarget "AppGrid" */; 243 | buildPhases = ( 244 | 94ED279416E01A440044ED33 /* Sources */, 245 | 94ED279516E01A440044ED33 /* Frameworks */, 246 | 94ED279616E01A440044ED33 /* Resources */, 247 | ); 248 | buildRules = ( 249 | ); 250 | dependencies = ( 251 | ); 252 | name = AppGrid; 253 | productName = AppGrid; 254 | productReference = 94ED279816E01A440044ED33 /* AppGrid.app */; 255 | productType = "com.apple.product-type.application"; 256 | }; 257 | /* End PBXNativeTarget section */ 258 | 259 | /* Begin PBXProject section */ 260 | 94ED279016E01A440044ED33 /* Project object */ = { 261 | isa = PBXProject; 262 | attributes = { 263 | LastUpgradeCheck = 1240; 264 | ORGANIZATIONNAME = "Steven Degutis"; 265 | }; 266 | buildConfigurationList = 94ED279316E01A440044ED33 /* Build configuration list for PBXProject "AppGrid" */; 267 | compatibilityVersion = "Xcode 3.2"; 268 | developmentRegion = en; 269 | hasScannedForEncodings = 0; 270 | knownRegions = ( 271 | en, 272 | Base, 273 | ); 274 | mainGroup = 94ED278F16E01A440044ED33; 275 | productRefGroup = 94ED279916E01A440044ED33 /* Products */; 276 | projectDirPath = ""; 277 | projectRoot = ""; 278 | targets = ( 279 | 94ED279716E01A440044ED33 /* AppGrid */, 280 | ); 281 | }; 282 | /* End PBXProject section */ 283 | 284 | /* Begin PBXResourcesBuildPhase section */ 285 | 94ED279616E01A440044ED33 /* Resources */ = { 286 | isa = PBXResourcesBuildPhase; 287 | buildActionMask = 2147483647; 288 | files = ( 289 | 94ED27FF16E034820044ED33 /* PreferencesWindow.xib in Resources */, 290 | 94ED280216E079C50044ED33 /* defaults.plist in Resources */, 291 | 94ED280916E08F880044ED33 /* statusitem.png in Resources */, 292 | 94D6C0DF16E0EA8E00868523 /* sd_intro.png in Resources */, 293 | 94F2E1DA19F8A27C0089E4CA /* Credits.rtf in Resources */, 294 | 942D394416F3898D006D869C /* WelcomeWindow.xib in Resources */, 295 | 9449AA1419FADBB900095E6E /* Images.xcassets in Resources */, 296 | 949B57DF19F8A38600F412CF /* MainMenu.xib in Resources */, 297 | 94FED95D1E8746CC003D5626 /* statusitem@2x.png in Resources */, 298 | ); 299 | runOnlyForDeploymentPostprocessing = 0; 300 | }; 301 | /* End PBXResourcesBuildPhase section */ 302 | 303 | /* Begin PBXSourcesBuildPhase section */ 304 | 94ED279416E01A440044ED33 /* Sources */ = { 305 | isa = PBXSourcesBuildPhase; 306 | buildActionMask = 2147483647; 307 | files = ( 308 | 94ED27AF16E01A440044ED33 /* SDAppDelegate.m in Sources */, 309 | 94ED27EC16E024F20044ED33 /* MASShortcut.m in Sources */, 310 | 94ED27ED16E024F20044ED33 /* MASShortcut+Monitoring.m in Sources */, 311 | 94ED27EE16E024F20044ED33 /* MASShortcut+UserDefaults.m in Sources */, 312 | 94ED27EF16E024F20044ED33 /* MASShortcutView.m in Sources */, 313 | 94ED27F016E024F20044ED33 /* MASShortcutView+UserDefaults.m in Sources */, 314 | 94ED27F816E02A900044ED33 /* SDWindow.m in Sources */, 315 | 94ED27FE16E034820044ED33 /* SDPreferencesWindowController.m in Sources */, 316 | 94D6C0C216E0DDD700868523 /* SDPreferences.m in Sources */, 317 | 94D6C0C516E0DF4300868523 /* SDGrid.m in Sources */, 318 | 946CF3FA16E62ECF002DA707 /* SDValueTransformers.m in Sources */, 319 | 947C108616E7971E00B1D52E /* SDOpenAtLogin.m in Sources */, 320 | 94F2E1D819F89C140089E4CA /* SDAccessibility.m in Sources */, 321 | 942D394516F3898D006D869C /* SDWelcomeWindowController.m in Sources */, 322 | ); 323 | runOnlyForDeploymentPostprocessing = 0; 324 | }; 325 | /* End PBXSourcesBuildPhase section */ 326 | 327 | /* Begin XCBuildConfiguration section */ 328 | 94ED27B316E01A440044ED33 /* Debug */ = { 329 | isa = XCBuildConfiguration; 330 | buildSettings = { 331 | ALWAYS_SEARCH_USER_PATHS = NO; 332 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 333 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 334 | CLANG_CXX_LIBRARY = "libc++"; 335 | CLANG_ENABLE_OBJC_ARC = YES; 336 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 337 | CLANG_WARN_BOOL_CONVERSION = YES; 338 | CLANG_WARN_COMMA = YES; 339 | CLANG_WARN_CONSTANT_CONVERSION = YES; 340 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 341 | CLANG_WARN_EMPTY_BODY = YES; 342 | CLANG_WARN_ENUM_CONVERSION = YES; 343 | CLANG_WARN_INFINITE_RECURSION = YES; 344 | CLANG_WARN_INT_CONVERSION = YES; 345 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 346 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 347 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 348 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 349 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 350 | CLANG_WARN_STRICT_PROTOTYPES = YES; 351 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 352 | CLANG_WARN_UNREACHABLE_CODE = YES; 353 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 354 | COPY_PHASE_STRIP = NO; 355 | ENABLE_STRICT_OBJC_MSGSEND = YES; 356 | ENABLE_TESTABILITY = YES; 357 | GCC_C_LANGUAGE_STANDARD = gnu99; 358 | GCC_DYNAMIC_NO_PIC = NO; 359 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 360 | GCC_NO_COMMON_BLOCKS = YES; 361 | GCC_OPTIMIZATION_LEVEL = 0; 362 | GCC_PREPROCESSOR_DEFINITIONS = ( 363 | "DEBUG=1", 364 | "$(inherited)", 365 | ); 366 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 367 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 368 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 369 | GCC_WARN_UNDECLARED_SELECTOR = YES; 370 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 371 | GCC_WARN_UNUSED_FUNCTION = YES; 372 | GCC_WARN_UNUSED_VARIABLE = YES; 373 | MACOSX_DEPLOYMENT_TARGET = 11.1; 374 | ONLY_ACTIVE_ARCH = YES; 375 | SDKROOT = macosx; 376 | }; 377 | name = Debug; 378 | }; 379 | 94ED27B416E01A440044ED33 /* Release */ = { 380 | isa = XCBuildConfiguration; 381 | buildSettings = { 382 | ALWAYS_SEARCH_USER_PATHS = NO; 383 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 384 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 385 | CLANG_CXX_LIBRARY = "libc++"; 386 | CLANG_ENABLE_OBJC_ARC = YES; 387 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 388 | CLANG_WARN_BOOL_CONVERSION = YES; 389 | CLANG_WARN_COMMA = YES; 390 | CLANG_WARN_CONSTANT_CONVERSION = YES; 391 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 392 | CLANG_WARN_EMPTY_BODY = YES; 393 | CLANG_WARN_ENUM_CONVERSION = YES; 394 | CLANG_WARN_INFINITE_RECURSION = YES; 395 | CLANG_WARN_INT_CONVERSION = YES; 396 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 397 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 398 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 399 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 400 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 401 | CLANG_WARN_STRICT_PROTOTYPES = YES; 402 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 403 | CLANG_WARN_UNREACHABLE_CODE = YES; 404 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 405 | COPY_PHASE_STRIP = YES; 406 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 407 | ENABLE_STRICT_OBJC_MSGSEND = YES; 408 | GCC_C_LANGUAGE_STANDARD = gnu99; 409 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 410 | GCC_NO_COMMON_BLOCKS = YES; 411 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 412 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 413 | GCC_WARN_UNDECLARED_SELECTOR = YES; 414 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 415 | GCC_WARN_UNUSED_FUNCTION = YES; 416 | GCC_WARN_UNUSED_VARIABLE = YES; 417 | MACOSX_DEPLOYMENT_TARGET = 11.1; 418 | SDKROOT = macosx; 419 | }; 420 | name = Release; 421 | }; 422 | 94ED27B616E01A440044ED33 /* Debug */ = { 423 | isa = XCBuildConfiguration; 424 | buildSettings = { 425 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 426 | CODE_SIGN_IDENTITY = "-"; 427 | COMBINE_HIDPI_IMAGES = YES; 428 | FRAMEWORK_SEARCH_PATHS = ( 429 | "$(inherited)", 430 | "\"$(SRCROOT)/AppGrid\"", 431 | "\"$(SRCROOT)\"", 432 | ); 433 | INFOPLIST_FILE = "AppGrid/AppGrid-Info.plist"; 434 | MACOSX_DEPLOYMENT_TARGET = 11.1; 435 | PRODUCT_BUNDLE_IDENTIFIER = com.sdegutis.AppGrid; 436 | PRODUCT_NAME = "$(TARGET_NAME)"; 437 | SDKROOT = macosx; 438 | WRAPPER_EXTENSION = app; 439 | }; 440 | name = Debug; 441 | }; 442 | 94ED27B716E01A440044ED33 /* Release */ = { 443 | isa = XCBuildConfiguration; 444 | buildSettings = { 445 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 446 | CODE_SIGN_IDENTITY = "-"; 447 | COMBINE_HIDPI_IMAGES = YES; 448 | FRAMEWORK_SEARCH_PATHS = ( 449 | "$(inherited)", 450 | "\"$(SRCROOT)/AppGrid\"", 451 | "\"$(SRCROOT)\"", 452 | ); 453 | INFOPLIST_FILE = "AppGrid/AppGrid-Info.plist"; 454 | MACOSX_DEPLOYMENT_TARGET = 11.1; 455 | PRODUCT_BUNDLE_IDENTIFIER = com.sdegutis.AppGrid; 456 | PRODUCT_NAME = "$(TARGET_NAME)"; 457 | SDKROOT = macosx; 458 | WRAPPER_EXTENSION = app; 459 | }; 460 | name = Release; 461 | }; 462 | /* End XCBuildConfiguration section */ 463 | 464 | /* Begin XCConfigurationList section */ 465 | 94ED279316E01A440044ED33 /* Build configuration list for PBXProject "AppGrid" */ = { 466 | isa = XCConfigurationList; 467 | buildConfigurations = ( 468 | 94ED27B316E01A440044ED33 /* Debug */, 469 | 94ED27B416E01A440044ED33 /* Release */, 470 | ); 471 | defaultConfigurationIsVisible = 0; 472 | defaultConfigurationName = Release; 473 | }; 474 | 94ED27B516E01A440044ED33 /* Build configuration list for PBXNativeTarget "AppGrid" */ = { 475 | isa = XCConfigurationList; 476 | buildConfigurations = ( 477 | 94ED27B616E01A440044ED33 /* Debug */, 478 | 94ED27B716E01A440044ED33 /* Release */, 479 | ); 480 | defaultConfigurationIsVisible = 0; 481 | defaultConfigurationName = Release; 482 | }; 483 | /* End XCConfigurationList section */ 484 | }; 485 | rootObject = 94ED279016E01A440044ED33 /* Project object */; 486 | } 487 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /AppGrid/PreferencesWindow.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | --------------------------------------------------------------------------------