Repository: jigish/slate
Branch: master
Commit: ff5ee5a53afc
Files: 483
Total size: 12.6 MB
Directory structure:
gitextract_axgbmzp9/
├── .gitignore
├── .gitmodules
├── .rbenv-version
├── LICENSE
├── README.md
├── Slate/
│ ├── ASCIIToCode.plist
│ ├── ASCIIToCode_Azerty.plist
│ ├── ASCIIToCode_Colemak.plist
│ ├── ASCIIToCode_Dvorak.plist
│ ├── AccessibilityWrapper.h
│ ├── AccessibilityWrapper.m
│ ├── ActivateSnapshotOperation.h
│ ├── ActivateSnapshotOperation.m
│ ├── ApplicationOptions.h
│ ├── ApplicationOptions.m
│ ├── Binding.h
│ ├── Binding.m
│ ├── ChainOperation.h
│ ├── ChainOperation.m
│ ├── ConfigurationHelperView.h
│ ├── ConfigurationHelperView.m
│ ├── Constants.h
│ ├── Constants.m
│ ├── CornerOperation.h
│ ├── CornerOperation.m
│ ├── DeleteSnapshotOperation.h
│ ├── DeleteSnapshotOperation.m
│ ├── ExpressionPoint.h
│ ├── ExpressionPoint.m
│ ├── FocusOperation.h
│ ├── FocusOperation.m
│ ├── GridCellView.h
│ ├── GridCellView.m
│ ├── GridOperation.h
│ ├── GridOperation.m
│ ├── GridView.h
│ ├── GridView.m
│ ├── GridWindow.h
│ ├── GridWindow.m
│ ├── HintOperation.h
│ ├── HintOperation.m
│ ├── HintView.h
│ ├── HintView.m
│ ├── HintWindow.h
│ ├── HintWindow.m
│ ├── JSApplicationWrapper.h
│ ├── JSApplicationWrapper.m
│ ├── JSController.h
│ ├── JSController.m
│ ├── JSInfoWrapper.h
│ ├── JSInfoWrapper.m
│ ├── JSONKit/
│ │ ├── JSONKit.h
│ │ └── JSONKit.m
│ ├── JSOperation.h
│ ├── JSOperation.m
│ ├── JSOperationWrapper.h
│ ├── JSOperationWrapper.m
│ ├── JSScreenWrapper.h
│ ├── JSScreenWrapper.m
│ ├── JSWindowWrapper.h
│ ├── JSWindowWrapper.m
│ ├── JSWrapperUtils.h
│ ├── JSWrapperUtils.m
│ ├── Layout.h
│ ├── Layout.m
│ ├── LayoutOperation.h
│ ├── LayoutOperation.m
│ ├── MathUtils.h
│ ├── MathUtils.m
│ ├── MoveOperation.h
│ ├── MoveOperation.m
│ ├── NSFileManager+ApplicationSupport.h
│ ├── NSFileManager+ApplicationSupport.m
│ ├── NSString+Indicies.h
│ ├── NSString+Indicies.m
│ ├── NSString+Levenshtein.h
│ ├── NSString+Levenshtein.m
│ ├── NudgeOperation.h
│ ├── NudgeOperation.m
│ ├── Operation.h
│ ├── Operation.m
│ ├── PushOperation.h
│ ├── PushOperation.m
│ ├── RelaunchOperation.h
│ ├── RelaunchOperation.m
│ ├── ResizeOperation.h
│ ├── ResizeOperation.m
│ ├── RunningApplications.h
│ ├── RunningApplications.m
│ ├── ScreenState.h
│ ├── ScreenState.m
│ ├── ScreenWrapper.h
│ ├── ScreenWrapper.m
│ ├── SequenceOperation.h
│ ├── SequenceOperation.m
│ ├── ShellOperation.h
│ ├── ShellOperation.m
│ ├── ShellUtils.h
│ ├── ShellUtils.m
│ ├── Slate-Info.plist
│ ├── Slate-Prefix.pch
│ ├── SlateAppDelegate.h
│ ├── SlateAppDelegate.m
│ ├── SlateConfig.h
│ ├── SlateConfig.m
│ ├── SlateLogger.h
│ ├── Snapshot.h
│ ├── Snapshot.m
│ ├── SnapshotList.h
│ ├── SnapshotList.m
│ ├── SnapshotOperation.h
│ ├── SnapshotOperation.m
│ ├── StringTokenizer.h
│ ├── StringTokenizer.m
│ ├── SwitchAppQuittingOverlayView.h
│ ├── SwitchAppQuittingOverlayView.m
│ ├── SwitchAppView.h
│ ├── SwitchAppView.m
│ ├── SwitchOperation.h
│ ├── SwitchOperation.m
│ ├── SwitchView.h
│ ├── SwitchView.m
│ ├── SwitchWindow.h
│ ├── SwitchWindow.m
│ ├── ThrowOperation.h
│ ├── ThrowOperation.m
│ ├── UndoOperation.h
│ ├── UndoOperation.m
│ ├── VisibilityOperation.h
│ ├── VisibilityOperation.m
│ ├── WindowInfoView.h
│ ├── WindowInfoView.m
│ ├── WindowSnapshot.h
│ ├── WindowSnapshot.m
│ ├── WindowState.h
│ ├── WindowState.m
│ ├── default.slate
│ ├── en.lproj/
│ │ ├── Credits.rtf
│ │ ├── InfoPlist.strings
│ │ └── MainMenu.xib
│ ├── icon.icns
│ ├── initialize.js
│ ├── main.m
│ ├── slate-mock.js
│ ├── slate-test.html
│ ├── underscore.js
│ └── utils.js
├── Slate.xcodeproj/
│ ├── project.pbxproj
│ └── project.xcworkspace/
│ └── contents.xcworkspacedata
├── SlateTests/
│ ├── SlateTests-Info.plist
│ ├── SlateTests-Prefix.pch
│ ├── SlateTests.h
│ ├── SlateTests.m
│ ├── TestExpressionPoint.h
│ ├── TestExpressionPoint.m
│ ├── TestMathUtils.h
│ ├── TestMathUtils.m
│ ├── TestNSString+Indicies.h
│ ├── TestNSString+Indicies.m
│ ├── TestNSString+Levenshtein.h
│ ├── TestNSString+Levenshtein.m
│ ├── TestShellUtils.h
│ ├── TestShellUtils.m
│ ├── TestStringTokenizer.h
│ ├── TestStringTokenizer.m
│ └── en.lproj/
│ └── InfoPlist.strings
├── Sparkle.framework/
│ └── Versions/
│ └── A/
│ ├── Headers/
│ │ ├── SUAppcast.h
│ │ ├── SUAppcastItem.h
│ │ ├── SUUpdater.h
│ │ ├── SUVersionComparisonProtocol.h
│ │ └── Sparkle.h
│ ├── Resources/
│ │ ├── Info.plist
│ │ ├── License.txt
│ │ ├── SUModelTranslation.plist
│ │ ├── SUStatus.nib/
│ │ │ ├── classes.nib
│ │ │ ├── info.nib
│ │ │ └── keyedobjects.nib
│ │ ├── de.lproj/
│ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ ├── classes.nib
│ │ │ │ ├── info.nib
│ │ │ │ └── keyedobjects.nib
│ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ ├── classes.nib
│ │ │ │ ├── info.nib
│ │ │ │ └── keyedobjects.nib
│ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ ├── classes.nib
│ │ │ │ ├── info.nib
│ │ │ │ └── keyedobjects.nib
│ │ │ └── Sparkle.strings
│ │ ├── en.lproj/
│ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ ├── classes.nib
│ │ │ │ ├── info.nib
│ │ │ │ └── keyedobjects.nib
│ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ ├── classes.nib
│ │ │ │ ├── info.nib
│ │ │ │ └── keyedobjects.nib
│ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ ├── classes.nib
│ │ │ │ ├── info.nib
│ │ │ │ └── keyedobjects.nib
│ │ │ └── Sparkle.strings
│ │ ├── es.lproj/
│ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ ├── classes.nib
│ │ │ │ ├── info.nib
│ │ │ │ └── keyedobjects.nib
│ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ ├── classes.nib
│ │ │ │ ├── info.nib
│ │ │ │ └── keyedobjects.nib
│ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ ├── classes.nib
│ │ │ │ ├── info.nib
│ │ │ │ └── keyedobjects.nib
│ │ │ └── Sparkle.strings
│ │ ├── fr.lproj/
│ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ ├── classes.nib
│ │ │ │ ├── info.nib
│ │ │ │ └── keyedobjects.nib
│ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ ├── classes.nib
│ │ │ │ ├── info.nib
│ │ │ │ └── keyedobjects.nib
│ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ ├── classes.nib
│ │ │ │ ├── info.nib
│ │ │ │ └── keyedobjects.nib
│ │ │ └── Sparkle.strings
│ │ ├── it.lproj/
│ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ ├── classes.nib
│ │ │ │ ├── info.nib
│ │ │ │ └── keyedobjects.nib
│ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ ├── classes.nib
│ │ │ │ ├── info.nib
│ │ │ │ └── keyedobjects.nib
│ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ ├── classes.nib
│ │ │ │ ├── info.nib
│ │ │ │ └── keyedobjects.nib
│ │ │ └── Sparkle.strings
│ │ ├── nl.lproj/
│ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ ├── classes.nib
│ │ │ │ ├── info.nib
│ │ │ │ └── keyedobjects.nib
│ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ ├── classes.nib
│ │ │ │ ├── info.nib
│ │ │ │ └── keyedobjects.nib
│ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ ├── classes.nib
│ │ │ │ ├── info.nib
│ │ │ │ └── keyedobjects.nib
│ │ │ └── Sparkle.strings
│ │ ├── relaunch
│ │ ├── ru.lproj/
│ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ ├── classes.nib
│ │ │ │ ├── info.nib
│ │ │ │ └── keyedobjects.nib
│ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ ├── classes.nib
│ │ │ │ ├── info.nib
│ │ │ │ └── keyedobjects.nib
│ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ ├── classes.nib
│ │ │ │ ├── info.nib
│ │ │ │ └── keyedobjects.nib
│ │ │ └── Sparkle.strings
│ │ └── sv.lproj/
│ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ ├── classes.nib
│ │ │ ├── info.nib
│ │ │ └── keyedobjects.nib
│ │ ├── SUUpdateAlert.nib/
│ │ │ ├── classes.nib
│ │ │ ├── info.nib
│ │ │ └── keyedobjects.nib
│ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ ├── classes.nib
│ │ │ ├── info.nib
│ │ │ └── keyedobjects.nib
│ │ └── Sparkle.strings
│ └── Sparkle
├── VERSION
├── build/
│ ├── Debug/
│ │ └── Slate.app/
│ │ └── Contents/
│ │ ├── Frameworks/
│ │ │ └── Sparkle.framework/
│ │ │ └── Versions/
│ │ │ └── A/
│ │ │ ├── Headers/
│ │ │ │ ├── SUAppcast.h
│ │ │ │ ├── SUAppcastItem.h
│ │ │ │ ├── SUUpdater.h
│ │ │ │ ├── SUVersionComparisonProtocol.h
│ │ │ │ └── Sparkle.h
│ │ │ ├── Resources/
│ │ │ │ ├── Info.plist
│ │ │ │ ├── License.txt
│ │ │ │ ├── SUModelTranslation.plist
│ │ │ │ ├── SUStatus.nib/
│ │ │ │ │ ├── classes.nib
│ │ │ │ │ ├── info.nib
│ │ │ │ │ └── keyedobjects.nib
│ │ │ │ ├── de.lproj/
│ │ │ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ └── Sparkle.strings
│ │ │ │ ├── en.lproj/
│ │ │ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ └── Sparkle.strings
│ │ │ │ ├── es.lproj/
│ │ │ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ └── Sparkle.strings
│ │ │ │ ├── fr.lproj/
│ │ │ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ └── Sparkle.strings
│ │ │ │ ├── it.lproj/
│ │ │ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ └── Sparkle.strings
│ │ │ │ ├── nl.lproj/
│ │ │ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ └── Sparkle.strings
│ │ │ │ ├── relaunch
│ │ │ │ ├── ru.lproj/
│ │ │ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ └── Sparkle.strings
│ │ │ │ └── sv.lproj/
│ │ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ │ ├── classes.nib
│ │ │ │ │ ├── info.nib
│ │ │ │ │ └── keyedobjects.nib
│ │ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ │ ├── classes.nib
│ │ │ │ │ ├── info.nib
│ │ │ │ │ └── keyedobjects.nib
│ │ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ │ ├── classes.nib
│ │ │ │ │ ├── info.nib
│ │ │ │ │ └── keyedobjects.nib
│ │ │ │ └── Sparkle.strings
│ │ │ └── Sparkle
│ │ ├── Info.plist
│ │ ├── MacOS/
│ │ │ └── Slate
│ │ ├── PkgInfo
│ │ └── Resources/
│ │ ├── ASCIIToCode.plist
│ │ ├── ASCIIToCode_Azerty.plist
│ │ ├── ASCIIToCode_Colemak.plist
│ │ ├── ASCIIToCode_Dvorak.plist
│ │ ├── default.slate
│ │ ├── en.lproj/
│ │ │ ├── Credits.rtf
│ │ │ ├── InfoPlist.strings
│ │ │ └── MainMenu.nib
│ │ ├── icon.icns
│ │ ├── initialize.js
│ │ ├── underscore.js
│ │ └── utils.js
│ └── Release/
│ ├── Slate.app/
│ │ └── Contents/
│ │ ├── Frameworks/
│ │ │ └── Sparkle.framework/
│ │ │ └── Versions/
│ │ │ └── A/
│ │ │ ├── Headers/
│ │ │ │ ├── SUAppcast.h
│ │ │ │ ├── SUAppcastItem.h
│ │ │ │ ├── SUUpdater.h
│ │ │ │ ├── SUVersionComparisonProtocol.h
│ │ │ │ └── Sparkle.h
│ │ │ ├── Resources/
│ │ │ │ ├── Info.plist
│ │ │ │ ├── License.txt
│ │ │ │ ├── SUModelTranslation.plist
│ │ │ │ ├── SUStatus.nib/
│ │ │ │ │ ├── classes.nib
│ │ │ │ │ ├── info.nib
│ │ │ │ │ └── keyedobjects.nib
│ │ │ │ ├── de.lproj/
│ │ │ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ └── Sparkle.strings
│ │ │ │ ├── en.lproj/
│ │ │ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ └── Sparkle.strings
│ │ │ │ ├── es.lproj/
│ │ │ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ └── Sparkle.strings
│ │ │ │ ├── fr.lproj/
│ │ │ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ └── Sparkle.strings
│ │ │ │ ├── it.lproj/
│ │ │ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ └── Sparkle.strings
│ │ │ │ ├── nl.lproj/
│ │ │ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ └── Sparkle.strings
│ │ │ │ ├── relaunch
│ │ │ │ ├── ru.lproj/
│ │ │ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ │ │ ├── classes.nib
│ │ │ │ │ │ ├── info.nib
│ │ │ │ │ │ └── keyedobjects.nib
│ │ │ │ │ └── Sparkle.strings
│ │ │ │ └── sv.lproj/
│ │ │ │ ├── SUAutomaticUpdateAlert.nib/
│ │ │ │ │ ├── classes.nib
│ │ │ │ │ ├── info.nib
│ │ │ │ │ └── keyedobjects.nib
│ │ │ │ ├── SUUpdateAlert.nib/
│ │ │ │ │ ├── classes.nib
│ │ │ │ │ ├── info.nib
│ │ │ │ │ └── keyedobjects.nib
│ │ │ │ ├── SUUpdatePermissionPrompt.nib/
│ │ │ │ │ ├── classes.nib
│ │ │ │ │ ├── info.nib
│ │ │ │ │ └── keyedobjects.nib
│ │ │ │ └── Sparkle.strings
│ │ │ └── Sparkle
│ │ ├── Info.plist
│ │ ├── MacOS/
│ │ │ └── Slate
│ │ ├── PkgInfo
│ │ └── Resources/
│ │ ├── ASCIIToCode.plist
│ │ ├── ASCIIToCode_Azerty.plist
│ │ ├── ASCIIToCode_Colemak.plist
│ │ ├── ASCIIToCode_Dvorak.plist
│ │ ├── default.slate
│ │ ├── en.lproj/
│ │ │ ├── Credits.rtf
│ │ │ ├── InfoPlist.strings
│ │ │ └── MainMenu.nib
│ │ ├── icon.icns
│ │ ├── initialize.js
│ │ ├── underscore.js
│ │ └── utils.js
│ └── Slate.dmg
├── icons/
│ ├── Contact.txt
│ └── PSD/
│ ├── icon_1024.psd
│ ├── status-bar icon 1.psd
│ ├── status-bar icon 2.psd
│ └── status-bar icon 3.psd
└── script/
└── slate.rb
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
build/Slate.build
build/Release/Slate.app.dSYM
build/debug_paths.json
build/release_paths.json
build/output
build/Release/*.tar.gz
build/Release/VERSION
build/Release/appcast.xml
Slate.xcodeproj/jigish.mode1v3
Slate.xcodeproj/jigish.pbxuser
Slate.xcodeproj/project.xcworkspace/xcuserdata/jigish.xcuserdatad/
Slate.xcodeproj/xcuserdata/jigish.xcuserdatad/
# don't version xcode artifacts
*.xcuserdatad
tmp
================================================
FILE: .gitmodules
================================================
[submodule "script/create-dmg"]
path = script/create-dmg
url = https://github.com/andreyvit/yoursway-create-dmg.git
================================================
FILE: .rbenv-version
================================================
1.9.3-p286
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Copyright (C)
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
.
================================================
FILE: README.md
================================================
# About Slate #
Slate is a window management application similar to Divvy and SizeUp (except better and free!). Originally written to replace them due to some limitations in how each work, it attempts to overcome them by simply being extremely configurable. As a result, it may be a bit daunting to get configured, but once it is done, the benefit is huge.
Slate currently works on Mac OS X 10.6 and above
## Summary of Features ##
* Highly customizable
* Bind keystrokes to:
* move and/or resize windows
* directionally focus windows
* activate preset layouts
* create, delete, and activate snapshots of the current state of windows
* Set default layouts for different monitor configurations which will activate when that configuration is detected.
* Window Hints: an intuitive way to change window focus
* \[Beta\] A better, more customizable, application switcher.
## Credits ##
Big thanks to [philc](https://github.com/philc) for the Window Hints idea (and initial implementation) as well as plenty of other suggestions and improvement ideas.
# Using Slate #
## Installing Slate ##
**NEW Installation Instructions**
**Note:** You must turn on the Accessibility API by checking System Preferences > Universal Access > Enable access for assistive devices
### Direct Download ###
* [`.dmg`](http://slate.ninjamonkeysoftware.com/Slate.dmg)
* [`.tar.gz`](http://slate.ninjamonkeysoftware.com/versions/slate-latest.tar.gz)
### Terminal ###
Just run this in your terminal:
cd /Applications && curl http://www.ninjamonkeysoftware.com/slate/versions/slate-latest.tar.gz | tar -xz
## Configuring Slate ##
**NEW:** You may now use a ".slate.js" file to configure slate using JavaScript. This allows for much more complex and dynamic configurations than the normal slate configuration style below. You can check out the documentation for this [here](https://github.com/jigish/slate/wiki/JavaScript-Configs).
Slate is configured using a ".slate" file in the current user's home directory. Configuration is loaded upon running Slate. You can also re-load the config using the "Load Config" menu option on the status menu (use this at your own risk. It is better to simply restart Slate).
**Note:** If no ".slate" file exists in the current user's home directory, the [default config file](https://github.com/jigish/slate/blob/master/Slate/default.slate) will be used.
Configuration is split into the following directives:
* `config` (for global configurations)
* `alias` (to create alias variables)
* `layout` (to configure layouts)
* `default` (to default certain screen configurations to layouts)
* `bind` (for key bindings)
* `source` (to load configs from another file)
**Note:** `#` is the comment character. Anything after a `#` will be ignored.
###Expressions###
Some directives allow parameters that can be expressions. The following strings will be replaced with the appropriate values when using expressions:
screenOriginX = target screen's top left x coordinate (should not be used in Window Hints configs)
screenOriginY = target screen's top left y coordinate (should not be used in Window Hints configs)
screenSizeX = target screen's width
screenSizeY = target screen's height
windowTopLeftX = window's current top left x coordinate (should not be used in Window Hints configs)
windowTopLeftY = window's current top left y coordinate (should not be used in Window Hints configs)
windowSizeX = window's width
windowSizeY = window's height
newWindowSizeX = window's new width (after resize, only usable in topLeftX and topLeftY, should not be
used in configs)
newWindowSizeY = window's new height (after resize, only usable in topLeftX and topLeftY, should not be
used in configs)
windowHintsWidth = the value of the windowHintsWidth config (only usable in windowHintsTopLeftX and
windowHintsTopLeftY)
windowHintsHeight = the value of the windowHintsHeight config (only usable in windowHintsTopLeftX and
windowHintsTopLeftY)
In addition to the variables above, expressions can be used with the following functions and operators:
+ e.g. 1+1 = 2
- e.g. 1-1 = 0
* e.g. 2*2 = 4
/ e.g. 4/2 = 2
** e.g. 3**2 = 9
sum e.g. sum({1,2,3}) = 6
count e.g. count({4,5,6}) = 3
min e.g. min({1,3,5}) = 1
max e.g. max({1,3,5}) = 5
average e.g. average({1,2,3,4}) = 2.5
median e.g. median({1,2,3,10,15}) = 3
stddev e.g. stddev({1,2,3,4,5}) = 1.4142135623730951
sqrt e.g. sqrt(9) = 3.0
log e.g. log(100) = 2.0
ln e.g. ln(8) = 2.0794415416798357
exp e.g. exp(2) = 7.3890560989306504 (this is "e**parameter")
floor e.g. floor(1.9) = 1.0
ceiling e.g. ceiling(1.1) = 2.0
abs e.g. abs(-1) = 1
trunc e.g. trunc(1.1123123123) = 1.0
random e.g. random() = 0.20607629744336009 (random float between 0 and 1)
randomn e.g. randomn(10) = 4 (random integer between 0 and parameter-1)
**Note:** When using expressions spaces are *not* allowed!
### The `config` Directive ###
The `config` directive follows the following format:
config name value
[List of allowed configs](https://github.com/jigish/slate/wiki/Global-Configs)
Example:
config defaultToCurrentScreen true
**Note:** the `.slate` file is read top-down directives that come before `config` directives may not have the `config` applied. As such, it is best to put `config` directives at the top of your `.slate` file.
### The `alias` Directive ###
The `alias` directive follows the following format:
alias name value
When you set an alias, you can refer to it in any directive (sequentially after that alias directive) by referencing like `${name}`.
Example:
alias bot-right-2nd-mon move screenOriginX+2*screenSizeX/3;screenOriginY+screenSizeY/2 screenSizeX/3;screenSizeY/2 1
Will allow you to use `${bot-right-2nd-mon}` as a reference to `move screenOriginX+2*screenSizeX/3;screenOriginY+screenSizeY/2 screenSizeX/3;screenSizeY/2 1` in any directive following the alias (including other alias directives)
### The `layout` Directive ###
The `layout` directive follows the following format:
layout name 'app name':OPTIONS operations
Where:
name = the name you want to use to reference the layout
'app name' = single-quoted name of the application to add to the layout **or** BEFORE or AFTER
OPTIONS = a comma separated list of options for this application (cannot be used with BEFORE or AFTER)
operations = a pipe separated list of operations (move, resize, push, nudge, throw, or corner)
Possible Options:
| Name | Function |
|:-----|:---------|
| `IGNORE_FAIL` | This will let slate move to the next operation if the current operation fails to resize/move on the current window |
| `REPEAT` | This will repeat the list of operations if the number of windows is larger than the number of operations |
| `REPEAT_LAST` | This will repeat the last operation in the list if the number of windows is larger than the number of operations |
| `MAIN_FIRST` | This will cause the main window to always use the first operation |
| `MAIN_LAST` | This will cause the main window to always use the last operation (mutally exclusive with `MAIN_FIRST`) |
| `SORT_TITLE` | This will cause the window operations to be triggered on the windows in sorted order by the window title (can be used with `MAIN_FIRST` or `MAIN_LAST`) |
| `TITLE_ORDER=order` | This will cause the operations to be triggered on the windows starting with order which is a semi-colon separated list of window titles |
| `TITLE_ORDER_REGEX=order` | This will cause the operations to be triggered on the windows starting with the order which is a semi-colon separated list of window title regexes to match. Note that once a match is seen, the next regex will be used to match. This means if you have two windows that match the same regex, only the first one seen will be matched. The second will not. |
You can have multiple layout directives that point to the same name in order to link any number of applications to the same layout.
Example:
layout myLayout 'iTerm' push up bar-resize:screenSizeY/2 | push down bar-resize:screenSizeY/2
layout myLayout 'Google Chrome' push left bar-resize:screenSizeX/2 | push right bar-resize:screenSizeX/2
layout myLayout BEFORE shell path:~/ '/opt/local/bin/mvim before'
layout myLayout AFTER shell path:~/ '/opt/local/bin/mvim after'
Will create a layout called `myLayout` with two operations for iTerm and two operations for Google Chrome. When activated, the first window of iTerm will be moved using the first operation in the first list and the second window of iTerm will be moved using the second operation in the first list. In addition, the first window of Google Chrome will be moved using the first operation in the second list and the second window of Google Chrome will be moved using the second operation in the second list. Finally, the operation `shell path:~/ '/opt/local/bin/mvim before'` will be run before any Applications are moved and the operation `shell path:~/ '/opt/local/bin/mvim after'` will be run after any Applications are moved. BEFORE and AFTER may also be used if the layout doesn't have any applications tied to it. Also, you may specify multiple BEFORE or AFTER lines (they will be run in the order that they appear). More information on how to actually use these layouts can be found under the `layout` operation in the `bind` directive section.
### The `default` Directive ###
The `default` directive follows the following format (tokens may be separated by any number of spaces):
default layout-or-snapshot-name screen-configuration
Where:
layout-or-snapshot-name = the name of the layout or snapshot you want to default to
screen-configuration = either "count:NUMBER_OF_SCREENS" or
"resolutions:SEMICOLON_SEPARATED_LIST_OF_RESOLUTIONS"
This directive will cause any screen configuration change (add monitor, remove monitor, screen resolution change) to trigger a search for a default layout or snapshot. If the screen configuration matches one of the defaults set, the layout or snapshot matching `layout-or-snapshot-name` will be triggered. For example:
default myLayout count:2
Will trigger `myLayout` anytime the screen configuration changes to have 2 monitors. Also:
default myLayout2 resolutions:1440x900;1024x768;1680x1050
Will trigger `myLayout2` anytime the screen configuration changes to have exactly 3 monitors with resolutions `1440x900`, `1024x768`, and `1680x1050`.
### The `bind` Directive ###
The `bind` directive follows one of the following formats (tokens may be separated by any number of spaces):
bind key:modifiers operation parameter+
bind key:modal-key operation parameter+
#### Key ####
`key` is a reference to a key on the keyboard. See Allowed Keys for a complete list. For example: the `s` key would simply be `s` while the `1` key on the number pad would be `pad1`.
#### Modifiers ####
`modifiers` is a comma or semicolon separated list of standard modifier keys. Allowed modifiers are:
* Control: `ctrl`
* Option/Alt: `alt`
* Command: `cmd`
* Shift: `shift`
**Note:** If you bind any binding to cmd-tab or cmd-shift-tab, Slate will completely disable the default Mac OS X Application switcher!
**Note:** Bindings that are used by Mac OS X spaces, expose, and mission control will override Slate bindings. Be sure to turn these bindings off if you want to use them in Slate.
#### Modal Key ####
`modal-key` is any one of the Allowed Keys. If using a `modal-key`, pressing that key will cause the Slate menu bar icon to change indicating modal mode is activated. then clicking `key` will activate the binding. Modal mode will remain active until `key` has been pressed or `modal-key` is pressed again. You may specify multiple bindings with the same `modal-key` as long as `key` is different. Also, `modal-key` can accompany a comma or semicolon separated list of modifier keys listed above. This will cause that entire keystroke to be considered the modal activation binding. For example: `bind 1:f4,ctrl,alt` will result in the modal keystroke being `ctrl+alt+f4`. After pressing that keystroke, modal mode will be activated and pressing `1` after that will activate the binding.
##### Modal Toggle Behavior #####
If you add `:toggle` to the end of a modal binding it will cause that binding to not end the modal mode. For example with the binding `1:ctrl,f4`, you press `ctrl+f4` and then press `1` to activate the binding. Once that binding is activated, modal mode will end and you have to press `ctrl+f4` again to activate it. However, with the binding `1:ctrl,f4:toggle` pressing `ctrl+f4` will toggle modal mode. pressing `1` will activate the binding but not end modal mode. To end modal mode, press `ctrl+f4` again or use the config `modalEscapeKey`.
#### Operation ####
Operations define what to actually do to the focused window.
**Screens**
Some operations allow you to specify a screen. Here are the list of possible values for screen:
* Integer representing the screen ID (indexed at 0). Screens are ordered from left to right (by X coordinate of the origin which is the top-left point). If `orderScreensLeftToRight` is set to false, the screen ID is the Mac OS internal ID (indexed at 0). If `orderScreensLeftToRight` is set to false but you still want to reference screens in the default ordered mode, prefix the screen ID with `ordered:`.
* Screen resolution in the format `WIDTHxHEIGHT` (e.g. `1440x900`)
* Screen direction relative to the current screen (`left|right|up|above|down|below`)
* `next` or `previous` (represents the `currentID+1` or `currentID-1` screen)
**Allowed operations are:**
##### move #####
Move/Resize the window any which way: `move topLeftX;topLeftY sizeX;sizeY screen`
topLeftX = top left x coordinate of the window's desired position (can be an expression)
topLeftY = top left y coordinate of the window's desired position (can be an expression)
sizeX = width of the window's desired position (can be an expression)
sizeY = height of the window's desired position (can be an expression)
screen = (optional) the reference to the screen of the window's desired position.
If this is not specified, it will default to the screen the window is currently on.
See the table at the beginning of the Operation section for more information.
Example:
bind pad1:ctrl move 0;0 100;100 1
Will bind the keystroke ctrl-numpad1 to moving the window to the screen at index `1` with top-left coordinate `0,0` and size `100,100`
**Note:** Remember to offset with `screenOriginX` in your `topLeftX` and `screenOriginY` in your `topLeftY` when using the `screen` option (or when using multiple screens in general) or your move operation will offset from the default origin `(0,0)` which is the origin of screen `0`.
##### resize #####
Resize the window (keeping top-left the same): `resize x y anchor`
x = amount to resize width either as a percent or a hard value (+10% or -100)
y = amount to resize height either as a percent or a hard value (+10% or -100)
anchor = (optional) which corner to anchor on top-left|top-right|bottom-left|bottom-right (default is top-left)
Example:
bind right:ctrl resize +10% +0
Will bind the keystroke ctrl-rightarrow to increase the width the current window by `10%`.
**Note:** ctrl-rightarrow is used by default in Mac OS X by spaces. Be sure to turn these bindings off if you want to use them in Slate.
##### push #####
Push the window to the edge of the screen: `push direction style`
direction = top|up|bottom|down|left|right
style = (optional) none|center|bar|bar-resize:expression (default is none)
screen = (optional) the reference to the screen of the window's desired position.
If this is not specified, it will default to the screen the window is currently on.
See the table at the beginning of the Operation section for more information.
Example:
bind up:alt,ctrl push up
Will bind the keystroke alt-ctrl-uparrow to push the window so that it is aligned with the top of the screen
##### nudge #####
Nudge the window in any direction: `nudge x y`
x = amount to nudge x either as a percent or a hard value (+10% or -100)
y = amount to nudge y either as a percent or a hard value (+10% or -100)
Example:
bind left:ctrl,shift nudge -100 +0
Will bind the keystroke ctrl-shift-leftarrow to nudge the window `100` pixels to the left
##### throw #####
Throw the window to any screen's origin: `throw screen style`
screen = the screen you want to throw the window to (0 indexed)
style = (optional) resize|resize:x-expression;y-expression (default will not resize)
Example:
bind pad1:alt,ctrl throw 1 resize
Will bind the keystroke alt-ctrl-numpad1 to throw the window to the 2nd screen and resize it to fit that screen
##### corner #####
Move/Resize the window into a corner: `corner direction style`
direction = top-left|top-right|bottom-left|bottom-right
style = (optional) resize:x-expression;y-expression (default will not resize)
screen = (optional) the reference to the screen of the window's desired position.
If this is not specified, it will default to the screen the window is currently on.
See the table at the beginning of the Operation section for more information.
Example:
bind 1:ctrl corner top-left resize:screenSizeX/2;screenSizeY/2
Will bind the keystroke ctrl-1 to move the window to the top-left corner and resize it to 1/4 of the screen
##### shell #####
Execute a shell command: `shell options 'command'`
command = (required) the command to run. note that it is a quoted string.
options = (optional) a space separated list of:
wait - block slate until the shell command exits. Useful when using shell commands in a
sequence binding
path: - the inital working directory to use when starting the command. For example
path:~/code would set the inital working directory to ~/code
Example:
bind 1:ctrl wait path:~/code '/opt/local/bin/mvim'
Will bind the keystroke ctrl-1 to run the command `/opt/local/bin/mvim` with the current working directory of `~/code`. Slate will also block until the command is done. Note that you may **not** use the tilda home directory shortcut within the command itself, it is only allowed within the path.
##### hide #####
Hide one or more applications: `hide applications`
applications = a comma separated list of application names. Individual application names must be
surrounded by quotes. You can also specify `current`, `all`, or `all-but:` for the
Application name (no quotes). `current` will apply to the currently focused
application, `all` will apply to all open applications and `all-but:'APP_NAME'` will
apply to all open applications except `APP_NAME`. Note that when trying to hide `all`
it will not work as intended because OS X will not allow every visible app to be
hidden. Hiding `all` will hide all apps but OS X will auto-show one of the apps that
were hidden.
Example:
bind 1:ctrl hide 'iTerm','Google Chrome'
Will bind the keystroke ctrl-1 to hide iTerm and Google Chrome.
##### show #####
Show one or more applications: `show applications`
applications = a comma separated list of application names. Individual application names must be
surrounded by quotes. You can also specify `current`, `all`, or `all-but:` for the
Application name (no quotes). `current` will apply to the currently focused
application, `all` will apply to all open applications and `all-but:'APP_NAME'` will
apply to all open applications except `APP_NAME`.
Example:
bind 1:ctrl show 'iTerm','Google Chrome'
Will bind the keystroke ctrl-1 to show (unhide) iTerm and Google Chrome.
##### toggle #####
Toggle one or more applications: `toggle applications`
applications = a comma separated list of application names. Individual application names must be
surrounded by quotes. You can also specify `current`, `all`, or `all-but:` for the
Application name (no quotes). `current` will apply to the currently focused
application, `all` will apply to all open applications and `all-but:'APP_NAME'` will
apply to all open applications except `APP_NAME`. Note that when trying to toggle `all`
it will may not work as intended because OS X will not allow every visible app to be
hidden. If at any point during the toggling all apps become hidden, OS X will auto-show
one of the apps that were hidden.
Example:
bind 1:ctrl toggle 'iTerm','Google Chrome'
Will bind the keystroke ctrl-1 to toggle iTerm and Google Chrome. Toggle meaning if the individual
application is currently hidden it will be shown and if it is currently shown it will be hidden.
**Note:** If you specify current in this toggle operation it will not toggle properly because after the current application is hidden, it is no longer the current application anymore.
##### chain #####
Chain multiple operations to one binding: `chain opAndParams1 | opAndParams2 ...`
opAndParamsX = any operation string (except sequence, hint and grid)
Example:
bind 1:ctrl chain push up | push right | push down | push left
Will bind the keystroke ctrl-1 to push up on the first press, then push right on the second press, then push down on the third press, the push left on the fourth press and rotate back to pushing up on the fifth press (etc).
##### sequence #####
Activate a sequence of operations in one binding: `sequence opAndParams1 separator opAndParams 2 ...`
opAndParamsX = any of the above operation strings (except chain and grid. hint must be last if present)
separator = | or >. | will cause the next operation to be performed on the window focused at the time of
execution of that operation, > will cause the next operation to be performed on the window
focused at the start of the > chain.
Example:
bind 1:ctrl sequence focus right > push left | push right
Will bind the keystroke ctrl-1 to first focus the window to the right, then push the previously focused window to the left, then push the newly focused window to the right. Obviously Hint will ignore `>` and `|` and just display because it doesn't care which window was focused.
##### layout #####
Activate a layout: `layout name`
name = the name of the layout to activate (set using the layout directive)
Example:
bind 1:ctrl layout myLayout
Will bind the keystroke ctrl-l to activate the layout called `myLayout`. Note that the layout **must** be created before you bind it.
##### focus #####
Focus a window in a direction or from an application: `focus direction|app`
direction = right|left|up|above|down|below|behind
app = an app name surrounded by quotes
Example:
bind 1:ctrl focus above
Will bind the keystroke ctrl-1 to focus the window Slate finds to be above the currently focused window (from any application). Minimized and hidden windows are ignored. A couple global configuration options set using the `config` directive exist to tweak this. Also, up and above are the same. Down and below are also the same.
bind 1:ctrl focus 'iTerm'
Will bind the keystroke ctrl-1 to focus the main window of the application iTerm. The main window is the last focused window of that application.
##### snapshot #####
Create a snapshot of your current window locations: `snapshot name options`
name = the name of the snapshot to create (used in delete-snapshot and activate-snapshot)
options = (optional) a semicolon separated list of any of the following options:
save-to-disk -> saves the snapshot to disk so Slate will load it when it starts up next
stack -> treats this snapshot as stack so you can use this binding multiple times to push snapshots on the stack
Example:
bind 1:ctrl snapshot theName save-to-disk;stack
Will bind the keystroke ctrl-1 to create a snapshot called `theName`, save that snapshot to disk, and treat it as a stack so you can hit the keystroke multiple times to push snapshots onto the stack.
**Note:** There is a menu option to take a snapshot of the current screen configuration.
##### delete-snapshot #####
Delete a snapshot: `delete-snapshot name options`
name = the name of the snapshot to delete
options = (optional) a semicolon separated list of any of the following options:
all -> if the snapshot is a stack (if it isn't, this option is useless), this will delete all snapshots in the
stack (if this option is not specified, the default is to only delete the top snapshot of the stack).
Example:
bind 1:ctrl delete-snapshot theName all
Will bind the keystroke ctrl-1 to delete the snapshot called `theName` if it exists. This will delete all instances of theName meaning if you have pushed multiple snapshots on the stack, it will completely clear them all.
##### activate-snapshot #####
Activate a snapshot: `activate-snapshot name options`
name = the name of the snapshot to activate
options = (optional) a semicolon separated list of any of the following options:
delete -> this will delete the snapshot after activating it (if the snapshot is a stack, it will pop the top
snapshot off and keep the rest)
Example:
bind 1:ctrl activate-snapshot theName delete
Will bind the keystroke ctrl-1 to activate the snapshot called `theName` if it exists. This will also delete the snapshot (or pop it off the stack if the snapshot is a stack).
**Note:** There is a menu option to activate the snapshot that you may have created using the menu option.
##### hint #####
Show Window Hints (similar to Link Hints in Vimium except for Windows): `hint characters`
characters = (optional) a simple string of characters to be used for the hints. each hint consists of one
character. if there are more windows than characters then some windows will not get hints.
this string can contain any of the single character Allowed Keys. Letters may be upper case or
lower case, but both will be bound to the lowercase letter for the hint. Using upper or lower
case only changes how they are displayed. The default string of characters is
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
Example:
bind 1:ctrl hint QWERTYUIOP
Will bind the keystroke ctrl-1 to show Window Hints using the letters `Q`, `W`, `E`, `R`, `T`, `Y`, `U`, `I`, `O`, and `P`. This will show an overlay in the top-left corner of every window on screen containing one of those letters. While the overlays are showing, if one of those letters is pressed, the corresponding window will be focused. If there are more than 10 windows, some windows will not get hints. Pressing ESC will dismiss the hints.
**Note:** There are *tons* of config options to tweak this.
##### grid #####
Show a Grid to one-off resize and move windows: `grid options`
options is a whitespace separated list of:
padding: = the padding between cells
screenRef:width,height = width and height are integers specifying the width and height of the grid
(number of cells, not absolute size). screenRef is either the screenID or
screen resolution (widthxheight)
Example:
bind 1:ctrl grid padding:5 1680x1050:16,9 1050x1680:9,16
Will bind the keystroke ctrl-1 to show Grids on each screen. The default width and height are 12. This will set the padding between the cells to be 5. Also, this will change the width and height of the grid on the monitor with the resolution 1680x1050 to 16 and 9 respectively. For the monitor with the resolution 1050x1680, it will set the width to 9 and height to 16. If you have multiple monitors, the Grid that is on the same screen as your mouse pointer will be focused. If you want to use a grid on a different monitor you **must** click it first and then click+drag.
**Note:** There are a bunch of config options to tweak how this looks.
##### relaunch #####
Relaunch Slate: `relaunch`
Example:
bind 1:ctrl relaunch
Will bind the keystroke ctrl-1 to relaunch Slate. This will also reload the `.slate` file from scratch.
##### undo #####
Undo an Operation: `undo`
Example
bind 1:ctrl undo
Will bind the keystroke ctrl-1 to undo the last binding that was triggered. By default you can undo up to the last 10 commands. This can be changed using the `undoMaxStackSize` config. Also, you can only undo movement-based operations. Focus-related operations will not undo.
##### switch #####
\[Beta\] A Better Application Switcher: `switch`
If you bind any binding to cmd-tab or cmd-shift-tab, Slate will completely disable the default Mac OS X Application switcher!
Example:
bind tab:cmd switch
Will disable the default Mac OS X Application switcher and bind the keystroke cmd-tab to a better application switcher.
**Note:** There are *tons* of config options to tweak this.
### The `source` Directive ###
The source directive follows the following format (tokens may be separated by any number of spaces):
source filename optional:if_exists
Where `filename` is the name of a file containing any of the directives above (including source). If no absolute path is specified, the user's home directory will be prepended to `filename`. If the user specifies the option `if_exists` as the second argument, Slate will not complain if it cannot find the file.
For Example:
source ~/.slate.test if_exists
Will append all of the configurations from the file `~/.slate.test` to the current configuration if the file `~/.slate.test` exists.
**Note:** You may use any aliases, layouts, etc that you specify before the source directive in the file you source. Any aliases, layouts, etc specified after cannot be used. Additionally, any aliases, layouts, etc that you specify in the file you source can be used after the source directive.
### Example Config ###
You can check out my own config [here](https://github.com/jigish/dotfiles/blob/master/slate).
### Useful Stuff ###
- [kvs](https://github.com/kvs) has created a [Sublime Text 2](http://www.sublimetext.com/2) preference for `.slate` files [here](https://github.com/kvs/ST2Slate).
- [trishume](https://github.com/trishume) has done a really nice writeup on getting started with Slate [here](http://thume.ca/howto/2012/11/19/using-slate/)
# Contact #
Please send all questions, bug reports, suggestions, or general commentary to [Jigish Patel](mailto:slate.issues@gmail.com) or [create an issue](https://github.com/jigish/slate/issues/new) on github.
# Allowed Keys #
**Note:** If you bind any binding to cmd-tab or cmd-shift-tab, Slate will completely disable the default Mac OS X Application switcher!
'
,
-
.
/
0
1
2
3
4
5
6
7
8
9
;
=
`
a
b
backslash
c
caps
d
delete
down
e
end
esc
f
f1
f10
f11
f12
f13
f14
f15
f16
f17
f18
f19
f2
f20
f3
f4
f5
f6
f7
f8
f9
g
h
help
home
i
j
k
l
left
m
mute
n
o
p
pad*
pad+
pad-
pad.
pad/
pad0
pad1
pad2
pad3
pad4
pad5
pad6
pad7
pad8
pad9
pad=
padClear
padEnter
pageDown
pageUp
q
r
return
right
s
space
t
tab
u
up
v
w
x
y
z
[
]
================================================
FILE: Slate/ASCIIToCode.plist
================================================
'39,43-27.47/44029118219320421523622726828925;41=24`50a0b11backslash42c8caps57d2delete51down125e14end119esc53f3f1122f10109f11103f12111f13105f14107f15113f16106f1764f1879f1980f2120f2090f399f4118f596f697f798f8100f9101g5h4help114home115i34j38k40l37left123m46mute74n45o31p35pad*67pad+69pad-78pad.65pad/75pad082pad183pad284pad385pad486pad587pad688pad789pad891pad992pad=81padClear71padEnter76pageDown121pageUp116q12r15return36right124s1space49t17tab48u32up126v9w13x7y16z6[33]30
================================================
FILE: Slate/ASCIIToCode_Azerty.plist
================================================
help114mute74pageUp116f13105down125f399padClear71'39]30,46.47029219421622828return36caps57pageDown121f1980`50b11d2f3h4j38l37n45p35f1879r15t17v9f12111x7f10109z13f16106f1764f14107delete51f1122f2120right124f4118f596f697f798f8100f9101backslash42up126tab48home115523end119space49-27/44118320f2090esc53726925;41=24padEnter76pad+69pad*67pad-78pad/75pad.65pad183pad082pad385pad284pad587pad486pad789pad688pad992pad891[33pad=81a12c8f11103e14g5i34k40m41o31q0s1u32w6y16f15113left123
================================================
FILE: Slate/ASCIIToCode_Colemak.plist
================================================
help114mute74pageUp116f13105down125f399padClear71'39]30,43.47029219421622828return36caps57pageDown121f1980`50b11d5f14h4j16l32n38p15f1879r1t3v9f12111x7f10109z6f16106f1764f14107delete51f1122f2120right124f4118f596f697f798f8100f9101backslash42up126tab48home115523end119space49-27/44118320f2090esc53726925;35=24padEnter76pad+69pad*67pad-78pad/75pad.65pad183pad082pad385pad284pad587pad486pad789pad688pad992pad891[33pad=81a0c8f11103e40g17i37k45m46o41q12s2u34w13y31f15113left123
================================================
FILE: Slate/ASCIIToCode_Dvorak.plist
================================================
-39w43[27v47z44029118219320421523622726828925s41]24`50a0x11backslash42j8caps57e2delete51down125.14end119esc53u3f1122f10109f11103f12111f13105f14107f15113f16106f1764f1879f1980f2120f2090f399f4118f596f697f798f8100f9101i5d4help114home115c34h38t40n37left123m46mute74b45r31l35pad*67pad+69pad-78pad.65pad/75pad082pad183pad284pad385pad486pad587pad688pad789pad891pad992pad=81padClear71padEnter76pageDown121pageUp116'12p15return36right124o1space49y17tab48g32up126k9,13q7f16;6/33=30
================================================
FILE: Slate/AccessibilityWrapper.h
================================================
//
// AccessibilityWrapper.h
// Slate
//
// Created by Jigish Patel on 6/10/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface AccessibilityWrapper : NSObject {
@private
AXUIElementRef app;
AXUIElementRef window;
BOOL inited;
}
@property (assign) AXUIElementRef app;
@property (assign) AXUIElementRef window;
@property (assign) BOOL inited;
- (id)initWithApp:(AXUIElementRef)appRef window:(AXUIElementRef)windowRef;
+ (NSPoint)getTopLeftForWindow:(AXUIElementRef)window;
- (NSPoint)getCurrentTopLeft;
+ (NSSize)getSizeForWindow:(AXUIElementRef)window;
- (NSSize)getCurrentSize;
- (BOOL)moveWindow:(NSPoint)thePoint;
- (BOOL)resizeWindow:(NSSize)theSize;
- (BOOL)focus;
- (BOOL)isMinimizedOrHidden;
- (NSString *)getTitle;
- (pid_t)processIdentifier;
+ (BOOL)focusWindow:(AXUIElementRef)window;
+ (BOOL)focusMainWindow:(NSRunningApplication *)app;
+ (BOOL)focusApp:(NSRunningApplication *)app;
+ (pid_t)processIdentifierOfUIElement:(AXUIElementRef)element;
+ (CFArrayRef)windowsInApp:(AXUIElementRef)app;
+ (CFArrayRef)windowsInRunningApp:(NSRunningApplication *)app;
+ (AXUIElementRef)focusedWindowInRunningApp:(NSRunningApplication *)app;
+ (BOOL)isMainWindow:(AXUIElementRef)window;
+ (NSString *)getTitle:(AXUIElementRef)window;
- (BOOL)isMovable;
- (BOOL)isResizable;
+ (BOOL)isWindowMinimizedOrHidden:(AXUIElementRef)window inApp:(AXUIElementRef)app;
+ (AXUIElementRef)windowUnderPoint:(NSPoint)point;
+ (void)createSystemWideElement;
+ (AXUIElementRef)applicationForElement:(AXUIElementRef)element;
+ (BOOL)isWindow:(AXUIElementRef)element;
+ (NSString *)getRole:(AXUIElementRef)element;
@end
================================================
FILE: Slate/AccessibilityWrapper.m
================================================
//
// AccessibilityWrapper.m
// Slate
//
// Created by Jigish Patel on 6/10/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "AccessibilityWrapper.h"
#import "Constants.h"
#import "SlateLogger.h"
static AXUIElementRef systemWideElement = NULL;
static NSDictionary *unselectableApps = nil;
@implementation AccessibilityWrapper
@synthesize app;
@synthesize window;
@synthesize inited;
- (id)init {
self = [super init];
if (self) {
[AccessibilityWrapper createSystemWideElement];
// Get App that has focus
CFTypeRef _app;
AXUIElementCopyAttributeValue(systemWideElement, (CFStringRef)kAXFocusedApplicationAttribute, (CFTypeRef *)&_app);
[self setApp:(AXUIElementRef)_app];
// Get Window that has focus
CFTypeRef _window;
if (AXUIElementCopyAttributeValue(app, (CFStringRef)NSAccessibilityFocusedWindowAttribute, (CFTypeRef *)&_window) == kAXErrorSuccess) {
[self setWindow:(AXUIElementRef)_window];
[self setInited:YES];
} else {
[self setInited:NO];
SlateLogger(@"ERROR: Could not fetch focused window");
}
}
return self;
}
- (id)initWithApp:(AXUIElementRef)appRef window:(AXUIElementRef)windowRef {
self = [super init];
if (self) {
[AccessibilityWrapper createSystemWideElement];
[self setApp:appRef];
[self setWindow:windowRef];
[self setInited:YES];
}
return self;
}
+ (NSPoint)getTopLeftForWindow:(AXUIElementRef)window {
CFTypeRef _cPosition;
NSPoint cTopLeft;
if (AXUIElementCopyAttributeValue(window, (CFStringRef)NSAccessibilityPositionAttribute, (CFTypeRef *)&_cPosition) == kAXErrorSuccess) {
if (!AXValueGetValue(_cPosition, kAXValueCGPointType, (void *)&cTopLeft)) {
SlateLogger(@"ERROR: Could not decode position");
cTopLeft = NSMakePoint(0, 0);
}
} else {
SlateLogger(@"ERROR: Could not fetch position");
cTopLeft = NSMakePoint(0, 0);
}
if (_cPosition != NULL) CFRelease(_cPosition);
return cTopLeft;
}
- (NSPoint)getCurrentTopLeft {
return [AccessibilityWrapper getTopLeftForWindow:window];
}
+ (NSSize)getSizeForWindow:(AXUIElementRef)window {
CFTypeRef _cSize;
NSSize cSize;
if (AXUIElementCopyAttributeValue(window, (CFStringRef)NSAccessibilitySizeAttribute, (CFTypeRef *)&_cSize) == kAXErrorSuccess) {
if (!AXValueGetValue(_cSize, kAXValueCGSizeType, (void *)&cSize)) {
SlateLogger(@"ERROR: Could not decode size");
cSize = NSMakeSize(0, 0);
}
} else {
SlateLogger(@"ERROR: Could not fetch size");
cSize = NSMakeSize(0, 0);
}
if (_cSize != NULL) CFRelease(_cSize);
return cSize;
}
- (NSSize)getCurrentSize {
return [AccessibilityWrapper getSizeForWindow:window];
}
- (BOOL)moveWindow:(NSPoint)thePoint {
CFTypeRef _position;
_position = (CFTypeRef)(AXValueCreate(kAXValueCGPointType, (const void *)&thePoint));
if (AXUIElementSetAttributeValue(window, (CFStringRef)NSAccessibilityPositionAttribute, (CFTypeRef *)_position) != kAXErrorSuccess) {
SlateLogger(@"ERROR: Could not change position");
if (_position != NULL) CFRelease(_position);
return NO;
}
if (_position != NULL) CFRelease(_position);
return YES;
}
- (BOOL)resizeWindow:(NSSize)theSize {
CFTypeRef _size;
_size = (CFTypeRef)(AXValueCreate(kAXValueCGSizeType, (const void *)&theSize));
if (AXUIElementSetAttributeValue(window, (CFStringRef)NSAccessibilitySizeAttribute, (CFTypeRef *)_size) != kAXErrorSuccess) {
SlateLogger(@"ERROR: Could not change size");
if (_size != NULL) CFRelease(_size);
return NO;
}
if (_size != NULL) CFRelease(_size);
return YES;
}
- (BOOL)focus {
return [AccessibilityWrapper focusWindow:[self window]];
}
- (BOOL)isMinimizedOrHidden {
return [AccessibilityWrapper isWindowMinimizedOrHidden:[self window] inApp:[self app]];
}
- (BOOL)isMovable {
return [self moveWindow:[self getCurrentTopLeft]];
}
- (BOOL)isResizable {
return [self resizeWindow:[self getCurrentSize]];
}
- (NSString *)getTitle {
return [AccessibilityWrapper getTitle:[self window]];
}
- (pid_t)processIdentifier {
return [AccessibilityWrapper processIdentifierOfUIElement:[self app]];
}
+ (BOOL)focusApp:(NSRunningApplication *)app {
SlateLogger(@"Focusing app: '%@'", [app localizedName]);
AXUIElementRef appRef = AXUIElementCreateApplication([app processIdentifier]);
if (AXUIElementSetAttributeValue(appRef, (CFStringRef)NSAccessibilityFrontmostAttribute, kCFBooleanTrue) != kAXErrorSuccess) {
SlateLogger(@"ERROR: Could not change focus to app");
if (appRef != NULL) CFRelease(appRef);
return NO;
}
if (appRef != NULL) CFRelease(appRef);
return YES;
}
+ (BOOL)focusMainWindow:(NSRunningApplication *)app {
BOOL couldFocus = YES;
CFTypeRef _window;
pid_t focusPID = [app processIdentifier];
AXUIElementCopyAttributeValue(AXUIElementCreateApplication(focusPID), (CFStringRef)NSAccessibilityFocusedWindowAttribute, (CFTypeRef *)&_window);
if (_window == NULL) return [AccessibilityWrapper focusApp:app];
if (AXUIElementSetAttributeValue((AXUIElementRef)_window, (CFStringRef)NSAccessibilityMainAttribute, kCFBooleanTrue) != kAXErrorSuccess) {
SlateLogger(@"ERROR: Could not change focus to window");
couldFocus = NO;
}
ProcessSerialNumber psn;
GetProcessForPID(focusPID, &psn);
SetFrontProcessWithOptions(&psn, kSetFrontProcessFrontWindowOnly);
if (_window != NULL) CFRelease(_window);
return couldFocus;
}
+ (BOOL)focusWindow:(AXUIElementRef)window {
BOOL couldFocus = YES;
if (AXUIElementSetAttributeValue(window, (CFStringRef)NSAccessibilityMainAttribute, kCFBooleanTrue) != kAXErrorSuccess) {
SlateLogger(@"ERROR: Could not change focus to window");
couldFocus = NO;
}
pid_t focusPID = [AccessibilityWrapper processIdentifierOfUIElement:window];
ProcessSerialNumber psn;
GetProcessForPID(focusPID, &psn);
SetFrontProcessWithOptions(&psn, kSetFrontProcessFrontWindowOnly);
return couldFocus;
}
+ (pid_t)processIdentifierOfUIElement:(AXUIElementRef)element {
[AccessibilityWrapper createSystemWideElement];
pid_t pid = 0;
if (AXUIElementGetPid (element, &pid) == kAXErrorSuccess) {
return pid;
} else {
return 0;
}
}
+ (CFArrayRef)windowsInApp:(AXUIElementRef)app {
[AccessibilityWrapper createSystemWideElement];
CFArrayRef _windows;
if (AXUIElementCopyAttributeValues(app, kAXWindowsAttribute, 0, 100, &_windows) == kAXErrorSuccess) {
return _windows;
}
return nil;
}
+ (CFArrayRef)windowsInRunningApp:(NSRunningApplication *)app {
return [AccessibilityWrapper windowsInApp:AXUIElementCreateApplication([app processIdentifier])];
}
+ (AXUIElementRef)focusedWindowInRunningApp:(NSRunningApplication *)app {
CFTypeRef _window;
AXUIElementCopyAttributeValue(AXUIElementCreateApplication([app processIdentifier]), (CFStringRef)NSAccessibilityFocusedWindowAttribute, (CFTypeRef *)&_window);
return _window;
}
+ (BOOL)isMainWindow:(AXUIElementRef)window {
[AccessibilityWrapper createSystemWideElement];
CFTypeRef _isMain;
if (AXUIElementCopyAttributeValue(window, (CFStringRef)NSAccessibilityMainAttribute, (CFTypeRef *)&_isMain) == kAXErrorSuccess) {
NSNumber *isMain = (__bridge NSNumber *) _isMain;
return [isMain boolValue];
}
return NO;
}
+ (NSString *)getTitle:(AXUIElementRef)window {
[AccessibilityWrapper createSystemWideElement];
CFTypeRef _title;
if (AXUIElementCopyAttributeValue(window, (CFStringRef)NSAccessibilityTitleAttribute, (CFTypeRef *)&_title) == kAXErrorSuccess) {
NSString *title = (__bridge NSString *) _title;
if (_title != NULL) CFRelease(_title);
return title;
}
if (_title != NULL) CFRelease(_title);
return @"";
}
+ (BOOL)isWindowMinimizedOrHidden:(AXUIElementRef)window inApp:(AXUIElementRef)app {
[AccessibilityWrapper createSystemWideElement];
CFTypeRef _isMinimized;
CFTypeRef _isHidden;
BOOL isMinimized = NO;
BOOL isHidden = NO;
if (AXUIElementCopyAttributeValue(app, (CFStringRef)NSAccessibilityHiddenAttribute, (CFTypeRef *)&_isHidden) == kAXErrorSuccess) {
NSNumber *isHiddenNum = (__bridge NSNumber *) _isHidden;
isHidden = [isHiddenNum boolValue];
}
if (AXUIElementCopyAttributeValue(window, (CFStringRef)NSAccessibilityMinimizedAttribute, (CFTypeRef *)&_isMinimized) == kAXErrorSuccess) {
NSNumber *isMinimizedNum = (__bridge NSNumber *) _isMinimized;
isMinimized = [isMinimizedNum boolValue];
}
return isMinimized || isHidden;
}
+ (AXUIElementRef)windowUnderPoint:(NSPoint)point {
[AccessibilityWrapper createSystemWideElement];
AXUIElementRef _element;
if ((AXUIElementCopyElementAtPosition(systemWideElement, point.x, point.y, &_element) == kAXErrorSuccess) && _element) {
CFTypeRef _role;
if (AXUIElementCopyAttributeValue(_element, (CFStringRef)NSAccessibilityRoleAttribute, (CFTypeRef *)&_role) == kAXErrorSuccess) {
if ([(__bridge NSString *)_role isEqualToString:NSAccessibilityWindowRole]) {
if (_role != NULL) CFRelease(_role);
return _element;
}
if (_role != NULL) CFRelease(_role);
}
CFTypeRef _window;
if (AXUIElementCopyAttributeValue(_element, (CFStringRef)NSAccessibilityWindowAttribute, (CFTypeRef *)&_window) == kAXErrorSuccess) {
if (_element != NULL) CFRelease(_element);
return (AXUIElementRef)_window;
}
}
SlateLogger(@"Returning null");
return NULL;
}
+ (AXUIElementRef)applicationForElement:(AXUIElementRef)element {
return AXUIElementCreateApplication([AccessibilityWrapper processIdentifierOfUIElement:element]);
}
+ (void)createSystemWideElement {
if (systemWideElement == NULL) {
systemWideElement = AXUIElementCreateSystemWide();
unselectableApps = [NSDictionary dictionaryWithObjectsAndKeys:@"SystemUIServer", @"SystemUIServer",
@"Slate", @"Slate",
@"Dropbox", @"Dropbox",
@"loginwindow", @"loginwindow", nil];
}
}
+ (BOOL)isWindow:(AXUIElementRef)element {
CFTypeRef _role;
AXUIElementCopyAttributeValue(element, (CFStringRef)NSAccessibilityRoleAttribute, &_role);
BOOL isWindow = [NSAccessibilityWindowRole isEqualToString:(__bridge NSString *)_role];
if (_role != NULL) CFRelease(_role);
return isWindow;
}
+ (NSString *)getRole:(AXUIElementRef)element {
if (element == NULL || element == nil) return nil;
CFTypeRef _role;
if (AXUIElementCopyAttributeValue(element, (CFStringRef)NSAccessibilityRoleAttribute, &_role) == kAXErrorSuccess) {
NSString *role = (__bridge NSString *)_role;
if (_role != NULL) CFRelease(_role);
return role;
}
return nil;
}
@end
================================================
FILE: Slate/ActivateSnapshotOperation.h
================================================
//
// ActivateSnapshotOperation.h
// Slate
//
// Created by Jigish Patel on 3/1/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "Operation.h"
@interface ActivateSnapshotOperation : Operation {
@private
NSString *name;
BOOL del;
}
@property NSString *name;
@property (assign) BOOL del;
- (id)initWithName:(NSString *)theName options:(NSString *)options;
+ (BOOL)activateSnapshot:(NSString *)name remove:(BOOL)del;
+ (id)activateSnapshotOperation;
+ (id)activateSnapshotOperationFromString:(NSString *)activateSnapshotOperation;
@end
================================================
FILE: Slate/ActivateSnapshotOperation.m
================================================
//
// ActivateSnapshotOperation.m
// Slate
//
// Created by Jigish Patel on 3/1/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "ActivateSnapshotOperation.h"
#import "Constants.h"
#import "Snapshot.h"
#import "SlateConfig.h"
#import "WindowSnapshot.h"
#import "NSString+Levenshtein.h"
#import "StringTokenizer.h"
#import "SlateLogger.h"
#import "RunningApplications.h"
@implementation ActivateSnapshotOperation
@synthesize name, del;
- (id)init {
self = [super init];
if (self) {
del = NO;
[self setName:nil];
}
return self;
}
- (id)initWithName:(NSString *)theName options:(NSString *)_options {
self = [self init];
if (self) {
[self setName:theName];
if (_options) {
NSArray *optionsTokens = [_options componentsSeparatedByString:SEMICOLON];
for (NSInteger i = 0; i < [optionsTokens count]; i++) {
NSString *option = [optionsTokens objectAtIndex:i];
if ([DELETE isEqualToString:option]) {
del = YES;
}
}
}
}
return self;
}
- (BOOL)doOperation {
SlateLogger(@"----------------- Begin Snapshot Operation -----------------");
BOOL success = [self doOperationWithAccessibilityWrapper:nil screenWrapper:nil];
SlateLogger(@"----------------- End Snapshot Operation -----------------");
return success;
}
- (BOOL)doOperationWithAccessibilityWrapper:(AccessibilityWrapper *)iamnil screenWrapper:(ScreenWrapper *)iamalsonil {
[self evalOptionsWithAccessibilityWrapper:iamnil screenWrapper:iamalsonil];
return [ActivateSnapshotOperation activateSnapshot:name remove:del];
}
- (BOOL)testOperation {
return YES;
}
- (NSArray *)requiredOptions {
return [NSArray arrayWithObject:OPT_NAME];
}
- (void)parseOption:(NSString *)_name value:(id)value {
if (value == nil) { return; }
if ([_name isEqualToString:OPT_NAME]) {
if (![value isKindOfClass:[NSString class]]) {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", _name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", _name, value] userInfo:nil]);
return;
}
[self setName:_name];
} else if ([_name isEqualToString:OPT_DELETE]) {
if (![value isKindOfClass:[NSValue class]] && ![value isKindOfClass:[NSString class]] && ![value isKindOfClass:[NSNumber class]]) {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", _name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", _name, value] userInfo:nil]);
return;
}
[self setDel:[value boolValue]];
}
}
+ (BOOL)activateSnapshot:(NSString *)name remove:(BOOL)del {
Snapshot *snapshot = [[SlateConfig getInstance] popSnapshot:name remove:del];
if (snapshot == nil) return YES;
for (NSRunningApplication *app in [RunningApplications getInstance]) {
NSString *appName = [app localizedName];
pid_t appPID = [app processIdentifier];
SlateLogger(@"I see application '%@' with pid '%d'", appName, appPID);
AXUIElementRef appRef = AXUIElementCreateApplication(appPID);
CFArrayRef windowsArrRef = [AccessibilityWrapper windowsInApp:appRef];
if (!windowsArrRef || CFArrayGetCount(windowsArrRef) == 0) continue;
CFMutableArrayRef windowsArr = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, windowsArrRef);
NSArray *windowSnapshots = [[snapshot apps] objectForKey:appName];
// Check windows
for (NSInteger i = 0; i < CFArrayGetCount(windowsArr); i++) {
SlateLogger(@" Checking Window: %@", [AccessibilityWrapper getTitle:CFArrayGetValueAtIndex(windowsArr, i)]);
NSString *title = [AccessibilityWrapper getTitle:CFArrayGetValueAtIndex(windowsArr, i)];
if ([title isEqualToString:@""]) continue;
AccessibilityWrapper *aw = [[AccessibilityWrapper alloc] initWithApp:appRef window:CFArrayGetValueAtIndex(windowsArr, i)];
// Find best snapshot
WindowSnapshot *bestSnapshot = nil;
if ([[[SlateConfig getInstance] getConfig:SNAPSHOT_TITLE_MATCH app:appName] isEqualToString:SEQUENTIAL]) {
float bestDistance = 0.0;
for (WindowSnapshot *ws in windowSnapshots) {
float sDistance = [title sequentialDistance:[ws title]];
if (sDistance > bestDistance || bestSnapshot == nil) {
bestDistance = sDistance;
bestSnapshot = ws;
}
}
} else {
float bestDistance = 1000.0;
for (WindowSnapshot *ws in windowSnapshots) {
float lDistance = [title levenshteinDistance:[ws title]];
if (lDistance < bestDistance || bestSnapshot == nil) {
bestDistance = lDistance;
bestSnapshot = ws;
}
}
}
if (bestSnapshot == nil) continue;
[aw moveWindow:[bestSnapshot topLeft]];
[aw resizeWindow:[bestSnapshot size]];
}
}
return YES;
}
+ (id)activateSnapshotOperation {
return [[ActivateSnapshotOperation alloc] init];
}
+ (id)activateSnapshotOperationFromString:(NSString *)activateSnapshotOperation {
// activate-snapshot name options
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:activateSnapshotOperation into:tokens maxTokens:3];
if ([tokens count] < 2) {
SlateLogger(@"ERROR: Invalid Parameters '%@'", activateSnapshotOperation);
@throw([NSException exceptionWithName:@"Invalid Parameters" reason:[NSString stringWithFormat:@"Invalid Parameters in '%@'. Activate Snapshot operations require the following format: 'delete-snapshot name options'", activateSnapshotOperation] userInfo:nil]);
}
Operation *op = [[ActivateSnapshotOperation alloc] initWithName:[tokens objectAtIndex:1] options:([tokens count] > 2 ? [tokens objectAtIndex:2] : nil)];
return op;
}
@end
================================================
FILE: Slate/ApplicationOptions.h
================================================
//
// ApplicationOptions.h
// Slate
//
// Created by Jigish Patel on 6/14/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface ApplicationOptions : NSObject {
@private
BOOL ignoreFail;
BOOL repeat;
BOOL repeatLast;
BOOL mainFirst;
BOOL mainLast;
BOOL sortTitle;
NSArray *titleOrder;
NSArray *titleOrderRegex;
}
@property (assign) BOOL ignoreFail;
@property (assign) BOOL repeat;
@property (assign) BOOL repeatLast;
@property (assign) BOOL mainFirst;
@property (assign) BOOL mainLast;
@property (assign) BOOL sortTitle;
@property NSArray *titleOrder;
@property NSArray *titleOrderRegex;
@end
================================================
FILE: Slate/ApplicationOptions.m
================================================
//
// ApplicationOptions.m
// Slate
//
// Created by Jigish Patel on 6/14/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "ApplicationOptions.h"
@implementation ApplicationOptions
@synthesize ignoreFail;
@synthesize repeat;
@synthesize repeatLast;
@synthesize mainFirst;
@synthesize mainLast;
@synthesize sortTitle;
@synthesize titleOrder;
@synthesize titleOrderRegex;
- (id)init {
self = [super init];
if (self) {
[self setIgnoreFail:NO];
[self setRepeat:NO];
[self setRepeatLast:NO];
[self setMainFirst:NO];
[self setMainLast:NO];
[self setSortTitle:NO];
[self setTitleOrder:nil];
[self setTitleOrderRegex:nil];
}
return self;
}
@end
================================================
FILE: Slate/Binding.h
================================================
//
// Binding.h
// Slate
//
// Created by Jigish Patel on 5/18/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
#import
@class Operation;
@interface Binding : NSObject {
@private
Operation *op;
UInt32 keyCode;
UInt32 modifiers;
NSNumber *modalKey;
EventHotKeyRef hotKeyRef;
BOOL repeat;
BOOL toggle;
}
@property Operation *op;
@property (assign) UInt32 keyCode;
@property (assign) UInt32 modifiers;
@property NSNumber *modalKey;
@property (assign) EventHotKeyRef hotKeyRef;
@property (assign) BOOL repeat;
@property (assign) BOOL toggle;
+ (NSDictionary *)asciiToCodeDict;
- (id)initWithString:(NSString *)binding;
- (id)initWithKeystroke:(NSString*)keystroke operation:(Operation*)op_ repeat:(BOOL)repeat_;
- (BOOL)doOperation;
- (NSString *)modalHashKey;
+ (NSArray *)modalHashKeyToKeyAndModifiers:(NSString *)modalHashKey;
+ (NSArray *)getKeystrokeFromString:(NSString *)keystroke;
@end
================================================
FILE: Slate/Binding.m
================================================
//
// Binding.m
// Slate
//
// Created by Jigish Patel on 5/18/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "Binding.h"
#import "Constants.h"
#import "SlateConfig.h"
#import "StringTokenizer.h"
#import "SlateLogger.h"
#import "Operation.h"
#import "SwitchOperation.h"
#import "SlateAppDelegate.h"
#import "SnapshotOperation.h"
@implementation Binding
@synthesize op;
@synthesize keyCode;
@synthesize modifiers;
@synthesize modalKey;
@synthesize hotKeyRef;
@synthesize repeat;
@synthesize toggle;
static NSDictionary *dictionary = nil;
- (id)init {
self = [super init];
if (self) {
[self setRepeat:NO];
[self setToggle:NO];
[self setModalKey:nil];
}
return self;
}
// Yes, this method is huge. Deal with it.
- (id)initWithString:(NSString *)binding {
self = [self init];
if (self) {
// bind
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:binding into:tokens maxTokens:3];
if ([tokens count] <=2) {
@throw([NSException exceptionWithName:@"Unrecognized Bind" reason:binding userInfo:nil]);
}
[self setKeystrokeFromString:[tokens objectAtIndex:1]];
[self setOperationAndRepeatFromString:[tokens objectAtIndex:2]];
}
return self;
}
- (id)initWithKeystroke:(NSString *)keystroke operation:(Operation *)op_ repeat:(BOOL)repeat_ {
self = [self init];
if (self) {
[self setKeystrokeFromString:keystroke];
[self setOp:op_];
if ([self op] == nil) {
SlateLogger(@"ERROR: Unable to create binding");
@throw([NSException exceptionWithName:@"Unable To Create Binding" reason:[NSString stringWithFormat:@"Unable to create %@", keystroke] userInfo:nil]);
}
@try {
AccessibilityWrapper *awTest = [[AccessibilityWrapper alloc] init];
ScreenWrapper *swTest = [[ScreenWrapper alloc] init];
[[self op] testOperationWithAccessibilityWrapper:awTest screenWrapper:swTest];
} @catch (NSException *ex) {
SlateLogger(@"ERROR: Unable to test binding");
@throw([NSException exceptionWithName:@"Unable To Parse Binding" reason:[NSString stringWithFormat:@"Unable to parse '%@' in '%@'", [ex reason], [[self op] opName]] userInfo:nil]);
}
if ([[self op] isKindOfClass:[SwitchOperation class]]) {
[(SwitchOperation *)op setModifiers:modifiers];
}
[self setRepeat:repeat_];
}
return self;
}
+ (UInt32)modifierFromString:(NSString *)mod {
if ([mod isEqualToString:CONTROL]) {
return controlKey;
} else if ([mod isEqualToString:OPTION]) {
return optionKey;
} else if ([mod isEqualToString:COMMAND]) {
return cmdKey;
} else if ([mod isEqualToString:SHIFT]) {
return shiftKey;
} else if ([mod isEqualToString:FUNCTION]) {
return FUNCTION_KEY;
} else {
SlateLogger(@"ERROR: Unrecognized modifier '%@'", mod);
@throw([NSException exceptionWithName:@"Unrecognized Modifier" reason:[NSString stringWithFormat:@"Unrecognized modifier '%@'", mod] userInfo:nil]);
}
}
+ (NSArray *)getKeystrokeFromString:(NSString *)keystroke {
NSNumber *theKeyCode = [NSNumber numberWithUnsignedInt:0];
UInt32 theModifiers = 0;
NSNumber *theModalKey = nil;
NSArray *keyAndModifiers = [keystroke componentsSeparatedByString:COLON];
if ([keyAndModifiers count] >= 1) {
NSString *theKey = [keyAndModifiers objectAtIndex:0];
theKeyCode = [[Binding asciiToCodeDict] objectForKey:theKey];
if (theKeyCode == nil) {
SlateLogger(@"ERROR: Unrecognized key \"%@\" in \"%@\"", theKey, keystroke);
@throw([NSException exceptionWithName:@"Unrecognized Key" reason:[NSString stringWithFormat:@"Unrecognized key \"%@\" in \"%@\"", theKey, keystroke] userInfo:nil]);
}
if ([keyAndModifiers count] >= 2) {
theModalKey = [[Binding asciiToCodeDict] objectForKey:[keyAndModifiers objectAtIndex:1]];
if (theModalKey == nil) {
// normal case
NSArray *modifiersArray = [[keyAndModifiers objectAtIndex:1] componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@",;"]];
NSEnumerator *modEnum = [modifiersArray objectEnumerator];
NSString *mod = [modEnum nextObject];
while (mod) {
NSNumber *_theModalKey = [[Binding asciiToCodeDict] objectForKey:mod];
if (_theModalKey != nil) {
theModalKey = _theModalKey;
} else {
theModifiers += [Binding modifierFromString:mod];
}
mod = [modEnum nextObject];
}
}
}
}
return [NSArray arrayWithObjects:theKeyCode, [NSNumber numberWithInteger:theModifiers], theModalKey, nil];
}
- (void)setKeystrokeFromString:(NSString*)keystroke {
NSArray *modalAndKey = [keystroke componentsSeparatedByString:COLON];
if ([modalAndKey count] > 0) {
NSArray *keyarr = [Binding getKeystrokeFromString:keystroke];
keyCode = [[keyarr objectAtIndex:0] unsignedIntValue];
modifiers = [[keyarr objectAtIndex:1] unsignedIntValue];
if ([keyarr count] >= 3 ) {
[self setModalKey:[keyarr objectAtIndex:2]];
}
}
if ([modalAndKey count] >= 3){ // modal toggle
if ([[modalAndKey objectAtIndex:2] isEqualToString:TOGGLE]) {
[self setToggle:YES];
}
}
}
- (void)setOperationAndRepeatFromString:(NSString*)token {
NSMutableString *opStr = [[NSMutableString alloc] initWithCapacity:10];
[StringTokenizer firstToken:token into:opStr];
BOOL theRepeat = [Operation isRepeatOnHoldOp:opStr];
Operation *theOp = [Operation operationFromString:token];
if (theOp == nil) {
SlateLogger(@"ERROR: Unable to create binding");
@throw([NSException exceptionWithName:@"Unable To Create Binding" reason:[NSString stringWithFormat:@"Unable to create '%@'", token] userInfo:nil]);
}
@try {
[theOp testOperation];
} @catch (NSException *ex) {
SlateLogger(@"ERROR: Unable to test binding '%@'", token);
@throw([NSException exceptionWithName:@"Unable To Parse Binding" reason:[NSString stringWithFormat:@"Unable to parse '%@' in '%@'", [ex reason], token] userInfo:nil]);
}
if ([theOp isKindOfClass:[SwitchOperation class]]) {
[(SwitchOperation *)op setModifiers:modifiers];
}
op = theOp;
repeat = theRepeat;
}
- (BOOL)doOperation {
if ([(SlateAppDelegate *)[NSApp delegate] hasUndoOperation] && [op shouldTakeUndoSnapshot]) {
[[(SlateAppDelegate *)[NSApp delegate] undoSnapshotOperation] doOperation];
}
@try {
return [op doOperation];
} @catch (NSException *ex) {
SlateLogger(@" ERROR %@",[ex name]);
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Quit", @"Skip", nil]];
[alert setMessageText:[ex name]];
[alert setInformativeText:[ex reason]];
if ([alert runModal] == NSAlertFirstButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
}
return NO;
}
- (NSString *)modalHashKey {
if ([self modalKey] == nil) {
return nil;
}
return [NSString stringWithFormat:@"%@%@%u", [self modalKey], PLUS, [self modifiers]];
}
+ (NSArray *)modalHashKeyToKeyAndModifiers:(NSString *)modalHashKey {
NSArray *modalKeyArr = [modalHashKey componentsSeparatedByString:PLUS];
NSNumberFormatter *nf = [[NSNumberFormatter alloc] init];
NSNumber *theKey = [nf numberFromString:[modalKeyArr objectAtIndex:0]];
NSNumber *theModifiers = [nf numberFromString:[modalKeyArr objectAtIndex:1]];
return [NSArray arrayWithObjects:theKey, theModifiers, nil];
}
- (void)dealloc {
[self setHotKeyRef:nil];
}
// This returns a dictionary containing mappings from ASCII to keyCode
+ (NSDictionary *)asciiToCodeDict {
if (dictionary == nil) {
NSString *configLayout = [[SlateConfig getInstance] getConfig:KEYBOARD_LAYOUT];
NSString *filename;
if ([configLayout isEqualToString:KEYBOARD_LAYOUT_DVORAK]) {
filename = @"ASCIIToCode_Dvorak";
} else if ([configLayout isEqualToString:KEYBOARD_LAYOUT_COLEMAK]) {
filename = @"ASCIIToCode_Colemak";
} else if ([configLayout isEqualToString:KEYBOARD_LAYOUT_AZERTY]) {
filename = @"ASCIIToCode_Azerty";
} else {
filename = @"ASCIIToCode";
}
dictionary = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:filename ofType:@"plist"]];
}
return dictionary;
}
@end
================================================
FILE: Slate/ChainOperation.h
================================================
//
// ChainOperation.h
// Slate
//
// Created by Jigish Patel on 5/28/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
#import "Operation.h"
@interface ChainOperation : Operation {
@private
NSArray *operations;
NSMutableDictionary *currentOp;
}
@property NSArray *operations;
@property NSMutableDictionary *currentOp;
- (BOOL)testOperation:(NSInteger)op;
- (void)afterComplete:(AccessibilityWrapper *)aw opRun:(NSInteger)op ;
- (id)initWithArray:(NSArray *)opArray;
- (NSInteger)getNextOperation:(AccessibilityWrapper *)aw;
- (void)setNextOperation:(AccessibilityWrapper *)aw nextOp:(NSNumber *)op;
+ (id)chainOperation;
+ (id)chainOperationFromString:(NSString *)chainOperation;
@end
================================================
FILE: Slate/ChainOperation.m
================================================
//
// ChainOperation.m
// Slate
//
// Created by Jigish Patel on 5/28/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "ChainOperation.h"
#import "ScreenWrapper.h"
#import "WindowState.h"
#import "StringTokenizer.h"
#import "Constants.h"
#import "SlateLogger.h"
#import "JSController.h"
#import
#import "JSOperation.h"
@implementation ChainOperation
@synthesize operations;
@synthesize currentOp;
- (id)init {
self = [super init];
if (self) {
[self setCurrentOp:[[NSMutableDictionary alloc] initWithCapacity:10]];
[self setOperations:[NSArray array]];
}
return self;
}
- (id)initWithArray:(NSArray *)opArray {
self = [self init];
if (self) {
[self setCurrentOp:[[NSMutableDictionary alloc] initWithCapacity:10]];
[self setOperations:opArray];
}
return self;
}
- (BOOL)doOperation {
SlateLogger(@"----------------- Begin Chain Operation -----------------");
AccessibilityWrapper *aw = [[AccessibilityWrapper alloc] init];
ScreenWrapper *sw = [[ScreenWrapper alloc] init];
BOOL success = NO;
if ([aw inited]) success = [self doOperationWithAccessibilityWrapper:aw screenWrapper:sw];
SlateLogger(@"----------------- End Chain Operation -----------------");
return success;
}
- (BOOL) doOperationWithAccessibilityWrapper:(AccessibilityWrapper *)aw screenWrapper:(ScreenWrapper *)sw {
BOOL success = NO;
[self evalOptionsWithAccessibilityWrapper:aw screenWrapper:sw];
NSInteger opRun = 0;
if ([aw inited]) {
opRun = [self getNextOperation:aw];
success = [[operations objectAtIndex:opRun] doOperationWithAccessibilityWrapper:aw screenWrapper:sw];
if (success)
[self afterComplete:aw opRun:opRun];
}
return success;
}
- (BOOL)testOperation:(NSInteger)op {
BOOL success = [[operations objectAtIndex:op] testOperation];
return success;
}
- (BOOL)testOperation {
BOOL success = YES;
for (NSInteger op = 0; op < [operations count]; op++) {
success = [self testOperation:op] && success;
}
return success;
}
- (void)afterComplete:(AccessibilityWrapper *)aw opRun:(NSInteger)op {
NSInteger nextOpInt = 0;
if (op+1 < [operations count])
nextOpInt = op+1;
NSNumber *nextOp = [NSNumber numberWithInteger:nextOpInt];
if (aw != nil) {
[self setNextOperation:aw nextOp:nextOp];
}
}
- (NSInteger)getNextOperation:(AccessibilityWrapper *)aw {
WindowState *ws = [[WindowState alloc] init:aw];
NSNumber *nextOp = [currentOp objectForKey:ws];
if (nextOp != nil)
return [nextOp integerValue];
return 0;
}
- (void)setNextOperation:(AccessibilityWrapper *)aw nextOp:(NSNumber *)op {
WindowState *ws = [[WindowState alloc] init:aw];
[currentOp setObject:op forKey:ws];
}
- (NSArray *)requiredOptions {
return [NSArray arrayWithObject:OPT_OPERATIONS];
}
- (void)parseOption:(NSString *)_name value:(id)value {
if (value == nil) { return; }
if ([_name isEqualToString:OPT_OPERATIONS]) {
if (![value isKindOfClass:[NSArray class]]) {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", _name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", _name, value] userInfo:nil]);
return;
}
NSMutableArray *ops = [NSMutableArray array];
for (id key in value) {
Operation *op = nil;
if ([key isKindOfClass:[WebScriptObject class]]) {
op = [JSOperation jsOperationWithFunction:key];
} else if ([key isKindOfClass:[Operation class]]) {
op = key;
}
if (op == nil) {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", _name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", _name, value] userInfo:nil]);
continue;
}
[ops addObject:op];
}
[self setOperations:ops];
}
}
+ (id)chainOperation {
return [[ChainOperation alloc] init];
}
+ (id)chainOperationFromString:(NSString *)chainOperation {
// chain op[ | op]+
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:chainOperation into:tokens maxTokens:2];
if ([tokens count] < 2) {
SlateLogger(@"ERROR: Invalid Parameters '%@'", chainOperation);
@throw([NSException exceptionWithName:@"Invalid Parameters" reason:[NSString stringWithFormat:@"Invalid Parameters in '%@'. Chain operations require the following format: 'chain op[|op]+'", chainOperation] userInfo:nil]);
}
NSString *opsString = [tokens objectAtIndex:1];
NSArray *ops = [opsString componentsSeparatedByString:PIPE_PADDED];
NSMutableArray *opArray = [[NSMutableArray alloc] initWithCapacity:10];
for (NSInteger i = 0; i < [ops count]; i++) {
Operation *op = [Operation operationFromString:[ops objectAtIndex:i]];
if (op != nil) {
[opArray addObject:op];
} else {
SlateLogger(@"ERROR: Invalid Operation in Chain: '%@'", [ops objectAtIndex:i]);
@throw([NSException exceptionWithName:@"Invalid Operation in Chain" reason:[NSString stringWithFormat:@"Invalid operation '%@' in chain.", [ops objectAtIndex:i]] userInfo:nil]);
}
}
Operation *op = [[ChainOperation alloc] initWithArray:opArray];
return op;
}
@end
================================================
FILE: Slate/ConfigurationHelperView.h
================================================
//
// ConfigurationHelperView.h
// Slate
//
// Created by Jigish Patel on 3/5/12.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface ConfigurationHelperView : NSView {
@private
NSTextField *directiveLabel;
NSPopUpButton *directive;
NSButton *save;
NSMutableDictionary *configs;
}
@property NSTextField *directiveLabel;
@property NSPopUpButton *directive;
@property NSButton *save;
@property NSMutableDictionary *configs;
- (void)directiveChanged;
- (void)updateDirectiveSpecificUI:(NSString *)str;
- (void)addDirectiveSpecificUIs;
- (void)addConfigUI;
- (void)hideAll:(NSArray *)theThings;
- (void)showAll:(NSArray *)theThings;
- (void)hide:(NSTextField *)theThings;
- (void)show:(NSTextField *)theThings;
- (void)saveToConfig;
@end
================================================
FILE: Slate/ConfigurationHelperView.m
================================================
//
// ConfigurationHelperView.m
// Slate
//
// Created by Jigish Patel on 3/5/12.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "ConfigurationHelperView.h"
#import "SlateLogger.h"
#import "Constants.h"
#import "SlateConfig.h"
@implementation ConfigurationHelperView
@synthesize directiveLabel, directive, save, configs;
- (NSTextField *)createLabel:(NSString *)text frame:(NSRect)frame {
NSTextField *label = [[NSTextField alloc] initWithFrame:frame];
[label setStringValue:text];
[label setFont:[NSFont fontWithName:@"Menlo" size:11]];
[label setSelectable:NO];
[label setEnabled:NO];
[label setDrawsBackground:NO];
[label setBordered:NO];
[label setBezeled:NO];
return label;
}
- (NSTextField *)createTextField:(NSString *)text frame:(NSRect)frame {
NSTextField *field = [[NSTextField alloc] initWithFrame:frame];
[field setStringValue:text];
[field setFont:[NSFont fontWithName:@"Menlo" size:11]];
return field;
}
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
configs = [NSMutableDictionary dictionary];
directiveLabel = [self createLabel:@"Choose a Directive:" frame:NSMakeRect(5, frame.size.height - 25, 150, 20)];
[self addSubview:directiveLabel];
directive = [[NSPopUpButton alloc] initWithFrame:NSMakeRect(150, frame.size.height - 25, 100, 22)];
[directive addItemsWithTitles:[NSArray arrayWithObjects:EMPTY, CONFIG, LAYOUT, DEFAULT, BIND, nil]];
[directive setTarget:self];
[directive setAction:@selector(directiveChanged)];
save = [[NSButton alloc] initWithFrame:NSMakeRect(frame.size.width/2 - 50, 30, 100, 25)];
[save setTitle:@"Save"];
[save setTarget:self];
[save setAction:@selector(saveToConfig)];
[save setBezelStyle:NSRoundedBezelStyle];
[self addSubview:directive];
[self addSubview:save];
[self addDirectiveSpecificUIs];
[self updateDirectiveSpecificUI:[[directive selectedItem] title]];
}
return self;
}
- (void)directiveChanged {
NSString *title = [[directive selectedItem] title];
SlateLogger(@"Directive Changed: %@", title);
[self updateDirectiveSpecificUI:title];
}
- (void)addDirectiveSpecificUIs {
SlateLogger(@"addDirectiveSpecificUIs");
[self addConfigUI];
}
- (void)hideDirectiveSpecificUIs {
[self hideConfigUI];
}
- (void)addConfigUI {
SlateLogger(@"addConfigUI");
NSInteger i = 0;
for (NSString *configName in [[[SlateConfig getInstance] configs] allKeys]) {
if ([configs objectForKey:configName]) continue;
SlateLogger(@"ADDING CONFIG %@", configName);
NSTextField *configLabel = [self createLabel:configName frame:NSMakeRect(5, [self frame].size.height - 25 * (i+2), 215, 20)];
NSTextField *configField = [self createTextField:[[SlateConfig getInstance] getConfig:configName] frame:NSMakeRect(220, [self frame].size.height - 25 * (i+2) + 1, 200, 20)];
NSTextField *configDefault = [self createLabel:[NSString stringWithFormat:@"Default: %@", [[SlateConfig getInstance] getConfigDefault:configName]] frame:NSMakeRect(420, [self frame].size.height - 25 * (i+2), 200, 20)];
NSArray *objects = [NSArray arrayWithObjects:configLabel, configField, configDefault, nil];
[configs setObject:objects forKey:configName];
[self performSelectorOnMainThread:@selector(hideAll:) withObject:objects waitUntilDone:YES];
[self addSubview:configLabel];
[self addSubview:configField];
[self addSubview:configDefault];
i++;
}
}
- (void)updateConfigUI {
for (NSString *configName in [configs allKeys]) {
[[[configs objectForKey:configName] objectAtIndex:1] setStringValue:[[SlateConfig getInstance] getConfig:configName]];
}
}
- (void)hideConfigUI {
for (NSString *configName in [configs allKeys]) {
SlateLogger(@"HIDING CONFIG %@", configName);
[self performSelectorOnMainThread:@selector(hideAll:) withObject:[configs objectForKey:configName] waitUntilDone:YES];
}
}
- (void)showConfigUI {
[self updateConfigUI];
for (NSString *configName in [configs allKeys]) {
SlateLogger(@"HIDING CONFIG %@", configName);
[self performSelectorOnMainThread:@selector(showAll:) withObject:[configs objectForKey:configName] waitUntilDone:YES];
}
}
- (void)hideAll:(NSArray *)theThings {
for (NSTextField *field in theThings) {
[self hide:field];
}
}
- (void)showAll:(NSArray *)theThings {
for (NSTextField *field in theThings) {
[self show:field];
[field needsDisplay];
}
}
- (void)hide:(NSTextField *)field {
[field setHidden:YES];
}
- (void)show:(NSTextField *)field {
[field setHidden:NO];
}
- (void)updateDirectiveSpecificUI:(NSString *)str {
// Remove everything first
[self hideDirectiveSpecificUIs];
if ([str isEqualToString:CONFIG]) {
SlateLogger(@"IN CONFIG");
[self showConfigUI];
} else if ([str isEqualToString:ALIAS]) {
} else if ([str isEqualToString:LAYOUT]) {
} else if ([str isEqualToString:DEFAULT]) {
} else if ([str isEqualToString:BIND]) {
} else if ([str isEqualToString:SOURCE]) {
} else if ([str isEqualToString:EMPTY]) {
// do nothing
}
[self setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];// Drawing code here.
}
- (void)saveConfig {
// Update SlateConfig
// Save to file
}
- (void)saveToConfig {
if ([[[directive selectedItem] title] isEqualToString:CONFIG]) {
[self saveConfig];
}
}
@end
================================================
FILE: Slate/Constants.h
================================================
//
// Constants.h
// Slate
//
// Created by Jigish Patel on 5/26/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
extern NSString *const SNAPSHOTS_FILE;
#define MODAL_BEGIN_ID 20000;
#define CURRENT_MODAL_BEGIN_ID 30000;
extern NSInteger const MODAL_ESCAPE_ID;
// Directive Keys
extern NSString *const BIND;
extern NSString *const CONFIG;
extern NSString *const DEFAULT;
extern NSString *const LAYOUT;
extern NSString *const ALIAS;
extern NSString *const SOURCE;
// Source Option Keys
extern NSString *const IF_EXISTS;
// Config Keys
extern NSString *const DEFAULT_TO_CURRENT_SCREEN;
extern NSString *const DEFAULT_TO_CURRENT_SCREEN_DEFAULT;
extern NSString *const NUDGE_PERCENT_OF;
extern NSString *const NUDGE_PERCENT_OF_DEFAULT;
extern NSString *const RESIZE_PERCENT_OF;
extern NSString *const RESIZE_PERCENT_OF_DEFAULT;
extern NSString *const REPEAT_ON_HOLD_OPS;
extern NSString *const REPEAT_ON_HOLD_OPS_DEFAULT;
extern NSString *const SECONDS_BEFORE_REPEAT;
extern NSString *const SECONDS_BEFORE_REPEAT_DEFAULT;
extern NSString *const SECONDS_BETWEEN_REPEAT;
extern NSString *const SECONDS_BETWEEN_REPEAT_DEFAULT;
extern NSString *const CHECK_DEFAULTS_ON_LOAD;
extern NSString *const CHECK_DEFAULTS_ON_LOAD_DEFAULT;
extern NSString *const FOCUS_CHECK_WIDTH;
extern NSString *const FOCUS_CHECK_WIDTH_DEFAULT;
extern NSString *const FOCUS_CHECK_WIDTH_MAX;
extern NSString *const FOCUS_CHECK_WIDTH_MAX_DEFAULT;
extern NSString *const FOCUS_PREFER_SAME_APP;
extern NSString *const FOCUS_PREFER_SAME_APP_DEFAULT;
extern NSString *const ORDER_SCREENS_LEFT_TO_RIGHT;
extern NSString *const ORDER_SCREENS_LEFT_TO_RIGHT_DEFAULT;
extern NSString *const WINDOW_HINTS_FONT_NAME;
extern NSString *const WINDOW_HINTS_FONT_NAME_DEFAULT;
extern NSString *const WINDOW_HINTS_FONT_SIZE;
extern NSString *const WINDOW_HINTS_FONT_SIZE_DEFAULT;
extern NSString *const WINDOW_HINTS_FONT_COLOR;
extern NSString *const WINDOW_HINTS_FONT_COLOR_DEFAULT;
extern NSString *const WINDOW_HINTS_WIDTH;
extern NSString *const WINDOW_HINTS_WIDTH_DEFAULT;
extern NSString *const WINDOW_HINTS_HEIGHT;
extern NSString *const WINDOW_HINTS_HEIGHT_DEFAULT;
extern NSString *const WINDOW_HINTS_BACKGROUND_COLOR;
extern NSString *const WINDOW_HINTS_BACKGROUND_COLOR_DEFAULT;
extern NSString *const WINDOW_HINTS_DURATION;
extern NSString *const WINDOW_HINTS_DURATION_DEFAULT;
extern NSString *const WINDOW_HINTS_ROUNDED_CORNER_SIZE;
extern NSString *const WINDOW_HINTS_ROUNDED_CORNER_SIZE_DEFAULT;
extern NSString *const WINDOW_HINTS_IGNORE_HIDDEN_WINDOWS;
extern NSString *const WINDOW_HINTS_IGNORE_HIDDEN_WINDOWS_DEFAULT;
extern NSString *const WINDOW_HINTS_TOP_LEFT_X;
extern NSString *const WINDOW_HINTS_TOP_LEFT_X_DEFAULT;
extern NSString *const WINDOW_HINTS_TOP_LEFT_Y;
extern NSString *const WINDOW_HINTS_TOP_LEFT_Y_DEFAULT;
extern NSString *const WINDOW_HINTS_ORDER;
extern NSString *const WINDOW_HINTS_ORDER_DEFAULT;
extern NSString *const WINDOW_HINTS_SHOW_ICONS;
extern NSString *const WINDOW_HINTS_SHOW_ICONS_DEFAULT;
extern NSString *const WINDOW_HINTS_ICON_ALPHA;
extern NSString *const WINDOW_HINTS_ICON_ALPHA_DEFAULT;
extern NSString *const WINDOW_HINTS_SPREAD;
extern NSString *const WINDOW_HINTS_SPREAD_DEFAULT;
extern NSString *const WINDOW_HINTS_SPREAD_SEARCH_WIDTH;
extern NSString *const WINDOW_HINTS_SPREAD_SEARCH_WIDTH_DEFAULT;
extern NSString *const WINDOW_HINTS_SPREAD_SEARCH_HEIGHT;
extern NSString *const WINDOW_HINTS_SPREAD_SEARCH_HEIGHT_DEFAULT;
extern NSString *const WINDOW_HINTS_SPREAD_PADDING;
extern NSString *const WINDOW_HINTS_SPREAD_PADDING_DEFAULT;
extern NSString *const SWITCH_ICON_SIZE;
extern NSString *const SWITCH_ICON_SIZE_DEFAULT;
extern NSString *const SWITCH_ICON_PADDING;
extern NSString *const SWITCH_ICON_PADDING_DEFAULT;
extern NSString *const SWITCH_BACKGROUND_COLOR;
extern NSString *const SWITCH_BACKGROUND_COLOR_DEFAULT;
extern NSString *const SWITCH_SELECTED_BACKGROUND_COLOR;
extern NSString *const SWITCH_SELECTED_BACKGROUND_COLOR_DEFAULT;
extern NSString *const SWITCH_SELECTED_BORDER_COLOR;
extern NSString *const SWITCH_SELECTED_BORDER_COLOR_DEFAULT;
extern NSString *const SWITCH_SELECTED_BORDER_SIZE;
extern NSString *const SWITCH_SELECTED_BORDER_SIZE_DEFAULT;
extern NSString *const SWITCH_ROUNDED_CORNER_SIZE;
extern NSString *const SWITCH_ROUNDED_CORNER_SIZE_DEFAULT;
extern NSString *const SWITCH_ORIENTATION;
extern NSString *const SWITCH_ORIENTATION_DEFAULT;
extern NSString *const SWITCH_SECONDS_BEFORE_REPEAT;
extern NSString *const SWITCH_SECONDS_BEFORE_REPEAT_DEFAULT;
extern NSString *const SWITCH_SECONDS_BETWEEN_REPEAT;
extern NSString *const SWITCH_SECONDS_BETWEEN_REPEAT_DEFAULT;
extern NSString *const SWITCH_STOP_REPEAT_AT_EDGE;
extern NSString *const SWITCH_STOP_REPEAT_AT_EDGE_DEFAULT;
extern NSString *const SWITCH_ONLY_FOCUS_MAIN_WINDOW;
extern NSString *const SWITCH_ONLY_FOCUS_MAIN_WINDOW_DEFAULT;
extern NSString *const SWITCH_FONT_SIZE;
extern NSString *const SWITCH_FONT_SIZE_DEFAULT;
extern NSString *const SWITCH_FONT_COLOR;
extern NSString *const SWITCH_FONT_COLOR_DEFAULT;
extern NSString *const SWITCH_FONT_NAME;
extern NSString *const SWITCH_FONT_NAME_DEFAULT;
extern NSString *const SWITCH_SHOW_TITLES;
extern NSString *const SWITCH_SHOW_TITLES_DEFAULT;
extern NSString *const SWITCH_TYPE;
extern NSString *const SWITCH_TYPE_DEFAULT;
extern NSString *const SWITCH_SELECTED_PADDING;
extern NSString *const SWITCH_SELECTED_PADDING_DEFAULT;
extern NSString *const KEYBOARD_LAYOUT;
extern NSString *const KEYBOARD_LAYOUT_DEFAULT;
extern NSString *const SNAPSHOT_TITLE_MATCH;
extern NSString *const SNAPSHOT_TITLE_MATCH_DEFAULT;
extern NSString *const LEVENSHTEIN;
extern NSString *const SEQUENTIAL;
extern NSString *const SNAPSHOT_MAX_STACK_SIZE;
extern NSString *const SNAPSHOT_MAX_STACK_SIZE_DEFAULT;
extern NSString *const UNDO_MAX_STACK_SIZE;
extern NSString *const UNDO_MAX_STACK_SIZE_DEFAULT;
extern NSString *const UNDO_OPS;
extern NSString *const UNDO_OPS_DEFAULT;
extern NSString *const GRID_BACKGROUND_COLOR;
extern NSString *const GRID_BACKGROUND_COLOR_DEFAULT;
extern NSString *const GRID_ROUNDED_CORNER_SIZE;
extern NSString *const GRID_ROUNDED_CORNER_SIZE_DEFAULT;
extern NSString *const GRID_CELL_BACKGROUND_COLOR;
extern NSString *const GRID_CELL_BACKGROUND_COLOR_DEFAULT;
extern NSString *const GRID_CELL_SELECTED_COLOR;
extern NSString *const GRID_CELL_SELECTED_COLOR_DEFAULT;
extern NSString *const GRID_CELL_ROUNDED_CORNER_SIZE;
extern NSString *const GRID_CELL_ROUNDED_CORNER_SIZE_DEFAULT;
extern NSString *const LAYOUT_FOCUS_ON_ACTIVATE;
extern NSString *const LAYOUT_FOCUS_ON_ACTIVATE_DEFAULT;
extern NSString *const MODAL_ESCAPE_KEY;
extern NSString *const MODAL_ESCAPE_KEY_DEFAULT;
extern NSString *const JS_RECEIVE_MOVE_EVENT;
extern NSString *const JS_RECEIVE_MOVE_EVENT_DEFAULT;
extern NSString *const JS_RECEIVE_RESIZE_EVENT;
extern NSString *const JS_RECEIVE_RESIZE_EVENT_DEFAULT;
// Application Option Keys
extern NSString *const IGNORE_FAIL;
extern NSString *const REPEAT;
extern NSString *const REPEAT_LAST;
extern NSString *const SORT_TITLE;
extern NSString *const MAIN_FIRST;
extern NSString *const MAIN_LAST;
extern NSString *const TITLE_ORDER;
extern NSString *const TITLE_ORDER_REGEX;
// Modifier Keys
extern NSString *const CONTROL;
extern NSString *const COMMAND;
extern NSString *const OPTION;
extern NSString *const SHIFT;
extern NSString *const FUNCTION;
extern UInt32 const FUNCTION_KEY;
// Expression Keys
extern NSString *const SCREEN_ORIGIN_X;
extern NSString *const SCREEN_ORIGIN_Y;
extern NSString *const SCREEN_SIZE;
extern NSString *const SCREEN_SIZE_X;
extern NSString *const SCREEN_SIZE_Y;
extern NSString *const WINDOW_TOP_LEFT_X;
extern NSString *const WINDOW_TOP_LEFT_Y;
extern NSString *const WINDOW_SIZE_X;
extern NSString *const WINDOW_SIZE_Y;
extern NSString *const NEW_WINDOW_SIZE;
extern NSString *const NEW_WINDOW_SIZE_X;
extern NSString *const NEW_WINDOW_SIZE_Y;
// Operations
extern NSString *const MOVE;
extern NSString *const RESIZE;
extern NSString *const PUSH;
extern NSString *const NUDGE;
extern NSString *const THROW;
extern NSString *const CORNER;
extern NSString *const CHAIN;
extern NSString *const FOCUS;
extern NSString *const SNAPSHOT;
extern NSString *const ACTIVATE_SNAPSHOT;
extern NSString *const DELETE_SNAPSHOT;
extern NSString *const HINT;
extern NSString *const SWITCH;
extern NSString *const GRID;
extern NSString *const SEQUENCE;
extern NSString *const HIDE;
extern NSString *const SHOW;
extern NSString *const TOGGLE;
extern NSString *const RELAUNCH;
extern NSString *const SHELL;
extern NSString *const UNDO;
// Parameters and Options
extern NSString *const CENTER;
extern NSString *const BAR;
extern NSString *const BAR_RESIZE_WITH_VALUE;
extern NSString *const NONE;
extern NSString *const NORESIZE;
extern NSString *const RESIZE_WITH_VALUE;
extern NSString *const SAVE_TO_DISK;
extern NSString *const STACK;
extern NSString *const NAME;
extern NSString *const SNAPSHOTS;
extern NSString *const APPS;
extern NSString *const APP_NAME;
extern NSString *const TITLE;
extern NSString *const SIZE;
extern NSString *const ALL;
extern NSString *const DELETE;
extern NSString *const BACK;
extern NSString *const QUIT;
extern NSString *const FORCE_QUIT;
extern NSString *const WINDOW_HINTS_ORDER_NONE;
extern NSString *const WINDOW_HINTS_ORDER_PERSIST;
extern NSString *const WINDOW_HINTS_ORDER_LEFT_TO_RIGHT;
extern NSString *const WINDOW_HINTS_ORDER_RIGHT_TO_LEFT;
extern NSString *const SWITCH_ORIENTATION_HORIZONTAL;
extern NSString *const SWITCH_ORIENTATION_VERTICAL;
extern NSString *const KEYBOARD_LAYOUT_DVORAK;
extern NSString *const KEYBOARD_LAYOUT_COLEMAK;
extern NSString *const KEYBOARD_LAYOUT_AZERTY;
extern NSString *const PADDING;
extern NSString *const CURRENT;
extern NSString *const ALL_BUT;
extern NSString *const WAIT;
extern NSString *const PATH;
extern NSString *const APP_NAME_BEFORE;
extern NSString *const APP_NAME_AFTER;
// Directions and Anchors
extern NSString *const UP;
extern NSString *const DOWN;
extern NSString *const LEFT;
extern NSString *const RIGHT;
extern NSString *const TOP;
extern NSString *const BOTTOM;
extern NSString *const ABOVE;
extern NSString *const BELOW;
extern NSString *const NEXT;
extern NSString *const PREVIOUS;
extern NSString *const PREV;
extern NSString *const BEHIND;
extern NSString *const TOP_LEFT;
extern NSString *const TOP_RIGHT;
extern NSString *const BOTTOM_LEFT;
extern NSString *const BOTTOM_RIGHT;
extern NSInteger const DIRECTION_UNKNOWN;
extern NSInteger const DIRECTION_UP;
extern NSInteger const DIRECTION_DOWN;
extern NSInteger const DIRECTION_LEFT;
extern NSInteger const DIRECTION_RIGHT;
extern NSInteger const DIRECTION_TOP;
extern NSInteger const DIRECTION_BOTTOM;
extern NSInteger const DIRECTION_ABOVE;
extern NSInteger const DIRECTION_BELOW;
extern NSInteger const DIRECTION_BEHIND;
extern NSInteger const ANCHOR_TOP_LEFT;
extern NSInteger const ANCHOR_TOP_RIGHT;
extern NSInteger const ANCHOR_BOTTOM_LEFT;
extern NSInteger const ANCHOR_BOTTOM_RIGHT;
// Seperators and such
extern unichar const COMMENT_CHARACTER;
extern NSString *const COMMA;
extern NSString *const COLON;
extern NSString *const SEMICOLON;
extern NSString *const MINUS;
extern NSString *const PLUS;
extern NSString *const PERCENT;
extern NSString *const EMPTY;
extern NSString *const PIPE_PADDED;
extern NSString *const GREATER_THAN_PADDED;
extern NSString *const QUOTES;
extern NSString *const EQUALS;
extern NSString *const TILDA;
extern NSString *const SLASH;
extern NSString *const X;
extern NSString *const Y;
extern NSString *const WIDTH;
extern NSString *const HEIGHT;
extern NSString *const WHITESPACE;
// Screen constants
extern NSString *const REF_CURRENT_SCREEN;
extern NSInteger const ID_MAIN_SCREEN;
extern NSInteger const ID_CURRENT_SCREEN;
extern NSInteger const ID_IGNORE_SCREEN;
extern NSInteger const TYPE_UNKNOWN;
extern NSInteger const TYPE_COUNT;
extern NSInteger const TYPE_RESOLUTIONS;
extern NSString *const COUNT;
extern NSString *const RESOLUTIONS;
extern NSString *const ORDERED;
// Notifications
extern NSString *const NOTIFICATION_SCREEN_CHANGE;
extern NSString *const NOTIFICATION_SCREEN_CHANGE_LION;
// Applications
extern NSString *const FINDER;
// Window Hint
extern NSInteger const HINT_X_PADDING;
extern NSString *const HINT_CHARACTERS;
extern NSInteger const MAIN_MENU_HEIGHT;
// Internal Snapshots
extern NSString *const MENU_SNAPSHOT;
extern NSString *const UNDO_SNAPSHOT;
// File Extensions
extern NSString *const EXT_JS;
// Javascript Operation Options Hash Keys
extern NSString *const OPT_STYLE;
extern NSString *const OPT_DIRECTION;
extern NSString *const OPT_SCREEN;
extern NSString *const OPT_X;
extern NSString *const OPT_Y;
extern NSString *const OPT_WIDTH;
extern NSString *const OPT_HEIGHT;
extern NSString *const OPT_ANCHOR;
extern NSString *const OPT_APP;
extern NSString *const OPT_COMMAND;
extern NSString *const OPT_WAIT;
extern NSString *const OPT_PATH;
extern NSString *const OPT_NAME;
extern NSString *const OPT_CHARACTERS;
extern NSString *const OPT_PADDING;
extern NSString *const OPT_GRIDS;
extern NSString *const OPT_BACK;
extern NSString *const OPT_QUIT;
extern NSString *const OPT_FORCE_QUIT;
extern NSString *const OPT_HIDE;
extern NSString *const OPT_DELETE;
extern NSString *const OPT_ALL;
extern NSString *const OPT_SAVE;
extern NSString *const OPT_STACK;
extern NSString *const OPT_OPERATIONS;
extern NSString *const OPT_IGNORE_FAIL;
extern NSString *const OPT_REPEAT;
extern NSString *const OPT_REPEAT_LAST;
extern NSString *const OPT_SORT_TITLE;
extern NSString *const OPT_MAIN_FIRST;
extern NSString *const OPT_MAIN_LAST;
extern NSString *const OPT_TITLE_ORDER;
extern NSString *const OPT_TITLE_ORDER_REGEX;
extern NSString *const OPT_BEFORE;
extern NSString *const OPT_AFTER;
================================================
FILE: Slate/Constants.m
================================================
//
// Constants.m
// Slate
//
// Created by Jigish Patel on 5/26/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "Constants.h"
NSInteger const MODAL_ESCAPE_ID = 40000;
NSString *const SNAPSHOTS_FILE = @"snapshots";
// Directive Keys
NSString *const BIND = @"bind";
NSString *const CONFIG = @"config";
NSString *const LAYOUT = @"layout";
NSString *const DEFAULT = @"default";
NSString *const ALIAS = @"alias";
NSString *const SOURCE = @"source";
// Source Option Keys
NSString *const IF_EXISTS = @"if_exists";
// Config Keys
NSString *const DEFAULT_TO_CURRENT_SCREEN = @"defaultToCurrentScreen";
NSString *const DEFAULT_TO_CURRENT_SCREEN_DEFAULT = @"false";
NSString *const NUDGE_PERCENT_OF = @"nudgePercentOf";
NSString *const NUDGE_PERCENT_OF_DEFAULT = @"screenSize";
NSString *const RESIZE_PERCENT_OF = @"resizePercentOf";
NSString *const RESIZE_PERCENT_OF_DEFAULT = @"screenSize";
NSString *const REPEAT_ON_HOLD_OPS = @"repeatOnHoldOps";
NSString *const REPEAT_ON_HOLD_OPS_DEFAULT = @"resize,nudge";
NSString *const SECONDS_BEFORE_REPEAT = @"secondsBeforeRepeat";
NSString *const SECONDS_BEFORE_REPEAT_DEFAULT = @"0.4";
NSString *const SECONDS_BETWEEN_REPEAT = @"secondsBetweenRepeat";
NSString *const SECONDS_BETWEEN_REPEAT_DEFAULT = @"0.2";
NSString *const CHECK_DEFAULTS_ON_LOAD = @"checkDefaultsOnLoad";
NSString *const CHECK_DEFAULTS_ON_LOAD_DEFAULT = @"false";
NSString *const FOCUS_CHECK_WIDTH = @"focusCheckWidth";
NSString *const FOCUS_CHECK_WIDTH_DEFAULT = @"100";
NSString *const FOCUS_CHECK_WIDTH_MAX = @"focusCheckWidthMax";
NSString *const FOCUS_CHECK_WIDTH_MAX_DEFAULT = @"100";
NSString *const FOCUS_PREFER_SAME_APP = @"focusPreferSameApp";
NSString *const FOCUS_PREFER_SAME_APP_DEFAULT = @"true";
NSString *const ORDER_SCREENS_LEFT_TO_RIGHT = @"orderScreensLeftToRight";
NSString *const ORDER_SCREENS_LEFT_TO_RIGHT_DEFAULT = @"true";
NSString *const WINDOW_HINTS_FONT_NAME = @"windowHintsFontName";
NSString *const WINDOW_HINTS_FONT_NAME_DEFAULT = @"Helvetica";
NSString *const WINDOW_HINTS_FONT_SIZE = @"windowHintsFontSize";
NSString *const WINDOW_HINTS_FONT_SIZE_DEFAULT = @"40";
NSString *const WINDOW_HINTS_FONT_COLOR = @"windowHintsFontColor";
NSString *const WINDOW_HINTS_FONT_COLOR_DEFAULT = @"255;255;255;1.0";
NSString *const WINDOW_HINTS_WIDTH = @"windowHintsWidth";
NSString *const WINDOW_HINTS_WIDTH_DEFAULT = @"100";
NSString *const WINDOW_HINTS_HEIGHT = @"windowHintsHeight";
NSString *const WINDOW_HINTS_HEIGHT_DEFAULT = @"100";
NSString *const WINDOW_HINTS_BACKGROUND_COLOR = @"windowHintsBackgroundColor";
NSString *const WINDOW_HINTS_BACKGROUND_COLOR_DEFAULT = @"50;53;58;0.9";
NSString *const WINDOW_HINTS_DURATION = @"windowHintsDuration";
NSString *const WINDOW_HINTS_DURATION_DEFAULT = @"3";
NSString *const WINDOW_HINTS_ROUNDED_CORNER_SIZE = @"windowHintsRoundedCornerSize";
NSString *const WINDOW_HINTS_ROUNDED_CORNER_SIZE_DEFAULT = @"5";
NSString *const WINDOW_HINTS_IGNORE_HIDDEN_WINDOWS = @"windowHintsIgnoreHiddenWindows";
NSString *const WINDOW_HINTS_IGNORE_HIDDEN_WINDOWS_DEFAULT = @"true";
NSString *const WINDOW_HINTS_TOP_LEFT_X = @"windowHintsTopLeftX";
NSString *const WINDOW_HINTS_TOP_LEFT_X_DEFAULT = @"(windowSizeX/2)-(windowHintsWidth/2);0";
NSString *const WINDOW_HINTS_TOP_LEFT_Y = @"windowHintsTopLeftY";
NSString *const WINDOW_HINTS_TOP_LEFT_Y_DEFAULT = @"(windowSizeY/2)-(windowHintsHeight/2);0";
NSString *const WINDOW_HINTS_ORDER = @"windowHintsOrder";
NSString *const WINDOW_HINTS_ORDER_DEFAULT = @"leftToRight";
NSString *const WINDOW_HINTS_SHOW_ICONS = @"windowHintsShowIcons";
NSString *const WINDOW_HINTS_SHOW_ICONS_DEFAULT = @"false";
NSString *const WINDOW_HINTS_ICON_ALPHA = @"windowHintsIconAlpha";
NSString *const WINDOW_HINTS_ICON_ALPHA_DEFAULT = @"1.0";
NSString *const WINDOW_HINTS_SPREAD = @"windowHintsSpread";
NSString *const WINDOW_HINTS_SPREAD_DEFAULT = @"false";
NSString *const WINDOW_HINTS_SPREAD_SEARCH_WIDTH = @"windowHintsSpreadSearchWidth";
NSString *const WINDOW_HINTS_SPREAD_SEARCH_WIDTH_DEFAULT = @"40";
NSString *const WINDOW_HINTS_SPREAD_SEARCH_HEIGHT = @"windowHintsSpreadSearchHeight";
NSString *const WINDOW_HINTS_SPREAD_SEARCH_HEIGHT_DEFAULT = @"40";
NSString *const WINDOW_HINTS_SPREAD_PADDING = @"windowHintsSpreadPadding";
NSString *const WINDOW_HINTS_SPREAD_PADDING_DEFAULT = @"20";
NSString *const SWITCH_ICON_SIZE = @"switchIconSize";
NSString *const SWITCH_ICON_SIZE_DEFAULT = @"100";
NSString *const SWITCH_ICON_PADDING = @"switchIconPadding";
NSString *const SWITCH_ICON_PADDING_DEFAULT = @"5";
NSString *const SWITCH_BACKGROUND_COLOR = @"switchBackgroundColor";
NSString *const SWITCH_BACKGROUND_COLOR_DEFAULT = @"50;53;58;0.3";
NSString *const SWITCH_SELECTED_BACKGROUND_COLOR = @"switchSelectedBackgroundColor";
NSString *const SWITCH_SELECTED_BACKGROUND_COLOR_DEFAULT = @"50;53;58;0.9";
NSString *const SWITCH_SELECTED_BORDER_COLOR = @"switchSelectedBorderColor";
NSString *const SWITCH_SELECTED_BORDER_COLOR_DEFAULT = @"230;230;230;0.9";
NSString *const SWITCH_SELECTED_BORDER_SIZE = @"switchSelectedBorderSize";
NSString *const SWITCH_SELECTED_BORDER_SIZE_DEFAULT = @"2";
NSString *const SWITCH_ROUNDED_CORNER_SIZE = @"switchRoundedCornerSize";
NSString *const SWITCH_ROUNDED_CORNER_SIZE_DEFAULT = @"5";
NSString *const SWITCH_ORIENTATION = @"switchOrientation";
NSString *const SWITCH_ORIENTATION_DEFAULT = @"horizontal";
NSString *const SWITCH_SECONDS_BEFORE_REPEAT = @"switchSecondsBeforeRepeat";
NSString *const SWITCH_SECONDS_BEFORE_REPEAT_DEFAULT = @"0.3";
NSString *const SWITCH_SECONDS_BETWEEN_REPEAT = @"switchSecondsBetweenRepeat";
NSString *const SWITCH_SECONDS_BETWEEN_REPEAT_DEFAULT = @"0.03";
NSString *const SWITCH_STOP_REPEAT_AT_EDGE = @"switchStopRepeatAtEdge";
NSString *const SWITCH_STOP_REPEAT_AT_EDGE_DEFAULT = @"true";
NSString *const SWITCH_ONLY_FOCUS_MAIN_WINDOW = @"switchOnlyFocusMainWindow";
NSString *const SWITCH_ONLY_FOCUS_MAIN_WINDOW_DEFAULT = @"true";
NSString *const SWITCH_FONT_SIZE = @"switchFontSize";
NSString *const SWITCH_FONT_SIZE_DEFAULT = @"14";
NSString *const SWITCH_FONT_COLOR = @"switchFontColor";
NSString *const SWITCH_FONT_COLOR_DEFAULT = @"255;255;255;1.0";
NSString *const SWITCH_FONT_NAME = @"switchFontName";
NSString *const SWITCH_FONT_NAME_DEFAULT = @"Helvetica";
NSString *const SWITCH_SHOW_TITLES = @"switchShowTitles";
NSString *const SWITCH_SHOW_TITLES_DEFAULT = @"false";
NSString *const SWITCH_TYPE = @"switchType";
NSString *const SWITCH_TYPE_DEFAULT = @"app";
NSString *const SWITCH_SELECTED_PADDING = @"switchSelectedPadding";
NSString *const SWITCH_SELECTED_PADDING_DEFAULT = @"10";
NSString *const KEYBOARD_LAYOUT = @"keyboardLayout";
NSString *const KEYBOARD_LAYOUT_DEFAULT = @"qwerty";
NSString *const SNAPSHOT_TITLE_MATCH = @"snapshotTitleMatch";
NSString *const SNAPSHOT_TITLE_MATCH_DEFAULT = @"levenshtein";
NSString *const LEVENSHTEIN = @"levenshtein";
NSString *const SEQUENTIAL = @"sequential";
NSString *const SNAPSHOT_MAX_STACK_SIZE = @"snapshotMaxStackSize";
NSString *const SNAPSHOT_MAX_STACK_SIZE_DEFAULT = @"0";
NSString *const UNDO_MAX_STACK_SIZE = @"undoMaxStackSize";
NSString *const UNDO_MAX_STACK_SIZE_DEFAULT = @"10";
NSString *const UNDO_OPS = @"undoOps";
NSString *const UNDO_OPS_DEFAULT = @"activate-snapshot,chain,grid,layout,move,push,nudge,corner,throw,resize,sequence,shell";
NSString *const GRID_BACKGROUND_COLOR = @"gridBackgroundColor";
NSString *const GRID_BACKGROUND_COLOR_DEFAULT = @"75;77;81;1.0";
NSString *const GRID_ROUNDED_CORNER_SIZE = @"gridRoundedCornerSize";
NSString *const GRID_ROUNDED_CORNER_SIZE_DEFAULT = @"5";
NSString *const GRID_CELL_BACKGROUND_COLOR = @"gridCellBackgroundColor";
NSString *const GRID_CELL_BACKGROUND_COLOR_DEFAULT = @"100;106;116;0.8";
NSString *const GRID_CELL_SELECTED_COLOR = @"gridCellSelectedColor";
NSString *const GRID_CELL_SELECTED_COLOR_DEFAULT = @"50;53;58;0.8";
NSString *const GRID_CELL_ROUNDED_CORNER_SIZE = @"gridCellRoundedCornerSize";
NSString *const GRID_CELL_ROUNDED_CORNER_SIZE_DEFAULT = @"5";
NSString *const LAYOUT_FOCUS_ON_ACTIVATE = @"layoutFocusOnActivate";
NSString *const LAYOUT_FOCUS_ON_ACTIVATE_DEFAULT = @"false";
NSString *const MODAL_ESCAPE_KEY = @"modalEscapeKey";
NSString *const MODAL_ESCAPE_KEY_DEFAULT = @"";
NSString *const JS_RECEIVE_MOVE_EVENT = @"jsReceiveMoveEvent";
NSString *const JS_RECEIVE_MOVE_EVENT_DEFAULT = @"false";
NSString *const JS_RECEIVE_RESIZE_EVENT = @"jsReceiveResizeEvent";
NSString *const JS_RECEIVE_RESIZE_EVENT_DEFAULT = @"false";
// Application Option Keys
NSString *const IGNORE_FAIL = @"IGNORE_FAIL";
NSString *const REPEAT = @"REPEAT";
NSString *const REPEAT_LAST = @"REPEAT_LAST";
NSString *const SORT_TITLE = @"SORT_TITLE";
NSString *const MAIN_FIRST = @"MAIN_FIRST";
NSString *const MAIN_LAST = @"MAIN_LAST";
NSString *const TITLE_ORDER = @"TITLE_ORDER=";
NSString *const TITLE_ORDER_REGEX = @"TITLE_ORDER_REGEX=";
// Modifier Keys
NSString *const CONTROL = @"ctrl";
NSString *const COMMAND = @"cmd";
NSString *const OPTION = @"alt";
NSString *const SHIFT = @"shift";
NSString *const FUNCTION = @"fn";
UInt32 const FUNCTION_KEY = 0x800000;
// Expression Keys
NSString *const SCREEN_ORIGIN_X = @"screenOriginX";
NSString *const SCREEN_ORIGIN_Y = @"screenOriginY";
NSString *const SCREEN_SIZE = @"screenSize";
NSString *const SCREEN_SIZE_X = @"screenSizeX";
NSString *const SCREEN_SIZE_Y = @"screenSizeY";
NSString *const WINDOW_TOP_LEFT_X = @"windowTopLeftX";
NSString *const WINDOW_TOP_LEFT_Y = @"windowTopLeftY";
NSString *const WINDOW_SIZE_X = @"windowSizeX";
NSString *const WINDOW_SIZE_Y = @"windowSizeY";
NSString *const NEW_WINDOW_SIZE = @"newWindowSize";
NSString *const NEW_WINDOW_SIZE_X = @"newWindowSizeX";
NSString *const NEW_WINDOW_SIZE_Y = @"newWindowSizeY";
// Operations
NSString *const MOVE = @"move";
NSString *const RESIZE = @"resize";
NSString *const PUSH = @"push";
NSString *const NUDGE = @"nudge";
NSString *const THROW = @"throw";
NSString *const CORNER = @"corner";
NSString *const CHAIN = @"chain";
NSString *const FOCUS = @"focus";
NSString *const SNAPSHOT = @"snapshot";
NSString *const ACTIVATE_SNAPSHOT = @"activate-snapshot";
NSString *const DELETE_SNAPSHOT = @"delete-snapshot";
NSString *const HINT = @"hint";
NSString *const SWITCH = @"switch";
NSString *const GRID = @"grid";
NSString *const SEQUENCE = @"sequence";
NSString *const HIDE = @"hide";
NSString *const SHOW = @"show";
NSString *const TOGGLE = @"toggle";
NSString *const RELAUNCH = @"relaunch";
NSString *const SHELL = @"shell";
NSString *const UNDO = @"undo";
// Parameters and Options
NSString *const CENTER = @"center";
NSString *const BAR = @"bar";
NSString *const BAR_RESIZE_WITH_VALUE = @"bar-resize:";
NSString *const NONE = @"none";
NSString *const NORESIZE = @"noresize";
NSString *const RESIZE_WITH_VALUE = @"resize:";
NSString *const SAVE_TO_DISK = @"save-to-disk";
NSString *const STACK = @"stack";
NSString *const NAME = @"name";
NSString *const SNAPSHOTS = @"snapshots";
NSString *const APPS = @"apps";
NSString *const APP_NAME = @"app-name";
NSString *const TITLE = @"title";
NSString *const SIZE = @"size";
NSString *const ALL = @"all";
NSString *const DELETE = @"delete";
NSString *const BACK = @"back";
NSString *const QUIT = @"quit";
NSString *const FORCE_QUIT = @"force-quit";
NSString *const WINDOW_HINTS_ORDER_NONE = @"none";
NSString *const WINDOW_HINTS_ORDER_PERSIST = @"persist";
NSString *const WINDOW_HINTS_ORDER_LEFT_TO_RIGHT = @"leftToRight";
NSString *const WINDOW_HINTS_ORDER_RIGHT_TO_LEFT = @"rightToLeft";
NSString *const SWITCH_ORIENTATION_HORIZONTAL = @"horizontal";
NSString *const SWITCH_ORIENTATION_VERTICAL = @"vertical";
NSString *const KEYBOARD_LAYOUT_DVORAK = @"dvorak";
NSString *const KEYBOARD_LAYOUT_COLEMAK = @"colemak";
NSString *const KEYBOARD_LAYOUT_AZERTY = @"azerty";
NSString *const PADDING = @"padding";
NSString *const CURRENT = @"current";
NSString *const ALL_BUT = @"all-but:";
NSString *const WAIT = @"wait";
NSString *const PATH = @"path:";
NSString *const APP_NAME_BEFORE = @"BEFORE";
NSString *const APP_NAME_AFTER = @"AFTER";
// Directions and Anchors
NSString *const UP = @"up";
NSString *const DOWN = @"down";
NSString *const LEFT = @"left";
NSString *const RIGHT = @"right";
NSString *const TOP = @"top";
NSString *const BOTTOM = @"bottom";
NSString *const ABOVE = @"above";
NSString *const BELOW = @"below";
NSString *const NEXT = @"next";
NSString *const PREVIOUS = @"previous";
NSString *const PREV = @"prev";
NSString *const BEHIND = @"behind";
NSString *const TOP_LEFT = @"top-left";
NSString *const TOP_RIGHT = @"top-right";
NSString *const BOTTOM_LEFT = @"bottom-left";
NSString *const BOTTOM_RIGHT = @"bottom-right";
NSInteger const DIRECTION_UNKNOWN = -1;
NSInteger const DIRECTION_UP = 0;
NSInteger const DIRECTION_DOWN = 1;
NSInteger const DIRECTION_LEFT = 2;
NSInteger const DIRECTION_RIGHT = 3;
NSInteger const DIRECTION_TOP = 4;
NSInteger const DIRECTION_BOTTOM = 5;
NSInteger const DIRECTION_ABOVE = 6;
NSInteger const DIRECTION_BELOW = 7;
NSInteger const DIRECTION_BEHIND = 8;
NSInteger const ANCHOR_TOP_LEFT = 0;
NSInteger const ANCHOR_TOP_RIGHT = 1;
NSInteger const ANCHOR_BOTTOM_LEFT = 2;
NSInteger const ANCHOR_BOTTOM_RIGHT = 3;
// Seperators and such
unichar const COMMENT_CHARACTER = '#';
NSString *const COMMA = @",";
NSString *const COLON = @":";
NSString *const SEMICOLON = @";";
NSString *const MINUS = @"-";
NSString *const PLUS = @"+";
NSString *const PERCENT = @"%";
NSString *const EMPTY = @"";
NSString *const PIPE_PADDED = @" | ";
NSString *const GREATER_THAN_PADDED = @" > ";
NSString *const QUOTES = @"'\"";
NSString *const EQUALS = @"=";
NSString *const TILDA = @"~";
NSString *const SLASH = @"/";
NSString *const X = @"x";
NSString *const Y = @"y";
NSString *const WIDTH = @"width";
NSString *const HEIGHT = @"height";
NSString *const WHITESPACE = @" \t";
// Screen constants
NSString *const REF_CURRENT_SCREEN = @"-1";
NSInteger const ID_MAIN_SCREEN = 0;
NSInteger const ID_CURRENT_SCREEN = -1;
NSInteger const ID_IGNORE_SCREEN = -2;
NSInteger const TYPE_UNKNOWN = -1;
NSInteger const TYPE_COUNT = 0;
NSInteger const TYPE_RESOLUTIONS = 1;
NSString *const COUNT = @"count";
NSString *const RESOLUTIONS = @"resolutions";
NSString *const ORDERED = @"ordered";
// Notifications
NSString *const NOTIFICATION_SCREEN_CHANGE = @"O3DeviceTimingChanged";
NSString *const NOTIFICATION_SCREEN_CHANGE_LION = @" com.apple.BezelServices.BMDisplayHWReconfiguredEvent";
// Applications
NSString *const FINDER = @"Finder";
// Window Hints
NSInteger const HINT_X_PADDING = 4;
NSString *const HINT_CHARACTERS = @"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
NSInteger const MAIN_MENU_HEIGHT = 22;
// Internal Snapshots
NSString *const MENU_SNAPSHOT = @"menuSnapshot"; // need to change this name.
NSString *const UNDO_SNAPSHOT = @"_internal_UndoSnapshot";
// File Extensions
NSString *const EXT_JS = @".js";
// Javascript Operation Options Hash Keys
NSString *const OPT_STYLE = @"style";
NSString *const OPT_DIRECTION = @"direction";
NSString *const OPT_SCREEN = @"screen";
NSString *const OPT_X = @"x";
NSString *const OPT_Y = @"y";
NSString *const OPT_WIDTH = @"width";
NSString *const OPT_HEIGHT = @"height";
NSString *const OPT_ANCHOR = @"anchor";
NSString *const OPT_APP = @"app";
NSString *const OPT_COMMAND = @"command";
NSString *const OPT_WAIT = @"wait";
NSString *const OPT_PATH = @"path";
NSString *const OPT_NAME = @"name";
NSString *const OPT_CHARACTERS = @"characters";
NSString *const OPT_PADDING = @"padding";
NSString *const OPT_GRIDS = @"grids";
NSString *const OPT_BACK = @"back";
NSString *const OPT_QUIT = @"quit";
NSString *const OPT_FORCE_QUIT = @"force-quit";
NSString *const OPT_HIDE = @"hide";
NSString *const OPT_DELETE = @"delete";
NSString *const OPT_ALL = @"all";
NSString *const OPT_SAVE = @"save";
NSString *const OPT_STACK = @"stack";
NSString *const OPT_OPERATIONS = @"operations";
NSString *const OPT_IGNORE_FAIL = @"ignore-fail";
NSString *const OPT_REPEAT = @"repeat";
NSString *const OPT_REPEAT_LAST = @"repeat-last";
NSString *const OPT_SORT_TITLE = @"sort-title";
NSString *const OPT_MAIN_FIRST = @"main-first";
NSString *const OPT_MAIN_LAST = @"main-last";
NSString *const OPT_TITLE_ORDER = @"title-order";
NSString *const OPT_TITLE_ORDER_REGEX = @"title-order-regex";
NSString *const OPT_BEFORE = @"_before_";
NSString *const OPT_AFTER = @"_after_";
================================================
FILE: Slate/CornerOperation.h
================================================
//
// CornerOperation.h
// Slate
//
// Created by Jigish Patel on 1/20/13.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "MoveOperation.h"
@interface CornerOperation : MoveOperation
+ (id)cornerOperation;
+ (id)cornerOperationFromString:(NSString *)cornerOperation;
@end
================================================
FILE: Slate/CornerOperation.m
================================================
//
// CornerOperation.m
// Slate
//
// Created by Jigish Patel on 1/20/13.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "CornerOperation.h"
#import "StringTokenizer.h"
#import "Constants.h"
#import "SlateLogger.h"
@implementation CornerOperation
- (NSArray *)requiredOptions {
return [NSArray arrayWithObjects:OPT_DIRECTION, nil];
}
- (void)afterEvalOptions {
NSString *width = [[self options] objectForKey:OPT_WIDTH];
if (width == nil) { width = @"windowSizeX"; }
NSString *height = [[self options] objectForKey:OPT_HEIGHT];
if (height == nil) { height = @"windowSizeY"; }
[self setDimensions:[[ExpressionPoint alloc] initWithX:width y:height]];
NSString *screen = [[self options] objectForKey:OPT_SCREEN];
if (screen == nil) { screen = REF_CURRENT_SCREEN; }
[self setMonitor:screen];
NSString *direction = [[self options] objectForKey:OPT_DIRECTION];
if ([direction isEqualToString:TOP_LEFT]) {
[self setTopLeft:[[ExpressionPoint alloc] initWithX:@"screenOriginX" y:@"screenOriginY"]];
} else if ([direction isEqualToString:TOP_RIGHT]) {
[self setTopLeft:[[ExpressionPoint alloc] initWithX:[NSString stringWithFormat:@"screenOriginX+screenSizeX-(%@)", [[self dimensions] x]] y:@"screenOriginY"]];
} else if ([direction isEqualToString:BOTTOM_LEFT]) {
[self setTopLeft:[[ExpressionPoint alloc] initWithX:@"screenOriginX" y:[NSString stringWithFormat:@"screenOriginY+screenSizeY-(%@)", [[self dimensions] y]]]];
} else if ([direction isEqualToString:BOTTOM_RIGHT]) {
[self setTopLeft:[[ExpressionPoint alloc] initWithX:[NSString stringWithFormat:@"screenOriginX+screenSizeX-(%@)", [[self dimensions] x]] y:[NSString stringWithFormat:@"screenOriginY+screenSizeY-(%@)", [[self dimensions] y]]]];
} else {
SlateLogger(@"ERROR: Unrecognized corner '%@'", direction);
@throw([NSException exceptionWithName:@"Unrecognized Corner" reason:[NSString stringWithFormat:@"Unrecognized corner '%@'", direction] userInfo:nil]);
}
}
+ (id)cornerOperation {
return [[CornerOperation alloc] init];
}
+ (id)cornerOperationFromString:(NSString *)cornerOperation {
// corner
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:cornerOperation into:tokens];
if ([tokens count] < 2) {
SlateLogger(@"ERROR: Invalid Parameters '%@'", cornerOperation);
@throw([NSException exceptionWithName:@"Invalid Parameters" reason:[NSString stringWithFormat:@"Invalid Parameters in '%@'. Corner operations require the following format: 'corner direction [optional:style]'", cornerOperation] userInfo:nil]);
}
NSString *tl = nil;
NSString *dim = @"windowSizeX;windowSizeY";
NSString *direction = [tokens objectAtIndex:1];
if ([tokens count] >= 3) {
NSString *style = [tokens objectAtIndex:2];
if ([style hasPrefix:RESIZE_WITH_VALUE]) {
dim = [[style componentsSeparatedByString:COLON] objectAtIndex:1];
}
}
if ([direction isEqualToString:TOP_LEFT]) {
tl = @"screenOriginX;screenOriginY";
} else if ([direction isEqualToString:TOP_RIGHT]) {
tl = [[@"screenOriginX+screenSizeX-" stringByAppendingString:[[dim componentsSeparatedByString:SEMICOLON] objectAtIndex:0]] stringByAppendingString:@";screenOriginY"];
} else if ([direction isEqualToString:BOTTOM_LEFT]) {
tl = [@"screenOriginX;screenOriginY+screenSizeY-" stringByAppendingString:[[dim componentsSeparatedByString:SEMICOLON] objectAtIndex:1]];
} else if ([direction isEqualToString:BOTTOM_RIGHT]) {
tl = [[[@"screenOriginX+screenSizeX-" stringByAppendingString:[[dim componentsSeparatedByString:SEMICOLON] objectAtIndex:0]] stringByAppendingString:@";screenOriginY+screenSizeY-"] stringByAppendingString:[[dim componentsSeparatedByString:SEMICOLON] objectAtIndex:1]];
} else {
SlateLogger(@"ERROR: Unrecognized corner '%@'", direction);
@throw([NSException exceptionWithName:@"Unrecognized Corner" reason:[NSString stringWithFormat:@"Unrecognized corner '%@' in '%@'", direction, cornerOperation] userInfo:nil]);
}
Operation *op = [[MoveOperation alloc] initWithTopLeft:tl dimensions:dim monitor:([tokens count] >=4 ? [tokens objectAtIndex:3] : REF_CURRENT_SCREEN)];
return op;
}
@end
================================================
FILE: Slate/DeleteSnapshotOperation.h
================================================
//
// DeleteSnapshotOperation.h
// Slate
//
// Created by Jigish Patel on 2/28/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "Operation.h"
@interface DeleteSnapshotOperation : Operation {
@private
NSString *name;
BOOL pop;
}
@property NSString *name;
@property (assign) BOOL pop;
- (id)initWithName:(NSString *)theName options:(NSString *)options;
+ (id)deleteSnapshotOperation;
+ (id)deleteSnapshotOperationFromString:(NSString *)deleteSnapshotOperation;
@end
================================================
FILE: Slate/DeleteSnapshotOperation.m
================================================
//
// DeleteSnapshotOperation.m
// Slate
//
// Created by Jigish Patel on 2/28/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "DeleteSnapshotOperation.h"
#import "Constants.h"
#import "SlateConfig.h"
#import "StringTokenizer.h"
#import "SlateLogger.h"
@implementation DeleteSnapshotOperation
@synthesize name, pop;
- (id)init {
self = [super init];
if (self) {
pop = YES;
}
return self;
}
- (id)initWithName:(NSString *)theName options:(NSString *)_options {
self = [self init];
if (self) {
[self setName:theName];
if (_options) {
NSArray *optionsTokens = [_options componentsSeparatedByString:SEMICOLON];
for (NSInteger i = 0; i < [optionsTokens count]; i++) {
NSString *option = [optionsTokens objectAtIndex:i];
if ([ALL isEqualToString:option]) {
pop = NO;
}
}
}
}
return self;
}
- (BOOL)doOperation {
SlateLogger(@"----------------- Begin Delete Snapshot Operation -----------------");
BOOL success = [self doOperationWithAccessibilityWrapper:nil screenWrapper:nil];
SlateLogger(@"----------------- End Delete Snapshot Operation -----------------");
return success;
}
- (BOOL)doOperationWithAccessibilityWrapper:(AccessibilityWrapper *)iamnil screenWrapper:(ScreenWrapper *)iamalsonil {
[self evalOptionsWithAccessibilityWrapper:iamnil screenWrapper:iamalsonil];
[[SlateConfig getInstance] deleteSnapshot:name pop:pop];
return YES;
}
- (BOOL)testOperation {
return YES;
}
- (NSArray *)requiredOptions {
return [NSArray arrayWithObject:OPT_NAME];
}
- (void)parseOption:(NSString *)_name value:(id)value {
if (value == nil) { return; }
if ([_name isEqualToString:OPT_NAME]) {
if (![value isKindOfClass:[NSString class]]) {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", _name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", _name, value] userInfo:nil]);
return;
}
[self setName:_name];
} else if ([_name isEqualToString:OPT_ALL]) {
if (![value isKindOfClass:[NSValue class]] && ![value isKindOfClass:[NSString class]] && ![value isKindOfClass:[NSNumber class]]) {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", _name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", _name, value] userInfo:nil]);
return;
}
[self setPop:![value boolValue]];
}
}
+ (id)deleteSnapshotOperation {
return [[DeleteSnapshotOperation alloc] init];
}
+ (id)deleteSnapshotOperationFromString:(NSString *)deleteSnapshotOperation {
// delete-snapshot name options
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:deleteSnapshotOperation into:tokens maxTokens:3];
if ([tokens count] < 2) {
SlateLogger(@"ERROR: Invalid Parameters '%@'", deleteSnapshotOperation);
@throw([NSException exceptionWithName:@"Invalid Parameters" reason:[NSString stringWithFormat:@"Invalid Parameters in '%@'. Delete Snapshot operations require the following format: 'delete-snapshot name options'", deleteSnapshotOperation] userInfo:nil]);
}
Operation *op = [[DeleteSnapshotOperation alloc] initWithName:[tokens objectAtIndex:1] options:([tokens count] > 2 ? [tokens objectAtIndex:2] : nil)];
return op;
}
@end
================================================
FILE: Slate/ExpressionPoint.h
================================================
//
// ExpressionPoint.h
// Slate
//
// Created by Jigish Patel on 5/18/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface ExpressionPoint : NSObject {
@private
NSString *x;
NSString *y;
}
@property (copy) NSString *x;
@property (copy) NSString *y;
- (id)initWithX:(NSString *)xVal y:(NSString *)yVal;
- (NSPoint)getPointWithDict:(NSDictionary *)values;
- (NSSize)getSizeWithDict:(NSDictionary *)values;
+ (float)expToFloat:(NSString *)exp withDict:(NSDictionary *)values;
@end
================================================
FILE: Slate/ExpressionPoint.m
================================================
//
// ExpressionPoint.m
// Slate
//
// Created by Jigish Patel on 5/18/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "ExpressionPoint.h"
@implementation ExpressionPoint
@synthesize x;
@synthesize y;
- (id)init {
self = [super init];
if (self) {
[self setX:@"0"];
[self setY:@"0"];
}
return self;
}
- (id)initWithX:(NSString *)xVal y:(NSString *)yVal {
self = [super init];
if (self) {
[self setX:xVal];
[self setY:yVal];
}
return self;
}
- (NSPoint)getPointWithDict:(NSDictionary *)values {
return NSMakePoint([ExpressionPoint expToFloat:x withDict:values],[ExpressionPoint expToFloat:y withDict:values]);
}
- (NSSize)getSizeWithDict:(NSDictionary *)values {
return NSMakeSize([ExpressionPoint expToFloat:x withDict:values],[ExpressionPoint expToFloat:y withDict:values]);
}
+ (float)expToFloat:(NSString *)exp withDict:(NSDictionary *)values {
if (exp != nil) {
// then do the normal predicate stuff
NSComparisonPredicate *pred = (NSComparisonPredicate *)[NSPredicate predicateWithFormat:[exp stringByAppendingString:@" == 42"]];
NSExpression *lexp = [pred leftExpression];
NSNumber *result = [lexp expressionValueWithObject:values context:nil];
if (result == nil)
@throw([NSException exceptionWithName:@"Unable to compute result" reason:exp userInfo:nil]);
return [result floatValue];
}
@throw([NSException exceptionWithName:@"Expression is nil" reason:exp userInfo:nil]);
}
@end
================================================
FILE: Slate/FocusOperation.h
================================================
//
// FocusOperation.h
// Slate
//
// Created by Jigish Patel on 6/21/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
#import "Operation.h"
@interface FocusOperation : Operation {
@private
NSInteger direction;
NSString *app;
}
@property (assign) NSInteger direction;
@property NSString *app;
- (id)initWithDirectionOrApp:(NSString *)s;
+ (id)focusOperation;
+ (id)focusOperationFromString:(NSString *)focusOperation;
@end
================================================
FILE: Slate/FocusOperation.m
================================================
//
// FocusOperation.m
// Slate
//
// Created by Jigish Patel on 6/21/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "Constants.h"
#import "FocusOperation.h"
#import "MathUtils.h"
#import "SlateConfig.h"
#import "StringTokenizer.h"
#import "SlateLogger.h"
#import "RunningApplications.h"
@implementation FocusOperation
@synthesize direction, app;
- (id)init {
self = [super init];
if (self) {
[self setDirection:DIRECTION_UNKNOWN];
[self setApp:nil];
}
return self;
}
- (id)initWithDirectionOrApp:(NSString *)s {
self = [super init];
if (self) {
[self setDirection:DIRECTION_UNKNOWN];
[self setApp:nil];
if ([s length] <= 1) {
// fail
} else if ([s isEqualToString:UP] || [s isEqualToString:ABOVE]) {
[self setDirection:DIRECTION_UP];
} else if ([s isEqualToString:DOWN] || [s isEqualToString:BELOW]) {
[self setDirection:DIRECTION_DOWN];
} else if ([s isEqualToString:LEFT]) {
[self setDirection:DIRECTION_LEFT];
} else if ([s isEqualToString:RIGHT]) {
[self setDirection:DIRECTION_RIGHT];
} else if ([s isEqualToString:BEHIND]) {
[self setDirection:DIRECTION_BEHIND];
} else if ([[NSCharacterSet characterSetWithCharactersInString:QUOTES] characterIsMember:[s characterAtIndex:0]] &&
[[NSCharacterSet characterSetWithCharactersInString:QUOTES] characterIsMember:[s characterAtIndex:([s length] - 1)]]) {
// App name
[self setApp:[s substringWithRange:NSMakeRange(1, [s length]-2)]];
}
}
return self;
}
- (BOOL)doOperation {
SlateLogger(@"----------------- Begin Focus Operation -----------------");
// We don't use the passed in AccessibilityWrapper or ScreenWrapper so they are nil. No need to waste time creating them here.
BOOL success = [self doOperationWithAccessibilityWrapper:nil screenWrapper:nil];
SlateLogger(@"----------------- End Focus Operation -----------------");
return success;
}
- (BOOL)doOperationWithAccessibilityWrapper:(AccessibilityWrapper *)iamnil screenWrapper:(ScreenWrapper *)iamalsonil {
[self evalOptionsWithAccessibilityWrapper:iamnil screenWrapper:iamalsonil];
// App case
if ([self app] != nil) {
for (NSRunningApplication *runningApp in [RunningApplications getInstance]) {
if ([[self app] isEqualToString:[runningApp localizedName]]) {
// Match!
if ([AccessibilityWrapper focusMainWindow:runningApp]) {
return YES;
}
return NO;
}
}
return NO;
}
// Direction case
AccessibilityWrapper *caAW = [[AccessibilityWrapper alloc] init];
if (![caAW inited]) return NO;
NSPoint cwTL = [caAW getCurrentTopLeft];
NSSize cwSize = [caAW getCurrentSize];
NSRect cwRect = NSMakeRect(cwTL.x, cwTL.y, cwSize.width, cwSize.height);
NSString *cwTitle = [AccessibilityWrapper getTitle:[caAW window]];
NSRect checkRect;
NSInteger focusCheckWidth = [[SlateConfig getInstance] getIntegerConfig:FOCUS_CHECK_WIDTH];
while (focusCheckWidth <= [[SlateConfig getInstance] getIntegerConfig:FOCUS_CHECK_WIDTH_MAX]) {
SlateLogger(@"Checking for adjacent windows with width=%i",(int)focusCheckWidth);
if (direction == DIRECTION_UP) checkRect = NSMakeRect(cwTL.x, cwTL.y-focusCheckWidth, cwSize.width, focusCheckWidth);
else if (direction == DIRECTION_DOWN) checkRect = NSMakeRect(cwTL.x, cwTL.y+cwSize.height, cwSize.width, focusCheckWidth);
else if (direction == DIRECTION_LEFT) checkRect = NSMakeRect(cwTL.x-focusCheckWidth, cwTL.y, focusCheckWidth, cwSize.height);
else if (direction == DIRECTION_RIGHT) checkRect = NSMakeRect(cwTL.x+cwSize.width, cwTL.y, focusCheckWidth, cwSize.height);
else if (direction == DIRECTION_BEHIND) checkRect = NSMakeRect(cwTL.x, cwTL.y, cwSize.width, cwSize.height);
else {
return NO;
}
NSRect biggestIntersection = NSZeroRect;
AXUIElementRef windowToFocus;
AXUIElementRef appToFocus;
BOOL foundFocus = NO;
BOOL foundFocusInSameApp = NO;
for (NSRunningApplication *runningApp in [RunningApplications getInstance]) {
pid_t appPID = [runningApp processIdentifier];
SlateLogger(@"I see application '%@' with pid '%d'", [runningApp localizedName], appPID);
AXUIElementRef appRef = AXUIElementCreateApplication(appPID);
CFArrayRef windows = [AccessibilityWrapper windowsInApp:appRef];
if (!windows || CFArrayGetCount(windows) == 0) continue;
for (NSInteger i = 0; i < CFArrayGetCount(windows); i++) {
AccessibilityWrapper *aw = [[AccessibilityWrapper alloc] initWithApp:appRef window:CFArrayGetValueAtIndex(windows, i)];
if ([aw isMinimizedOrHidden]) {
SlateLogger(@" Window is minimized, skipping");
continue;
}
NSString *wTitle = [aw getTitle];
if ([wTitle isEqualToString:@""]){
SlateLogger(@" Title is empty, skipping");
continue; // Chrome and Finder have invisible windows for some reason
}
NSPoint wTL = [aw getCurrentTopLeft];
NSSize wSize = [aw getCurrentSize];
SlateLogger(@" Checking window in %@ in direction %i with rect: (%f,%f %f,%f), title: [%@]",[runningApp localizedName],(int)direction,wTL.x,wTL.y,wSize.width,wSize.height,wTitle);
NSRect windowRect = NSMakeRect(wTL.x, wTL.y, wSize.width, wSize.height);
if ([wTitle isEqualToString:cwTitle] && NSEqualRects(windowRect, cwRect) && NSEqualPoints(wTL, cwTL)) {
SlateLogger(@" Ignoring current window");
continue;
}
NSRect intersection = NSIntersectionRect(checkRect, windowRect);
if ([MathUtils isRect:intersection biggerThan:NSZeroRect] && [AccessibilityWrapper processIdentifierOfUIElement:[caAW app]] == appPID) {
SlateLogger(@" Found window in same app in direction %i",(int)direction);
if ([[SlateConfig getInstance] getBoolConfig:FOCUS_PREFER_SAME_APP]
&& (!foundFocusInSameApp || [MathUtils isRect:intersection biggerThan:biggestIntersection])) {
SlateLogger(@" Preferring same app.");
appToFocus = appRef;
windowToFocus = CFArrayGetValueAtIndex(windows, i);
biggestIntersection = intersection;
foundFocus = YES;
foundFocusInSameApp = YES;
} else if ([MathUtils isRect:intersection biggerThan:biggestIntersection]) {
appToFocus = appRef;
windowToFocus = CFArrayGetValueAtIndex(windows, i);
biggestIntersection = intersection;
foundFocus = YES;
}
} else if ([MathUtils isRect:intersection biggerThan:biggestIntersection]) {
SlateLogger(@" Found window in %@ in direction %i (intersection: %f,%f %f,%f)",[runningApp localizedName],(int)direction,intersection.origin.x,intersection.origin.y,intersection.size.width,intersection.size.height);
appToFocus = appRef;
windowToFocus = CFArrayGetValueAtIndex(windows, i);
biggestIntersection = intersection;
foundFocus = YES;
}
}
// check if same app && foundFocus && prefer_same_app
if(foundFocusInSameApp && [AccessibilityWrapper processIdentifierOfUIElement:[caAW app]] == appPID && [[SlateConfig getInstance] getBoolConfig:FOCUS_PREFER_SAME_APP]) {
AccessibilityWrapper *aw = [[AccessibilityWrapper alloc] initWithApp:appToFocus window:windowToFocus];
[aw focus];
return YES;
}
}
if (foundFocus) {
AccessibilityWrapper *aw = [[AccessibilityWrapper alloc] initWithApp:appToFocus window:windowToFocus];
[aw focus];
return YES;
}
focusCheckWidth += [[SlateConfig getInstance] getIntegerConfig:FOCUS_CHECK_WIDTH];
}
return NO;
}
- (BOOL)testOperation {
if ([self direction] == DIRECTION_UNKNOWN && [self app] == nil)
@throw [NSException exceptionWithName:@"Unknown Direction" reason:@"direction" userInfo:nil];
return YES;
}
- (NSString *)checkRequiredOptions:(NSDictionary *)_options {
if ([_options objectForKey:OPT_APP] == nil && [_options objectForKey:OPT_DIRECTION] == nil) {
return [[NSArray arrayWithObjects:OPT_APP, OPT_DIRECTION, nil] componentsJoinedByString:@" or "];
}
return nil;
}
- (void)parseOption:(NSString *)name value:(id)value {
if (value == nil) { return; }
if ([name isEqualToString:OPT_APP]) {
// should be string
if (![value isKindOfClass:[NSString class]]) {
@throw([NSException exceptionWithName:@"Invalid App" reason:[NSString stringWithFormat:@"Invalid App %@", value] userInfo:nil]);
return;
}
[self setApp:value];
} else if ([name isEqualToString:OPT_DIRECTION]) {
// should be a string
if (![value isKindOfClass:[NSString class]]) {
@throw([NSException exceptionWithName:@"Invalid Direction" reason:[NSString stringWithFormat:@"Invalid Direction %@", value] userInfo:nil]);
return;
}
if ([value isEqualToString:UP] || [value isEqualToString:ABOVE]) {
[self setDirection:DIRECTION_UP];
} else if ([value isEqualToString:DOWN] || [value isEqualToString:BELOW]) {
[self setDirection:DIRECTION_DOWN];
} else if ([value isEqualToString:LEFT]) {
[self setDirection:DIRECTION_LEFT];
} else if ([value isEqualToString:RIGHT]) {
[self setDirection:DIRECTION_RIGHT];
} else if ([value isEqualToString:BEHIND]) {
[self setDirection:DIRECTION_BEHIND];
} else {
@throw([NSException exceptionWithName:@"Invalid Direction" reason:[NSString stringWithFormat:@"Invalid Direction %@", value] userInfo:nil]);
return;
}
}
}
+ (id)focusOperation {
return [[FocusOperation alloc] init];
}
+ (id)focusOperationFromString:(NSString *)focusOperation {
// focus direction
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:focusOperation into:tokens maxTokens:2];
if ([tokens count] < 2) {
SlateLogger(@"ERROR: Invalid Parameters '%@'", focusOperation);
@throw([NSException exceptionWithName:@"Invalid Parameters" reason:[NSString stringWithFormat:@"Invalid Parameters in '%@'. Focus operations require the following format: 'focus direction'", focusOperation] userInfo:nil]);
}
Operation *op = [[FocusOperation alloc] initWithDirectionOrApp:[tokens objectAtIndex:1]];
return op;
}
@end
================================================
FILE: Slate/GridCellView.h
================================================
//
// GridCellView.h
// Slate
//
// Created by Jigish Patel on 9/10/12.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface GridCellView : NSView
@property NSColor *inactiveBg;
@property NSColor *activeBg;
@property NSColor *bg;
@property float cornerSize;
- (void)activate;
- (void)deactivate;
@end
================================================
FILE: Slate/GridCellView.m
================================================
//
// GridCellView.m
// Slate
//
// Created by Jigish Patel on 9/10/12.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "GridCellView.h"
#import "SlateConfig.h"
#import "Constants.h"
@implementation GridCellView
@synthesize bg, inactiveBg, activeBg, cornerSize;
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setWantsLayer:YES];
NSArray *bgColorArr = [[SlateConfig getInstance] getArrayConfig:GRID_CELL_BACKGROUND_COLOR];
if ([bgColorArr count] < 4) bgColorArr = [GRID_CELL_BACKGROUND_COLOR_DEFAULT componentsSeparatedByString:SEMICOLON];
NSColor *backgroundColor = [NSColor colorWithDeviceRed:[[bgColorArr objectAtIndex:0] floatValue]/255.0
green:[[bgColorArr objectAtIndex:1] floatValue]/255.0
blue:[[bgColorArr objectAtIndex:2] floatValue]/255.0
alpha:[[bgColorArr objectAtIndex:3] floatValue]];
[self setBg:backgroundColor];
[self setInactiveBg:backgroundColor];
NSArray *activeColorArr = [[SlateConfig getInstance] getArrayConfig:GRID_CELL_SELECTED_COLOR];
if ([activeColorArr count] < 4) activeColorArr = [GRID_CELL_SELECTED_COLOR_DEFAULT componentsSeparatedByString:SEMICOLON];
NSColor *activeColor = [NSColor colorWithDeviceRed:[[activeColorArr objectAtIndex:0] floatValue]/255.0
green:[[activeColorArr objectAtIndex:1] floatValue]/255.0
blue:[[activeColorArr objectAtIndex:2] floatValue]/255.0
alpha:[[activeColorArr objectAtIndex:3] floatValue]];
[self setActiveBg:activeColor];
[self setCornerSize:[[SlateConfig getInstance] getFloatConfig:GRID_CELL_ROUNDED_CORNER_SIZE]];
}
return self;
}
- (void)activate {
[self setBg:[self activeBg]];
[self setNeedsDisplay:YES];
}
- (void)deactivate {
[self setBg:[self inactiveBg]];
[self setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)dirtyRect {
[[self bg] set];
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:[self bounds] xRadius:[self cornerSize] yRadius:[self cornerSize]];
[path fill];
}
@end
================================================
FILE: Slate/GridOperation.h
================================================
//
// GridOperation.h
// Slate
//
// Created by Jigish Patel on 9/7/12.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
#import "Operation.h"
@class ExpressionPoint;
@interface GridOperation : Operation {
NSDictionary *screenConfigs;
NSMutableArray *grids;
EventHotKeyRef escHotKeyRef;
AccessibilityWrapper *focusedWindow;
NSInteger padding;
}
@property NSDictionary *screenConfigs;
@property NSMutableArray *grids;
@property AccessibilityWrapper *focusedWindow;
@property NSInteger padding;
- (id)initWithScreenConfigs:(NSDictionary *)myScreenConfigs padding:(NSInteger)myPadding;
- (void)killGrids;
- (void)activateGridKey:(NSInteger)hintId;
- (void)activateLayoutWithOrigin:(ExpressionPoint *)origin size:(ExpressionPoint *)size screenId:(NSInteger)screenId;
+ (id)gridOperation;
+ (id)gridOperationFromString:(NSString *) gridOperation;
@end
================================================
FILE: Slate/GridOperation.m
================================================
//
// GridOperation.m
// Slate
//
// Created by Jigish Patel on 9/7/12.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "GridOperation.h"
#import "StringTokenizer.h"
#import "SlateLogger.h"
#import "Constants.h"
#import "GridWindow.h"
#import "GridView.h"
#import "Binding.h"
#import "SlateAppDelegate.h"
#import "ExpressionPoint.h"
#import "MoveOperation.h"
@interface ScreenConfig : NSObject {
NSInteger width;
NSInteger height;
NSString *key;
}
@property NSInteger width;
@property NSInteger height;
@property NSString *key;
+ (ScreenConfig *)screenConfigFromDictionary:(NSDictionary *)dict key:(NSString *)key;
@end
@implementation ScreenConfig
@synthesize width, height, key;
+ (ScreenConfig *)screenConfigFromDictionary:(NSDictionary *)dict key:(NSString *)key {
if ([dict objectForKey:WIDTH] == nil || [dict objectForKey:HEIGHT] == nil || key == nil) {
@throw([NSException exceptionWithName:@"Invalid Grid" reason:[NSString stringWithFormat:@"Invalid Grid '%@'", key] userInfo:nil]);
}
ScreenConfig *sc = [[ScreenConfig alloc] init];
[sc setWidth:[[dict objectForKey:WIDTH] integerValue]];
[sc setHeight:[[dict objectForKey:HEIGHT] integerValue]];
[sc setKey:key];
return sc;
}
@end
@implementation GridOperation
@synthesize screenConfigs, grids, focusedWindow, padding;
static const UInt32 ESC_GRID_ID = 10002;
- (id)init {
self = [super init];
if (self) {
[self setScreenConfigs:[NSMutableDictionary dictionary]];
[self setGrids:[NSMutableArray array]];
[self setPadding:2];
}
return self;
}
- (id)initWithScreenConfigs:(NSDictionary *)myScreenConfigs padding:(NSInteger)myPadding {
self = [super init];
if (self) {
[self setScreenConfigs:myScreenConfigs];
[self setGrids:[NSMutableArray array]];
[self setPadding:myPadding];
}
return self;
}
- (BOOL)doOperation {
SlateLogger(@"----------------- Begin Grid Operation -----------------");
ScreenWrapper *sw = [[ScreenWrapper alloc] init];
BOOL success = [self doOperationWithAccessibilityWrapper:nil screenWrapper:sw];
SlateLogger(@"----------------- End Grid Operation -----------------");
return success;
}
- (BOOL)doOperationWithAccessibilityWrapper:(AccessibilityWrapper *)aw screenWrapper:(ScreenWrapper *)sw {
[self evalOptionsWithAccessibilityWrapper:aw screenWrapper:sw];
[self setFocusedWindow:[[AccessibilityWrapper alloc] init]];
NSInteger currentScreenId = [sw getScreenIdForPoint:[NSEvent mouseLocation]];
[(SlateAppDelegate *)[NSApp delegate] setCurrentGridOperation:self];
NSInteger screenId = 0;
NSWindow *toFocus = nil;
for (NSScreen *screen in [sw screens]) {
NSRect frame;
frame = NSMakeRect([screen frame].size.width/2-[screen frame].size.width/8,
[screen frame].size.height/2-[screen frame].size.height/8,
[screen frame].size.width/4,
[screen frame].size.height/4);
NSWindow *window = [[GridWindow alloc] initWithContentRect:frame
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO
screen:screen];
[window setReleasedWhenClosed:NO];
[window setOpaque:NO];
[window setBackgroundColor:[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.0]];
[window makeKeyAndOrderFront:NSApp];
[window setLevel:(NSScreenSaverWindowLevel - 1)];
GridView *view = [[GridView alloc] initWithFrame:frame];
NSInteger width = 12;
NSInteger height = 12;
NSString *resolution = [NSString stringWithFormat:@"%ldx%ld", [[NSNumber numberWithFloat:[screen frame].size.width] integerValue], [[NSNumber numberWithFloat:[screen frame].size.height] integerValue]];
ScreenConfig *myConfig = [[self screenConfigs] objectForKey:resolution];
if (myConfig == nil) {
NSInteger screenRefId = [sw convertDefaultOrderToLeftToRightOrderIfNeeded:screenId];
myConfig = [[self screenConfigs] objectForKey:[NSString stringWithFormat:@"%ld",screenRefId]];
}
if (myConfig != nil) {
height = [myConfig height];
width = [myConfig width];
}
[view addGridWithWidth:width height:height padding:[self padding]];
[view setOp:self];
[window setContentView:view];
NSWindowController *wc = [[NSWindowController alloc] initWithWindow:window];
[grids addObject:wc];
if (screenId == currentScreenId) { toFocus = window; }
screenId++;
}
if (toFocus != nil) {
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
[toFocus makeKeyAndOrderFront:self];
}
// Register the escape hotkey
NSNumber *keyCode = [[Binding asciiToCodeDict] objectForKey:@"esc"];
EventHotKeyID myHotKeyID;
EventHotKeyRef myHotKeyRef;
myHotKeyID.signature = *[@"hotkeyESC" cStringUsingEncoding:NSASCIIStringEncoding];
myHotKeyID.id = (UInt32)(ESC_GRID_ID);
RegisterEventHotKey([keyCode unsignedIntValue], 0, myHotKeyID, GetEventMonitorTarget(), 0, &myHotKeyRef);
escHotKeyRef = myHotKeyRef;
return YES;
}
- (void)killGrids {
for (NSWindowController *controller in grids) {
[controller close];
}
UnregisterEventHotKey(escHotKeyRef);
[grids removeAllObjects];
[(SlateAppDelegate *)[NSApp delegate] setCurrentGridOperation:nil];
}
- (void)activateGridKey:(NSInteger)hintId {
[self killGrids];
}
- (BOOL)testOperation {
return YES;
}
- (void)activateLayoutWithOrigin:(ExpressionPoint *)origin size:(ExpressionPoint *)size screenId:(NSInteger)screenId {
SlateLogger(@"Activate Layout: %@, %@, %@, %@", origin.x, origin.y, size.x, size.y);
MoveOperation *mo = [[MoveOperation alloc] initWithTopLeftEP:origin dimensionsEP:size screenId:screenId];
[focusedWindow focus];
[mo doOperation];
}
- (void)parseOption:(NSString *)name value:(id)value {
if (value == nil) { return; }
if ([name isEqualToString:OPT_PADDING]) {
// should be a string or integer
if (![value isKindOfClass:[NSString class]] && ![value isKindOfClass:[NSValue class]] && ![value isKindOfClass:[NSNumber class]]) {
@throw([NSException exceptionWithName:@"Invalid Padding" reason:[NSString stringWithFormat:@"Invalid Padding '%@'", value] userInfo:nil]);
}
[self setPadding:[value integerValue]];
} else if ([name isEqualToString:OPT_GRIDS]) {
// should be a dictionary
if (![value isKindOfClass:[NSDictionary class]]) {
@throw([NSException exceptionWithName:@"Invalid Grids" reason:[NSString stringWithFormat:@"Invalid Grids '%@'", value] userInfo:nil]);
}
NSMutableDictionary *configs = [NSMutableDictionary dictionary];
for (NSString *screen in [value allKeys]) {
id dict = [value objectForKey:screen];
if (![dict isKindOfClass:[NSDictionary class]]) {
@throw([NSException exceptionWithName:@"Invalid Grid" reason:[NSString stringWithFormat:@"Invalid Grid '%@'", value] userInfo:nil]);
}
[configs setObject:[ScreenConfig screenConfigFromDictionary:dict key:screen] forKey:screen];
}
[self setScreenConfigs:configs];
}
}
+ (id)gridOperation {
return [[GridOperation alloc] init];
}
+ (id)gridOperationFromString:(NSString *) gridOperation {
// grid padding::, ...
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:gridOperation into:tokens];
if ([tokens count] < 1) {
SlateLogger(@"ERROR: Invalid Parameters '%@'", gridOperation);
@throw([NSException exceptionWithName:@"Invalid Parameters" reason:[NSString stringWithFormat:@"Invalid Parameters in '%@'. Grid operations require the following format: 'grid :, ...'", gridOperation] userInfo:nil]);
}
NSMutableDictionary *screenConfigs = [NSMutableDictionary dictionary];
NSInteger padding = 2;
if ([tokens count] > 1) {
for (NSInteger i = 1; i < [tokens count]; i++) {
NSArray *split = [[tokens objectAtIndex:i] componentsSeparatedByString:COLON];
if ([split count] != 2) { continue; }
NSString *key = [split objectAtIndex:0];
if ([key isEqualToString:PADDING]) {
padding = [[split objectAtIndex:1] integerValue];
continue;
}
NSArray *widthHeight = [[split objectAtIndex:1] componentsSeparatedByString:COMMA];
if ([widthHeight count] != 2) { continue; }
ScreenConfig *screenConfig = [[ScreenConfig alloc] init];
[screenConfig setKey:key];
[screenConfig setWidth:[[widthHeight objectAtIndex:0] integerValue]];
[screenConfig setHeight:[[widthHeight objectAtIndex:1] integerValue]];
[screenConfigs setObject:screenConfig forKey:key];
}
}
Operation *op = [[GridOperation alloc] initWithScreenConfigs:screenConfigs padding:padding];
return op;
}
@end
================================================
FILE: Slate/GridView.h
================================================
//
// GridView.h
// Slate
//
// Created by Jigish Patel on 9/7/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@class GridOperation;
@interface GridView : NSView
@property GridOperation *op;
@property NSInteger width;
@property NSInteger height;
@property float padding;
@property float cellWidth;
@property float cellHeight;
@property NSMutableArray *grid;
@property NSRect previousActiveRect;
- (void)addGridWithWidth:(NSInteger)width height:(NSInteger)height padding:(float)myPadding;
@end
================================================
FILE: Slate/GridView.m
================================================
//
// GridView.m
// Slate
//
// Created by Jigish Patel on 9/7/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "GridView.h"
#import "GridCellView.h"
#import "SlateConfig.h"
#import "Constants.h"
#import "GridOperation.h"
#import "ExpressionPoint.h"
#import "SlateLogger.h"
@implementation GridView
@synthesize op, width, height, padding, cellWidth, cellHeight, previousActiveRect;
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setGrid:[NSMutableArray array]];
[self setWantsLayer:YES];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
NSArray *bgColorArr = [[SlateConfig getInstance] getArrayConfig:GRID_BACKGROUND_COLOR];
if ([bgColorArr count] < 4) bgColorArr = [GRID_BACKGROUND_COLOR_DEFAULT componentsSeparatedByString:SEMICOLON];
NSColor *backgroundColor = [NSColor colorWithDeviceRed:[[bgColorArr objectAtIndex:0] floatValue]/255.0
green:[[bgColorArr objectAtIndex:1] floatValue]/255.0
blue:[[bgColorArr objectAtIndex:2] floatValue]/255.0
alpha:[[bgColorArr objectAtIndex:3] floatValue]];
[backgroundColor set];
float cornerSize = [[SlateConfig getInstance] getFloatConfig:GRID_ROUNDED_CORNER_SIZE];
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:[self bounds] xRadius:cornerSize yRadius:cornerSize];
[path fill];
}
- (void)addGridWithWidth:(NSInteger)myWidth height:(NSInteger)myHeight padding:(float)myPadding {
[self setWidth:myWidth];
[self setHeight:myHeight];
[self setPadding:myPadding];
float usableHeight = self.frame.size.height - [self padding]*2;
float usableWidth = self.frame.size.width - [self padding]*2;
float x = [self padding];
float y = [self padding];
[self setCellHeight:(usableHeight - (height-1)*[self padding])/height];
[self setCellWidth:(usableWidth - (width-1)*[self padding])/width];
for (NSInteger r = 0; r < height; r++) {
NSMutableArray *rowArr = [NSMutableArray array];
for (NSInteger c = 0; c < width; c++) {
GridCellView *newView = [[GridCellView alloc] initWithFrame:NSMakeRect(x,y,[self cellWidth],[self cellHeight])];
[self addSubview:newView];
x += [self padding]+[self cellWidth];
[rowArr addObject:newView];
}
x = [self padding];
y += [self padding]+[self cellHeight];
[[self grid] addObject:rowArr];
}
}
- (NSRect)rectFromBegin:(NSPoint)begin end:(NSPoint)end {
// 0,0 is bottom left
float myWidth = self.bounds.size.width;
float myHeight = self.bounds.size.height;
float bX = begin.x < 0 ? 0 : (begin.x > myWidth ? myWidth : begin.x);
float bY = begin.y < 0 ? 0 : (begin.y > myHeight ? myHeight : begin.y);
float eX = end.x < 0 ? 0 : (end.x > myWidth ? myWidth : end.x);
float eY = end.y < 0 ? 0 : (end.y > myHeight ? myHeight : end.y);
float blX = 0;
float blY = 0;
float trX = 0;
float trY = 0;
if (bX <= eX) {
blX = bX;
trX = eX;
} else { // bX > eX
blX = eX;
trX = bX;
}
if (bY <= eY) {
blY = bY;
trY = eY;
} else { // bY > eY
blY = eY;
trY = bY;
}
SlateLogger(@"POINTS: %f,%f -> %f,%f", bX,bY,eX,eY);
NSInteger cellX = [self linearPosToCell:blX cellLength:[self cellWidth] totalCells:[self width]];
if (cellX >= [self width]) { cellX = [self width] - 1; }
NSInteger cellY = [self linearPosToCell:blY cellLength:[self cellHeight] totalCells:[self height]];
if (cellY >= [self height]) { cellY = [self height] - 1; }
NSInteger endCellX = [self linearPosToCell:trX cellLength:[self cellWidth] totalCells:[self width]];
if (endCellX >= [self width]) { endCellX = [self width] - 1; }
NSInteger endCellY = [self linearPosToCell:trY cellLength:[self cellHeight] totalCells:[self height]];
if (endCellY >= [self height]) { endCellY = [self height] - 1; }
SlateLogger(@"CELLS: %ld,%ld -> %ld,%ld", cellX,cellY,endCellX,endCellY);
return NSMakeRect(cellX, cellY, endCellX-cellX, endCellY-cellY);
}
- (NSInteger)linearPosToCell:(float)pos cellLength:(float)cellLength totalCells:(NSInteger)totalCells {
return [[NSNumber numberWithFloat:totalCells*(pos/(cellLength*totalCells + [self padding]*(totalCells+1)))] integerValue];
}
- (void)activateCellsInRect:(NSRect)rect {
if (NSEqualRects(rect, [self previousActiveRect])) {
[self setPreviousActiveRect:rect];
return;
}
[self setPreviousActiveRect:rect];
SlateLogger(@"activate cells in: %f,%f,%f,%f", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
for (NSInteger r = 0; r < height; r++) {
for (NSInteger c = 0; c < width; c++) {
if (r < rect.origin.y) {
[[[[self grid] objectAtIndex:r] objectAtIndex:c] deactivate];
} else if (c < rect.origin.x) {
[[[[self grid] objectAtIndex:r] objectAtIndex:c] deactivate];
} else if (r > rect.origin.y+rect.size.height) {
[[[[self grid] objectAtIndex:r] objectAtIndex:c] deactivate];
} else if (c > rect.origin.x+rect.size.width) {
[[[[self grid] objectAtIndex:r] objectAtIndex:c] deactivate];
} else {
[[[[self grid] objectAtIndex:r] objectAtIndex:c] activate];
}
}
}
[self setNeedsDisplay:YES];
}
- (void)mouseDown:(NSEvent *)theEvent {
BOOL keepOn = YES;
NSPoint mouseLoc;
NSPoint initialMouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil];
NSRect activeRect;
while (keepOn) {
theEvent = [[self window] nextEventMatchingMask: NSLeftMouseUpMask |
NSLeftMouseDraggedMask];
mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:self];
activeRect = [self rectFromBegin:initialMouseLoc end:mouseLoc];
NSRect flippedRect = NSMakeRect(activeRect.origin.x, [self height]-1-(activeRect.origin.y+activeRect.size.height), activeRect.size.width+1, activeRect.size.height+1);
switch ([theEvent type]) {
case NSLeftMouseDragged:
SlateLogger(@"Dragged - (%f,%f) -> (%f,%f)", initialMouseLoc.x, initialMouseLoc.y, mouseLoc.x, mouseLoc.y);
[self activateCellsInRect:activeRect];
break;
case NSLeftMouseUp:
SlateLogger(@"Up - (%f,%f)", mouseLoc.x, mouseLoc.y);
// activate shit
[[self op] activateLayoutWithOrigin:[[ExpressionPoint alloc] initWithX:[NSString stringWithFormat:@"screenOriginX+(screenSizeX*%f/%ld)", flippedRect.origin.x, [self width]]
y:[NSString stringWithFormat:@"screenOriginY+(screenSizeY*%f/%ld)", flippedRect.origin.y, [self height]]]
size:[[ExpressionPoint alloc] initWithX:[NSString stringWithFormat:@"screenSizeX*%f/%ld", flippedRect.size.width, [self width]]
y:[NSString stringWithFormat:@"screenSizeY*%f/%ld", flippedRect.size.height, [self height]]]
screenId:[[[ScreenWrapper alloc] init] getScreenIdForPoint:[NSEvent mouseLocation]]];
[[self op] performSelectorOnMainThread:@selector(killGrids) withObject:nil waitUntilDone:NO];
keepOn = NO;
break;
default:
/* Ignore any other kind of event. */
break;
}
};
return;
}
@end
================================================
FILE: Slate/GridWindow.h
================================================
//
// GridWindow.h
// Slate
//
// Created by Jigish Patel on 9/7/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface GridWindow : NSWindow
@end
================================================
FILE: Slate/GridWindow.m
================================================
//
// GridWindow.m
// Slate
//
// Created by Jigish Patel on 9/7/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "GridWindow.h"
@implementation GridWindow
- (BOOL)canBecomeKeyWindow {
return YES;
}
@end
================================================
FILE: Slate/HintOperation.h
================================================
//
// HintOperation.h
// Slate
//
// Created by Jigish Patel on 3/2/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
#import "Operation.h"
@interface HintOperation : Operation {
@private
NSMutableDictionary *hints;
NSMutableDictionary *windows;
NSMutableDictionary *apps;
NSMutableArray *hotkeyRefs;
NSMutableArray *frames;
NSTimer *hideTimer;
AccessibilityWrapper *currentWindow;
NSInteger currentHint;
NSString *hintCharacters;
BOOL ignoreHidden;
BOOL spreadOnCollision;
NSUInteger spreadSearchWidth;
NSUInteger spreadSearchHeight;
NSUInteger spreadPadding;
}
@property NSMutableDictionary *hints;
@property NSMutableDictionary *windows;
@property NSMutableDictionary *apps;
@property NSMutableArray *frames;
@property NSMutableArray *hotkeyRefs;
@property NSTimer *hideTimer;
@property AccessibilityWrapper *currentWindow;
@property NSInteger currentHint;
@property NSString *hintCharacters;
- (id)initWithCharacters:(NSString *)characters;
- (void)killHints;
- (void)activateHintKey:(NSInteger)hintId;
- (BOOL)collidesWithExistingHint:(NSPoint)origin;
+ (id)hintOperation;
+ (id)hintOperationFromString:(NSString *)hintOperation;
@end
================================================
FILE: Slate/HintOperation.m
================================================
//
// HintOperation.m
// Slate
//
// Created by Jigish Patel on 3/2/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "HintOperation.h"
#import "Constants.h"
#import "ScreenWrapper.h"
#import "HintWindow.h"
#import "HintView.h"
#import "AccessibilityWrapper.h"
#import "Binding.h"
#import "SlateAppDelegate.h"
#import "SlateConfig.h"
#import "StringTokenizer.h"
#import "SlateLogger.h"
#import "ExpressionPoint.h"
#import "RunningApplications.h"
@implementation HintOperation
@synthesize hints, windows, apps, hotkeyRefs, frames, hideTimer, currentWindow, currentHint, hintCharacters;
static const UInt32 ESC_HINT_ID = 10001;
- (id)init {
self = [super init];
if (self) {
hints = [NSMutableDictionary dictionary];
windows = [NSMutableDictionary dictionary];
apps = [NSMutableDictionary dictionary];
hotkeyRefs = [NSMutableArray array];
frames = [NSMutableArray arrayWithCapacity:7];
hideTimer = nil;
currentHint = 0;
[self setHintCharacters:HINT_CHARACTERS];
ignoreHidden = [[SlateConfig getInstance] getBoolConfig:WINDOW_HINTS_IGNORE_HIDDEN_WINDOWS];
spreadOnCollision = [[SlateConfig getInstance] getBoolConfig:WINDOW_HINTS_SPREAD];
spreadPadding = [[SlateConfig getInstance] getIntegerConfig:WINDOW_HINTS_SPREAD_PADDING];
spreadSearchHeight = [[SlateConfig getInstance] getIntegerConfig:WINDOW_HINTS_SPREAD_SEARCH_HEIGHT];
spreadSearchWidth = [[SlateConfig getInstance] getIntegerConfig:WINDOW_HINTS_SPREAD_SEARCH_WIDTH];
}
return self;
}
- (id)initWithCharacters:(NSString *)characters {
self = [self init];
if (self) {
if (characters != nil) [self setHintCharacters:characters];
}
return self;
}
- (NSString *)currentHintCode {
NSString *code = nil;
if (currentHint >= [hintCharacters length]) {
SlateLogger(@" %ld > %ld, not giving code", currentHint, [hintCharacters length]);
return code;
}
code = [hintCharacters substringWithRange:NSMakeRange(currentHint, 1)];
SlateLogger(@" GIVING CODE: %@", code);
return code;
}
- (BOOL)doOperation {
SlateLogger(@"----------------- Begin Hint Operation -----------------");
ScreenWrapper *sw = [[ScreenWrapper alloc] init];
BOOL success = [self doOperationWithAccessibilityWrapper:nil screenWrapper:sw];
SlateLogger(@"----------------- End Hint Operation -----------------");
return success;
}
// the nsApp parameter is used to retrieve the icon if necessary.
// if it is nil and the icon is needed, the NSRunningApplication will be retrieved via process ID
// from the appRef.
- (void)createHintWindowFor:(AXUIElementRef)windowRef inApp:(AXUIElementRef)appRef screenWrapper:(ScreenWrapper *)sw {
SlateLogger(@" attempting to hint");
NSString *hintCode = [self currentHintCode];
if (hintCode == nil) {
SlateLogger(@" HINTCODE NIL!");
return;
}
NSNumber *currentHintNumber = [NSNumber numberWithInteger:currentHint];
AccessibilityWrapper *aw = [[AccessibilityWrapper alloc] initWithApp:appRef window:windowRef];
NSPoint wTL = [aw getCurrentTopLeft];
NSSize wSize = [aw getCurrentSize];
// check corners and center to see which screen window is on
NSInteger screenId = [sw getScreenIdForPoint:wTL];
if (screenId < 0) screenId = [sw getScreenIdForPoint:NSMakePoint(wTL.x+wSize.width/2, wTL.y+wSize.height/2)];
if (screenId < 0) screenId = [sw getScreenIdForPoint:NSMakePoint(wTL.x+wSize.width, wTL.y)];
if (screenId < 0) screenId = [sw getScreenIdForPoint:NSMakePoint(wTL.x+wSize.width, wTL.y+wSize.height)];
if (screenId < 0) screenId = [sw getScreenIdForPoint:NSMakePoint(wTL.x, wTL.y+wSize.height)];
if (screenId < 0) {
SlateLogger(@" Window is offscreen, not creating hint.");
return;
}
NSScreen *screen = [[sw screens] objectAtIndex:screenId];
// convert top left to screen relative for the NSWindow
NSPoint tl = [sw convertTopLeftToScreenRelative:wTL screen:screenId];
// now need to flip y coord
tl.y = [screen frame].size.height - ([sw isMainScreen:screenId] ? MAIN_MENU_HEIGHT : 0) - tl.y;
NSMutableDictionary *values = [[sw getScreenAndWindowValues:screenId window:NSMakeRect(tl.x, tl.y, wSize.width, wSize.height) newSize:wSize] mutableCopy];
float whHeight = [ExpressionPoint expToFloat:[[SlateConfig getInstance] getConfig:WINDOW_HINTS_HEIGHT] withDict:values];
float whWidth = [ExpressionPoint expToFloat:[[SlateConfig getInstance] getConfig:WINDOW_HINTS_WIDTH] withDict:values];
[values setObject:[NSNumber numberWithFloat:whWidth] forKey:WINDOW_HINTS_WIDTH];
[values setObject:[NSNumber numberWithFloat:whHeight] forKey:WINDOW_HINTS_HEIGHT];
// these two arrays are guarenteed to be the same size because of testOperation
NSArray *whTLXArr = [[SlateConfig getInstance] getArrayConfig:WINDOW_HINTS_TOP_LEFT_X];
NSArray *whTLYArr = [[SlateConfig getInstance] getArrayConfig:WINDOW_HINTS_TOP_LEFT_Y];
NSInteger i = 0;
float whTLXRel = [ExpressionPoint expToFloat:[whTLXArr objectAtIndex:i] withDict:values];
float whTLYRel = [ExpressionPoint expToFloat:[whTLYArr objectAtIndex:i] withDict:values];
float whTLX = tl.x + whTLXRel;
float whTLY = tl.y - whTLYRel;
NSRect frame = NSMakeRect(whTLX, whTLY - whHeight, whWidth, whHeight);
// check frame boundaries to make sure it is over the window we want it to be over
// cycle through config for all possible locations
if (ignoreHidden) {
BOOL foundValidLocation = NO;
do {
whTLXRel = [ExpressionPoint expToFloat:[whTLXArr objectAtIndex:i] withDict:values];
whTLYRel = [ExpressionPoint expToFloat:[whTLYArr objectAtIndex:i] withDict:values];
whTLX = tl.x + whTLXRel;
whTLY = tl.y - whTLYRel;
frame = NSMakeRect(whTLX, whTLY - whHeight, whWidth, whHeight);
if ([[AccessibilityWrapper getTitle:[AccessibilityWrapper
windowUnderPoint:NSMakePoint(wTL.x + whTLXRel + whWidth/2,
wTL.y + whTLYRel + whHeight/2)]]
isEqualToString:[AccessibilityWrapper getTitle:windowRef]]) {
foundValidLocation = YES;
}
i++;
} while (!foundValidLocation && i < [whTLXArr count]);
if (!foundValidLocation) {
SlateLogger(@" No locations visible, do not show hint!");
return;
}
}
if (spreadOnCollision) {
// if it collides, spread it down
while ([self collidesWithExistingHint:frame.origin]) {
frame = NSMakeRect(frame.origin.x, frame.origin.y - whHeight - spreadPadding,
frame.size.width, frame.size.height);
}
[frames addObject:[NSValue valueWithRect:frame]];
}
if ([hints objectForKey:currentHintNumber] == nil) {
SlateLogger(@" New Window!");
NSWindow *window = [[HintWindow alloc] initWithContentRect:frame
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO
screen:screen];
[window setReleasedWhenClosed:NO];
[window setOpaque:NO];
[window setBackgroundColor:[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.0]];
[window makeKeyAndOrderFront:NSApp];
[window setLevel:(NSScreenSaverWindowLevel - 1)];
HintView *label = [[HintView alloc] initWithFrame:frame];
[label setText:hintCode];
[label setIconFromAppRef:appRef];
[window setContentView:label];
NSWindowController *wc = [[NSWindowController alloc] initWithWindow:window];
[hints setObject:wc forKey:currentHintNumber];
} else {
SlateLogger(@" Existing Window!");
NSWindowController *wc = [hints objectForKey:currentHintNumber];
[[wc window] setFrame:NSMakeRect(frame.origin.x+screen.frame.origin.x, frame.origin.y+screen.frame.origin.y, frame.size.width, frame.size.height) display:NO];
HintView *label = (HintView*)[[wc window] contentView];
[label setIconFromAppRef:appRef];
[wc showWindow:[wc window]];
}
[windows setObject:[NSValue valueWithPointer:windowRef] forKey:currentHintNumber];
[apps setObject:[NSValue valueWithPointer:appRef] forKey:currentHintNumber];
// Register the hotkey
NSNumber *keyCode = [[Binding asciiToCodeDict] objectForKey:[hintCode lowercaseString]];
EventHotKeyID myHotKeyID;
EventHotKeyRef myHotKeyRef;
myHotKeyID.signature = *[[NSString stringWithFormat:@"hotkey%ld",currentHint] cStringUsingEncoding:NSASCIIStringEncoding];
myHotKeyID.id = (UInt32)currentHint;
RegisterEventHotKey([keyCode unsignedIntValue], 0, myHotKeyID, GetEventMonitorTarget(), 0, &myHotKeyRef);
[hotkeyRefs addObject:[NSValue valueWithPointer:myHotKeyRef]];
currentHint++;
}
// check if the origin of this hint is in
- (BOOL)collidesWithExistingHint:(NSPoint)origin {
for (NSValue *rectVal in frames) {
NSPoint other = [rectVal rectValue].origin;
// make a rect of the search width and height centered on the origin of the other point
NSRect otherRect = NSMakeRect(other.x - spreadSearchWidth/2, other.y - spreadSearchHeight/2,
spreadSearchWidth, spreadSearchHeight);
if(NSPointInRect(origin, otherRect)) {
return true;
}
}
return false;
}
CFComparisonResult leftToRightWindows(const void *val1, const void *val2, void *context) {
AXUIElementRef w1 = val1;
AXUIElementRef w2 = val2;
NSPoint w1TL = [AccessibilityWrapper getTopLeftForWindow:w1];
NSPoint w2TL = [AccessibilityWrapper getTopLeftForWindow:w2];
if (w1TL.x < w2TL.x) {
return kCFCompareLessThan;
} else if (w1TL.x > w2TL.x) {
return kCFCompareGreaterThan;
} else {
if (w1TL.y < w2TL.y) {
return kCFCompareLessThan;
} else if (w1TL.y > w2TL.y) {
return kCFCompareGreaterThan;
}
}
return kCFCompareEqualTo;
}
CFComparisonResult rightToLeftWindows(const void *val1, const void *val2, void *context) {
AXUIElementRef w1 = val1;
AXUIElementRef w2 = val2;
NSPoint w1TL = [AccessibilityWrapper getTopLeftForWindow:w1];
NSPoint w2TL = [AccessibilityWrapper getTopLeftForWindow:w2];
if (w1TL.x < w2TL.x) {
return kCFCompareGreaterThan;
} else if (w1TL.x > w2TL.x) {
return kCFCompareLessThan;
} else {
if (w1TL.y < w2TL.y) {
return kCFCompareLessThan;
} else if (w1TL.y > w2TL.y) {
return kCFCompareGreaterThan;
}
}
return kCFCompareEqualTo;
}
- (BOOL)doOperationWithAccessibilityWrapper:(AccessibilityWrapper *)iamnil screenWrapper:(ScreenWrapper *)sw {
if (hideTimer != nil) return YES;
[self evalOptionsWithAccessibilityWrapper:iamnil screenWrapper:sw];
[(SlateAppDelegate *)[NSApp delegate] setCurrentHintOperation:self];
ignoreHidden = [[SlateConfig getInstance] getBoolConfig:WINDOW_HINTS_IGNORE_HIDDEN_WINDOWS];
[self setCurrentHint:0];
[frames removeAllObjects];
[self setCurrentWindow:[[AccessibilityWrapper alloc] init]];
if ([[[SlateConfig getInstance] getConfig:WINDOW_HINTS_ORDER] isEqualToString:WINDOW_HINTS_ORDER_NONE]) {
// normal fast way
for (NSRunningApplication *app in [RunningApplications getInstance]) {
pid_t appPID = [app processIdentifier];
SlateLogger(@"I see application '%@' with pid '%d'", [app localizedName], appPID);
AXUIElementRef appRef = AXUIElementCreateApplication(appPID);
CFArrayRef windowsArr = [AccessibilityWrapper windowsInApp:appRef];
if (!windowsArr || CFArrayGetCount(windowsArr) == 0) continue;
for (NSInteger i = 0; i < CFArrayGetCount(windowsArr); i++) {
NSString *title = [AccessibilityWrapper getTitle:CFArrayGetValueAtIndex(windowsArr, i)];
if (title == nil || [EMPTY isEqualToString:title]) continue; // skip empty title windows because they are invisible
SlateLogger(@" Hinting Window: %@", title);
[self createHintWindowFor:CFArrayGetValueAtIndex(windowsArr, i) inApp:appRef screenWrapper:sw];
}
}
} else if ([[[SlateConfig getInstance] getConfig:WINDOW_HINTS_ORDER] isEqualToString:WINDOW_HINTS_ORDER_PERSIST]) {
// same hint always
for (NSRunningApplication *app in [RunningApplications getInstance]) {
pid_t appPID = [app processIdentifier];
SlateLogger(@"I see application '%@' with pid '%d'", [app localizedName], appPID);
AXUIElementRef appRef = AXUIElementCreateApplication(appPID);
CFArrayRef windowsArr = [AccessibilityWrapper windowsInApp:appRef];
if (!windowsArr || CFArrayGetCount(windowsArr) == 0) continue;
for (NSInteger i = 0; i < CFArrayGetCount(windowsArr); i++) {
NSString *title = [AccessibilityWrapper getTitle:CFArrayGetValueAtIndex(windowsArr, i)];
if (title == nil || [EMPTY isEqualToString:title]) continue; // skip empty title windows because they are invisible
if ([[RunningApplications getInstance] windowIdsForTitle:title] == nil) continue;
SlateLogger(@" Hinting Window: %@", title);
NSArray *possibleHints = [[RunningApplications getInstance] windowIdsForTitle:title];
for (NSNumber *possibleHint in possibleHints) {
if ([hints objectForKey:possibleHint]) continue;
[self setCurrentHint:[possibleHint integerValue]];
[self createHintWindowFor:CFArrayGetValueAtIndex(windowsArr, i) inApp:appRef screenWrapper:sw];
break;
}
}
}
} else {
// custom sort order
CFMutableArrayRef allWindows = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
for (NSRunningApplication *app in [RunningApplications getInstance]) {
pid_t appPID = [app processIdentifier];
SlateLogger(@"I see application '%@' with pid '%d'", [app localizedName], appPID);
AXUIElementRef appRef = AXUIElementCreateApplication(appPID);
CFArrayRef windowsArr = [AccessibilityWrapper windowsInApp:appRef];
if (windowsArr != nil && windowsArr != NULL && CFArrayGetCount(windowsArr) > 0) {
CFArrayAppendArray(allWindows, windowsArr, CFRangeMake(0, CFArrayGetCount(windowsArr)));
}
}
// check which custom order and sort accordingly. if someone was stupid and entered a bad config, do nothing.
if ([[[SlateConfig getInstance] getConfig:WINDOW_HINTS_ORDER] isEqualToString:WINDOW_HINTS_ORDER_LEFT_TO_RIGHT]) {
CFArraySortValues(allWindows, CFRangeMake(0, CFArrayGetCount(allWindows)), leftToRightWindows, NULL);
} else if ([[[SlateConfig getInstance] getConfig:WINDOW_HINTS_ORDER] isEqualToString:WINDOW_HINTS_ORDER_RIGHT_TO_LEFT]) {
CFArraySortValues(allWindows, CFRangeMake(0, CFArrayGetCount(allWindows)), rightToLeftWindows, NULL);
}
for (NSInteger i = 0; i < CFArrayGetCount(allWindows); i++) {
NSString *title = [AccessibilityWrapper getTitle:CFArrayGetValueAtIndex(allWindows, i)];
if (title == nil || [EMPTY isEqualToString:title]) continue; // skip empty title windows because they are invisible
SlateLogger(@" Hinting Window: %@", title);
CFTypeRef _window = CFArrayGetValueAtIndex(allWindows, i);
[self createHintWindowFor:(AXUIElementRef)_window inApp:[AccessibilityWrapper applicationForElement:(AXUIElementRef)_window] screenWrapper:sw];
}
}
// Register the escape hotkey
NSNumber *keyCode = [[Binding asciiToCodeDict] objectForKey:@"esc"];
EventHotKeyID myHotKeyID;
EventHotKeyRef myHotKeyRef;
myHotKeyID.signature = *[@"hotkeyESC" cStringUsingEncoding:NSASCIIStringEncoding];
myHotKeyID.id = (UInt32)(ESC_HINT_ID);
RegisterEventHotKey([keyCode unsignedIntValue], 0, myHotKeyID, GetEventMonitorTarget(), 0, &myHotKeyRef);
[hotkeyRefs addObject:[NSValue valueWithPointer:myHotKeyRef]];
// Set the hide timer
[self setHideTimer:[NSTimer scheduledTimerWithTimeInterval:[[SlateConfig getInstance] getFloatConfig:WINDOW_HINTS_DURATION]
target:self
selector:@selector(killHints)
userInfo:nil
repeats:NO]];
return YES;
}
- (BOOL)testOperation {
NSArray *whTLXArr = [[SlateConfig getInstance] getArrayConfig:WINDOW_HINTS_TOP_LEFT_X];
NSArray *whTLYArr = [[SlateConfig getInstance] getArrayConfig:WINDOW_HINTS_TOP_LEFT_Y];
return [whTLXArr count] == [whTLYArr count] && [whTLXArr count] > 0;
}
- (void)killHints {
[self killHints:NO];
}
- (void)killHints:(BOOL)refocus {
for (NSValue *hotkeyRef in hotkeyRefs) {
UnregisterEventHotKey([hotkeyRef pointerValue]);
}
[hotkeyRefs removeAllObjects];
if ([self hideTimer]) [[self hideTimer] invalidate];
[self setHideTimer:nil];
for (NSWindowController *hint in [hints allValues]) {
[hint close];
}
[windows removeAllObjects];
[apps removeAllObjects];
if (refocus && currentWindow) [currentWindow focus];
[self setCurrentWindow:nil];
[(SlateAppDelegate *)[NSApp delegate] setCurrentHintOperation:nil];
}
- (void)activateHintKey:(NSInteger)hintId {
if (hintId == ESC_HINT_ID) {
// escape key
[self killHints];
return;
}
NSNumber *currentHintNumber = [NSNumber numberWithInteger:hintId];
if ([hints objectForKey:currentHintNumber] == nil) {
SlateLogger(@"no hint window");
return;
}
AccessibilityWrapper *aw = [[AccessibilityWrapper alloc] initWithApp:[[apps objectForKey:currentHintNumber] pointerValue] window:[[windows objectForKey:currentHintNumber] pointerValue]];
[aw focus];
[self killHints];
SlateLogger(@"focus fail");
}
- (void)parseOption:(NSString *)name value:(id)value {
if (value == nil) { return; }
if ([name isEqualToString:OPT_CHARACTERS]) {
if (![value isKindOfClass:[NSString class]]) {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", name, value] userInfo:nil]);
return;
}
[self setHintCharacters:value];
}
}
+ (id)hintOperation {
return [[HintOperation alloc] init];
}
+ (id)hintOperationFromString:(NSString *)hintOperation {
// hint characters
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:hintOperation into:tokens maxTokens:2];
Operation *op = [[HintOperation alloc] initWithCharacters:([tokens count] > 1) ? [tokens objectAtIndex:1] : nil];
return op;
}
@end
================================================
FILE: Slate/HintView.h
================================================
//
// HintView.h
// Slate
//
// Created by Jigish Patel on 3/3/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface HintView : NSView {
@private
NSString *text;
NSImage *icon;
}
- (void)setIconFromAppRef:(AXUIElementRef)appRef;
@property NSString *text;
@property NSImage *icon;
@end
================================================
FILE: Slate/HintView.m
================================================
//
// HintView.m
// Slate
//
// Created by Jigish Patel on 3/3/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "HintView.h"
#import "Constants.h"
#import "SlateConfig.h"
@implementation HintView
@synthesize text,icon;
static NSColor *hintBackgroundColor = nil;
static NSColor *hintFontColor = nil;
static NSFont *hintFont = nil;
static float hintIconAlpha = -1.0;
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setWantsLayer:YES];
[self setIcon:nil];
if (hintBackgroundColor == nil) {
NSArray *bgColorArr = [[SlateConfig getInstance] getArrayConfig:WINDOW_HINTS_BACKGROUND_COLOR];
if ([bgColorArr count] < 4) bgColorArr = [WINDOW_HINTS_BACKGROUND_COLOR_DEFAULT componentsSeparatedByString:SEMICOLON];
hintBackgroundColor = [NSColor colorWithDeviceRed:[[bgColorArr objectAtIndex:0] floatValue]/255.0
green:[[bgColorArr objectAtIndex:1] floatValue]/255.0
blue:[[bgColorArr objectAtIndex:2] floatValue]/255.0
alpha:[[bgColorArr objectAtIndex:3] floatValue]];
}
if (hintFontColor == nil) {
NSArray *fColorArr = [[SlateConfig getInstance] getArrayConfig:WINDOW_HINTS_FONT_COLOR];
if ([fColorArr count] < 4) fColorArr = [WINDOW_HINTS_FONT_COLOR_DEFAULT componentsSeparatedByString:SEMICOLON];
hintFontColor = [NSColor colorWithDeviceRed:[[fColorArr objectAtIndex:0] floatValue]/255.0
green:[[fColorArr objectAtIndex:1] floatValue]/255.0
blue:[[fColorArr objectAtIndex:2] floatValue]/255.0
alpha:[[fColorArr objectAtIndex:3] floatValue]];
}
if (hintFont == nil) {
hintFont = [NSFont fontWithName:[[SlateConfig getInstance] getConfig:WINDOW_HINTS_FONT_NAME]
size:[[SlateConfig getInstance] getFloatConfig:WINDOW_HINTS_FONT_SIZE]];
}
if (hintIconAlpha < 0.0) {
hintIconAlpha = [[SlateConfig getInstance] getFloatConfig:WINDOW_HINTS_ICON_ALPHA];
}
}
return self;
}
- (void)setIconFromAppRef:(AXUIElementRef)appRef {
if ([[SlateConfig getInstance] getBoolConfig:WINDOW_HINTS_SHOW_ICONS]) {
pid_t pid;
AXUIElementGetPid(appRef, &pid);
NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:pid];
[self setIcon:[app icon]];
}
}
- (void)drawCenteredText:(NSString *)string bounds:(NSRect)rect attributes:(NSDictionary *)attributes {
NSSize size = [string sizeWithAttributes:attributes];
NSPoint origin = NSMakePoint(rect.origin.x + (rect.size.width - size.width) / 2,
rect.origin.y + (rect.size.height - size.height) / 2);
[string drawAtPoint:origin withAttributes:attributes];
}
- (void)drawRect:(NSRect)dirtyRect {
[[NSGraphicsContext currentContext] saveGraphicsState];
[[NSGraphicsContext currentContext] setShouldAntialias:YES];
// draw the rounded rect
[hintBackgroundColor set];
float cornerSize = [[SlateConfig getInstance] getFloatConfig:WINDOW_HINTS_ROUNDED_CORNER_SIZE];
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:[self bounds] xRadius:cornerSize yRadius:cornerSize];
[path fill];
// draw the icon on top of the rounded rect, if specified
if (icon != nil) {
[icon drawInRect:[self bounds] fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:hintIconAlpha];
}
// draw hint letter
[self drawCenteredText:text
bounds:self.bounds
attributes:[NSDictionary dictionaryWithObjectsAndKeys:hintFont,
NSFontAttributeName,
hintFontColor,
NSForegroundColorAttributeName, nil]];
[[NSGraphicsContext currentContext] restoreGraphicsState];
}
@end
================================================
FILE: Slate/HintWindow.h
================================================
//
// HintWindow.h
// Slate
//
// Created by Jigish Patel on 3/3/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface HintWindow : NSWindow
@end
================================================
FILE: Slate/HintWindow.m
================================================
//
// HintWindow.m
// Slate
//
// Created by Jigish Patel on 3/3/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "HintWindow.h"
@implementation HintWindow
- (BOOL)canBecomeKeyWindow {
return YES;
}
@end
================================================
FILE: Slate/JSApplicationWrapper.h
================================================
//
// JSApplicationWrapper.h
// Slate
//
// Created by Jigish Patel on 1/21/13.
// Copyright 2013 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@class AccessibilityWrapper;
@class ScreenWrapper;
@interface JSApplicationWrapper : NSObject {
AccessibilityWrapper *aw;
ScreenWrapper *sw;
NSRunningApplication *app;
}
@property (strong) AccessibilityWrapper *aw;
@property (strong) ScreenWrapper *sw;
@property (strong) NSRunningApplication *app;
- (id)initWithAccessibilityWrapper:(AccessibilityWrapper *)_aw screenWrapper:(ScreenWrapper *)_sw;
- (id)initWithRunningApplication:(NSRunningApplication *)_app screenWrapper:(ScreenWrapper *)_sw;
- (NSString *)toString;
@end
================================================
FILE: Slate/JSApplicationWrapper.m
================================================
//
// JSApplicationWrapper.m
// Slate
//
// Created by Jigish Patel on 1/21/13.
// Copyright 2013 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "JSApplicationWrapper.h"
#import "AccessibilityWrapper.h"
#import "ScreenWrapper.h"
#import "JSController.h"
#import "JSWindowWrapper.h"
#import "JSOperationWrapper.h"
@implementation JSApplicationWrapper
static NSDictionary *jsawJsMethods;
@synthesize aw, sw, app;
- (id)init {
self = [super init];
if (self) {
[self setAw:[[AccessibilityWrapper alloc] init]];
[self setSw:[[ScreenWrapper alloc] init]];
[self setApp:[NSRunningApplication runningApplicationWithProcessIdentifier:[aw processIdentifier]]];
[JSApplicationWrapper setJsMethods];
}
return self;
}
- (id)initWithAccessibilityWrapper:(AccessibilityWrapper *)_aw screenWrapper:(ScreenWrapper *)_sw {
self = [super init];
if (self) {
[self setAw:_aw];
[self setSw:_sw];
[self setApp:[NSRunningApplication runningApplicationWithProcessIdentifier:[aw processIdentifier]]];
[JSApplicationWrapper setJsMethods];
}
return self;
}
- (id)initWithRunningApplication:(NSRunningApplication *)_app screenWrapper:(ScreenWrapper *)_sw {
self = [super init];
if (self) {
[self setApp:_app];
[self setAw:nil];
[self setSw:_sw];
[JSApplicationWrapper setJsMethods];
}
return self;
}
- (pid_t)pid {
return [app processIdentifier];
}
- (NSString *)name {
return [app localizedName];
}
- (id)mwindow {
return [self mainWindow];
}
- (id)mainWindow {
AccessibilityWrapper *_aw = [[AccessibilityWrapper alloc] initWithApp:AXUIElementCreateApplication([app processIdentifier])
window:[AccessibilityWrapper focusedWindowInRunningApp:app]];
return [[JSWindowWrapper alloc] initWithAccessibilityWrapper:_aw screenWrapper:sw];
}
- (void)ewindow:(id)obj {
[self eachWindow:obj];
}
- (void)eachWindow:(id)funcOrOp {
NSString *type = [[JSController getInstance] jsTypeof:funcOrOp];
CFArrayRef windowsArrRef = [AccessibilityWrapper windowsInRunningApp:app];
if (!windowsArrRef || CFArrayGetCount(windowsArrRef) == 0) return;
CFMutableArrayRef windowsArr = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, windowsArrRef);
if ([funcOrOp isKindOfClass:[Operation class]] || [funcOrOp isKindOfClass:[JSOperationWrapper class]]) {
for (NSInteger i = 0; i < CFArrayGetCount(windowsArr); i++) {
AccessibilityWrapper *_aw = [[AccessibilityWrapper alloc] initWithApp:AXUIElementCreateApplication([app processIdentifier])
window:CFArrayGetValueAtIndex(windowsArr, i)];
[funcOrOp doOperationWithAccessibilityWrapper:_aw screenWrapper:sw];
}
} else if ([@"function" isEqualToString:type]) {
for (NSInteger i = 0; i < CFArrayGetCount(windowsArr); i++) {
AccessibilityWrapper *_aw = [[AccessibilityWrapper alloc] initWithApp:AXUIElementCreateApplication([app processIdentifier])
window:CFArrayGetValueAtIndex(windowsArr, i)];
[[JSController getInstance] runFunction:funcOrOp withArg:[[JSWindowWrapper alloc] initWithAccessibilityWrapper:_aw screenWrapper:sw]];
}
}
}
- (NSString *)toString {
return [self name];
}
+ (void)setJsMethods {
if (jsawJsMethods == nil) {
jsawJsMethods = @{
NSStringFromSelector(@selector(pid)): @"pid",
NSStringFromSelector(@selector(name)): @"name",
NSStringFromSelector(@selector(eachWindow:)): @"eachWindow",
NSStringFromSelector(@selector(ewindow:)): @"ewindow",
NSStringFromSelector(@selector(mainWindow)): @"mainWindow",
NSStringFromSelector(@selector(mwindow)): @"mwindow",
};
}
}
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)sel {
return [jsawJsMethods objectForKey:NSStringFromSelector(sel)] == NULL;
}
+ (NSString *)webScriptNameForSelector:(SEL)sel {
return [jsawJsMethods objectForKey:NSStringFromSelector(sel)];
}
@end
================================================
FILE: Slate/JSController.h
================================================
//
// JSController.h
// Slate
//
// Created by Alex Morega on 2013-01-16.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
#import
#import "Operation.h"
@interface JSController : NSObject {
WebView *webView;
WebScriptObject *scriptObject;
NSMutableDictionary *functions;
BOOL inited;
NSMutableDictionary *eventCallbacks;
}
@property NSMutableDictionary *functions;
@property NSMutableDictionary *eventCallbacks;
- (BOOL)loadConfigFileWithPath:(NSString *)path;
- (void)runCallbacks:(NSString *)what payload:(id)payload;
- (NSString *)addCallableFunction:(WebScriptObject *)function;
- (id)runCallableFunction:(NSString *)key;
- (id)runFunction:(WebScriptObject*)function;
- (id)runFunction:(WebScriptObject *)function withArg:(id)arg;
- (id)unmarshall:(id)obj;
- (id)marshall:(id)obj;
- (NSString *)jsTypeof:(WebScriptObject *)obj;
+ (JSController *)getInstance;
@end
================================================
FILE: Slate/JSController.m
================================================
//
// JSController.m
// Slate
//
// Created by Alex Morega on 2013-01-16.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "JSController.h"
#import "Binding.h"
#import "SlateLogger.h"
#import "SlateConfig.h"
#import "JSInfoWrapper.h"
#import "JSScreenWrapper.h"
#import "Constants.h"
#import "JSOperation.h"
#import "ShellUtils.h"
#import "JSApplicationWrapper.h"
#import "JSOperationWrapper.h"
@implementation JSController
@synthesize functions;
@synthesize eventCallbacks;
static JSController *_instance = nil;
static NSDictionary *jscJsMethods;
- (JSController *) init {
self = [super init];
if (self) {
inited = NO;
self.functions = [NSMutableDictionary dictionary];
self.eventCallbacks = [NSMutableDictionary dictionary];
webView = [[WebView alloc] init];
[JSController setJsMethods];
}
return self;
}
- (id)run:(NSString*)code {
NSString* script = [NSString stringWithFormat:@"try { %@ } catch (___ex___) { 'EXCEPTION: '+___ex___; }", code];
id data = [scriptObject evaluateWebScript:script];
if(![data isMemberOfClass:[WebUndefined class]]) {
SlateLogger(@"%@", data);
if ([data isKindOfClass:[NSString class]] && [data hasPrefix:@"EXCEPTION: "]) {
@throw([NSException exceptionWithName:@"JavaScript Error" reason:data userInfo:nil]);
}
}
return [self unmarshall:data];
}
- (NSString *)genFuncKey {
return [NSString stringWithFormat:@"javascript:function[%ld]", [functions count]];
}
- (NSString *)addCallableFunction:(WebScriptObject *)function {
NSString *key = [self genFuncKey];
[functions setObject:function forKey:key];
return key;
}
- (id)runCallableFunction:(NSString *)key {
WebScriptObject *func = [functions objectForKey:key];
if (func == nil) { return nil; }
return [self runFunction:func];
}
- (id)runFunction:(WebScriptObject *)function {
[scriptObject setValue:function forKey:@"_slate_callback"];
return [self run:@"window._slate_callback();"];
}
- (id)runFunction:(WebScriptObject *)function withArg:(id)arg {
[scriptObject setValue:function forKey:@"_slate_callback"];
[scriptObject setValue:arg forKey:@"_slate_callback_arg"];
return [self run:@"window._slate_callback(window._slate_callback_arg);"];
}
- (id)runFunction:(WebScriptObject *)function withArg:(id)arg secondArg:(id)arg2 {
[scriptObject setValue:function forKey:@"_slate_callback"];
[scriptObject setValue:arg forKey:@"_slate_callback_arg"];
[scriptObject setValue:arg2 forKey:@"_slate_callback_arg2"];
return [self run:@"window._slate_callback(window._slate_callback_arg, window._slate_callback_arg2);"];
}
- (BOOL)runFile:(NSString*)path {
NSError *err;
NSString *fileString = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&err];
if(err == nil && fileString != nil && fileString != NULL) {
[self run:fileString];
return YES;
}
return NO;
}
- (void)setInfo {
[scriptObject setValue:[JSInfoWrapper getInstance] forKey:@"_info"];
}
- (void)initializeWebView {
if (inited) { return; }
[[webView mainFrame] loadHTMLString:@"" baseURL:NULL];
scriptObject = [webView windowScriptObject];
[scriptObject setValue:self forKey:@"_controller"];
[self setInfo];
@try {
[self runFile:[[NSBundle mainBundle] pathForResource:@"underscore" ofType:@"js"]];
[self runFile:[[NSBundle mainBundle] pathForResource:@"utils" ofType:@"js"]];
[self runFile:[[NSBundle mainBundle] pathForResource:@"initialize" ofType:@"js"]];
} @catch (NSException *ex) {
SlateLogger(@" ERROR %@",[ex name]);
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Quit", @"Skip", nil]];
[alert setMessageText:[ex name]];
[alert setInformativeText:[ex reason]];
if ([alert runModal] == NSAlertFirstButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
}
inited = YES;
}
- (BOOL)loadConfigFileWithPath:(NSString *)path {
[self initializeWebView];
@try {
return [self runFile:[path stringByExpandingTildeInPath]];
} @catch (NSException *ex) {
SlateLogger(@" ERROR %@",[ex name]);
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Quit", @"Skip", nil]];
[alert setMessageText:[ex name]];
[alert setInformativeText:[ex reason]];
if ([alert runModal] == NSAlertFirstButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
}
return NO;
}
- (void)configFunction:(NSString *)key callback:(WebScriptObject *)callback {
NSString *fkey = [self addCallableFunction:callback];
[[[SlateConfig getInstance] configs] setValue:[NSString stringWithFormat:@"_javascript_::%@", fkey] forKey:key];
}
- (void)configNative:(NSString *)key callback:(id)callback {
[[[SlateConfig getInstance] configs] setValue:[NSString stringWithFormat:@"%@", callback] forKey:key];
}
- (void)bindFunction:(NSString *)hotkey callback:(WebScriptObject *)callback repeat:(id)_repeat {
JSOperation *op = [JSOperation jsOperationWithFunction:callback];
BOOL repeat = NO;
if (_repeat != nil && ([_repeat isKindOfClass:[NSNumber class]] || [_repeat isKindOfClass:[NSValue class]] || [_repeat isKindOfClass:[NSString class]])) {
repeat = [_repeat boolValue];
} else {
repeat = [Operation isRepeatOnHoldOp:[op opName]];
}
@try {
Binding *bind = [[Binding alloc] initWithKeystroke:hotkey operation:op repeat:repeat];
[[SlateConfig getInstance] addBinding:bind];
} @catch (NSException *ex) {
SlateLogger(@" ERROR %@",[ex name]);
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Quit", @"Skip", nil]];
[alert setMessageText:[ex name]];
[alert setInformativeText:[ex reason]];
if ([alert runModal] == NSAlertFirstButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
}
}
- (void)bindNative:(NSString *)hotkey callback:(JSOperationWrapper *)opWrapper repeat:(id)_repeat {
Operation *op = [opWrapper op];
BOOL repeat = NO;
if (_repeat != nil && ([_repeat isKindOfClass:[NSNumber class]] || [_repeat isKindOfClass:[NSValue class]] || [_repeat isKindOfClass:[NSString class]])) {
repeat = [_repeat boolValue];
} else {
repeat = [Operation isRepeatOnHoldOp:[op opName]];
}
@try {
Binding *bind = [[Binding alloc] initWithKeystroke:hotkey operation:op repeat:repeat];
[[SlateConfig getInstance] addBinding:bind];
} @catch (NSException *ex) {
SlateLogger(@" ERROR %@",[ex name]);
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Quit", @"Skip", nil]];
[alert setMessageText:[ex name]];
[alert setInformativeText:[ex reason]];
if ([alert runModal] == NSAlertFirstButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
}
}
- (NSString *)layout:(NSString *)name hash:(WebScriptObject *)hash {
NSMutableDictionary *dict = [[self unmarshall:hash] mutableCopy];
for (NSString *app in [dict allKeys]) {
id tmpDict = [dict objectForKey:app];
if (![tmpDict isKindOfClass:[NSDictionary class]]) {
continue;
}
NSMutableDictionary *appDict = [tmpDict mutableCopy];
if ([appDict objectForKey:OPT_OPERATIONS] == nil) {
continue;
}
id _operations = [appDict objectForKey:OPT_OPERATIONS];
NSMutableArray *ops = [NSMutableArray array];
if ([_operations isKindOfClass:[Operation class]]) {
// this is an operation wrapper
[ops addObject:_operations];
} else if ([_operations isKindOfClass:[WebScriptObject class]]) {
// this is a function
Operation *op = [JSOperation jsOperationWithFunction:_operations];
if (op == nil) { continue; }
[ops addObject:op];
} else if ([_operations isKindOfClass:[NSArray class]]) {
// array of operations and/or functions
for (id obj in _operations) {
if ([obj isKindOfClass:[Operation class]]) {
// this is an operation key
[ops addObject:obj];
} else if ([obj isKindOfClass:[WebScriptObject class]]) {
// this is a function
Operation *op = [JSOperation jsOperationWithFunction:obj];
if (op == nil) { continue; }
[ops addObject:op];
}
}
}
if ([ops count] == 0) { continue; }
[appDict setObject:ops forKey:OPT_OPERATIONS];
[dict setObject:appDict forKey:app];
}
if (![[SlateConfig getInstance] addLayout:name dict:dict]) { return nil; }
return name;
}
- (void)default:(id)config toAction:(id)_action {
id screenConfig = [self unmarshall:config];
if ([screenConfig isKindOfClass:[NSNumber class]] || [screenConfig isKindOfClass:[NSValue class]] || [screenConfig isKindOfClass:[NSString class]]) {
// count
} else if ([screenConfig isKindOfClass:[NSArray class]]) {
// resolutions
} else {
// wtf?
return;
}
id action = [self unmarshall:_action];
id name = nil;
if ([action isKindOfClass:[NSString class]]) {
name = action;
} else if ([action isKindOfClass:[WebScriptObject class]]) {
name = [JSOperation jsOperationWithFunction:action];
} else {
// wtf?
return;
}
[[SlateConfig getInstance] addDefault:screenConfig layout:name];
}
- (NSString *)shell:(NSString *)commandAndArgs wait:(NSNumber *)wait path:(NSString *)path {
if ([path isMemberOfClass:[WebUndefined class]]) {
return [ShellUtils run:commandAndArgs wait:[wait boolValue] path:nil];
}
return [ShellUtils run:commandAndArgs wait:[wait boolValue] path:path];
}
- (JSOperationWrapper *)operation:(NSString *)name options:(WebScriptObject *)opts {
@try {
return [JSOperationWrapper operation:name options:opts];
} @catch (NSException *ex) {
SlateLogger(@" ERROR %@",[ex name]);
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Quit", @"Skip", nil]];
[alert setMessageText:[ex name]];
[alert setInformativeText:[ex reason]];
if ([alert runModal] == NSAlertFirstButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
}
return nil;
}
- (BOOL)doOperation:(NSString *)op options:(id)opts {
@try {
id options = [self unmarshall:opts];
if ([options isKindOfClass:[NSDictionary class]]) {
return [Operation doOperation:op options:options aw:[[AccessibilityWrapper alloc] init] sw:[[ScreenWrapper alloc] init]];
}
} @catch (NSException *ex) {
SlateLogger(@" ERROR %@",[ex name]);
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Quit", @"Skip", nil]];
[alert setMessageText:[ex name]];
[alert setInformativeText:[ex reason]];
if ([alert runModal] == NSAlertFirstButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
}
return NO;
}
- (JSOperationWrapper *)operationFromString:(NSString *)opString {
return [JSOperationWrapper operationFromString:opString];
}
- (BOOL)source:(NSString *)path {
return [[SlateConfig getInstance] loadConfigFileWithPath:path];
}
- (void)log:(id)msg {
NSLog(@"%@", msg);
}
- (BOOL)isValidEvent:(NSString *)what {
return [what isEqualToString:@"windowClosed"] || [what isEqualToString:@"windowMoved"] || [what isEqualToString:@"windowResized"] ||
[what isEqualToString:@"windowOpened"] || [what isEqualToString:@"windowFocused"] || [what isEqualToString:@"windowTitleChanged"] ||
[what isEqualToString:@"appClosed"] || [what isEqualToString:@"appOpened"] || [what isEqualToString:@"appHidden"] ||
[what isEqualToString:@"appUnhidden"] || [what isEqualToString:@"appDeactivated"] || [what isEqualToString:@"appActivated"] ||
[what isEqualToString:@"screenConfigurationChanged"];
}
- (void)on:(NSString *)what do:(WebScriptObject *)callback {
if (![self isValidEvent:what]) {
SlateLogger(@" ERROR: Invalid Event %@",what);
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Quit", @"Skip", nil]];
[alert setMessageText:@"ERROR: Invalid Event"];
[alert setInformativeText:what];
if ([alert runModal] == NSAlertFirstButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
}
if ([what isEqualToString:@"windowMoved"]) {
[[SlateConfig getInstance] setConfig:JS_RECEIVE_MOVE_EVENT to:@"true"];
} else if ([what isEqualToString:@"windowResized"]) {
[[SlateConfig getInstance] setConfig:JS_RECEIVE_RESIZE_EVENT to:@"true"];
}
NSMutableArray *callbacks = [[self eventCallbacks] objectForKey:what];
if (callbacks == nil) {
callbacks = [NSMutableArray array];
[[self eventCallbacks] setObject:callbacks forKey:what];
}
[callbacks addObject:callback];
}
- (void)runCallbacks:(NSString *)what payload:(id)payload {
NSArray *callbacks = [[self eventCallbacks] objectForKey:what];
if (callbacks == nil || [callbacks count] == 0) { return; }
for (WebScriptObject *callback in callbacks) {
[self runFunction:callback withArg:what secondArg:payload];
}
}
- (WebScriptObject *)getJsArray:(NSArray *)arr {
id type = [scriptObject callWebScriptMethod:@"_array_with_" withArguments:arr];
if (![type isKindOfClass:[WebScriptObject class]]) {
return nil;
}
return type;
}
- (WebScriptObject *)getJsArray {
id type = [scriptObject callWebScriptMethod:@"_array_" withArguments:[NSArray array]];
if ([type isKindOfClass:[WebScriptObject class]]) {
return type;
}
return nil;
}
- (WebScriptObject *)getJsHash {
id type = [scriptObject callWebScriptMethod:@"_hash_" withArguments:[NSArray array]];
if ([type isKindOfClass:[WebScriptObject class]]) {
return type;
}
return nil;
}
- (id)marshall:(id)obj {
if (obj == nil) {
return [WebUndefined undefined];
}
if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSValue class]] ||
[obj isKindOfClass:[NSNumber class]]) {
return obj;
}
if ([obj isKindOfClass:[NSDictionary class]]) {
WebScriptObject *hash = [self getJsHash];
for (NSString *key in [obj allKeys]) {
[hash setValue:[obj objectForKey:key] forKey:key];
}
return hash;
}
if ([obj isKindOfClass:[NSArray class]]) {
WebScriptObject *arr = [self getJsArray:obj];
return arr;
}
return nil;
}
- (id)unmarshall:(id)obj {
if (obj == nil || [obj isMemberOfClass:[WebUndefined class]]) {
return nil;
}
if ([obj isKindOfClass:[JSOperationWrapper class]]) {
return [obj op];
}
if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSValue class]] ||
[obj isKindOfClass:[NSNumber class]]) {
return obj;
}
if ([obj isKindOfClass:[JSScreenWrapper class]] || [obj isKindOfClass:[JSApplicationWrapper class]]) {
return [obj toString];
}
if ([obj isKindOfClass:[WebScriptObject class]]) {
return [self jsToSomething:obj];
}
return nil;
}
- (id)jsToSomething:(WebScriptObject *)obj {
if (obj == nil) { return nil; }
NSString *type = [self jsTypeof:obj];
if (type == nil) { return nil; }
if ([type isEqualToString:@"array"]) {
return [self jsToArray:obj];
}
if ([type isEqualToString:@"function"]) {
return obj;
}
if ([type isEqualToString:@"object"]) {
return [self jsToDictionary:obj];
}
// nothing else should be here, primitives become NSString or NSValue or NSNumber
return nil;
}
- (NSString *)jsTypeof:(WebScriptObject *)obj {
id type = [scriptObject callWebScriptMethod:@"_typeof_" withArguments:[NSArray arrayWithObjects:obj, nil]];
if ([type isKindOfClass:[NSString class]]) {
return type; // should be a string
}
return @"unknown";
}
- (NSArray *)jsToArray:(WebScriptObject *)obj {
UInt16 count = [[obj valueForKey:@"length"] unsignedIntValue];
NSMutableArray *a = [NSMutableArray array];
for (UInt16 i = 0; i < count; i++) {
id item = [obj webScriptValueAtIndex:i];
if (item == nil || [item isMemberOfClass:[WebUndefined class]]) {
continue;
}
[a addObject:[self unmarshall:item]];
}
return a;
}
- (NSDictionary *)jsToDictionary:(WebScriptObject *)obj {
NSMutableDictionary *ret = [NSMutableDictionary dictionary];
if (obj == nil || [obj isMemberOfClass:[WebUndefined class]]) { return ret; }
id keys = [scriptObject callWebScriptMethod:@"_keys_" withArguments:[NSArray arrayWithObjects:obj, nil]];
NSArray *keyArr = [self jsToArray:keys];
for(NSUInteger i = 0; i < [keyArr count]; i++) {
NSString* key = [keyArr objectAtIndex:i];
id ele = [obj valueForKey:key];
if (ele == nil || [ele isMemberOfClass:[WebUndefined class]]) { continue; }
[ret setObject:[self unmarshall:ele] forKey:key];
}
return ret;
}
+ (JSController *)getInstance {
@synchronized([JSController class]) {
if (!_instance)
_instance = [[[JSController class] alloc] init];
return _instance;
}
}
+ (void)setJsMethods {
jscJsMethods = @{
NSStringFromSelector(@selector(log:)): @"log",
NSStringFromSelector(@selector(bindFunction:callback:repeat:)): @"bindFunction",
NSStringFromSelector(@selector(bindNative:callback:repeat:)): @"bindNative",
NSStringFromSelector(@selector(configFunction:callback:)): @"configFunction",
NSStringFromSelector(@selector(configNative:callback:)): @"configNative",
NSStringFromSelector(@selector(doOperation:options:)): @"doOperation",
NSStringFromSelector(@selector(operation:options:)): @"operation",
NSStringFromSelector(@selector(operationFromString:)): @"operationFromString",
NSStringFromSelector(@selector(source:)): @"source",
NSStringFromSelector(@selector(layout:hash:)): @"layout",
NSStringFromSelector(@selector(default:toAction:)): @"default",
NSStringFromSelector(@selector(shell:wait:path:)): @"shell",
NSStringFromSelector(@selector(on:do:)): @"on",
};
}
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)sel {
return [jscJsMethods objectForKey:NSStringFromSelector(sel)] == NULL;
}
+ (NSString *)webScriptNameForSelector:(SEL)sel {
return [jscJsMethods objectForKey:NSStringFromSelector(sel)];
}
@end
================================================
FILE: Slate/JSInfoWrapper.h
================================================
//
// JSInfoWrapper.h
// Slate
//
// Created by Jigish Patel on 1/21/13.
// Copyright 2013 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@class ScreenWrapper;
@class AccessibilityWrapper;
@interface JSInfoWrapper : NSObject {
ScreenWrapper *sw;
AccessibilityWrapper *aw;
}
@property (strong) ScreenWrapper *sw;
@property (strong) AccessibilityWrapper *aw;
- (id)initWithAccessibilityWrapper:(AccessibilityWrapper *)_aw screenWrapper:(ScreenWrapper *)_sw;
+ (JSInfoWrapper *)getInstance;
@end
================================================
FILE: Slate/JSInfoWrapper.m
================================================
//
// JSInfoWrapper.m
// Slate
//
// Created by Jigish Patel on 1/21/13.
// Copyright 2013 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "JSInfoWrapper.h"
#import "ScreenWrapper.h"
#import
#import "JSWindowWrapper.h"
#import "JSApplicationWrapper.h"
#import "AccessibilityWrapper.h"
#import "JSController.h"
#import "RunningApplications.h"
#import "ExpressionPoint.h"
#import "JSScreenWrapper.h"
#import "JSWrapperUtils.h"
@implementation JSInfoWrapper
@synthesize sw, aw;
static JSInfoWrapper *_instance = nil;
static NSDictionary *jsiwJsMethods;
+ (JSInfoWrapper *)getInstance {
@synchronized([JSInfoWrapper class]) {
if (!_instance)
_instance = [[[JSInfoWrapper class] alloc] init];
return _instance;
}
}
- (id)init {
self = [super init];
if (self) {
[self setAw:[[AccessibilityWrapper alloc] init]];
[self setSw:[[ScreenWrapper alloc] init]];
[JSInfoWrapper setJsMethods];
}
return self;
}
- (id)initWithAccessibilityWrapper:(AccessibilityWrapper *)_aw screenWrapper:(ScreenWrapper *)_sw {
self = [super init];
if (self) {
[self setAw:_aw];
[self setSw:_sw];
[JSInfoWrapper setJsMethods];
}
return self;
}
- (JSWindowWrapper *)window {
return [[JSWindowWrapper alloc] initWithAccessibilityWrapper:aw screenWrapper:sw];
}
- (JSApplicationWrapper *)app {
return [[JSApplicationWrapper alloc] initWithAccessibilityWrapper:aw screenWrapper:sw];
}
- (JSWindowWrapper *)wup:(id)point {
return [self windowUnderPoint:point];
}
- (JSWindowWrapper *)windowUnderPoint:(id)point {
id pointDict = [[JSController getInstance] unmarshall:point];
NSValue *p = [JSWrapperUtils pointFromDict:pointDict aw:aw sw:sw];
if (p == nil) { return nil; }
AXUIElementRef win = [AccessibilityWrapper windowUnderPoint:[p pointValue]];
if (win == nil || win == NULL) { return nil; }
AXUIElementRef app = [AccessibilityWrapper applicationForElement:win];
AccessibilityWrapper *_aw = [[AccessibilityWrapper alloc] initWithApp:app window:win];
return [[JSWindowWrapper alloc] initWithAccessibilityWrapper:_aw screenWrapper:sw];
}
- (JSScreenWrapper *)sup:(id)point {
return [self screenUnderPoint:point];
}
- (JSScreenWrapper *)screenUnderPoint:(id)point {
id pointDict = [[JSController getInstance] unmarshall:point];
NSValue *p = [JSWrapperUtils pointFromDict:pointDict aw:aw sw:sw];
if (p == nil) { return nil; }
return [[JSScreenWrapper alloc] initWithScreenId:[sw getScreenRefIdForPoint:[p pointValue]] screenWrapper:sw];
}
- (void)eapp:(id)func {
[self eachApp:func];
}
- (void)eachApp:(id)func {
for (NSRunningApplication *runningApp in [RunningApplications getInstance]) {
[[JSController getInstance] runFunction:func withArg:[[JSApplicationWrapper alloc] initWithRunningApplication:runningApp
screenWrapper:sw]];
}
}
- (JSScreenWrapper *)screen {
NSPoint tl = [aw getCurrentTopLeft];
NSSize size = [aw getCurrentSize];
NSRect wRect = NSMakeRect(tl.x, tl.y, size.width, size.height);
return [[JSScreenWrapper alloc] initWithScreenId:[sw getScreenRefIdForRect:wRect] screenWrapper:sw];
}
- (BOOL)rectoff:(id)rect {
return [self isRectOffScreen:rect];
}
- (BOOL)isRectOffScreen:(id)rect {
id rectDict = [[JSController getInstance] unmarshall:rect];
NSValue *r = [JSWrapperUtils rectFromDict:rectDict aw:aw sw:sw];
if (r == nil) { return NO; }
return [sw isRectOffScreen:[r rectValue]];
}
- (BOOL)pntoff:(id)point {
return [self isPointOffScreen:point];
}
- (BOOL)isPointOffScreen:(id)point {
id pointDict = [[JSController getInstance] unmarshall:point];
NSValue *p = [JSWrapperUtils pointFromDict:pointDict aw:aw sw:sw];
if (p == nil) { return NO; }
NSPoint _point = [p pointValue];
return [sw isRectOffScreen:NSMakeRect(_point.x, _point.y, 0, 0)];
}
- (JSScreenWrapper *)screenr:(id)ref {
return [self screenForRef:ref];
}
- (JSScreenWrapper *)screenForRef:(id)ref {
NSString *stringRef = nil;
if ([ref isKindOfClass:[NSString class]]) {
stringRef = ref;
} else if ([ref isKindOfClass:[NSNumber class]]) {
stringRef = [ref stringValue];
} else {
return nil;
}
NSPoint tl = [aw getCurrentTopLeft];
NSSize size = [aw getCurrentSize];
NSRect wRect = NSMakeRect(tl.x, tl.y, size.width, size.height);
return [[JSScreenWrapper alloc] initWithScreenId:[sw getScreenRefId:stringRef windowRect:wRect] screenWrapper:sw];
}
- (NSInteger)screenc {
return [self screenCount];
}
- (NSInteger)screenCount {
return [sw getScreenCount];
}
- (void)escreen:(id)func {
[self eachScreen:func];
}
- (void)eachScreen:(id)func {
for (NSInteger i = 0; i < [sw getScreenCount]; i++) {
[[JSController getInstance] runFunction:func withArg:[[JSScreenWrapper alloc] initWithScreenId:i
screenWrapper:sw]];
}
}
- (id)jsMethods {
NSMutableArray *methods = [[jsiwJsMethods allValues] mutableCopy];
[methods removeObject:@"jsMethods"];
return [[JSController getInstance] marshall:methods];
}
+ (void)setJsMethods {
if (jsiwJsMethods == nil) {
jsiwJsMethods = @{
NSStringFromSelector(@selector(window)): @"window",
NSStringFromSelector(@selector(app)): @"app",
NSStringFromSelector(@selector(screen)): @"screen",
NSStringFromSelector(@selector(eachApp:)): @"eachApp",
NSStringFromSelector(@selector(eapp:)): @"eapp",
NSStringFromSelector(@selector(windowUnderPoint:)): @"windowUnderPoint",
NSStringFromSelector(@selector(wup:)): @"wup",
NSStringFromSelector(@selector(isRectOffScreen:)): @"isRectOffScreen",
NSStringFromSelector(@selector(rectoff:)): @"rectoff",
NSStringFromSelector(@selector(isPointOffScreen:)): @"isPointOffScreen",
NSStringFromSelector(@selector(pntoff:)): @"pntoff",
NSStringFromSelector(@selector(screenForRef:)): @"screenForRef",
NSStringFromSelector(@selector(screenr:)): @"screenr",
NSStringFromSelector(@selector(screenCount)): @"screenCount",
NSStringFromSelector(@selector(screenc)): @"screenc",
NSStringFromSelector(@selector(screenUnderPoint:)): @"screenUnderPoint",
NSStringFromSelector(@selector(sup:)): @"sup",
NSStringFromSelector(@selector(eachScreen:)): @"eachScreen",
NSStringFromSelector(@selector(escreen:)): @"escreen",
NSStringFromSelector(@selector(jsMethods)): @"jsMethods",
};
}
}
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)sel {
return [jsiwJsMethods objectForKey:NSStringFromSelector(sel)] == NULL;
}
+ (NSString *)webScriptNameForSelector:(SEL)sel {
return [jsiwJsMethods objectForKey:NSStringFromSelector(sel)];
}
@end
================================================
FILE: Slate/JSONKit/JSONKit.h
================================================
//
// JSONKit.h
// http://github.com/johnezang/JSONKit
// Dual licensed under either the terms of the BSD License, or alternatively
// under the terms of the Apache License, Version 2.0, as specified below.
//
/*
Copyright (c) 2011, John Engelhart
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Zang Industries nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
Copyright 2011 John Engelhart
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include
#include
#include
#include
#include
#ifdef __OBJC__
#import
#import
#import
#import
#import
#import
#endif // __OBJC__
#ifdef __cplusplus
extern "C" {
#endif
// For Mac OS X < 10.5.
#ifndef NSINTEGER_DEFINED
#define NSINTEGER_DEFINED
#if defined(__LP64__) || defined(NS_BUILD_32_LIKE_64)
typedef long NSInteger;
typedef unsigned long NSUInteger;
#define NSIntegerMin LONG_MIN
#define NSIntegerMax LONG_MAX
#define NSUIntegerMax ULONG_MAX
#else // defined(__LP64__) || defined(NS_BUILD_32_LIKE_64)
typedef int NSInteger;
typedef unsigned int NSUInteger;
#define NSIntegerMin INT_MIN
#define NSIntegerMax INT_MAX
#define NSUIntegerMax UINT_MAX
#endif // defined(__LP64__) || defined(NS_BUILD_32_LIKE_64)
#endif // NSINTEGER_DEFINED
#ifndef _JSONKIT_H_
#define _JSONKIT_H_
#if defined(__GNUC__) && (__GNUC__ >= 4) && defined(__APPLE_CC__) && (__APPLE_CC__ >= 5465)
#define JK_DEPRECATED_ATTRIBUTE __attribute__((deprecated))
#else
#define JK_DEPRECATED_ATTRIBUTE
#endif
#define JSONKIT_VERSION_MAJOR 1
#define JSONKIT_VERSION_MINOR 4
typedef NSUInteger JKFlags;
/*
JKParseOptionComments : Allow C style // and /_* ... *_/ (without a _, obviously) comments in JSON.
JKParseOptionUnicodeNewlines : Allow Unicode recommended (?:\r\n|[\n\v\f\r\x85\p{Zl}\p{Zp}]) newlines.
JKParseOptionLooseUnicode : Normally the decoder will stop with an error at any malformed Unicode.
This option allows JSON with malformed Unicode to be parsed without reporting an error.
Any malformed Unicode is replaced with \uFFFD, or "REPLACEMENT CHARACTER".
*/
enum {
JKParseOptionNone = 0,
JKParseOptionStrict = 0,
JKParseOptionComments = (1 << 0),
JKParseOptionUnicodeNewlines = (1 << 1),
JKParseOptionLooseUnicode = (1 << 2),
JKParseOptionPermitTextAfterValidJSON = (1 << 3),
JKParseOptionValidFlags = (JKParseOptionComments | JKParseOptionUnicodeNewlines | JKParseOptionLooseUnicode | JKParseOptionPermitTextAfterValidJSON),
};
typedef JKFlags JKParseOptionFlags;
enum {
JKSerializeOptionNone = 0,
JKSerializeOptionPretty = (1 << 0),
JKSerializeOptionEscapeUnicode = (1 << 1),
JKSerializeOptionEscapeForwardSlashes = (1 << 4),
JKSerializeOptionValidFlags = (JKSerializeOptionPretty | JKSerializeOptionEscapeUnicode | JKSerializeOptionEscapeForwardSlashes),
};
typedef JKFlags JKSerializeOptionFlags;
#ifdef __OBJC__
typedef struct JKParseState JKParseState; // Opaque internal, private type.
// As a general rule of thumb, if you use a method that doesn't accept a JKParseOptionFlags argument, it defaults to JKParseOptionStrict
@interface JSONDecoder : NSObject {
JKParseState *parseState;
}
+ (id)decoder;
+ (id)decoderWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
- (id)initWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
- (void)clearCache;
// The parse... methods were deprecated in v1.4 in favor of the v1.4 objectWith... methods.
- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length JK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithUTF8String:length: instead.
- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length error:(NSError **)error JK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithUTF8String:length:error: instead.
// The NSData MUST be UTF8 encoded JSON.
- (id)parseJSONData:(NSData *)jsonData JK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithData: instead.
- (id)parseJSONData:(NSData *)jsonData error:(NSError **)error JK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithData:error: instead.
// Methods that return immutable collection objects.
- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length;
- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error;
// The NSData MUST be UTF8 encoded JSON.
- (id)objectWithData:(NSData *)jsonData;
- (id)objectWithData:(NSData *)jsonData error:(NSError **)error;
// Methods that return mutable collection objects.
- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length;
- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error;
// The NSData MUST be UTF8 encoded JSON.
- (id)mutableObjectWithData:(NSData *)jsonData;
- (id)mutableObjectWithData:(NSData *)jsonData error:(NSError **)error;
@end
////////////
#pragma mark Deserializing methods
////////////
@interface NSString (JSONKitDeserializing)
- (id)objectFromJSONString;
- (id)objectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
- (id)objectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error;
- (id)mutableObjectFromJSONString;
- (id)mutableObjectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
- (id)mutableObjectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error;
@end
@interface NSData (JSONKitDeserializing)
// The NSData MUST be UTF8 encoded JSON.
- (id)objectFromJSONData;
- (id)objectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
- (id)objectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error;
- (id)mutableObjectFromJSONData;
- (id)mutableObjectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
- (id)mutableObjectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error;
@end
////////////
#pragma mark Serializing methods
////////////
@interface NSString (JSONKitSerializing)
// Convenience methods for those that need to serialize the receiving NSString (i.e., instead of having to serialize a NSArray with a single NSString, you can "serialize to JSON" just the NSString).
// Normally, a string that is serialized to JSON has quotation marks surrounding it, which you may or may not want when serializing a single string, and can be controlled with includeQuotes:
// includeQuotes:YES `a "test"...` -> `"a \"test\"..."`
// includeQuotes:NO `a "test"...` -> `a \"test\"...`
- (NSData *)JSONData; // Invokes JSONDataWithOptions:JKSerializeOptionNone includeQuotes:YES
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error;
- (NSString *)JSONString; // Invokes JSONStringWithOptions:JKSerializeOptionNone includeQuotes:YES
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error;
@end
@interface NSArray (JSONKitSerializing)
- (NSData *)JSONData;
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error;
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error;
- (NSString *)JSONString;
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error;
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error;
@end
@interface NSDictionary (JSONKitSerializing)
- (NSData *)JSONData;
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error;
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error;
- (NSString *)JSONString;
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error;
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error;
@end
#ifdef __BLOCKS__
@interface NSArray (JSONKitSerializingBlockAdditions)
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error;
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error;
@end
@interface NSDictionary (JSONKitSerializingBlockAdditions)
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error;
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error;
@end
#endif
#endif // __OBJC__
#endif // _JSONKIT_H_
#ifdef __cplusplus
} // extern "C"
#endif
================================================
FILE: Slate/JSONKit/JSONKit.m
================================================
//
// JSONKit.m
// http://github.com/johnezang/JSONKit
// Dual licensed under either the terms of the BSD License, or alternatively
// under the terms of the Apache License, Version 2.0, as specified below.
//
/*
Copyright (c) 2011, John Engelhart
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Zang Industries nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
Copyright 2011 John Engelhart
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
Acknowledgments:
The bulk of the UTF8 / UTF32 conversion and verification comes
from ConvertUTF.[hc]. It has been modified from the original sources.
The original sources were obtained from http://www.unicode.org/.
However, the web site no longer seems to host the files. Instead,
the Unicode FAQ http://www.unicode.org/faq//utf_bom.html#gen4
points to International Components for Unicode (ICU)
http://site.icu-project.org/ as an example of how to write a UTF
converter.
The decision to use the ConvertUTF.[ch] code was made to leverage
"proven" code. Hopefully the local modifications are bug free.
The code in isValidCodePoint() is derived from the ICU code in
utf.h for the macros U_IS_UNICODE_NONCHAR and U_IS_UNICODE_CHAR.
From the original ConvertUTF.[ch]:
* Copyright 2001-2004 Unicode, Inc.
*
* Disclaimer
*
* This source code is provided as is by Unicode, Inc. No claims are
* made as to fitness for any particular purpose. No warranties of any
* kind are expressed or implied. The recipient agrees to determine
* applicability of information provided. If this file has been
* purchased on magnetic or optical media from Unicode, Inc., the
* sole remedy for any claim will be exchange of defective media
* within 90 days of receipt.
*
* Limitations on Rights to Redistribute This Code
*
* Unicode, Inc. hereby grants the right to freely use the information
* supplied in this file in the creation of products supporting the
* Unicode Standard, and to make copies of this file in any form
* for internal or external distribution as long as this notice
* remains attached.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#import "JSONKit.h"
//#include
#include
#include
#include
#include
//#import
#import
#import
#import
#import
#import
#import
#import
#ifndef __has_feature
#define __has_feature(x) 0
#endif
#ifdef JK_ENABLE_CF_TRANSFER_OWNERSHIP_CALLBACKS
#warning As of JSONKit v1.4, JK_ENABLE_CF_TRANSFER_OWNERSHIP_CALLBACKS is no longer required. It is no longer a valid option.
#endif
#ifdef __OBJC_GC__
#error JSONKit does not support Objective-C Garbage Collection
#endif
#if __has_feature(objc_arc)
#error JSONKit does not support Objective-C Automatic Reference Counting (ARC)
#endif
// The following checks are really nothing more than sanity checks.
// JSONKit technically has a few problems from a "strictly C99 conforming" standpoint, though they are of the pedantic nitpicking variety.
// In practice, though, for the compilers and architectures we can reasonably expect this code to be compiled for, these pedantic nitpicks aren't really a problem.
// Since we're limited as to what we can do with pre-processor #if checks, these checks are not nearly as through as they should be.
#if (UINT_MAX != 0xffffffffU) || (INT_MIN != (-0x7fffffff-1)) || (ULLONG_MAX != 0xffffffffffffffffULL) || (LLONG_MIN != (-0x7fffffffffffffffLL-1LL))
#error JSONKit requires the C 'int' and 'long long' types to be 32 and 64 bits respectively.
#endif
#if !defined(__LP64__) && ((UINT_MAX != ULONG_MAX) || (INT_MAX != LONG_MAX) || (INT_MIN != LONG_MIN) || (WORD_BIT != LONG_BIT))
#error JSONKit requires the C 'int' and 'long' types to be the same on 32-bit architectures.
#endif
// Cocoa / Foundation uses NS*Integer as the type for a lot of arguments. We make sure that NS*Integer is something we are expecting and is reasonably compatible with size_t / ssize_t
#if (NSUIntegerMax != ULONG_MAX) || (NSIntegerMax != LONG_MAX) || (NSIntegerMin != LONG_MIN)
#error JSONKit requires NSInteger and NSUInteger to be the same size as the C 'long' type.
#endif
#if (NSUIntegerMax != SIZE_MAX) || (NSIntegerMax != SSIZE_MAX)
#error JSONKit requires NSInteger and NSUInteger to be the same size as the C 'size_t' type.
#endif
// For DJB hash.
#define JK_HASH_INIT (1402737925UL)
// Use __builtin_clz() instead of trailingBytesForUTF8[] table lookup.
#define JK_FAST_TRAILING_BYTES
// JK_CACHE_SLOTS must be a power of 2. Default size is 1024 slots.
#define JK_CACHE_SLOTS_BITS (10)
#define JK_CACHE_SLOTS (1UL << JK_CACHE_SLOTS_BITS)
// JK_CACHE_PROBES is the number of probe attempts.
#define JK_CACHE_PROBES (4UL)
// JK_INIT_CACHE_AGE must be (1 << AGE) - 1
#define JK_INIT_CACHE_AGE (0)
// JK_TOKENBUFFER_SIZE is the default stack size for the temporary buffer used to hold "non-simple" strings (i.e., contains \ escapes)
#define JK_TOKENBUFFER_SIZE (1024UL * 2UL)
// JK_STACK_OBJS is the default number of spaces reserved on the stack for temporarily storing pointers to Obj-C objects before they can be transferred to a NSArray / NSDictionary.
#define JK_STACK_OBJS (1024UL * 1UL)
#define JK_JSONBUFFER_SIZE (1024UL * 4UL)
#define JK_UTF8BUFFER_SIZE (1024UL * 16UL)
#define JK_ENCODE_CACHE_SLOTS (1024UL)
#if defined (__GNUC__) && (__GNUC__ >= 4)
#define JK_ATTRIBUTES(attr, ...) __attribute__((attr, ##__VA_ARGS__))
#define JK_EXPECTED(cond, expect) __builtin_expect((long)(cond), (expect))
#define JK_EXPECT_T(cond) JK_EXPECTED(cond, 1U)
#define JK_EXPECT_F(cond) JK_EXPECTED(cond, 0U)
#define JK_PREFETCH(ptr) __builtin_prefetch(ptr)
#else // defined (__GNUC__) && (__GNUC__ >= 4)
#define JK_ATTRIBUTES(attr, ...)
#define JK_EXPECTED(cond, expect) (cond)
#define JK_EXPECT_T(cond) (cond)
#define JK_EXPECT_F(cond) (cond)
#define JK_PREFETCH(ptr)
#endif // defined (__GNUC__) && (__GNUC__ >= 4)
#define JK_STATIC_INLINE static __inline__ JK_ATTRIBUTES(always_inline)
#define JK_ALIGNED(arg) JK_ATTRIBUTES(aligned(arg))
#define JK_UNUSED_ARG JK_ATTRIBUTES(unused)
#define JK_WARN_UNUSED JK_ATTRIBUTES(warn_unused_result)
#define JK_WARN_UNUSED_CONST JK_ATTRIBUTES(warn_unused_result, const)
#define JK_WARN_UNUSED_PURE JK_ATTRIBUTES(warn_unused_result, pure)
#define JK_WARN_UNUSED_SENTINEL JK_ATTRIBUTES(warn_unused_result, sentinel)
#define JK_NONNULL_ARGS(arg, ...) JK_ATTRIBUTES(nonnull(arg, ##__VA_ARGS__))
#define JK_WARN_UNUSED_NONNULL_ARGS(arg, ...) JK_ATTRIBUTES(warn_unused_result, nonnull(arg, ##__VA_ARGS__))
#define JK_WARN_UNUSED_CONST_NONNULL_ARGS(arg, ...) JK_ATTRIBUTES(warn_unused_result, const, nonnull(arg, ##__VA_ARGS__))
#define JK_WARN_UNUSED_PURE_NONNULL_ARGS(arg, ...) JK_ATTRIBUTES(warn_unused_result, pure, nonnull(arg, ##__VA_ARGS__))
#if defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3)
#define JK_ALLOC_SIZE_NON_NULL_ARGS_WARN_UNUSED(as, nn, ...) JK_ATTRIBUTES(warn_unused_result, nonnull(nn, ##__VA_ARGS__), alloc_size(as))
#else // defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3)
#define JK_ALLOC_SIZE_NON_NULL_ARGS_WARN_UNUSED(as, nn, ...) JK_ATTRIBUTES(warn_unused_result, nonnull(nn, ##__VA_ARGS__))
#endif // defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3)
@class JKArray, JKDictionaryEnumerator, JKDictionary;
enum {
JSONNumberStateStart = 0,
JSONNumberStateFinished = 1,
JSONNumberStateError = 2,
JSONNumberStateWholeNumberStart = 3,
JSONNumberStateWholeNumberMinus = 4,
JSONNumberStateWholeNumberZero = 5,
JSONNumberStateWholeNumber = 6,
JSONNumberStatePeriod = 7,
JSONNumberStateFractionalNumberStart = 8,
JSONNumberStateFractionalNumber = 9,
JSONNumberStateExponentStart = 10,
JSONNumberStateExponentPlusMinus = 11,
JSONNumberStateExponent = 12,
};
enum {
JSONStringStateStart = 0,
JSONStringStateParsing = 1,
JSONStringStateFinished = 2,
JSONStringStateError = 3,
JSONStringStateEscape = 4,
JSONStringStateEscapedUnicode1 = 5,
JSONStringStateEscapedUnicode2 = 6,
JSONStringStateEscapedUnicode3 = 7,
JSONStringStateEscapedUnicode4 = 8,
JSONStringStateEscapedUnicodeSurrogate1 = 9,
JSONStringStateEscapedUnicodeSurrogate2 = 10,
JSONStringStateEscapedUnicodeSurrogate3 = 11,
JSONStringStateEscapedUnicodeSurrogate4 = 12,
JSONStringStateEscapedNeedEscapeForSurrogate = 13,
JSONStringStateEscapedNeedEscapedUForSurrogate = 14,
};
enum {
JKParseAcceptValue = (1 << 0),
JKParseAcceptComma = (1 << 1),
JKParseAcceptEnd = (1 << 2),
JKParseAcceptValueOrEnd = (JKParseAcceptValue | JKParseAcceptEnd),
JKParseAcceptCommaOrEnd = (JKParseAcceptComma | JKParseAcceptEnd),
};
enum {
JKClassUnknown = 0,
JKClassString = 1,
JKClassNumber = 2,
JKClassArray = 3,
JKClassDictionary = 4,
JKClassNull = 5,
};
enum {
JKManagedBufferOnStack = 1,
JKManagedBufferOnHeap = 2,
JKManagedBufferLocationMask = (0x3),
JKManagedBufferLocationShift = (0),
JKManagedBufferMustFree = (1 << 2),
};
typedef JKFlags JKManagedBufferFlags;
enum {
JKObjectStackOnStack = 1,
JKObjectStackOnHeap = 2,
JKObjectStackLocationMask = (0x3),
JKObjectStackLocationShift = (0),
JKObjectStackMustFree = (1 << 2),
};
typedef JKFlags JKObjectStackFlags;
enum {
JKTokenTypeInvalid = 0,
JKTokenTypeNumber = 1,
JKTokenTypeString = 2,
JKTokenTypeObjectBegin = 3,
JKTokenTypeObjectEnd = 4,
JKTokenTypeArrayBegin = 5,
JKTokenTypeArrayEnd = 6,
JKTokenTypeSeparator = 7,
JKTokenTypeComma = 8,
JKTokenTypeTrue = 9,
JKTokenTypeFalse = 10,
JKTokenTypeNull = 11,
JKTokenTypeWhiteSpace = 12,
};
typedef NSUInteger JKTokenType;
// These are prime numbers to assist with hash slot probing.
enum {
JKValueTypeNone = 0,
JKValueTypeString = 5,
JKValueTypeLongLong = 7,
JKValueTypeUnsignedLongLong = 11,
JKValueTypeDouble = 13,
};
typedef NSUInteger JKValueType;
enum {
JKEncodeOptionAsData = 1,
JKEncodeOptionAsString = 2,
JKEncodeOptionAsTypeMask = 0x7,
JKEncodeOptionCollectionObj = (1 << 3),
JKEncodeOptionStringObj = (1 << 4),
JKEncodeOptionStringObjTrimQuotes = (1 << 5),
};
typedef NSUInteger JKEncodeOptionType;
typedef NSUInteger JKHash;
typedef struct JKTokenCacheItem JKTokenCacheItem;
typedef struct JKTokenCache JKTokenCache;
typedef struct JKTokenValue JKTokenValue;
typedef struct JKParseToken JKParseToken;
typedef struct JKPtrRange JKPtrRange;
typedef struct JKObjectStack JKObjectStack;
typedef struct JKBuffer JKBuffer;
typedef struct JKConstBuffer JKConstBuffer;
typedef struct JKConstPtrRange JKConstPtrRange;
typedef struct JKRange JKRange;
typedef struct JKManagedBuffer JKManagedBuffer;
typedef struct JKFastClassLookup JKFastClassLookup;
typedef struct JKEncodeCache JKEncodeCache;
typedef struct JKEncodeState JKEncodeState;
typedef struct JKObjCImpCache JKObjCImpCache;
typedef struct JKHashTableEntry JKHashTableEntry;
typedef id (*NSNumberAllocImp)(id receiver, SEL selector);
typedef id (*NSNumberInitWithUnsignedLongLongImp)(id receiver, SEL selector, unsigned long long value);
typedef id (*JKClassFormatterIMP)(id receiver, SEL selector, id object);
#ifdef __BLOCKS__
typedef id (^JKClassFormatterBlock)(id formatObject);
#endif
struct JKPtrRange {
unsigned char *ptr;
size_t length;
};
struct JKConstPtrRange {
const unsigned char *ptr;
size_t length;
};
struct JKRange {
size_t location, length;
};
struct JKManagedBuffer {
JKPtrRange bytes;
JKManagedBufferFlags flags;
size_t roundSizeUpToMultipleOf;
};
struct JKObjectStack {
void **objects, **keys;
CFHashCode *cfHashes;
size_t count, index, roundSizeUpToMultipleOf;
JKObjectStackFlags flags;
};
struct JKBuffer {
JKPtrRange bytes;
};
struct JKConstBuffer {
JKConstPtrRange bytes;
};
struct JKTokenValue {
JKConstPtrRange ptrRange;
JKValueType type;
JKHash hash;
union {
long long longLongValue;
unsigned long long unsignedLongLongValue;
double doubleValue;
} number;
JKTokenCacheItem *cacheItem;
};
struct JKParseToken {
JKConstPtrRange tokenPtrRange;
JKTokenType type;
JKTokenValue value;
JKManagedBuffer tokenBuffer;
};
struct JKTokenCacheItem {
void *object;
JKHash hash;
CFHashCode cfHash;
size_t size;
unsigned char *bytes;
JKValueType type;
};
struct JKTokenCache {
JKTokenCacheItem *items;
size_t count;
unsigned int prng_lfsr;
unsigned char age[JK_CACHE_SLOTS];
};
struct JKObjCImpCache {
Class NSNumberClass;
NSNumberAllocImp NSNumberAlloc;
NSNumberInitWithUnsignedLongLongImp NSNumberInitWithUnsignedLongLong;
};
struct JKParseState {
JKParseOptionFlags parseOptionFlags;
JKConstBuffer stringBuffer;
size_t atIndex, lineNumber, lineStartIndex;
size_t prev_atIndex, prev_lineNumber, prev_lineStartIndex;
JKParseToken token;
JKObjectStack objectStack;
JKTokenCache cache;
JKObjCImpCache objCImpCache;
NSError *error;
int errorIsPrev;
BOOL mutableCollections;
};
struct JKFastClassLookup {
void *stringClass;
void *numberClass;
void *arrayClass;
void *dictionaryClass;
void *nullClass;
};
struct JKEncodeCache {
id object;
size_t offset;
size_t length;
};
struct JKEncodeState {
JKManagedBuffer utf8ConversionBuffer;
JKManagedBuffer stringBuffer;
size_t atIndex;
JKFastClassLookup fastClassLookup;
JKEncodeCache cache[JK_ENCODE_CACHE_SLOTS];
JKSerializeOptionFlags serializeOptionFlags;
JKEncodeOptionType encodeOption;
size_t depth;
NSError *error;
id classFormatterDelegate;
SEL classFormatterSelector;
JKClassFormatterIMP classFormatterIMP;
#ifdef __BLOCKS__
JKClassFormatterBlock classFormatterBlock;
#endif
};
// This is a JSONKit private class.
@interface JKSerializer : NSObject {
JKEncodeState *encodeState;
}
#ifdef __BLOCKS__
#define JKSERIALIZER_BLOCKS_PROTO id(^)(id object)
#else
#define JKSERIALIZER_BLOCKS_PROTO id
#endif
+ (id)serializeObject:(id)object options:(JKSerializeOptionFlags)optionFlags encodeOption:(JKEncodeOptionType)encodeOption block:(JKSERIALIZER_BLOCKS_PROTO)block delegate:(id)delegate selector:(SEL)selector error:(NSError **)error;
- (id)serializeObject:(id)object options:(JKSerializeOptionFlags)optionFlags encodeOption:(JKEncodeOptionType)encodeOption block:(JKSERIALIZER_BLOCKS_PROTO)block delegate:(id)delegate selector:(SEL)selector error:(NSError **)error;
- (void)releaseState;
@end
struct JKHashTableEntry {
NSUInteger keyHash;
id key, object;
};
typedef uint32_t UTF32; /* at least 32 bits */
typedef uint16_t UTF16; /* at least 16 bits */
typedef uint8_t UTF8; /* typically 8 bits */
typedef enum {
conversionOK, /* conversion successful */
sourceExhausted, /* partial character in source, but hit end */
targetExhausted, /* insuff. room in target for conversion */
sourceIllegal /* source sequence is illegal/malformed */
} ConversionResult;
#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD
#define UNI_MAX_BMP (UTF32)0x0000FFFF
#define UNI_MAX_UTF16 (UTF32)0x0010FFFF
#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF
#define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF
#define UNI_SUR_HIGH_START (UTF32)0xD800
#define UNI_SUR_HIGH_END (UTF32)0xDBFF
#define UNI_SUR_LOW_START (UTF32)0xDC00
#define UNI_SUR_LOW_END (UTF32)0xDFFF
#if !defined(JK_FAST_TRAILING_BYTES)
static const char trailingBytesForUTF8[256] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5
};
#endif
static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, 0x03C82080UL, 0xFA082080UL, 0x82082080UL };
static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
#define JK_AT_STRING_PTR(x) (&((x)->stringBuffer.bytes.ptr[(x)->atIndex]))
#define JK_END_STRING_PTR(x) (&((x)->stringBuffer.bytes.ptr[(x)->stringBuffer.bytes.length]))
static JKArray *_JKArrayCreate(id *objects, NSUInteger count, BOOL mutableCollection);
static void _JKArrayInsertObjectAtIndex(JKArray *array, id newObject, NSUInteger objectIndex);
static void _JKArrayReplaceObjectAtIndexWithObject(JKArray *array, NSUInteger objectIndex, id newObject);
static void _JKArrayRemoveObjectAtIndex(JKArray *array, NSUInteger objectIndex);
static NSUInteger _JKDictionaryCapacityForCount(NSUInteger count);
static JKDictionary *_JKDictionaryCreate(id *keys, NSUInteger *keyHashes, id *objects, NSUInteger count, BOOL mutableCollection);
static JKHashTableEntry *_JKDictionaryHashEntry(JKDictionary *dictionary);
static NSUInteger _JKDictionaryCapacity(JKDictionary *dictionary);
static void _JKDictionaryResizeIfNeccessary(JKDictionary *dictionary);
static void _JKDictionaryRemoveObjectWithEntry(JKDictionary *dictionary, JKHashTableEntry *entry);
static void _JKDictionaryAddObject(JKDictionary *dictionary, NSUInteger keyHash, id key, id object);
static JKHashTableEntry *_JKDictionaryHashTableEntryForKey(JKDictionary *dictionary, id aKey);
static void _JSONDecoderCleanup(JSONDecoder *decoder);
static id _NSStringObjectFromJSONString(NSString *jsonString, JKParseOptionFlags parseOptionFlags, NSError **error, BOOL mutableCollection);
static void jk_managedBuffer_release(JKManagedBuffer *managedBuffer);
static void jk_managedBuffer_setToStackBuffer(JKManagedBuffer *managedBuffer, unsigned char *ptr, size_t length);
static unsigned char *jk_managedBuffer_resize(JKManagedBuffer *managedBuffer, size_t newSize);
static void jk_objectStack_release(JKObjectStack *objectStack);
static void jk_objectStack_setToStackBuffer(JKObjectStack *objectStack, void **objects, void **keys, CFHashCode *cfHashes, size_t count);
static int jk_objectStack_resize(JKObjectStack *objectStack, size_t newCount);
static void jk_error(JKParseState *parseState, NSString *format, ...);
static int jk_parse_string(JKParseState *parseState);
static int jk_parse_number(JKParseState *parseState);
static size_t jk_parse_is_newline(JKParseState *parseState, const unsigned char *atCharacterPtr);
JK_STATIC_INLINE int jk_parse_skip_newline(JKParseState *parseState);
JK_STATIC_INLINE void jk_parse_skip_whitespace(JKParseState *parseState);
static int jk_parse_next_token(JKParseState *parseState);
static void jk_error_parse_accept_or3(JKParseState *parseState, int state, NSString *or1String, NSString *or2String, NSString *or3String);
static void *jk_create_dictionary(JKParseState *parseState, size_t startingObjectIndex);
static void *jk_parse_dictionary(JKParseState *parseState);
static void *jk_parse_array(JKParseState *parseState);
static void *jk_object_for_token(JKParseState *parseState);
static void *jk_cachedObjects(JKParseState *parseState);
JK_STATIC_INLINE void jk_cache_age(JKParseState *parseState);
JK_STATIC_INLINE void jk_set_parsed_token(JKParseState *parseState, const unsigned char *ptr, size_t length, JKTokenType type, size_t advanceBy);
static void jk_encode_error(JKEncodeState *encodeState, NSString *format, ...);
static int jk_encode_printf(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format, ...);
static int jk_encode_write(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format);
static int jk_encode_writePrettyPrintWhiteSpace(JKEncodeState *encodeState);
static int jk_encode_write1slow(JKEncodeState *encodeState, ssize_t depthChange, const char *format);
static int jk_encode_write1fast(JKEncodeState *encodeState, ssize_t depthChange JK_UNUSED_ARG, const char *format);
static int jk_encode_writen(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format, size_t length);
JK_STATIC_INLINE JKHash jk_encode_object_hash(void *objectPtr);
JK_STATIC_INLINE void jk_encode_updateCache(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object);
static int jk_encode_add_atom_to_buffer(JKEncodeState *encodeState, void *objectPtr);
#define jk_encode_write1(es, dc, f) (JK_EXPECT_F(_jk_encode_prettyPrint) ? jk_encode_write1slow(es, dc, f) : jk_encode_write1fast(es, dc, f))
JK_STATIC_INLINE size_t jk_min(size_t a, size_t b);
JK_STATIC_INLINE size_t jk_max(size_t a, size_t b);
JK_STATIC_INLINE JKHash calculateHash(JKHash currentHash, unsigned char c);
// JSONKit v1.4 used both a JKArray : NSArray and JKMutableArray : NSMutableArray, and the same for the dictionary collection type.
// However, Louis Gerbarg (via cocoa-dev) pointed out that Cocoa / Core Foundation actually implements only a single class that inherits from the
// mutable version, and keeps an ivar bit for whether or not that instance is mutable. This means that the immutable versions of the collection
// classes receive the mutating methods, but this is handled by having those methods throw an exception when the ivar bit is set to immutable.
// We adopt the same strategy here. It's both cleaner and gets rid of the method swizzling hackery used in JSONKit v1.4.
// This is a workaround for issue #23 https://github.com/johnezang/JSONKit/pull/23
// Basically, there seem to be a problem with using +load in static libraries on iOS. However, __attribute__ ((constructor)) does work correctly.
// Since we do not require anything "special" that +load provides, and we can accomplish the same thing using __attribute__ ((constructor)), the +load logic was moved here.
static Class _JKArrayClass = NULL;
static size_t _JKArrayInstanceSize = 0UL;
static Class _JKDictionaryClass = NULL;
static size_t _JKDictionaryInstanceSize = 0UL;
// For JSONDecoder...
static Class _jk_NSNumberClass = NULL;
static NSNumberAllocImp _jk_NSNumberAllocImp = NULL;
static NSNumberInitWithUnsignedLongLongImp _jk_NSNumberInitWithUnsignedLongLongImp = NULL;
extern void jk_collectionClassLoadTimeInitialization(void) __attribute__ ((constructor));
void jk_collectionClassLoadTimeInitialization(void) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Though technically not required, the run time environment at load time initialization may be less than ideal.
_JKArrayClass = objc_getClass("JKArray");
_JKArrayInstanceSize = jk_max(16UL, class_getInstanceSize(_JKArrayClass));
_JKDictionaryClass = objc_getClass("JKDictionary");
_JKDictionaryInstanceSize = jk_max(16UL, class_getInstanceSize(_JKDictionaryClass));
// For JSONDecoder...
_jk_NSNumberClass = [NSNumber class];
_jk_NSNumberAllocImp = (NSNumberAllocImp)[NSNumber methodForSelector:@selector(alloc)];
// Hacktacular. Need to do it this way due to the nature of class clusters.
id temp_NSNumber = [NSNumber alloc];
_jk_NSNumberInitWithUnsignedLongLongImp = (NSNumberInitWithUnsignedLongLongImp)[temp_NSNumber methodForSelector:@selector(initWithUnsignedLongLong:)];
[[temp_NSNumber init] release];
temp_NSNumber = NULL;
[pool release]; pool = NULL;
}
#pragma mark -
@interface JKArray : NSMutableArray {
id *objects;
NSUInteger count, capacity, mutations;
}
@end
@implementation JKArray
+ (id)allocWithZone:(NSZone *)zone
{
#pragma unused(zone)
[NSException raise:NSInvalidArgumentException format:@"*** - [%@ %@]: The %@ class is private to JSONKit and should not be used in this fashion.", NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSStringFromClass([self class])];
return(NULL);
}
static JKArray *_JKArrayCreate(id *objects, NSUInteger count, BOOL mutableCollection) {
NSCParameterAssert((objects != NULL) && (_JKArrayClass != NULL) && (_JKArrayInstanceSize > 0UL));
JKArray *array = NULL;
if(JK_EXPECT_T((array = (JKArray *)calloc(1UL, _JKArrayInstanceSize)) != NULL)) { // Directly allocate the JKArray instance via calloc.
array->isa = _JKArrayClass;
if((array = [array init]) == NULL) { return(NULL); }
array->capacity = count;
array->count = count;
if(JK_EXPECT_F((array->objects = (id *)malloc(sizeof(id) * array->capacity)) == NULL)) { [array autorelease]; return(NULL); }
memcpy(array->objects, objects, array->capacity * sizeof(id));
array->mutations = (mutableCollection == NO) ? 0UL : 1UL;
}
return(array);
}
// Note: The caller is responsible for -retaining the object that is to be added.
static void _JKArrayInsertObjectAtIndex(JKArray *array, id newObject, NSUInteger objectIndex) {
NSCParameterAssert((array != NULL) && (array->objects != NULL) && (array->count <= array->capacity) && (objectIndex <= array->count) && (newObject != NULL));
if(!((array != NULL) && (array->objects != NULL) && (objectIndex <= array->count) && (newObject != NULL))) { [newObject autorelease]; return; }
if((array->count + 1UL) >= array->capacity) {
id *newObjects = NULL;
if((newObjects = (id *)realloc(array->objects, sizeof(id) * (array->capacity + 16UL))) == NULL) { [NSException raise:NSMallocException format:@"Unable to resize objects array."]; }
array->objects = newObjects;
array->capacity += 16UL;
memset(&array->objects[array->count], 0, sizeof(id) * (array->capacity - array->count));
}
array->count++;
if((objectIndex + 1UL) < array->count) { memmove(&array->objects[objectIndex + 1UL], &array->objects[objectIndex], sizeof(id) * ((array->count - 1UL) - objectIndex)); array->objects[objectIndex] = NULL; }
array->objects[objectIndex] = newObject;
}
// Note: The caller is responsible for -retaining the object that is to be added.
static void _JKArrayReplaceObjectAtIndexWithObject(JKArray *array, NSUInteger objectIndex, id newObject) {
NSCParameterAssert((array != NULL) && (array->objects != NULL) && (array->count <= array->capacity) && (objectIndex < array->count) && (array->objects[objectIndex] != NULL) && (newObject != NULL));
if(!((array != NULL) && (array->objects != NULL) && (objectIndex < array->count) && (array->objects[objectIndex] != NULL) && (newObject != NULL))) { [newObject autorelease]; return; }
CFRelease(array->objects[objectIndex]);
array->objects[objectIndex] = NULL;
array->objects[objectIndex] = newObject;
}
static void _JKArrayRemoveObjectAtIndex(JKArray *array, NSUInteger objectIndex) {
NSCParameterAssert((array != NULL) && (array->objects != NULL) && (array->count > 0UL) && (array->count <= array->capacity) && (objectIndex < array->count) && (array->objects[objectIndex] != NULL));
if(!((array != NULL) && (array->objects != NULL) && (array->count > 0UL) && (array->count <= array->capacity) && (objectIndex < array->count) && (array->objects[objectIndex] != NULL))) { return; }
CFRelease(array->objects[objectIndex]);
array->objects[objectIndex] = NULL;
if((objectIndex + 1UL) < array->count) { memmove(&array->objects[objectIndex], &array->objects[objectIndex + 1UL], sizeof(id) * ((array->count - 1UL) - objectIndex)); array->objects[array->count - 1UL] = NULL; }
array->count--;
}
- (void)dealloc
{
if(JK_EXPECT_T(objects != NULL)) {
NSUInteger atObject = 0UL;
for(atObject = 0UL; atObject < count; atObject++) { if(JK_EXPECT_T(objects[atObject] != NULL)) { CFRelease(objects[atObject]); objects[atObject] = NULL; } }
free(objects); objects = NULL;
}
[super dealloc];
}
- (NSUInteger)count
{
NSParameterAssert((objects != NULL) && (count <= capacity));
return(count);
}
- (void)getObjects:(id *)objectsPtr range:(NSRange)range
{
NSParameterAssert((objects != NULL) && (count <= capacity));
if((objectsPtr == NULL) && (NSMaxRange(range) > 0UL)) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: pointer to objects array is NULL but range length is %lu", NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSMaxRange(range)]; }
if((range.location > count) || (NSMaxRange(range) > count)) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSMaxRange(range), count]; }
memcpy(objectsPtr, objects + range.location, range.length * sizeof(id));
}
- (id)objectAtIndex:(NSUInteger)objectIndex
{
if(objectIndex >= count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), objectIndex, count]; }
NSParameterAssert((objects != NULL) && (count <= capacity) && (objects[objectIndex] != NULL));
return(objects[objectIndex]);
}
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len
{
NSParameterAssert((state != NULL) && (stackbuf != NULL) && (len > 0UL) && (objects != NULL) && (count <= capacity));
if(JK_EXPECT_F(state->state == 0UL)) { state->mutationsPtr = (unsigned long *)&mutations; state->itemsPtr = stackbuf; }
if(JK_EXPECT_F(state->state >= count)) { return(0UL); }
NSUInteger enumeratedCount = 0UL;
while(JK_EXPECT_T(enumeratedCount < len) && JK_EXPECT_T(state->state < count)) { NSParameterAssert(objects[state->state] != NULL); stackbuf[enumeratedCount++] = objects[state->state++]; }
return(enumeratedCount);
}
- (void)insertObject:(id)anObject atIndex:(NSUInteger)objectIndex
{
if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; }
if(anObject == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to insert nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; }
if(objectIndex > count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), objectIndex, count + 1UL]; }
#ifdef __clang_analyzer__
[anObject retain]; // Stupid clang analyzer... Issue #19.
#else
anObject = [anObject retain];
#endif
_JKArrayInsertObjectAtIndex(self, anObject, objectIndex);
mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL;
}
- (void)removeObjectAtIndex:(NSUInteger)objectIndex
{
if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; }
if(objectIndex >= count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), objectIndex, count]; }
_JKArrayRemoveObjectAtIndex(self, objectIndex);
mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL;
}
- (void)replaceObjectAtIndex:(NSUInteger)objectIndex withObject:(id)anObject
{
if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; }
if(anObject == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to insert nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; }
if(objectIndex >= count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), objectIndex, count]; }
#ifdef __clang_analyzer__
[anObject retain]; // Stupid clang analyzer... Issue #19.
#else
anObject = [anObject retain];
#endif
_JKArrayReplaceObjectAtIndexWithObject(self, objectIndex, anObject);
mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL;
}
- (id)copyWithZone:(NSZone *)zone
{
NSParameterAssert((objects != NULL) && (count <= capacity));
return((mutations == 0UL) ? [self retain] : [(NSArray *)[NSArray allocWithZone:zone] initWithObjects:objects count:count]);
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
NSParameterAssert((objects != NULL) && (count <= capacity));
return([(NSMutableArray *)[NSMutableArray allocWithZone:zone] initWithObjects:objects count:count]);
}
@end
#pragma mark -
@interface JKDictionaryEnumerator : NSEnumerator {
id collection;
NSUInteger nextObject;
}
- (id)initWithJKDictionary:(JKDictionary *)initDictionary;
- (NSArray *)allObjects;
- (id)nextObject;
@end
@implementation JKDictionaryEnumerator
- (id)initWithJKDictionary:(JKDictionary *)initDictionary
{
NSParameterAssert(initDictionary != NULL);
if((self = [super init]) == NULL) { return(NULL); }
if((collection = (id)CFRetain(initDictionary)) == NULL) { [self autorelease]; return(NULL); }
return(self);
}
- (void)dealloc
{
if(collection != NULL) { CFRelease(collection); collection = NULL; }
[super dealloc];
}
- (NSArray *)allObjects
{
NSParameterAssert(collection != NULL);
NSUInteger count = [(NSDictionary *)collection count], atObject = 0UL;
id objects[count];
while((objects[atObject] = [self nextObject]) != NULL) { NSParameterAssert(atObject < count); atObject++; }
return([NSArray arrayWithObjects:objects count:atObject]);
}
- (id)nextObject
{
NSParameterAssert((collection != NULL) && (_JKDictionaryHashEntry(collection) != NULL));
JKHashTableEntry *entry = _JKDictionaryHashEntry(collection);
NSUInteger capacity = _JKDictionaryCapacity(collection);
id returnObject = NULL;
if(entry != NULL) { while((nextObject < capacity) && ((returnObject = entry[nextObject++].key) == NULL)) { /* ... */ } }
return(returnObject);
}
@end
#pragma mark -
@interface JKDictionary : NSMutableDictionary {
NSUInteger count, capacity, mutations;
JKHashTableEntry *entry;
}
@end
@implementation JKDictionary
+ (id)allocWithZone:(NSZone *)zone
{
#pragma unused(zone)
[NSException raise:NSInvalidArgumentException format:@"*** - [%@ %@]: The %@ class is private to JSONKit and should not be used in this fashion.", NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSStringFromClass([self class])];
return(NULL);
}
// These values are taken from Core Foundation CF-550 CFBasicHash.m. As a bonus, they align very well with our JKHashTableEntry struct too.
static const NSUInteger jk_dictionaryCapacities[] = {
0UL, 3UL, 7UL, 13UL, 23UL, 41UL, 71UL, 127UL, 191UL, 251UL, 383UL, 631UL, 1087UL, 1723UL,
2803UL, 4523UL, 7351UL, 11959UL, 19447UL, 31231UL, 50683UL, 81919UL, 132607UL,
214519UL, 346607UL, 561109UL, 907759UL, 1468927UL, 2376191UL, 3845119UL,
6221311UL, 10066421UL, 16287743UL, 26354171UL, 42641881UL, 68996069UL,
111638519UL, 180634607UL, 292272623UL, 472907251UL
};
static NSUInteger _JKDictionaryCapacityForCount(NSUInteger count) {
NSUInteger bottom = 0UL, top = sizeof(jk_dictionaryCapacities) / sizeof(NSUInteger), mid = 0UL, tableSize = lround(floor((count) * 1.33));
while(top > bottom) { mid = (top + bottom) / 2UL; if(jk_dictionaryCapacities[mid] < tableSize) { bottom = mid + 1UL; } else { top = mid; } }
return(jk_dictionaryCapacities[bottom]);
}
static void _JKDictionaryResizeIfNeccessary(JKDictionary *dictionary) {
NSCParameterAssert((dictionary != NULL) && (dictionary->entry != NULL) && (dictionary->count <= dictionary->capacity));
NSUInteger capacityForCount = 0UL;
if(dictionary->capacity < (capacityForCount = _JKDictionaryCapacityForCount(dictionary->count + 1UL))) { // resize
NSUInteger oldCapacity = dictionary->capacity;
#ifndef NS_BLOCK_ASSERTIONS
NSUInteger oldCount = dictionary->count;
#endif
JKHashTableEntry *oldEntry = dictionary->entry;
if(JK_EXPECT_F((dictionary->entry = (JKHashTableEntry *)calloc(1UL, sizeof(JKHashTableEntry) * capacityForCount)) == NULL)) { [NSException raise:NSMallocException format:@"Unable to allocate memory for hash table."]; }
dictionary->capacity = capacityForCount;
dictionary->count = 0UL;
NSUInteger idx = 0UL;
for(idx = 0UL; idx < oldCapacity; idx++) { if(oldEntry[idx].key != NULL) { _JKDictionaryAddObject(dictionary, oldEntry[idx].keyHash, oldEntry[idx].key, oldEntry[idx].object); oldEntry[idx].keyHash = 0UL; oldEntry[idx].key = NULL; oldEntry[idx].object = NULL; } }
NSCParameterAssert((oldCount == dictionary->count));
free(oldEntry); oldEntry = NULL;
}
}
static JKDictionary *_JKDictionaryCreate(id *keys, NSUInteger *keyHashes, id *objects, NSUInteger count, BOOL mutableCollection) {
NSCParameterAssert((keys != NULL) && (keyHashes != NULL) && (objects != NULL) && (_JKDictionaryClass != NULL) && (_JKDictionaryInstanceSize > 0UL));
JKDictionary *dictionary = NULL;
if(JK_EXPECT_T((dictionary = (JKDictionary *)calloc(1UL, _JKDictionaryInstanceSize)) != NULL)) { // Directly allocate the JKDictionary instance via calloc.
dictionary->isa = _JKDictionaryClass;
if((dictionary = [dictionary init]) == NULL) { return(NULL); }
dictionary->capacity = _JKDictionaryCapacityForCount(count);
dictionary->count = 0UL;
if(JK_EXPECT_F((dictionary->entry = (JKHashTableEntry *)calloc(1UL, sizeof(JKHashTableEntry) * dictionary->capacity)) == NULL)) { [dictionary autorelease]; return(NULL); }
NSUInteger idx = 0UL;
for(idx = 0UL; idx < count; idx++) { _JKDictionaryAddObject(dictionary, keyHashes[idx], keys[idx], objects[idx]); }
dictionary->mutations = (mutableCollection == NO) ? 0UL : 1UL;
}
return(dictionary);
}
- (void)dealloc
{
if(JK_EXPECT_T(entry != NULL)) {
NSUInteger atEntry = 0UL;
for(atEntry = 0UL; atEntry < capacity; atEntry++) {
if(JK_EXPECT_T(entry[atEntry].key != NULL)) { CFRelease(entry[atEntry].key); entry[atEntry].key = NULL; }
if(JK_EXPECT_T(entry[atEntry].object != NULL)) { CFRelease(entry[atEntry].object); entry[atEntry].object = NULL; }
}
free(entry); entry = NULL;
}
[super dealloc];
}
static JKHashTableEntry *_JKDictionaryHashEntry(JKDictionary *dictionary) {
NSCParameterAssert(dictionary != NULL);
return(dictionary->entry);
}
static NSUInteger _JKDictionaryCapacity(JKDictionary *dictionary) {
NSCParameterAssert(dictionary != NULL);
return(dictionary->capacity);
}
static void _JKDictionaryRemoveObjectWithEntry(JKDictionary *dictionary, JKHashTableEntry *entry) {
NSCParameterAssert((dictionary != NULL) && (entry != NULL) && (entry->key != NULL) && (entry->object != NULL) && (dictionary->count > 0UL) && (dictionary->count <= dictionary->capacity));
CFRelease(entry->key); entry->key = NULL;
CFRelease(entry->object); entry->object = NULL;
entry->keyHash = 0UL;
dictionary->count--;
// In order for certain invariants that are used to speed up the search for a particular key, we need to "re-add" all the entries in the hash table following this entry until we hit a NULL entry.
NSUInteger removeIdx = entry - dictionary->entry, idx = 0UL;
NSCParameterAssert((removeIdx < dictionary->capacity));
for(idx = 0UL; idx < dictionary->capacity; idx++) {
NSUInteger entryIdx = (removeIdx + idx + 1UL) % dictionary->capacity;
JKHashTableEntry *atEntry = &dictionary->entry[entryIdx];
if(atEntry->key == NULL) { break; }
NSUInteger keyHash = atEntry->keyHash;
id key = atEntry->key, object = atEntry->object;
NSCParameterAssert(object != NULL);
atEntry->keyHash = 0UL;
atEntry->key = NULL;
atEntry->object = NULL;
NSUInteger addKeyEntry = keyHash % dictionary->capacity, addIdx = 0UL;
for(addIdx = 0UL; addIdx < dictionary->capacity; addIdx++) {
JKHashTableEntry *atAddEntry = &dictionary->entry[((addKeyEntry + addIdx) % dictionary->capacity)];
if(JK_EXPECT_T(atAddEntry->key == NULL)) { NSCParameterAssert((atAddEntry->keyHash == 0UL) && (atAddEntry->object == NULL)); atAddEntry->key = key; atAddEntry->object = object; atAddEntry->keyHash = keyHash; break; }
}
}
}
static void _JKDictionaryAddObject(JKDictionary *dictionary, NSUInteger keyHash, id key, id object) {
NSCParameterAssert((dictionary != NULL) && (key != NULL) && (object != NULL) && (dictionary->count < dictionary->capacity) && (dictionary->entry != NULL));
NSUInteger keyEntry = keyHash % dictionary->capacity, idx = 0UL;
for(idx = 0UL; idx < dictionary->capacity; idx++) {
NSUInteger entryIdx = (keyEntry + idx) % dictionary->capacity;
JKHashTableEntry *atEntry = &dictionary->entry[entryIdx];
if(JK_EXPECT_F(atEntry->keyHash == keyHash) && JK_EXPECT_T(atEntry->key != NULL) && (JK_EXPECT_F(key == atEntry->key) || JK_EXPECT_F(CFEqual(atEntry->key, key)))) { _JKDictionaryRemoveObjectWithEntry(dictionary, atEntry); }
if(JK_EXPECT_T(atEntry->key == NULL)) { NSCParameterAssert((atEntry->keyHash == 0UL) && (atEntry->object == NULL)); atEntry->key = key; atEntry->object = object; atEntry->keyHash = keyHash; dictionary->count++; return; }
}
// We should never get here. If we do, we -release the key / object because it's our responsibility.
CFRelease(key);
CFRelease(object);
}
- (NSUInteger)count
{
return(count);
}
static JKHashTableEntry *_JKDictionaryHashTableEntryForKey(JKDictionary *dictionary, id aKey) {
NSCParameterAssert((dictionary != NULL) && (dictionary->entry != NULL) && (dictionary->count <= dictionary->capacity));
if((aKey == NULL) || (dictionary->capacity == 0UL)) { return(NULL); }
NSUInteger keyHash = CFHash(aKey), keyEntry = (keyHash % dictionary->capacity), idx = 0UL;
JKHashTableEntry *atEntry = NULL;
for(idx = 0UL; idx < dictionary->capacity; idx++) {
atEntry = &dictionary->entry[(keyEntry + idx) % dictionary->capacity];
if(JK_EXPECT_T(atEntry->keyHash == keyHash) && JK_EXPECT_T(atEntry->key != NULL) && ((atEntry->key == aKey) || CFEqual(atEntry->key, aKey))) { NSCParameterAssert(atEntry->object != NULL); return(atEntry); break; }
if(JK_EXPECT_F(atEntry->key == NULL)) { NSCParameterAssert(atEntry->object == NULL); return(NULL); break; } // If the key was in the table, we would have found it by now.
}
return(NULL);
}
- (id)objectForKey:(id)aKey
{
NSParameterAssert((entry != NULL) && (count <= capacity));
JKHashTableEntry *entryForKey = _JKDictionaryHashTableEntryForKey(self, aKey);
return((entryForKey != NULL) ? entryForKey->object : NULL);
}
- (void)getObjects:(id *)objects andKeys:(id *)keys
{
NSParameterAssert((entry != NULL) && (count <= capacity));
NSUInteger atEntry = 0UL; NSUInteger arrayIdx = 0UL;
for(atEntry = 0UL; atEntry < capacity; atEntry++) {
if(JK_EXPECT_T(entry[atEntry].key != NULL)) {
NSCParameterAssert((entry[atEntry].object != NULL) && (arrayIdx < count));
if(JK_EXPECT_T(keys != NULL)) { keys[arrayIdx] = entry[atEntry].key; }
if(JK_EXPECT_T(objects != NULL)) { objects[arrayIdx] = entry[atEntry].object; }
arrayIdx++;
}
}
}
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len
{
NSParameterAssert((state != NULL) && (stackbuf != NULL) && (len > 0UL) && (entry != NULL) && (count <= capacity));
if(JK_EXPECT_F(state->state == 0UL)) { state->mutationsPtr = (unsigned long *)&mutations; state->itemsPtr = stackbuf; }
if(JK_EXPECT_F(state->state >= capacity)) { return(0UL); }
NSUInteger enumeratedCount = 0UL;
while(JK_EXPECT_T(enumeratedCount < len) && JK_EXPECT_T(state->state < capacity)) { if(JK_EXPECT_T(entry[state->state].key != NULL)) { stackbuf[enumeratedCount++] = entry[state->state].key; } state->state++; }
return(enumeratedCount);
}
- (NSEnumerator *)keyEnumerator
{
return([[[JKDictionaryEnumerator alloc] initWithJKDictionary:self] autorelease]);
}
- (void)setObject:(id)anObject forKey:(id)aKey
{
if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; }
if(aKey == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to insert nil key", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; }
if(anObject == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to insert nil value (key: %@)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), aKey]; }
_JKDictionaryResizeIfNeccessary(self);
#ifndef __clang_analyzer__
aKey = [aKey copy]; // Why on earth would clang complain that this -copy "might leak",
anObject = [anObject retain]; // but this -retain doesn't!?
#endif // __clang_analyzer__
_JKDictionaryAddObject(self, CFHash(aKey), aKey, anObject);
mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL;
}
- (void)removeObjectForKey:(id)aKey
{
if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; }
if(aKey == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to remove nil key", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; }
JKHashTableEntry *entryForKey = _JKDictionaryHashTableEntryForKey(self, aKey);
if(entryForKey != NULL) {
_JKDictionaryRemoveObjectWithEntry(self, entryForKey);
mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL;
}
}
- (id)copyWithZone:(NSZone *)zone
{
NSParameterAssert((entry != NULL) && (count <= capacity));
return((mutations == 0UL) ? [self retain] : [[NSDictionary allocWithZone:zone] initWithDictionary:self]);
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
NSParameterAssert((entry != NULL) && (count <= capacity));
return([[NSMutableDictionary allocWithZone:zone] initWithDictionary:self]);
}
@end
#pragma mark -
JK_STATIC_INLINE size_t jk_min(size_t a, size_t b) { return((a < b) ? a : b); }
JK_STATIC_INLINE size_t jk_max(size_t a, size_t b) { return((a > b) ? a : b); }
JK_STATIC_INLINE JKHash calculateHash(JKHash currentHash, unsigned char c) { return(((currentHash << 5) + currentHash) + c); }
static void jk_error(JKParseState *parseState, NSString *format, ...) {
NSCParameterAssert((parseState != NULL) && (format != NULL));
va_list varArgsList;
va_start(varArgsList, format);
NSString *formatString = [[[NSString alloc] initWithFormat:format arguments:varArgsList] autorelease];
va_end(varArgsList);
#if 0
const unsigned char *lineStart = parseState->stringBuffer.bytes.ptr + parseState->lineStartIndex;
const unsigned char *lineEnd = lineStart;
const unsigned char *atCharacterPtr = NULL;
for(atCharacterPtr = lineStart; atCharacterPtr < JK_END_STRING_PTR(parseState); atCharacterPtr++) { lineEnd = atCharacterPtr; if(jk_parse_is_newline(parseState, atCharacterPtr)) { break; } }
NSString *lineString = @"", *carretString = @"";
if(lineStart < JK_END_STRING_PTR(parseState)) {
lineString = [[[NSString alloc] initWithBytes:lineStart length:(lineEnd - lineStart) encoding:NSUTF8StringEncoding] autorelease];
carretString = [NSString stringWithFormat:@"%*.*s^", (int)(parseState->atIndex - parseState->lineStartIndex), (int)(parseState->atIndex - parseState->lineStartIndex), " "];
}
#endif
if(parseState->error == NULL) {
parseState->error = [NSError errorWithDomain:@"JKErrorDomain" code:-1L userInfo:
[NSDictionary dictionaryWithObjectsAndKeys:
formatString, NSLocalizedDescriptionKey,
[NSNumber numberWithUnsignedLong:parseState->atIndex], @"JKAtIndexKey",
[NSNumber numberWithUnsignedLong:parseState->lineNumber], @"JKLineNumberKey",
//lineString, @"JKErrorLine0Key",
//carretString, @"JKErrorLine1Key",
NULL]];
}
}
#pragma mark -
#pragma mark Buffer and Object Stack management functions
static void jk_managedBuffer_release(JKManagedBuffer *managedBuffer) {
if((managedBuffer->flags & JKManagedBufferMustFree)) {
if(managedBuffer->bytes.ptr != NULL) { free(managedBuffer->bytes.ptr); managedBuffer->bytes.ptr = NULL; }
managedBuffer->flags &= ~JKManagedBufferMustFree;
}
managedBuffer->bytes.ptr = NULL;
managedBuffer->bytes.length = 0UL;
managedBuffer->flags &= ~JKManagedBufferLocationMask;
}
static void jk_managedBuffer_setToStackBuffer(JKManagedBuffer *managedBuffer, unsigned char *ptr, size_t length) {
jk_managedBuffer_release(managedBuffer);
managedBuffer->bytes.ptr = ptr;
managedBuffer->bytes.length = length;
managedBuffer->flags = (managedBuffer->flags & ~JKManagedBufferLocationMask) | JKManagedBufferOnStack;
}
static unsigned char *jk_managedBuffer_resize(JKManagedBuffer *managedBuffer, size_t newSize) {
size_t roundedUpNewSize = newSize;
if(managedBuffer->roundSizeUpToMultipleOf > 0UL) { roundedUpNewSize = newSize + ((managedBuffer->roundSizeUpToMultipleOf - (newSize % managedBuffer->roundSizeUpToMultipleOf)) % managedBuffer->roundSizeUpToMultipleOf); }
if((roundedUpNewSize != managedBuffer->bytes.length) && (roundedUpNewSize > managedBuffer->bytes.length)) {
if((managedBuffer->flags & JKManagedBufferLocationMask) == JKManagedBufferOnStack) {
NSCParameterAssert((managedBuffer->flags & JKManagedBufferMustFree) == 0);
unsigned char *newBuffer = NULL, *oldBuffer = managedBuffer->bytes.ptr;
if((newBuffer = (unsigned char *)malloc(roundedUpNewSize)) == NULL) { return(NULL); }
memcpy(newBuffer, oldBuffer, jk_min(managedBuffer->bytes.length, roundedUpNewSize));
managedBuffer->flags = (managedBuffer->flags & ~JKManagedBufferLocationMask) | (JKManagedBufferOnHeap | JKManagedBufferMustFree);
managedBuffer->bytes.ptr = newBuffer;
managedBuffer->bytes.length = roundedUpNewSize;
} else {
NSCParameterAssert(((managedBuffer->flags & JKManagedBufferMustFree) != 0) && ((managedBuffer->flags & JKManagedBufferLocationMask) == JKManagedBufferOnHeap));
if((managedBuffer->bytes.ptr = (unsigned char *)reallocf(managedBuffer->bytes.ptr, roundedUpNewSize)) == NULL) { return(NULL); }
managedBuffer->bytes.length = roundedUpNewSize;
}
}
return(managedBuffer->bytes.ptr);
}
static void jk_objectStack_release(JKObjectStack *objectStack) {
NSCParameterAssert(objectStack != NULL);
NSCParameterAssert(objectStack->index <= objectStack->count);
size_t atIndex = 0UL;
for(atIndex = 0UL; atIndex < objectStack->index; atIndex++) {
if(objectStack->objects[atIndex] != NULL) { CFRelease(objectStack->objects[atIndex]); objectStack->objects[atIndex] = NULL; }
if(objectStack->keys[atIndex] != NULL) { CFRelease(objectStack->keys[atIndex]); objectStack->keys[atIndex] = NULL; }
}
objectStack->index = 0UL;
if(objectStack->flags & JKObjectStackMustFree) {
NSCParameterAssert((objectStack->flags & JKObjectStackLocationMask) == JKObjectStackOnHeap);
if(objectStack->objects != NULL) { free(objectStack->objects); objectStack->objects = NULL; }
if(objectStack->keys != NULL) { free(objectStack->keys); objectStack->keys = NULL; }
if(objectStack->cfHashes != NULL) { free(objectStack->cfHashes); objectStack->cfHashes = NULL; }
objectStack->flags &= ~JKObjectStackMustFree;
}
objectStack->objects = NULL;
objectStack->keys = NULL;
objectStack->cfHashes = NULL;
objectStack->count = 0UL;
objectStack->flags &= ~JKObjectStackLocationMask;
}
static void jk_objectStack_setToStackBuffer(JKObjectStack *objectStack, void **objects, void **keys, CFHashCode *cfHashes, size_t count) {
NSCParameterAssert((objectStack != NULL) && (objects != NULL) && (keys != NULL) && (cfHashes != NULL) && (count > 0UL));
jk_objectStack_release(objectStack);
objectStack->objects = objects;
objectStack->keys = keys;
objectStack->cfHashes = cfHashes;
objectStack->count = count;
objectStack->flags = (objectStack->flags & ~JKObjectStackLocationMask) | JKObjectStackOnStack;
#ifndef NS_BLOCK_ASSERTIONS
size_t idx;
for(idx = 0UL; idx < objectStack->count; idx++) { objectStack->objects[idx] = NULL; objectStack->keys[idx] = NULL; objectStack->cfHashes[idx] = 0UL; }
#endif
}
static int jk_objectStack_resize(JKObjectStack *objectStack, size_t newCount) {
size_t roundedUpNewCount = newCount;
int returnCode = 0;
void **newObjects = NULL, **newKeys = NULL;
CFHashCode *newCFHashes = NULL;
if(objectStack->roundSizeUpToMultipleOf > 0UL) { roundedUpNewCount = newCount + ((objectStack->roundSizeUpToMultipleOf - (newCount % objectStack->roundSizeUpToMultipleOf)) % objectStack->roundSizeUpToMultipleOf); }
if((roundedUpNewCount != objectStack->count) && (roundedUpNewCount > objectStack->count)) {
if((objectStack->flags & JKObjectStackLocationMask) == JKObjectStackOnStack) {
NSCParameterAssert((objectStack->flags & JKObjectStackMustFree) == 0);
if((newObjects = (void ** )calloc(1UL, roundedUpNewCount * sizeof(void * ))) == NULL) { returnCode = 1; goto errorExit; }
memcpy(newObjects, objectStack->objects, jk_min(objectStack->count, roundedUpNewCount) * sizeof(void *));
if((newKeys = (void ** )calloc(1UL, roundedUpNewCount * sizeof(void * ))) == NULL) { returnCode = 1; goto errorExit; }
memcpy(newKeys, objectStack->keys, jk_min(objectStack->count, roundedUpNewCount) * sizeof(void *));
if((newCFHashes = (CFHashCode *)calloc(1UL, roundedUpNewCount * sizeof(CFHashCode))) == NULL) { returnCode = 1; goto errorExit; }
memcpy(newCFHashes, objectStack->cfHashes, jk_min(objectStack->count, roundedUpNewCount) * sizeof(CFHashCode));
objectStack->flags = (objectStack->flags & ~JKObjectStackLocationMask) | (JKObjectStackOnHeap | JKObjectStackMustFree);
objectStack->objects = newObjects; newObjects = NULL;
objectStack->keys = newKeys; newKeys = NULL;
objectStack->cfHashes = newCFHashes; newCFHashes = NULL;
objectStack->count = roundedUpNewCount;
} else {
NSCParameterAssert(((objectStack->flags & JKObjectStackMustFree) != 0) && ((objectStack->flags & JKObjectStackLocationMask) == JKObjectStackOnHeap));
if((newObjects = (void ** )realloc(objectStack->objects, roundedUpNewCount * sizeof(void * ))) != NULL) { objectStack->objects = newObjects; newObjects = NULL; } else { returnCode = 1; goto errorExit; }
if((newKeys = (void ** )realloc(objectStack->keys, roundedUpNewCount * sizeof(void * ))) != NULL) { objectStack->keys = newKeys; newKeys = NULL; } else { returnCode = 1; goto errorExit; }
if((newCFHashes = (CFHashCode *)realloc(objectStack->cfHashes, roundedUpNewCount * sizeof(CFHashCode))) != NULL) { objectStack->cfHashes = newCFHashes; newCFHashes = NULL; } else { returnCode = 1; goto errorExit; }
#ifndef NS_BLOCK_ASSERTIONS
size_t idx;
for(idx = objectStack->count; idx < roundedUpNewCount; idx++) { objectStack->objects[idx] = NULL; objectStack->keys[idx] = NULL; objectStack->cfHashes[idx] = 0UL; }
#endif
objectStack->count = roundedUpNewCount;
}
}
errorExit:
if(newObjects != NULL) { free(newObjects); newObjects = NULL; }
if(newKeys != NULL) { free(newKeys); newKeys = NULL; }
if(newCFHashes != NULL) { free(newCFHashes); newCFHashes = NULL; }
return(returnCode);
}
////////////
#pragma mark -
#pragma mark Unicode related functions
JK_STATIC_INLINE ConversionResult isValidCodePoint(UTF32 *u32CodePoint) {
ConversionResult result = conversionOK;
UTF32 ch = *u32CodePoint;
if(JK_EXPECT_F(ch >= UNI_SUR_HIGH_START) && (JK_EXPECT_T(ch <= UNI_SUR_LOW_END))) { result = sourceIllegal; ch = UNI_REPLACEMENT_CHAR; goto finished; }
if(JK_EXPECT_F(ch >= 0xFDD0U) && (JK_EXPECT_F(ch <= 0xFDEFU) || JK_EXPECT_F((ch & 0xFFFEU) == 0xFFFEU)) && JK_EXPECT_T(ch <= 0x10FFFFU)) { result = sourceIllegal; ch = UNI_REPLACEMENT_CHAR; goto finished; }
if(JK_EXPECT_F(ch == 0U)) { result = sourceIllegal; ch = UNI_REPLACEMENT_CHAR; goto finished; }
finished:
*u32CodePoint = ch;
return(result);
}
static int isLegalUTF8(const UTF8 *source, size_t length) {
const UTF8 *srcptr = source + length;
UTF8 a;
switch(length) {
default: return(0); // Everything else falls through when "true"...
case 4: if(JK_EXPECT_F(((a = (*--srcptr)) < 0x80) || (a > 0xBF))) { return(0); }
case 3: if(JK_EXPECT_F(((a = (*--srcptr)) < 0x80) || (a > 0xBF))) { return(0); }
case 2: if(JK_EXPECT_F( (a = (*--srcptr)) > 0xBF )) { return(0); }
switch(*source) { // no fall-through in this inner switch
case 0xE0: if(JK_EXPECT_F(a < 0xA0)) { return(0); } break;
case 0xED: if(JK_EXPECT_F(a > 0x9F)) { return(0); } break;
case 0xF0: if(JK_EXPECT_F(a < 0x90)) { return(0); } break;
case 0xF4: if(JK_EXPECT_F(a > 0x8F)) { return(0); } break;
default: if(JK_EXPECT_F(a < 0x80)) { return(0); }
}
case 1: if(JK_EXPECT_F((JK_EXPECT_T(*source < 0xC2)) && JK_EXPECT_F(*source >= 0x80))) { return(0); }
}
if(JK_EXPECT_F(*source > 0xF4)) { return(0); }
return(1);
}
static ConversionResult ConvertSingleCodePointInUTF8(const UTF8 *sourceStart, const UTF8 *sourceEnd, UTF8 const **nextUTF8, UTF32 *convertedUTF32) {
ConversionResult result = conversionOK;
const UTF8 *source = sourceStart;
UTF32 ch = 0UL;
#if !defined(JK_FAST_TRAILING_BYTES)
unsigned short extraBytesToRead = trailingBytesForUTF8[*source];
#else
unsigned short extraBytesToRead = __builtin_clz(((*source)^0xff) << 25);
#endif
if(JK_EXPECT_F((source + extraBytesToRead + 1) > sourceEnd) || JK_EXPECT_F(!isLegalUTF8(source, extraBytesToRead + 1))) {
source++;
while((source < sourceEnd) && (((*source) & 0xc0) == 0x80) && ((source - sourceStart) < (extraBytesToRead + 1))) { source++; }
NSCParameterAssert(source <= sourceEnd);
result = ((source < sourceEnd) && (((*source) & 0xc0) != 0x80)) ? sourceIllegal : ((sourceStart + extraBytesToRead + 1) > sourceEnd) ? sourceExhausted : sourceIllegal;
ch = UNI_REPLACEMENT_CHAR;
goto finished;
}
switch(extraBytesToRead) { // The cases all fall through.
case 5: ch += *source++; ch <<= 6;
case 4: ch += *source++; ch <<= 6;
case 3: ch += *source++; ch <<= 6;
case 2: ch += *source++; ch <<= 6;
case 1: ch += *source++; ch <<= 6;
case 0: ch += *source++;
}
ch -= offsetsFromUTF8[extraBytesToRead];
result = isValidCodePoint(&ch);
finished:
*nextUTF8 = source;
*convertedUTF32 = ch;
return(result);
}
static ConversionResult ConvertUTF32toUTF8 (UTF32 u32CodePoint, UTF8 **targetStart, UTF8 *targetEnd) {
const UTF32 byteMask = 0xBF, byteMark = 0x80;
ConversionResult result = conversionOK;
UTF8 *target = *targetStart;
UTF32 ch = u32CodePoint;
unsigned short bytesToWrite = 0;
result = isValidCodePoint(&ch);
// Figure out how many bytes the result will require. Turn any illegally large UTF32 things (> Plane 17) into replacement chars.
if(ch < (UTF32)0x80) { bytesToWrite = 1; }
else if(ch < (UTF32)0x800) { bytesToWrite = 2; }
else if(ch < (UTF32)0x10000) { bytesToWrite = 3; }
else if(ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; }
else { bytesToWrite = 3; ch = UNI_REPLACEMENT_CHAR; result = sourceIllegal; }
target += bytesToWrite;
if (target > targetEnd) { target -= bytesToWrite; result = targetExhausted; goto finished; }
switch (bytesToWrite) { // note: everything falls through.
case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
case 1: *--target = (UTF8) (ch | firstByteMark[bytesToWrite]);
}
target += bytesToWrite;
finished:
*targetStart = target;
return(result);
}
JK_STATIC_INLINE int jk_string_add_unicodeCodePoint(JKParseState *parseState, uint32_t unicodeCodePoint, size_t *tokenBufferIdx, JKHash *stringHash) {
UTF8 *u8s = &parseState->token.tokenBuffer.bytes.ptr[*tokenBufferIdx];
ConversionResult result;
if((result = ConvertUTF32toUTF8(unicodeCodePoint, &u8s, (parseState->token.tokenBuffer.bytes.ptr + parseState->token.tokenBuffer.bytes.length))) != conversionOK) { if(result == targetExhausted) { return(1); } }
size_t utf8len = u8s - &parseState->token.tokenBuffer.bytes.ptr[*tokenBufferIdx], nextIdx = (*tokenBufferIdx) + utf8len;
while(*tokenBufferIdx < nextIdx) { *stringHash = calculateHash(*stringHash, parseState->token.tokenBuffer.bytes.ptr[(*tokenBufferIdx)++]); }
return(0);
}
////////////
#pragma mark -
#pragma mark Decoding / parsing / deserializing functions
static int jk_parse_string(JKParseState *parseState) {
NSCParameterAssert((parseState != NULL) && (JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState)));
const unsigned char *stringStart = JK_AT_STRING_PTR(parseState) + 1;
const unsigned char *endOfBuffer = JK_END_STRING_PTR(parseState);
const unsigned char *atStringCharacter = stringStart;
unsigned char *tokenBuffer = parseState->token.tokenBuffer.bytes.ptr;
size_t tokenStartIndex = parseState->atIndex;
size_t tokenBufferIdx = 0UL;
int onlySimpleString = 1, stringState = JSONStringStateStart;
uint16_t escapedUnicode1 = 0U, escapedUnicode2 = 0U;
uint32_t escapedUnicodeCodePoint = 0U;
JKHash stringHash = JK_HASH_INIT;
while(1) {
unsigned long currentChar;
if(JK_EXPECT_F(atStringCharacter == endOfBuffer)) { /* XXX Add error message */ stringState = JSONStringStateError; goto finishedParsing; }
if(JK_EXPECT_F((currentChar = *atStringCharacter++) >= 0x80UL)) {
const unsigned char *nextValidCharacter = NULL;
UTF32 u32ch = 0U;
ConversionResult result;
if(JK_EXPECT_F((result = ConvertSingleCodePointInUTF8(atStringCharacter - 1, endOfBuffer, (UTF8 const **)&nextValidCharacter, &u32ch)) != conversionOK)) { goto switchToSlowPath; }
stringHash = calculateHash(stringHash, currentChar);
while(atStringCharacter < nextValidCharacter) { NSCParameterAssert(JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState)); stringHash = calculateHash(stringHash, *atStringCharacter++); }
continue;
} else {
if(JK_EXPECT_F(currentChar == (unsigned long)'"')) { stringState = JSONStringStateFinished; goto finishedParsing; }
if(JK_EXPECT_F(currentChar == (unsigned long)'\\')) {
switchToSlowPath:
onlySimpleString = 0;
stringState = JSONStringStateParsing;
tokenBufferIdx = (atStringCharacter - stringStart) - 1L;
if(JK_EXPECT_F((tokenBufferIdx + 16UL) > parseState->token.tokenBuffer.bytes.length)) { if((tokenBuffer = jk_managedBuffer_resize(&parseState->token.tokenBuffer, tokenBufferIdx + 1024UL)) == NULL) { jk_error(parseState, @"Internal error: Unable to resize temporary buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = JSONStringStateError; goto finishedParsing; } }
memcpy(tokenBuffer, stringStart, tokenBufferIdx);
goto slowMatch;
}
if(JK_EXPECT_F(currentChar < 0x20UL)) { jk_error(parseState, @"Invalid character < 0x20 found in string: 0x%2.2x.", currentChar); stringState = JSONStringStateError; goto finishedParsing; }
stringHash = calculateHash(stringHash, currentChar);
}
}
slowMatch:
for(atStringCharacter = (stringStart + ((atStringCharacter - stringStart) - 1L)); (atStringCharacter < endOfBuffer) && (tokenBufferIdx < parseState->token.tokenBuffer.bytes.length); atStringCharacter++) {
if((tokenBufferIdx + 16UL) > parseState->token.tokenBuffer.bytes.length) { if((tokenBuffer = jk_managedBuffer_resize(&parseState->token.tokenBuffer, tokenBufferIdx + 1024UL)) == NULL) { jk_error(parseState, @"Internal error: Unable to resize temporary buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = JSONStringStateError; goto finishedParsing; } }
NSCParameterAssert(tokenBufferIdx < parseState->token.tokenBuffer.bytes.length);
unsigned long currentChar = (*atStringCharacter), escapedChar;
if(JK_EXPECT_T(stringState == JSONStringStateParsing)) {
if(JK_EXPECT_T(currentChar >= 0x20UL)) {
if(JK_EXPECT_T(currentChar < (unsigned long)0x80)) { // Not a UTF8 sequence
if(JK_EXPECT_F(currentChar == (unsigned long)'"')) { stringState = JSONStringStateFinished; atStringCharacter++; goto finishedParsing; }
if(JK_EXPECT_F(currentChar == (unsigned long)'\\')) { stringState = JSONStringStateEscape; continue; }
stringHash = calculateHash(stringHash, currentChar);
tokenBuffer[tokenBufferIdx++] = currentChar;
continue;
} else { // UTF8 sequence
const unsigned char *nextValidCharacter = NULL;
UTF32 u32ch = 0U;
ConversionResult result;
if(JK_EXPECT_F((result = ConvertSingleCodePointInUTF8(atStringCharacter, endOfBuffer, (UTF8 const **)&nextValidCharacter, &u32ch)) != conversionOK)) {
if((result == sourceIllegal) && ((parseState->parseOptionFlags & JKParseOptionLooseUnicode) == 0)) { jk_error(parseState, @"Illegal UTF8 sequence found in \"\" string."); stringState = JSONStringStateError; goto finishedParsing; }
if(result == sourceExhausted) { jk_error(parseState, @"End of buffer reached while parsing UTF8 in \"\" string."); stringState = JSONStringStateError; goto finishedParsing; }
if(jk_string_add_unicodeCodePoint(parseState, u32ch, &tokenBufferIdx, &stringHash)) { jk_error(parseState, @"Internal error: Unable to add UTF8 sequence to internal string buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = JSONStringStateError; goto finishedParsing; }
atStringCharacter = nextValidCharacter - 1;
continue;
} else {
while(atStringCharacter < nextValidCharacter) { tokenBuffer[tokenBufferIdx++] = *atStringCharacter; stringHash = calculateHash(stringHash, *atStringCharacter++); }
atStringCharacter--;
continue;
}
}
} else { // currentChar < 0x20
jk_error(parseState, @"Invalid character < 0x20 found in string: 0x%2.2x.", currentChar); stringState = JSONStringStateError; goto finishedParsing;
}
} else { // stringState != JSONStringStateParsing
int isSurrogate = 1;
switch(stringState) {
case JSONStringStateEscape:
switch(currentChar) {
case 'u': escapedUnicode1 = 0U; escapedUnicode2 = 0U; escapedUnicodeCodePoint = 0U; stringState = JSONStringStateEscapedUnicode1; break;
case 'b': escapedChar = '\b'; goto parsedEscapedChar;
case 'f': escapedChar = '\f'; goto parsedEscapedChar;
case 'n': escapedChar = '\n'; goto parsedEscapedChar;
case 'r': escapedChar = '\r'; goto parsedEscapedChar;
case 't': escapedChar = '\t'; goto parsedEscapedChar;
case '\\': escapedChar = '\\'; goto parsedEscapedChar;
case '/': escapedChar = '/'; goto parsedEscapedChar;
case '"': escapedChar = '"'; goto parsedEscapedChar;
parsedEscapedChar:
stringState = JSONStringStateParsing;
stringHash = calculateHash(stringHash, escapedChar);
tokenBuffer[tokenBufferIdx++] = escapedChar;
break;
default: jk_error(parseState, @"Invalid escape sequence found in \"\" string."); stringState = JSONStringStateError; goto finishedParsing; break;
}
break;
case JSONStringStateEscapedUnicode1:
case JSONStringStateEscapedUnicode2:
case JSONStringStateEscapedUnicode3:
case JSONStringStateEscapedUnicode4: isSurrogate = 0;
case JSONStringStateEscapedUnicodeSurrogate1:
case JSONStringStateEscapedUnicodeSurrogate2:
case JSONStringStateEscapedUnicodeSurrogate3:
case JSONStringStateEscapedUnicodeSurrogate4:
{
uint16_t hexValue = 0U;
switch(currentChar) {
case '0' ... '9': hexValue = currentChar - '0'; goto parsedHex;
case 'a' ... 'f': hexValue = (currentChar - 'a') + 10U; goto parsedHex;
case 'A' ... 'F': hexValue = (currentChar - 'A') + 10U; goto parsedHex;
parsedHex:
if(!isSurrogate) { escapedUnicode1 = (escapedUnicode1 << 4) | hexValue; } else { escapedUnicode2 = (escapedUnicode2 << 4) | hexValue; }
if(stringState == JSONStringStateEscapedUnicode4) {
if(((escapedUnicode1 >= 0xD800U) && (escapedUnicode1 < 0xE000U))) {
if((escapedUnicode1 >= 0xD800U) && (escapedUnicode1 < 0xDC00U)) { stringState = JSONStringStateEscapedNeedEscapeForSurrogate; }
else if((escapedUnicode1 >= 0xDC00U) && (escapedUnicode1 < 0xE000U)) {
if((parseState->parseOptionFlags & JKParseOptionLooseUnicode)) { escapedUnicodeCodePoint = UNI_REPLACEMENT_CHAR; }
else { jk_error(parseState, @"Illegal \\u Unicode escape sequence."); stringState = JSONStringStateError; goto finishedParsing; }
}
}
else { escapedUnicodeCodePoint = escapedUnicode1; }
}
if(stringState == JSONStringStateEscapedUnicodeSurrogate4) {
if((escapedUnicode2 < 0xdc00) || (escapedUnicode2 > 0xdfff)) {
if((parseState->parseOptionFlags & JKParseOptionLooseUnicode)) { escapedUnicodeCodePoint = UNI_REPLACEMENT_CHAR; }
else { jk_error(parseState, @"Illegal \\u Unicode escape sequence."); stringState = JSONStringStateError; goto finishedParsing; }
}
else { escapedUnicodeCodePoint = ((escapedUnicode1 - 0xd800) * 0x400) + (escapedUnicode2 - 0xdc00) + 0x10000; }
}
if((stringState == JSONStringStateEscapedUnicode4) || (stringState == JSONStringStateEscapedUnicodeSurrogate4)) {
if((isValidCodePoint(&escapedUnicodeCodePoint) == sourceIllegal) && ((parseState->parseOptionFlags & JKParseOptionLooseUnicode) == 0)) { jk_error(parseState, @"Illegal \\u Unicode escape sequence."); stringState = JSONStringStateError; goto finishedParsing; }
stringState = JSONStringStateParsing;
if(jk_string_add_unicodeCodePoint(parseState, escapedUnicodeCodePoint, &tokenBufferIdx, &stringHash)) { jk_error(parseState, @"Internal error: Unable to add UTF8 sequence to internal string buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = JSONStringStateError; goto finishedParsing; }
}
else if((stringState >= JSONStringStateEscapedUnicode1) && (stringState <= JSONStringStateEscapedUnicodeSurrogate4)) { stringState++; }
break;
default: jk_error(parseState, @"Unexpected character found in \\u Unicode escape sequence. Found '%c', expected [0-9a-fA-F].", currentChar); stringState = JSONStringStateError; goto finishedParsing; break;
}
}
break;
case JSONStringStateEscapedNeedEscapeForSurrogate:
if(currentChar == '\\') { stringState = JSONStringStateEscapedNeedEscapedUForSurrogate; }
else {
if((parseState->parseOptionFlags & JKParseOptionLooseUnicode) == 0) { jk_error(parseState, @"Required a second \\u Unicode escape sequence following a surrogate \\u Unicode escape sequence."); stringState = JSONStringStateError; goto finishedParsing; }
else { stringState = JSONStringStateParsing; atStringCharacter--; if(jk_string_add_unicodeCodePoint(parseState, UNI_REPLACEMENT_CHAR, &tokenBufferIdx, &stringHash)) { jk_error(parseState, @"Internal error: Unable to add UTF8 sequence to internal string buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = JSONStringStateError; goto finishedParsing; } }
}
break;
case JSONStringStateEscapedNeedEscapedUForSurrogate:
if(currentChar == 'u') { stringState = JSONStringStateEscapedUnicodeSurrogate1; }
else {
if((parseState->parseOptionFlags & JKParseOptionLooseUnicode) == 0) { jk_error(parseState, @"Required a second \\u Unicode escape sequence following a surrogate \\u Unicode escape sequence."); stringState = JSONStringStateError; goto finishedParsing; }
else { stringState = JSONStringStateParsing; atStringCharacter -= 2; if(jk_string_add_unicodeCodePoint(parseState, UNI_REPLACEMENT_CHAR, &tokenBufferIdx, &stringHash)) { jk_error(parseState, @"Internal error: Unable to add UTF8 sequence to internal string buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = JSONStringStateError; goto finishedParsing; } }
}
break;
default: jk_error(parseState, @"Internal error: Unknown stringState. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = JSONStringStateError; goto finishedParsing; break;
}
}
}
finishedParsing:
if(JK_EXPECT_T(stringState == JSONStringStateFinished)) {
NSCParameterAssert((parseState->stringBuffer.bytes.ptr + tokenStartIndex) < atStringCharacter);
parseState->token.tokenPtrRange.ptr = parseState->stringBuffer.bytes.ptr + tokenStartIndex;
parseState->token.tokenPtrRange.length = (atStringCharacter - parseState->token.tokenPtrRange.ptr);
if(JK_EXPECT_T(onlySimpleString)) {
NSCParameterAssert(((parseState->token.tokenPtrRange.ptr + 1) < endOfBuffer) && (parseState->token.tokenPtrRange.length >= 2UL) && (((parseState->token.tokenPtrRange.ptr + 1) + (parseState->token.tokenPtrRange.length - 2)) < endOfBuffer));
parseState->token.value.ptrRange.ptr = parseState->token.tokenPtrRange.ptr + 1;
parseState->token.value.ptrRange.length = parseState->token.tokenPtrRange.length - 2UL;
} else {
parseState->token.value.ptrRange.ptr = parseState->token.tokenBuffer.bytes.ptr;
parseState->token.value.ptrRange.length = tokenBufferIdx;
}
parseState->token.value.hash = stringHash;
parseState->token.value.type = JKValueTypeString;
parseState->atIndex = (atStringCharacter - parseState->stringBuffer.bytes.ptr);
}
if(JK_EXPECT_F(stringState != JSONStringStateFinished)) { jk_error(parseState, @"Invalid string."); }
return(JK_EXPECT_T(stringState == JSONStringStateFinished) ? 0 : 1);
}
static int jk_parse_number(JKParseState *parseState) {
NSCParameterAssert((parseState != NULL) && (JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState)));
const unsigned char *numberStart = JK_AT_STRING_PTR(parseState);
const unsigned char *endOfBuffer = JK_END_STRING_PTR(parseState);
const unsigned char *atNumberCharacter = NULL;
int numberState = JSONNumberStateWholeNumberStart, isFloatingPoint = 0, isNegative = 0, backup = 0;
size_t startingIndex = parseState->atIndex;
for(atNumberCharacter = numberStart; (JK_EXPECT_T(atNumberCharacter < endOfBuffer)) && (JK_EXPECT_T(!(JK_EXPECT_F(numberState == JSONNumberStateFinished) || JK_EXPECT_F(numberState == JSONNumberStateError)))); atNumberCharacter++) {
unsigned long currentChar = (unsigned long)(*atNumberCharacter), lowerCaseCC = currentChar | 0x20UL;
switch(numberState) {
case JSONNumberStateWholeNumberStart: if (currentChar == '-') { numberState = JSONNumberStateWholeNumberMinus; isNegative = 1; break; }
case JSONNumberStateWholeNumberMinus: if (currentChar == '0') { numberState = JSONNumberStateWholeNumberZero; break; }
else if( (currentChar >= '1') && (currentChar <= '9')) { numberState = JSONNumberStateWholeNumber; break; }
else { /* XXX Add error message */ numberState = JSONNumberStateError; break; }
case JSONNumberStateExponentStart: if( (currentChar == '+') || (currentChar == '-')) { numberState = JSONNumberStateExponentPlusMinus; break; }
case JSONNumberStateFractionalNumberStart:
case JSONNumberStateExponentPlusMinus:if(!((currentChar >= '0') && (currentChar <= '9'))) { /* XXX Add error message */ numberState = JSONNumberStateError; break; }
else { if(numberState == JSONNumberStateFractionalNumberStart) { numberState = JSONNumberStateFractionalNumber; }
else { numberState = JSONNumberStateExponent; } break; }
case JSONNumberStateWholeNumberZero:
case JSONNumberStateWholeNumber: if (currentChar == '.') { numberState = JSONNumberStateFractionalNumberStart; isFloatingPoint = 1; break; }
case JSONNumberStateFractionalNumber: if (lowerCaseCC == 'e') { numberState = JSONNumberStateExponentStart; isFloatingPoint = 1; break; }
case JSONNumberStateExponent: if(!((currentChar >= '0') && (currentChar <= '9')) || (numberState == JSONNumberStateWholeNumberZero)) { numberState = JSONNumberStateFinished; backup = 1; break; }
break;
default: /* XXX Add error message */ numberState = JSONNumberStateError; break;
}
}
parseState->token.tokenPtrRange.ptr = parseState->stringBuffer.bytes.ptr + startingIndex;
parseState->token.tokenPtrRange.length = (atNumberCharacter - parseState->token.tokenPtrRange.ptr) - backup;
parseState->atIndex = (parseState->token.tokenPtrRange.ptr + parseState->token.tokenPtrRange.length) - parseState->stringBuffer.bytes.ptr;
if(JK_EXPECT_T(numberState == JSONNumberStateFinished)) {
unsigned char numberTempBuf[parseState->token.tokenPtrRange.length + 4UL];
unsigned char *endOfNumber = NULL;
memcpy(numberTempBuf, parseState->token.tokenPtrRange.ptr, parseState->token.tokenPtrRange.length);
numberTempBuf[parseState->token.tokenPtrRange.length] = 0;
errno = 0;
// Treat "-0" as a floating point number, which is capable of representing negative zeros.
if(JK_EXPECT_F(parseState->token.tokenPtrRange.length == 2UL) && JK_EXPECT_F(numberTempBuf[1] == '0') && JK_EXPECT_F(isNegative)) { isFloatingPoint = 1; }
if(isFloatingPoint) {
parseState->token.value.number.doubleValue = strtod((const char *)numberTempBuf, (char **)&endOfNumber); // strtod is documented to return U+2261 (identical to) 0.0 on an underflow error (along with setting errno to ERANGE).
parseState->token.value.type = JKValueTypeDouble;
parseState->token.value.ptrRange.ptr = (const unsigned char *)&parseState->token.value.number.doubleValue;
parseState->token.value.ptrRange.length = sizeof(double);
parseState->token.value.hash = (JK_HASH_INIT + parseState->token.value.type);
} else {
if(isNegative) {
parseState->token.value.number.longLongValue = strtoll((const char *)numberTempBuf, (char **)&endOfNumber, 10);
parseState->token.value.type = JKValueTypeLongLong;
parseState->token.value.ptrRange.ptr = (const unsigned char *)&parseState->token.value.number.longLongValue;
parseState->token.value.ptrRange.length = sizeof(long long);
parseState->token.value.hash = (JK_HASH_INIT + parseState->token.value.type) + (JKHash)parseState->token.value.number.longLongValue;
} else {
parseState->token.value.number.unsignedLongLongValue = strtoull((const char *)numberTempBuf, (char **)&endOfNumber, 10);
parseState->token.value.type = JKValueTypeUnsignedLongLong;
parseState->token.value.ptrRange.ptr = (const unsigned char *)&parseState->token.value.number.unsignedLongLongValue;
parseState->token.value.ptrRange.length = sizeof(unsigned long long);
parseState->token.value.hash = (JK_HASH_INIT + parseState->token.value.type) + (JKHash)parseState->token.value.number.unsignedLongLongValue;
}
}
if(JK_EXPECT_F(errno != 0)) {
numberState = JSONNumberStateError;
if(errno == ERANGE) {
switch(parseState->token.value.type) {
case JKValueTypeDouble: jk_error(parseState, @"The value '%s' could not be represented as a 'double' due to %s.", numberTempBuf, (parseState->token.value.number.doubleValue == 0.0) ? "underflow" : "overflow"); break; // see above for == 0.0.
case JKValueTypeLongLong: jk_error(parseState, @"The value '%s' exceeded the minimum value that could be represented: %lld.", numberTempBuf, parseState->token.value.number.longLongValue); break;
case JKValueTypeUnsignedLongLong: jk_error(parseState, @"The value '%s' exceeded the maximum value that could be represented: %llu.", numberTempBuf, parseState->token.value.number.unsignedLongLongValue); break;
default: jk_error(parseState, @"Internal error: Unknown token value type. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break;
}
}
}
if(JK_EXPECT_F(endOfNumber != &numberTempBuf[parseState->token.tokenPtrRange.length]) && JK_EXPECT_F(numberState != JSONNumberStateError)) { numberState = JSONNumberStateError; jk_error(parseState, @"The conversion function did not consume all of the number tokens characters."); }
size_t hashIndex = 0UL;
for(hashIndex = 0UL; hashIndex < parseState->token.value.ptrRange.length; hashIndex++) { parseState->token.value.hash = calculateHash(parseState->token.value.hash, parseState->token.value.ptrRange.ptr[hashIndex]); }
}
if(JK_EXPECT_F(numberState != JSONNumberStateFinished)) { jk_error(parseState, @"Invalid number."); }
return(JK_EXPECT_T((numberState == JSONNumberStateFinished)) ? 0 : 1);
}
JK_STATIC_INLINE void jk_set_parsed_token(JKParseState *parseState, const unsigned char *ptr, size_t length, JKTokenType type, size_t advanceBy) {
parseState->token.tokenPtrRange.ptr = ptr;
parseState->token.tokenPtrRange.length = length;
parseState->token.type = type;
parseState->atIndex += advanceBy;
}
static size_t jk_parse_is_newline(JKParseState *parseState, const unsigned char *atCharacterPtr) {
NSCParameterAssert((parseState != NULL) && (atCharacterPtr != NULL) && (atCharacterPtr >= parseState->stringBuffer.bytes.ptr) && (atCharacterPtr < JK_END_STRING_PTR(parseState)));
const unsigned char *endOfStringPtr = JK_END_STRING_PTR(parseState);
if(JK_EXPECT_F(atCharacterPtr >= endOfStringPtr)) { return(0UL); }
if(JK_EXPECT_F((*(atCharacterPtr + 0)) == '\n')) { return(1UL); }
if(JK_EXPECT_F((*(atCharacterPtr + 0)) == '\r')) { if((JK_EXPECT_T((atCharacterPtr + 1) < endOfStringPtr)) && ((*(atCharacterPtr + 1)) == '\n')) { return(2UL); } return(1UL); }
if(parseState->parseOptionFlags & JKParseOptionUnicodeNewlines) {
if((JK_EXPECT_F((*(atCharacterPtr + 0)) == 0xc2)) && (((atCharacterPtr + 1) < endOfStringPtr) && ((*(atCharacterPtr + 1)) == 0x85))) { return(2UL); }
if((JK_EXPECT_F((*(atCharacterPtr + 0)) == 0xe2)) && (((atCharacterPtr + 2) < endOfStringPtr) && ((*(atCharacterPtr + 1)) == 0x80) && (((*(atCharacterPtr + 2)) == 0xa8) || ((*(atCharacterPtr + 2)) == 0xa9)))) { return(3UL); }
}
return(0UL);
}
JK_STATIC_INLINE int jk_parse_skip_newline(JKParseState *parseState) {
size_t newlineAdvanceAtIndex = 0UL;
if(JK_EXPECT_F((newlineAdvanceAtIndex = jk_parse_is_newline(parseState, JK_AT_STRING_PTR(parseState))) > 0UL)) { parseState->lineNumber++; parseState->atIndex += (newlineAdvanceAtIndex - 1UL); parseState->lineStartIndex = parseState->atIndex + 1UL; return(1); }
return(0);
}
JK_STATIC_INLINE void jk_parse_skip_whitespace(JKParseState *parseState) {
#ifndef __clang_analyzer__
NSCParameterAssert((parseState != NULL) && (JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState)));
const unsigned char *atCharacterPtr = NULL;
const unsigned char *endOfStringPtr = JK_END_STRING_PTR(parseState);
for(atCharacterPtr = JK_AT_STRING_PTR(parseState); (JK_EXPECT_T((atCharacterPtr = JK_AT_STRING_PTR(parseState)) < endOfStringPtr)); parseState->atIndex++) {
if(((*(atCharacterPtr + 0)) == ' ') || ((*(atCharacterPtr + 0)) == '\t')) { continue; }
if(jk_parse_skip_newline(parseState)) { continue; }
if(parseState->parseOptionFlags & JKParseOptionComments) {
if((JK_EXPECT_F((*(atCharacterPtr + 0)) == '/')) && (JK_EXPECT_T((atCharacterPtr + 1) < endOfStringPtr))) {
if((*(atCharacterPtr + 1)) == '/') {
parseState->atIndex++;
for(atCharacterPtr = JK_AT_STRING_PTR(parseState); (JK_EXPECT_T((atCharacterPtr = JK_AT_STRING_PTR(parseState)) < endOfStringPtr)); parseState->atIndex++) { if(jk_parse_skip_newline(parseState)) { break; } }
continue;
}
if((*(atCharacterPtr + 1)) == '*') {
parseState->atIndex++;
for(atCharacterPtr = JK_AT_STRING_PTR(parseState); (JK_EXPECT_T((atCharacterPtr = JK_AT_STRING_PTR(parseState)) < endOfStringPtr)); parseState->atIndex++) {
if(jk_parse_skip_newline(parseState)) { continue; }
if(((*(atCharacterPtr + 0)) == '*') && (((atCharacterPtr + 1) < endOfStringPtr) && ((*(atCharacterPtr + 1)) == '/'))) { parseState->atIndex++; break; }
}
continue;
}
}
}
break;
}
#endif
}
static int jk_parse_next_token(JKParseState *parseState) {
NSCParameterAssert((parseState != NULL) && (JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState)));
const unsigned char *atCharacterPtr = NULL;
const unsigned char *endOfStringPtr = JK_END_STRING_PTR(parseState);
unsigned char currentCharacter = 0U;
int stopParsing = 0;
parseState->prev_atIndex = parseState->atIndex;
parseState->prev_lineNumber = parseState->lineNumber;
parseState->prev_lineStartIndex = parseState->lineStartIndex;
jk_parse_skip_whitespace(parseState);
if((JK_AT_STRING_PTR(parseState) == endOfStringPtr)) { stopParsing = 1; }
if((JK_EXPECT_T(stopParsing == 0)) && (JK_EXPECT_T((atCharacterPtr = JK_AT_STRING_PTR(parseState)) < endOfStringPtr))) {
currentCharacter = *atCharacterPtr;
if(JK_EXPECT_T(currentCharacter == '"')) { if(JK_EXPECT_T((stopParsing = jk_parse_string(parseState)) == 0)) { jk_set_parsed_token(parseState, parseState->token.tokenPtrRange.ptr, parseState->token.tokenPtrRange.length, JKTokenTypeString, 0UL); } }
else if(JK_EXPECT_T(currentCharacter == ':')) { jk_set_parsed_token(parseState, atCharacterPtr, 1UL, JKTokenTypeSeparator, 1UL); }
else if(JK_EXPECT_T(currentCharacter == ',')) { jk_set_parsed_token(parseState, atCharacterPtr, 1UL, JKTokenTypeComma, 1UL); }
else if((JK_EXPECT_T(currentCharacter >= '0') && JK_EXPECT_T(currentCharacter <= '9')) || JK_EXPECT_T(currentCharacter == '-')) { if(JK_EXPECT_T((stopParsing = jk_parse_number(parseState)) == 0)) { jk_set_parsed_token(parseState, parseState->token.tokenPtrRange.ptr, parseState->token.tokenPtrRange.length, JKTokenTypeNumber, 0UL); } }
else if(JK_EXPECT_T(currentCharacter == '{')) { jk_set_parsed_token(parseState, atCharacterPtr, 1UL, JKTokenTypeObjectBegin, 1UL); }
else if(JK_EXPECT_T(currentCharacter == '}')) { jk_set_parsed_token(parseState, atCharacterPtr, 1UL, JKTokenTypeObjectEnd, 1UL); }
else if(JK_EXPECT_T(currentCharacter == '[')) { jk_set_parsed_token(parseState, atCharacterPtr, 1UL, JKTokenTypeArrayBegin, 1UL); }
else if(JK_EXPECT_T(currentCharacter == ']')) { jk_set_parsed_token(parseState, atCharacterPtr, 1UL, JKTokenTypeArrayEnd, 1UL); }
else if(JK_EXPECT_T(currentCharacter == 't')) { if(!((JK_EXPECT_T((atCharacterPtr + 4UL) < endOfStringPtr)) && (JK_EXPECT_T(atCharacterPtr[1] == 'r')) && (JK_EXPECT_T(atCharacterPtr[2] == 'u')) && (JK_EXPECT_T(atCharacterPtr[3] == 'e')))) { stopParsing = 1; /* XXX Add error message */ } else { jk_set_parsed_token(parseState, atCharacterPtr, 4UL, JKTokenTypeTrue, 4UL); } }
else if(JK_EXPECT_T(currentCharacter == 'f')) { if(!((JK_EXPECT_T((atCharacterPtr + 5UL) < endOfStringPtr)) && (JK_EXPECT_T(atCharacterPtr[1] == 'a')) && (JK_EXPECT_T(atCharacterPtr[2] == 'l')) && (JK_EXPECT_T(atCharacterPtr[3] == 's')) && (JK_EXPECT_T(atCharacterPtr[4] == 'e')))) { stopParsing = 1; /* XXX Add error message */ } else { jk_set_parsed_token(parseState, atCharacterPtr, 5UL, JKTokenTypeFalse, 5UL); } }
else if(JK_EXPECT_T(currentCharacter == 'n')) { if(!((JK_EXPECT_T((atCharacterPtr + 4UL) < endOfStringPtr)) && (JK_EXPECT_T(atCharacterPtr[1] == 'u')) && (JK_EXPECT_T(atCharacterPtr[2] == 'l')) && (JK_EXPECT_T(atCharacterPtr[3] == 'l')))) { stopParsing = 1; /* XXX Add error message */ } else { jk_set_parsed_token(parseState, atCharacterPtr, 4UL, JKTokenTypeNull, 4UL); } }
else { stopParsing = 1; /* XXX Add error message */ }
}
if(JK_EXPECT_F(stopParsing)) { jk_error(parseState, @"Unexpected token, wanted '{', '}', '[', ']', ',', ':', 'true', 'false', 'null', '\"STRING\"', 'NUMBER'."); }
return(stopParsing);
}
static void jk_error_parse_accept_or3(JKParseState *parseState, int state, NSString *or1String, NSString *or2String, NSString *or3String) {
NSString *acceptStrings[16];
int acceptIdx = 0;
if(state & JKParseAcceptValue) { acceptStrings[acceptIdx++] = or1String; }
if(state & JKParseAcceptComma) { acceptStrings[acceptIdx++] = or2String; }
if(state & JKParseAcceptEnd) { acceptStrings[acceptIdx++] = or3String; }
if(acceptIdx == 1) { jk_error(parseState, @"Expected %@, not '%*.*s'", acceptStrings[0], (int)parseState->token.tokenPtrRange.length, (int)parseState->token.tokenPtrRange.length, parseState->token.tokenPtrRange.ptr); }
else if(acceptIdx == 2) { jk_error(parseState, @"Expected %@ or %@, not '%*.*s'", acceptStrings[0], acceptStrings[1], (int)parseState->token.tokenPtrRange.length, (int)parseState->token.tokenPtrRange.length, parseState->token.tokenPtrRange.ptr); }
else if(acceptIdx == 3) { jk_error(parseState, @"Expected %@, %@, or %@, not '%*.*s", acceptStrings[0], acceptStrings[1], acceptStrings[2], (int)parseState->token.tokenPtrRange.length, (int)parseState->token.tokenPtrRange.length, parseState->token.tokenPtrRange.ptr); }
}
static void *jk_parse_array(JKParseState *parseState) {
size_t startingObjectIndex = parseState->objectStack.index;
int arrayState = JKParseAcceptValueOrEnd, stopParsing = 0;
void *parsedArray = NULL;
while(JK_EXPECT_T((JK_EXPECT_T(stopParsing == 0)) && (JK_EXPECT_T(parseState->atIndex < parseState->stringBuffer.bytes.length)))) {
if(JK_EXPECT_F(parseState->objectStack.index > (parseState->objectStack.count - 4UL))) { if(jk_objectStack_resize(&parseState->objectStack, parseState->objectStack.count + 128UL)) { jk_error(parseState, @"Internal error: [array] objectsIndex > %zu, resize failed? %@ line %#ld", (parseState->objectStack.count - 4UL), [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break; } }
if(JK_EXPECT_T((stopParsing = jk_parse_next_token(parseState)) == 0)) {
void *object = NULL;
#ifndef NS_BLOCK_ASSERTIONS
parseState->objectStack.objects[parseState->objectStack.index] = NULL;
parseState->objectStack.keys [parseState->objectStack.index] = NULL;
#endif
switch(parseState->token.type) {
case JKTokenTypeNumber:
case JKTokenTypeString:
case JKTokenTypeTrue:
case JKTokenTypeFalse:
case JKTokenTypeNull:
case JKTokenTypeArrayBegin:
case JKTokenTypeObjectBegin:
if(JK_EXPECT_F((arrayState & JKParseAcceptValue) == 0)) { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected value."); stopParsing = 1; break; }
if(JK_EXPECT_F((object = jk_object_for_token(parseState)) == NULL)) { jk_error(parseState, @"Internal error: Object == NULL"); stopParsing = 1; break; } else { parseState->objectStack.objects[parseState->objectStack.index++] = object; arrayState = JKParseAcceptCommaOrEnd; }
break;
case JKTokenTypeArrayEnd: if(JK_EXPECT_T(arrayState & JKParseAcceptEnd)) { NSCParameterAssert(parseState->objectStack.index >= startingObjectIndex); parsedArray = (void *)_JKArrayCreate((id *)&parseState->objectStack.objects[startingObjectIndex], (parseState->objectStack.index - startingObjectIndex), parseState->mutableCollections); } else { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected ']'."); } stopParsing = 1; break;
case JKTokenTypeComma: if(JK_EXPECT_T(arrayState & JKParseAcceptComma)) { arrayState = JKParseAcceptValue; } else { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected ','."); stopParsing = 1; } break;
default: parseState->errorIsPrev = 1; jk_error_parse_accept_or3(parseState, arrayState, @"a value", @"a comma", @"a ']'"); stopParsing = 1; break;
}
}
}
if(JK_EXPECT_F(parsedArray == NULL)) { size_t idx = 0UL; for(idx = startingObjectIndex; idx < parseState->objectStack.index; idx++) { if(parseState->objectStack.objects[idx] != NULL) { CFRelease(parseState->objectStack.objects[idx]); parseState->objectStack.objects[idx] = NULL; } } }
#if !defined(NS_BLOCK_ASSERTIONS)
else { size_t idx = 0UL; for(idx = startingObjectIndex; idx < parseState->objectStack.index; idx++) { parseState->objectStack.objects[idx] = NULL; parseState->objectStack.keys[idx] = NULL; } }
#endif
parseState->objectStack.index = startingObjectIndex;
return(parsedArray);
}
static void *jk_create_dictionary(JKParseState *parseState, size_t startingObjectIndex) {
void *parsedDictionary = NULL;
parseState->objectStack.index--;
parsedDictionary = _JKDictionaryCreate((id *)&parseState->objectStack.keys[startingObjectIndex], (NSUInteger *)&parseState->objectStack.cfHashes[startingObjectIndex], (id *)&parseState->objectStack.objects[startingObjectIndex], (parseState->objectStack.index - startingObjectIndex), parseState->mutableCollections);
return(parsedDictionary);
}
static void *jk_parse_dictionary(JKParseState *parseState) {
size_t startingObjectIndex = parseState->objectStack.index;
int dictState = JKParseAcceptValueOrEnd, stopParsing = 0;
void *parsedDictionary = NULL;
while(JK_EXPECT_T((JK_EXPECT_T(stopParsing == 0)) && (JK_EXPECT_T(parseState->atIndex < parseState->stringBuffer.bytes.length)))) {
if(JK_EXPECT_F(parseState->objectStack.index > (parseState->objectStack.count - 4UL))) { if(jk_objectStack_resize(&parseState->objectStack, parseState->objectStack.count + 128UL)) { jk_error(parseState, @"Internal error: [dictionary] objectsIndex > %zu, resize failed? %@ line #%ld", (parseState->objectStack.count - 4UL), [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break; } }
size_t objectStackIndex = parseState->objectStack.index++;
parseState->objectStack.keys[objectStackIndex] = NULL;
parseState->objectStack.objects[objectStackIndex] = NULL;
void *key = NULL, *object = NULL;
if(JK_EXPECT_T((JK_EXPECT_T(stopParsing == 0)) && (JK_EXPECT_T((stopParsing = jk_parse_next_token(parseState)) == 0)))) {
switch(parseState->token.type) {
case JKTokenTypeString:
if(JK_EXPECT_F((dictState & JKParseAcceptValue) == 0)) { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected string."); stopParsing = 1; break; }
if(JK_EXPECT_F((key = jk_object_for_token(parseState)) == NULL)) { jk_error(parseState, @"Internal error: Key == NULL."); stopParsing = 1; break; }
else {
parseState->objectStack.keys[objectStackIndex] = key;
if(JK_EXPECT_T(parseState->token.value.cacheItem != NULL)) { if(JK_EXPECT_F(parseState->token.value.cacheItem->cfHash == 0UL)) { parseState->token.value.cacheItem->cfHash = CFHash(key); } parseState->objectStack.cfHashes[objectStackIndex] = parseState->token.value.cacheItem->cfHash; }
else { parseState->objectStack.cfHashes[objectStackIndex] = CFHash(key); }
}
break;
case JKTokenTypeObjectEnd: if((JK_EXPECT_T(dictState & JKParseAcceptEnd))) { NSCParameterAssert(parseState->objectStack.index >= startingObjectIndex); parsedDictionary = jk_create_dictionary(parseState, startingObjectIndex); } else { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected '}'."); } stopParsing = 1; break;
case JKTokenTypeComma: if((JK_EXPECT_T(dictState & JKParseAcceptComma))) { dictState = JKParseAcceptValue; parseState->objectStack.index--; continue; } else { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected ','."); stopParsing = 1; } break;
default: parseState->errorIsPrev = 1; jk_error_parse_accept_or3(parseState, dictState, @"a \"STRING\"", @"a comma", @"a '}'"); stopParsing = 1; break;
}
}
if(JK_EXPECT_T(stopParsing == 0)) {
if(JK_EXPECT_T((stopParsing = jk_parse_next_token(parseState)) == 0)) { if(JK_EXPECT_F(parseState->token.type != JKTokenTypeSeparator)) { parseState->errorIsPrev = 1; jk_error(parseState, @"Expected ':'."); stopParsing = 1; } }
}
if((JK_EXPECT_T(stopParsing == 0)) && (JK_EXPECT_T((stopParsing = jk_parse_next_token(parseState)) == 0))) {
switch(parseState->token.type) {
case JKTokenTypeNumber:
case JKTokenTypeString:
case JKTokenTypeTrue:
case JKTokenTypeFalse:
case JKTokenTypeNull:
case JKTokenTypeArrayBegin:
case JKTokenTypeObjectBegin:
if(JK_EXPECT_F((dictState & JKParseAcceptValue) == 0)) { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected value."); stopParsing = 1; break; }
if(JK_EXPECT_F((object = jk_object_for_token(parseState)) == NULL)) { jk_error(parseState, @"Internal error: Object == NULL."); stopParsing = 1; break; } else { parseState->objectStack.objects[objectStackIndex] = object; dictState = JKParseAcceptCommaOrEnd; }
break;
default: parseState->errorIsPrev = 1; jk_error_parse_accept_or3(parseState, dictState, @"a value", @"a comma", @"a '}'"); stopParsing = 1; break;
}
}
}
if(JK_EXPECT_F(parsedDictionary == NULL)) { size_t idx = 0UL; for(idx = startingObjectIndex; idx < parseState->objectStack.index; idx++) { if(parseState->objectStack.keys[idx] != NULL) { CFRelease(parseState->objectStack.keys[idx]); parseState->objectStack.keys[idx] = NULL; } if(parseState->objectStack.objects[idx] != NULL) { CFRelease(parseState->objectStack.objects[idx]); parseState->objectStack.objects[idx] = NULL; } } }
#if !defined(NS_BLOCK_ASSERTIONS)
else { size_t idx = 0UL; for(idx = startingObjectIndex; idx < parseState->objectStack.index; idx++) { parseState->objectStack.objects[idx] = NULL; parseState->objectStack.keys[idx] = NULL; } }
#endif
parseState->objectStack.index = startingObjectIndex;
return(parsedDictionary);
}
static id json_parse_it(JKParseState *parseState) {
id parsedObject = NULL;
int stopParsing = 0;
while((JK_EXPECT_T(stopParsing == 0)) && (JK_EXPECT_T(parseState->atIndex < parseState->stringBuffer.bytes.length))) {
if((JK_EXPECT_T(stopParsing == 0)) && (JK_EXPECT_T((stopParsing = jk_parse_next_token(parseState)) == 0))) {
switch(parseState->token.type) {
case JKTokenTypeArrayBegin:
case JKTokenTypeObjectBegin: parsedObject = [(id)jk_object_for_token(parseState) autorelease]; stopParsing = 1; break;
default: jk_error(parseState, @"Expected either '[' or '{'."); stopParsing = 1; break;
}
}
}
NSCParameterAssert((parseState->objectStack.index == 0) && (JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState)));
if((parsedObject == NULL) && (JK_AT_STRING_PTR(parseState) == JK_END_STRING_PTR(parseState))) { jk_error(parseState, @"Reached the end of the buffer."); }
if(parsedObject == NULL) { jk_error(parseState, @"Unable to parse JSON."); }
if((parsedObject != NULL) && (JK_AT_STRING_PTR(parseState) < JK_END_STRING_PTR(parseState))) {
jk_parse_skip_whitespace(parseState);
if((parsedObject != NULL) && ((parseState->parseOptionFlags & JKParseOptionPermitTextAfterValidJSON) == 0) && (JK_AT_STRING_PTR(parseState) < JK_END_STRING_PTR(parseState))) {
jk_error(parseState, @"A valid JSON object was parsed but there were additional non-white-space characters remaining.");
parsedObject = NULL;
}
}
return(parsedObject);
}
////////////
#pragma mark -
#pragma mark Object cache
// This uses a Galois Linear Feedback Shift Register (LFSR) PRNG to pick which item in the cache to age. It has a period of (2^32)-1.
// NOTE: A LFSR *MUST* be initialized to a non-zero value and must always have a non-zero value.
JK_STATIC_INLINE void jk_cache_age(JKParseState *parseState) {
NSCParameterAssert((parseState != NULL) && (parseState->cache.prng_lfsr != 0U));
parseState->cache.prng_lfsr = (parseState->cache.prng_lfsr >> 1) ^ ((0U - (parseState->cache.prng_lfsr & 1U)) & 0x80200003U);
parseState->cache.age[parseState->cache.prng_lfsr & (parseState->cache.count - 1UL)] >>= 1;
}
// The object cache is nothing more than a hash table with open addressing collision resolution that is bounded by JK_CACHE_PROBES attempts.
//
// The hash table is a linear C array of JKTokenCacheItem. The terms "item" and "bucket" are synonymous with the index in to the cache array, i.e. cache.items[bucket].
//
// Items in the cache have an age associated with them. The age is the number of rightmost 1 bits, i.e. 0000 = 0, 0001 = 1, 0011 = 2, 0111 = 3, 1111 = 4.
// This allows us to use left and right shifts to add or subtract from an items age. Add = (age << 1) | 1. Subtract = age >> 0. Subtract is synonymous with "age" (i.e., age an item).
// The reason for this is it allows us to perform saturated adds and subtractions and is branchless.
// The primitive C type MUST be unsigned. It is currently a "char", which allows (at a minimum and in practice) 8 bits.
//
// A "useable bucket" is a bucket that is not in use (never populated), or has an age == 0.
//
// When an item is found in the cache, it's age is incremented.
// If a useable bucket hasn't been found, the current item (bucket) is aged along with two random items.
//
// If a value is not found in the cache, and no useable bucket has been found, that value is not added to the cache.
static void *jk_cachedObjects(JKParseState *parseState) {
unsigned long bucket = parseState->token.value.hash & (parseState->cache.count - 1UL), setBucket = 0UL, useableBucket = 0UL, x = 0UL;
void *parsedAtom = NULL;
if(JK_EXPECT_F(parseState->token.value.ptrRange.length == 0UL) && JK_EXPECT_T(parseState->token.value.type == JKValueTypeString)) { return(@""); }
for(x = 0UL; x < JK_CACHE_PROBES; x++) {
if(JK_EXPECT_F(parseState->cache.items[bucket].object == NULL)) { setBucket = 1UL; useableBucket = bucket; break; }
if((JK_EXPECT_T(parseState->cache.items[bucket].hash == parseState->token.value.hash)) && (JK_EXPECT_T(parseState->cache.items[bucket].size == parseState->token.value.ptrRange.length)) && (JK_EXPECT_T(parseState->cache.items[bucket].type == parseState->token.value.type)) && (JK_EXPECT_T(parseState->cache.items[bucket].bytes != NULL)) && (JK_EXPECT_T(memcmp(parseState->cache.items[bucket].bytes, parseState->token.value.ptrRange.ptr, parseState->token.value.ptrRange.length) == 0U))) {
parseState->cache.age[bucket] = (parseState->cache.age[bucket] << 1) | 1U;
parseState->token.value.cacheItem = &parseState->cache.items[bucket];
NSCParameterAssert(parseState->cache.items[bucket].object != NULL);
return((void *)CFRetain(parseState->cache.items[bucket].object));
} else {
if(JK_EXPECT_F(setBucket == 0UL) && JK_EXPECT_F(parseState->cache.age[bucket] == 0U)) { setBucket = 1UL; useableBucket = bucket; }
if(JK_EXPECT_F(setBucket == 0UL)) { parseState->cache.age[bucket] >>= 1; jk_cache_age(parseState); jk_cache_age(parseState); }
// This is the open addressing function. The values length and type are used as a form of "double hashing" to distribute values with the same effective value hash across different object cache buckets.
// The values type is a prime number that is relatively coprime to the other primes in the set of value types and the number of hash table buckets.
bucket = (parseState->token.value.hash + (parseState->token.value.ptrRange.length * (x + 1UL)) + (parseState->token.value.type * (x + 1UL)) + (3UL * (x + 1UL))) & (parseState->cache.count - 1UL);
}
}
switch(parseState->token.value.type) {
case JKValueTypeString: parsedAtom = (void *)CFStringCreateWithBytes(NULL, parseState->token.value.ptrRange.ptr, parseState->token.value.ptrRange.length, kCFStringEncodingUTF8, 0); break;
case JKValueTypeLongLong: parsedAtom = (void *)CFNumberCreate(NULL, kCFNumberLongLongType, &parseState->token.value.number.longLongValue); break;
case JKValueTypeUnsignedLongLong:
if(parseState->token.value.number.unsignedLongLongValue <= LLONG_MAX) { parsedAtom = (void *)CFNumberCreate(NULL, kCFNumberLongLongType, &parseState->token.value.number.unsignedLongLongValue); }
else { parsedAtom = (void *)parseState->objCImpCache.NSNumberInitWithUnsignedLongLong(parseState->objCImpCache.NSNumberAlloc(parseState->objCImpCache.NSNumberClass, @selector(alloc)), @selector(initWithUnsignedLongLong:), parseState->token.value.number.unsignedLongLongValue); }
break;
case JKValueTypeDouble: parsedAtom = (void *)CFNumberCreate(NULL, kCFNumberDoubleType, &parseState->token.value.number.doubleValue); break;
default: jk_error(parseState, @"Internal error: Unknown token value type. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break;
}
if(JK_EXPECT_T(setBucket) && (JK_EXPECT_T(parsedAtom != NULL))) {
bucket = useableBucket;
if(JK_EXPECT_T((parseState->cache.items[bucket].object != NULL))) { CFRelease(parseState->cache.items[bucket].object); parseState->cache.items[bucket].object = NULL; }
if(JK_EXPECT_T((parseState->cache.items[bucket].bytes = (unsigned char *)reallocf(parseState->cache.items[bucket].bytes, parseState->token.value.ptrRange.length)) != NULL)) {
memcpy(parseState->cache.items[bucket].bytes, parseState->token.value.ptrRange.ptr, parseState->token.value.ptrRange.length);
parseState->cache.items[bucket].object = (void *)CFRetain(parsedAtom);
parseState->cache.items[bucket].hash = parseState->token.value.hash;
parseState->cache.items[bucket].cfHash = 0UL;
parseState->cache.items[bucket].size = parseState->token.value.ptrRange.length;
parseState->cache.items[bucket].type = parseState->token.value.type;
parseState->token.value.cacheItem = &parseState->cache.items[bucket];
parseState->cache.age[bucket] = JK_INIT_CACHE_AGE;
} else { // The realloc failed, so clear the appropriate fields.
parseState->cache.items[bucket].hash = 0UL;
parseState->cache.items[bucket].cfHash = 0UL;
parseState->cache.items[bucket].size = 0UL;
parseState->cache.items[bucket].type = 0UL;
}
}
return(parsedAtom);
}
static void *jk_object_for_token(JKParseState *parseState) {
void *parsedAtom = NULL;
parseState->token.value.cacheItem = NULL;
switch(parseState->token.type) {
case JKTokenTypeString: parsedAtom = jk_cachedObjects(parseState); break;
case JKTokenTypeNumber: parsedAtom = jk_cachedObjects(parseState); break;
case JKTokenTypeObjectBegin: parsedAtom = jk_parse_dictionary(parseState); break;
case JKTokenTypeArrayBegin: parsedAtom = jk_parse_array(parseState); break;
case JKTokenTypeTrue: parsedAtom = (void *)kCFBooleanTrue; break;
case JKTokenTypeFalse: parsedAtom = (void *)kCFBooleanFalse; break;
case JKTokenTypeNull: parsedAtom = (void *)kCFNull; break;
default: jk_error(parseState, @"Internal error: Unknown token type. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break;
}
return(parsedAtom);
}
#pragma mark -
@implementation JSONDecoder
+ (id)decoder
{
return([self decoderWithParseOptions:JKParseOptionStrict]);
}
+ (id)decoderWithParseOptions:(JKParseOptionFlags)parseOptionFlags
{
return([[[self alloc] initWithParseOptions:parseOptionFlags] autorelease]);
}
- (id)init
{
return([self initWithParseOptions:JKParseOptionStrict]);
}
- (id)initWithParseOptions:(JKParseOptionFlags)parseOptionFlags
{
if((self = [super init]) == NULL) { return(NULL); }
if(parseOptionFlags & ~JKParseOptionValidFlags) { [self autorelease]; [NSException raise:NSInvalidArgumentException format:@"Invalid parse options."]; }
if((parseState = (JKParseState *)calloc(1UL, sizeof(JKParseState))) == NULL) { goto errorExit; }
parseState->parseOptionFlags = parseOptionFlags;
parseState->token.tokenBuffer.roundSizeUpToMultipleOf = 4096UL;
parseState->objectStack.roundSizeUpToMultipleOf = 2048UL;
parseState->objCImpCache.NSNumberClass = _jk_NSNumberClass;
parseState->objCImpCache.NSNumberAlloc = _jk_NSNumberAllocImp;
parseState->objCImpCache.NSNumberInitWithUnsignedLongLong = _jk_NSNumberInitWithUnsignedLongLongImp;
parseState->cache.prng_lfsr = 1U;
parseState->cache.count = JK_CACHE_SLOTS;
if((parseState->cache.items = (JKTokenCacheItem *)calloc(1UL, sizeof(JKTokenCacheItem) * parseState->cache.count)) == NULL) { goto errorExit; }
return(self);
errorExit:
if(self) { [self autorelease]; self = NULL; }
return(NULL);
}
// This is here primarily to support the NSString and NSData convenience functions so the autoreleased JSONDecoder can release most of its resources before the pool pops.
static void _JSONDecoderCleanup(JSONDecoder *decoder) {
if((decoder != NULL) && (decoder->parseState != NULL)) {
jk_managedBuffer_release(&decoder->parseState->token.tokenBuffer);
jk_objectStack_release(&decoder->parseState->objectStack);
[decoder clearCache];
if(decoder->parseState->cache.items != NULL) { free(decoder->parseState->cache.items); decoder->parseState->cache.items = NULL; }
free(decoder->parseState); decoder->parseState = NULL;
}
}
- (void)dealloc
{
_JSONDecoderCleanup(self);
[super dealloc];
}
- (void)clearCache
{
if(JK_EXPECT_T(parseState != NULL)) {
if(JK_EXPECT_T(parseState->cache.items != NULL)) {
size_t idx = 0UL;
for(idx = 0UL; idx < parseState->cache.count; idx++) {
if(JK_EXPECT_T(parseState->cache.items[idx].object != NULL)) { CFRelease(parseState->cache.items[idx].object); parseState->cache.items[idx].object = NULL; }
if(JK_EXPECT_T(parseState->cache.items[idx].bytes != NULL)) { free(parseState->cache.items[idx].bytes); parseState->cache.items[idx].bytes = NULL; }
memset(&parseState->cache.items[idx], 0, sizeof(JKTokenCacheItem));
parseState->cache.age[idx] = 0U;
}
}
}
}
// This needs to be completely rewritten.
static id _JKParseUTF8String(JKParseState *parseState, BOOL mutableCollections, const unsigned char *string, size_t length, NSError **error) {
NSCParameterAssert((parseState != NULL) && (string != NULL) && (parseState->cache.prng_lfsr != 0U));
parseState->stringBuffer.bytes.ptr = string;
parseState->stringBuffer.bytes.length = length;
parseState->atIndex = 0UL;
parseState->lineNumber = 1UL;
parseState->lineStartIndex = 0UL;
parseState->prev_atIndex = 0UL;
parseState->prev_lineNumber = 1UL;
parseState->prev_lineStartIndex = 0UL;
parseState->error = NULL;
parseState->errorIsPrev = 0;
parseState->mutableCollections = (mutableCollections == NO) ? NO : YES;
unsigned char stackTokenBuffer[JK_TOKENBUFFER_SIZE] JK_ALIGNED(64);
jk_managedBuffer_setToStackBuffer(&parseState->token.tokenBuffer, stackTokenBuffer, sizeof(stackTokenBuffer));
void *stackObjects [JK_STACK_OBJS] JK_ALIGNED(64);
void *stackKeys [JK_STACK_OBJS] JK_ALIGNED(64);
CFHashCode stackCFHashes[JK_STACK_OBJS] JK_ALIGNED(64);
jk_objectStack_setToStackBuffer(&parseState->objectStack, stackObjects, stackKeys, stackCFHashes, JK_STACK_OBJS);
id parsedJSON = json_parse_it(parseState);
if((error != NULL) && (parseState->error != NULL)) { *error = parseState->error; }
jk_managedBuffer_release(&parseState->token.tokenBuffer);
jk_objectStack_release(&parseState->objectStack);
parseState->stringBuffer.bytes.ptr = NULL;
parseState->stringBuffer.bytes.length = 0UL;
parseState->atIndex = 0UL;
parseState->lineNumber = 1UL;
parseState->lineStartIndex = 0UL;
parseState->prev_atIndex = 0UL;
parseState->prev_lineNumber = 1UL;
parseState->prev_lineStartIndex = 0UL;
parseState->error = NULL;
parseState->errorIsPrev = 0;
parseState->mutableCollections = NO;
return(parsedJSON);
}
////////////
#pragma mark Deprecated as of v1.4
////////////
// Deprecated in JSONKit v1.4. Use objectWithUTF8String:length: instead.
- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length
{
return([self objectWithUTF8String:string length:length error:NULL]);
}
// Deprecated in JSONKit v1.4. Use objectWithUTF8String:length:error: instead.
- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length error:(NSError **)error
{
return([self objectWithUTF8String:string length:length error:error]);
}
// Deprecated in JSONKit v1.4. Use objectWithData: instead.
- (id)parseJSONData:(NSData *)jsonData
{
return([self objectWithData:jsonData error:NULL]);
}
// Deprecated in JSONKit v1.4. Use objectWithData:error: instead.
- (id)parseJSONData:(NSData *)jsonData error:(NSError **)error
{
return([self objectWithData:jsonData error:error]);
}
////////////
#pragma mark Methods that return immutable collection objects
////////////
- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length
{
return([self objectWithUTF8String:string length:length error:NULL]);
}
- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error
{
if(parseState == NULL) { [NSException raise:NSInternalInconsistencyException format:@"parseState is NULL."]; }
if(string == NULL) { [NSException raise:NSInvalidArgumentException format:@"The string argument is NULL."]; }
return(_JKParseUTF8String(parseState, NO, string, (size_t)length, error));
}
- (id)objectWithData:(NSData *)jsonData
{
return([self objectWithData:jsonData error:NULL]);
}
- (id)objectWithData:(NSData *)jsonData error:(NSError **)error
{
if(jsonData == NULL) { [NSException raise:NSInvalidArgumentException format:@"The jsonData argument is NULL."]; }
return([self objectWithUTF8String:(const unsigned char *)[jsonData bytes] length:[jsonData length] error:error]);
}
////////////
#pragma mark Methods that return mutable collection objects
////////////
- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length
{
return([self mutableObjectWithUTF8String:string length:length error:NULL]);
}
- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error
{
if(parseState == NULL) { [NSException raise:NSInternalInconsistencyException format:@"parseState is NULL."]; }
if(string == NULL) { [NSException raise:NSInvalidArgumentException format:@"The string argument is NULL."]; }
return(_JKParseUTF8String(parseState, YES, string, (size_t)length, error));
}
- (id)mutableObjectWithData:(NSData *)jsonData
{
return([self mutableObjectWithData:jsonData error:NULL]);
}
- (id)mutableObjectWithData:(NSData *)jsonData error:(NSError **)error
{
if(jsonData == NULL) { [NSException raise:NSInvalidArgumentException format:@"The jsonData argument is NULL."]; }
return([self mutableObjectWithUTF8String:(const unsigned char *)[jsonData bytes] length:[jsonData length] error:error]);
}
@end
/*
The NSString and NSData convenience methods need a little bit of explanation.
Prior to JSONKit v1.4, the NSString -objectFromJSONStringWithParseOptions:error: method looked like
const unsigned char *utf8String = (const unsigned char *)[self UTF8String];
if(utf8String == NULL) { return(NULL); }
size_t utf8Length = strlen((const char *)utf8String);
return([[JSONDecoder decoderWithParseOptions:parseOptionFlags] parseUTF8String:utf8String length:utf8Length error:error]);
This changed with v1.4 to a more complicated method. The reason for this is to keep the amount of memory that is
allocated, but not yet freed because it is dependent on the autorelease pool to pop before it can be reclaimed.
In the simpler v1.3 code, this included all the bytes used to store the -UTF8String along with the JSONDecoder and all its overhead.
Now we use an autoreleased CFMutableData that is sized to the UTF8 length of the NSString in question and is used to hold the UTF8
conversion of said string.
Once parsed, the CFMutableData has its length set to 0. This should, hopefully, allow the CFMutableData to realloc and/or free
the buffer.
Another change made was a slight modification to JSONDecoder so that most of the cleanup work that was done in -dealloc was moved
to a private, internal function. These convenience routines keep the pointer to the autoreleased JSONDecoder and calls
_JSONDecoderCleanup() to early release the decoders resources since we already know that particular decoder is not going to be used
again.
If everything goes smoothly, this will most likely result in perhaps a few hundred bytes that are allocated but waiting for the
autorelease pool to pop. This is compared to the thousands and easily hundreds of thousands of bytes that would have been in
autorelease limbo. It's more complicated for us, but a win for the user.
Autorelease objects are used in case things don't go smoothly. By having them autoreleased, we effectively guarantee that our
requirement to -release the object is always met, not matter what goes wrong. The downside is having a an object or two in
autorelease limbo, but we've done our best to minimize that impact, so it all balances out.
*/
@implementation NSString (JSONKitDeserializing)
static id _NSStringObjectFromJSONString(NSString *jsonString, JKParseOptionFlags parseOptionFlags, NSError **error, BOOL mutableCollection) {
id returnObject = NULL;
CFMutableDataRef mutableData = NULL;
JSONDecoder *decoder = NULL;
CFIndex stringLength = CFStringGetLength((CFStringRef)jsonString);
NSUInteger stringUTF8Length = [jsonString lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
if((mutableData = (CFMutableDataRef)[(id)CFDataCreateMutable(NULL, (NSUInteger)stringUTF8Length) autorelease]) != NULL) {
UInt8 *utf8String = CFDataGetMutableBytePtr(mutableData);
CFIndex usedBytes = 0L, convertedCount = 0L;
convertedCount = CFStringGetBytes((CFStringRef)jsonString, CFRangeMake(0L, stringLength), kCFStringEncodingUTF8, '?', NO, utf8String, (NSUInteger)stringUTF8Length, &usedBytes);
if(JK_EXPECT_F(convertedCount != stringLength) || JK_EXPECT_F(usedBytes < 0L)) { if(error != NULL) { *error = [NSError errorWithDomain:@"JKErrorDomain" code:-1L userInfo:[NSDictionary dictionaryWithObject:@"An error occurred converting the contents of a NSString to UTF8." forKey:NSLocalizedDescriptionKey]]; } goto exitNow; }
if(mutableCollection == NO) { returnObject = [(decoder = [JSONDecoder decoderWithParseOptions:parseOptionFlags]) objectWithUTF8String:(const unsigned char *)utf8String length:(size_t)usedBytes error:error]; }
else { returnObject = [(decoder = [JSONDecoder decoderWithParseOptions:parseOptionFlags]) mutableObjectWithUTF8String:(const unsigned char *)utf8String length:(size_t)usedBytes error:error]; }
}
exitNow:
if(mutableData != NULL) { CFDataSetLength(mutableData, 0L); }
if(decoder != NULL) { _JSONDecoderCleanup(decoder); }
return(returnObject);
}
- (id)objectFromJSONString
{
return([self objectFromJSONStringWithParseOptions:JKParseOptionStrict error:NULL]);
}
- (id)objectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags
{
return([self objectFromJSONStringWithParseOptions:parseOptionFlags error:NULL]);
}
- (id)objectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error
{
return(_NSStringObjectFromJSONString(self, parseOptionFlags, error, NO));
}
- (id)mutableObjectFromJSONString
{
return([self mutableObjectFromJSONStringWithParseOptions:JKParseOptionStrict error:NULL]);
}
- (id)mutableObjectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags
{
return([self mutableObjectFromJSONStringWithParseOptions:parseOptionFlags error:NULL]);
}
- (id)mutableObjectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error
{
return(_NSStringObjectFromJSONString(self, parseOptionFlags, error, YES));
}
@end
@implementation NSData (JSONKitDeserializing)
- (id)objectFromJSONData
{
return([self objectFromJSONDataWithParseOptions:JKParseOptionStrict error:NULL]);
}
- (id)objectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags
{
return([self objectFromJSONDataWithParseOptions:parseOptionFlags error:NULL]);
}
- (id)objectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error
{
JSONDecoder *decoder = NULL;
id returnObject = [(decoder = [JSONDecoder decoderWithParseOptions:parseOptionFlags]) objectWithData:self error:error];
if(decoder != NULL) { _JSONDecoderCleanup(decoder); }
return(returnObject);
}
- (id)mutableObjectFromJSONData
{
return([self mutableObjectFromJSONDataWithParseOptions:JKParseOptionStrict error:NULL]);
}
- (id)mutableObjectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags
{
return([self mutableObjectFromJSONDataWithParseOptions:parseOptionFlags error:NULL]);
}
- (id)mutableObjectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error
{
JSONDecoder *decoder = NULL;
id returnObject = [(decoder = [JSONDecoder decoderWithParseOptions:parseOptionFlags]) mutableObjectWithData:self error:error];
if(decoder != NULL) { _JSONDecoderCleanup(decoder); }
return(returnObject);
}
@end
////////////
#pragma mark -
#pragma mark Encoding / deserializing functions
static void jk_encode_error(JKEncodeState *encodeState, NSString *format, ...) {
NSCParameterAssert((encodeState != NULL) && (format != NULL));
va_list varArgsList;
va_start(varArgsList, format);
NSString *formatString = [[[NSString alloc] initWithFormat:format arguments:varArgsList] autorelease];
va_end(varArgsList);
if(encodeState->error == NULL) {
encodeState->error = [NSError errorWithDomain:@"JKErrorDomain" code:-1L userInfo:
[NSDictionary dictionaryWithObjectsAndKeys:
formatString, NSLocalizedDescriptionKey,
NULL]];
}
}
JK_STATIC_INLINE void jk_encode_updateCache(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object) {
NSCParameterAssert(encodeState != NULL);
if(JK_EXPECT_T(cacheSlot != NULL)) {
NSCParameterAssert((object != NULL) && (startingAtIndex <= encodeState->atIndex));
cacheSlot->object = object;
cacheSlot->offset = startingAtIndex;
cacheSlot->length = (size_t)(encodeState->atIndex - startingAtIndex);
}
}
static int jk_encode_printf(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format, ...) {
va_list varArgsList, varArgsListCopy;
va_start(varArgsList, format);
va_copy(varArgsListCopy, varArgsList);
NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (startingAtIndex <= encodeState->atIndex) && (format != NULL));
ssize_t formattedStringLength = 0L;
int returnValue = 0;
if(JK_EXPECT_T((formattedStringLength = vsnprintf((char *)&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex], (encodeState->stringBuffer.bytes.length - encodeState->atIndex), format, varArgsList)) >= (ssize_t)(encodeState->stringBuffer.bytes.length - encodeState->atIndex))) {
NSCParameterAssert(((encodeState->atIndex + (formattedStringLength * 2UL) + 256UL) > encodeState->stringBuffer.bytes.length));
if(JK_EXPECT_F(((encodeState->atIndex + (formattedStringLength * 2UL) + 256UL) > encodeState->stringBuffer.bytes.length)) && JK_EXPECT_F((jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + (formattedStringLength * 2UL)+ 4096UL) == NULL))) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); returnValue = 1; goto exitNow; }
if(JK_EXPECT_F((formattedStringLength = vsnprintf((char *)&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex], (encodeState->stringBuffer.bytes.length - encodeState->atIndex), format, varArgsListCopy)) >= (ssize_t)(encodeState->stringBuffer.bytes.length - encodeState->atIndex))) { jk_encode_error(encodeState, @"vsnprintf failed unexpectedly."); returnValue = 1; goto exitNow; }
}
exitNow:
va_end(varArgsList);
va_end(varArgsListCopy);
if(JK_EXPECT_T(returnValue == 0)) { encodeState->atIndex += formattedStringLength; jk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, object); }
return(returnValue);
}
static int jk_encode_write(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format) {
NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (startingAtIndex <= encodeState->atIndex) && (format != NULL));
if(JK_EXPECT_F(((encodeState->atIndex + strlen(format) + 256UL) > encodeState->stringBuffer.bytes.length)) && JK_EXPECT_F((jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + strlen(format) + 1024UL) == NULL))) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); }
size_t formatIdx = 0UL;
for(formatIdx = 0UL; format[formatIdx] != 0; formatIdx++) { NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length); encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = format[formatIdx]; }
jk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, object);
return(0);
}
static int jk_encode_writePrettyPrintWhiteSpace(JKEncodeState *encodeState) {
NSCParameterAssert((encodeState != NULL) && ((encodeState->serializeOptionFlags & JKSerializeOptionPretty) != 0UL));
if(JK_EXPECT_F((encodeState->atIndex + ((encodeState->depth + 1UL) * 2UL) + 16UL) > encodeState->stringBuffer.bytes.length) && JK_EXPECT_T(jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + ((encodeState->depth + 1UL) * 2UL) + 4096UL) == NULL)) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); }
encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\n';
size_t depthWhiteSpace = 0UL;
for(depthWhiteSpace = 0UL; depthWhiteSpace < (encodeState->depth * 2UL); depthWhiteSpace++) { NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length); encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = ' '; }
return(0);
}
static int jk_encode_write1slow(JKEncodeState *encodeState, ssize_t depthChange, const char *format) {
NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (format != NULL) && ((depthChange >= -1L) && (depthChange <= 1L)) && ((encodeState->depth == 0UL) ? (depthChange >= 0L) : 1) && ((encodeState->serializeOptionFlags & JKSerializeOptionPretty) != 0UL));
if(JK_EXPECT_F((encodeState->atIndex + ((encodeState->depth + 1UL) * 2UL) + 16UL) > encodeState->stringBuffer.bytes.length) && JK_EXPECT_F(jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + ((encodeState->depth + 1UL) * 2UL) + 4096UL) == NULL)) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); }
encodeState->depth += depthChange;
if(JK_EXPECT_T(format[0] == ':')) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = format[0]; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = ' '; }
else {
if(JK_EXPECT_F(depthChange == -1L)) { if(JK_EXPECT_F(jk_encode_writePrettyPrintWhiteSpace(encodeState))) { return(1); } }
encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = format[0];
if(JK_EXPECT_T(depthChange != -1L)) { if(JK_EXPECT_F(jk_encode_writePrettyPrintWhiteSpace(encodeState))) { return(1); } }
}
NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length);
return(0);
}
static int jk_encode_write1fast(JKEncodeState *encodeState, ssize_t depthChange JK_UNUSED_ARG, const char *format) {
NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && ((encodeState->serializeOptionFlags & JKSerializeOptionPretty) == 0UL));
if(JK_EXPECT_T((encodeState->atIndex + 4UL) < encodeState->stringBuffer.bytes.length)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = format[0]; }
else { return(jk_encode_write(encodeState, NULL, 0UL, NULL, format)); }
return(0);
}
static int jk_encode_writen(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format, size_t length) {
NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (startingAtIndex <= encodeState->atIndex));
if(JK_EXPECT_F((encodeState->stringBuffer.bytes.length - encodeState->atIndex) < (length + 4UL))) { if(jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + 4096UL + length) == NULL) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } }
memcpy(encodeState->stringBuffer.bytes.ptr + encodeState->atIndex, format, length);
encodeState->atIndex += length;
jk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, object);
return(0);
}
JK_STATIC_INLINE JKHash jk_encode_object_hash(void *objectPtr) {
return( ( (((JKHash)objectPtr) >> 21) ^ (((JKHash)objectPtr) >> 9) ) + (((JKHash)objectPtr) >> 4) );
}
static int jk_encode_add_atom_to_buffer(JKEncodeState *encodeState, void *objectPtr) {
NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (objectPtr != NULL));
id object = (id)objectPtr, encodeCacheObject = object;
int isClass = JKClassUnknown;
size_t startingAtIndex = encodeState->atIndex;
JKHash objectHash = jk_encode_object_hash(objectPtr);
JKEncodeCache *cacheSlot = &encodeState->cache[objectHash % JK_ENCODE_CACHE_SLOTS];
if(JK_EXPECT_T(cacheSlot->object == object)) {
NSCParameterAssert((cacheSlot->object != NULL) &&
(cacheSlot->offset < encodeState->atIndex) && ((cacheSlot->offset + cacheSlot->length) < encodeState->atIndex) &&
(cacheSlot->offset < encodeState->stringBuffer.bytes.length) && ((cacheSlot->offset + cacheSlot->length) < encodeState->stringBuffer.bytes.length) &&
((encodeState->stringBuffer.bytes.ptr + encodeState->atIndex) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) &&
((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) &&
((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset + cacheSlot->length) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)));
if(JK_EXPECT_F(((encodeState->atIndex + cacheSlot->length + 256UL) > encodeState->stringBuffer.bytes.length)) && JK_EXPECT_F((jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + cacheSlot->length + 1024UL) == NULL))) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); }
NSCParameterAssert(((encodeState->atIndex + cacheSlot->length) < encodeState->stringBuffer.bytes.length) &&
((encodeState->stringBuffer.bytes.ptr + encodeState->atIndex) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) &&
((encodeState->stringBuffer.bytes.ptr + encodeState->atIndex + cacheSlot->length) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) &&
((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) &&
((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset + cacheSlot->length) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) &&
((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset + cacheSlot->length) < (encodeState->stringBuffer.bytes.ptr + encodeState->atIndex)));
memcpy(encodeState->stringBuffer.bytes.ptr + encodeState->atIndex, encodeState->stringBuffer.bytes.ptr + cacheSlot->offset, cacheSlot->length);
encodeState->atIndex += cacheSlot->length;
return(0);
}
// When we encounter a class that we do not handle, and we have either a delegate or block that the user supplied to format unsupported classes,
// we "re-run" the object check. However, we re-run the object check exactly ONCE. If the user supplies an object that isn't one of the
// supported classes, we fail the second time (i.e., double fault error).
BOOL rerunningAfterClassFormatter = NO;
rerunAfterClassFormatter:;
// XXX XXX XXX XXX
//
// We need to work around a bug in 10.7, which breaks ABI compatibility with Objective-C going back not just to 10.0, but OpenStep and even NextStep.
//
// It has long been documented that "the very first thing that a pointer to an Objective-C object "points to" is a pointer to that objects class".
//
// This is euphemistically called "tagged pointers". There are a number of highly technical problems with this, most involving long passages from
// the C standard(s). In short, one can make a strong case, couched from the perspective of the C standard(s), that that 10.7 "tagged pointers" are
// fundamentally Wrong and Broken, and should have never been implemented. Assuming those points are glossed over, because the change is very clearly
// breaking ABI compatibility, this should have resulted in a minimum of a "minimum version required" bump in various shared libraries to prevent
// causes code that used to work just fine to suddenly break without warning.
//
// In fact, the C standard says that the hack below is "undefined behavior"- there is no requirement that the 10.7 tagged pointer hack of setting the
// "lower, unused bits" must be preserved when casting the result to an integer type, but this "works" because for most architectures
// `sizeof(long) == sizeof(void *)` and the compiler uses the same representation for both. (note: this is informal, not meant to be
// normative or pedantically correct).
//
// In other words, while this "works" for now, technically the compiler is not obligated to do "what we want", and a later version of the compiler
// is not required in any way to produce the same results or behavior that earlier versions of the compiler did for the statement below.
//
// Fan-fucking-tastic.
//
// Why not just use `object_getClass()`? Because `object->isa` reduces to (typically) a *single* instruction. Calling `object_getClass()` requires
// that the compiler potentially spill registers, establish a function call frame / environment, and finally execute a "jump subroutine" instruction.
// Then, the called subroutine must spend half a dozen instructions in its prolog, however many instructions doing whatever it does, then half a dozen
// instructions in its prolog. One instruction compared to dozens, maybe a hundred instructions.
//
// Yes, that's one to two orders of magnitude difference. Which is compelling in its own right. When going for performance, you're often happy with
// gains in the two to three percent range.
//
// XXX XXX XXX XXX
BOOL workAroundMacOSXABIBreakingBug = NO;
if(JK_EXPECT_F(((NSUInteger)object) & 0x1)) { workAroundMacOSXABIBreakingBug = YES; goto slowClassLookup; }
if(JK_EXPECT_T(object_getClass(object) == encodeState->fastClassLookup.stringClass)) { isClass = JKClassString; }
else if(JK_EXPECT_T(object_getClass(object) == encodeState->fastClassLookup.numberClass)) { isClass = JKClassNumber; }
else if(JK_EXPECT_T(object_getClass(object) == encodeState->fastClassLookup.dictionaryClass)) { isClass = JKClassDictionary; }
else if(JK_EXPECT_T(object_getClass(object) == encodeState->fastClassLookup.arrayClass)) { isClass = JKClassArray; }
else if(JK_EXPECT_T(object_getClass(object) == encodeState->fastClassLookup.nullClass)) { isClass = JKClassNull; }
else {
slowClassLookup:
if(JK_EXPECT_T([object isKindOfClass:[NSString class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.stringClass = object_getClass(object); } isClass = JKClassString; }
else if(JK_EXPECT_T([object isKindOfClass:[NSNumber class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.numberClass = object_getClass(object); } isClass = JKClassNumber; }
else if(JK_EXPECT_T([object isKindOfClass:[NSDictionary class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.dictionaryClass = object_getClass(object); } isClass = JKClassDictionary; }
else if(JK_EXPECT_T([object isKindOfClass:[NSArray class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.arrayClass = object_getClass(object); } isClass = JKClassArray; }
else if(JK_EXPECT_T([object isKindOfClass:[NSNull class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.nullClass = object_getClass(object); } isClass = JKClassNull; }
else {
if((rerunningAfterClassFormatter == NO) && (
#ifdef __BLOCKS__
((encodeState->classFormatterBlock) && ((object = encodeState->classFormatterBlock(object)) != NULL)) ||
#endif
((encodeState->classFormatterIMP) && ((object = encodeState->classFormatterIMP(encodeState->classFormatterDelegate, encodeState->classFormatterSelector, object)) != NULL)) )) { rerunningAfterClassFormatter = YES; goto rerunAfterClassFormatter; }
if(rerunningAfterClassFormatter == NO) { jk_encode_error(encodeState, @"Unable to serialize object class %@.", NSStringFromClass([encodeCacheObject class])); return(1); }
else { jk_encode_error(encodeState, @"Unable to serialize object class %@ that was returned by the unsupported class formatter. Original object class was %@.", (object == NULL) ? @"NULL" : NSStringFromClass([object class]), NSStringFromClass([encodeCacheObject class])); return(1); }
}
}
// This is here for the benefit of the optimizer. It allows the optimizer to do loop invariant code motion for the JKClassArray
// and JKClassDictionary cases when printing simple, single characters via jk_encode_write(), which is actually a macro:
// #define jk_encode_write1(es, dc, f) (_jk_encode_prettyPrint ? jk_encode_write1slow(es, dc, f) : jk_encode_write1fast(es, dc, f))
int _jk_encode_prettyPrint = JK_EXPECT_T((encodeState->serializeOptionFlags & JKSerializeOptionPretty) == 0) ? 0 : 1;
switch(isClass) {
case JKClassString:
{
{
const unsigned char *cStringPtr = (const unsigned char *)CFStringGetCStringPtr((CFStringRef)object, kCFStringEncodingMacRoman);
if(cStringPtr != NULL) {
const unsigned char *utf8String = cStringPtr;
size_t utf8Idx = 0UL;
CFIndex stringLength = CFStringGetLength((CFStringRef)object);
if(JK_EXPECT_F(((encodeState->atIndex + (stringLength * 2UL) + 256UL) > encodeState->stringBuffer.bytes.length)) && JK_EXPECT_F((jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + (stringLength * 2UL) + 1024UL) == NULL))) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); }
if(JK_EXPECT_T((encodeState->encodeOption & JKEncodeOptionStringObjTrimQuotes) == 0UL)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\"'; }
for(utf8Idx = 0UL; utf8String[utf8Idx] != 0U; utf8Idx++) {
NSCParameterAssert(((&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex]) - encodeState->stringBuffer.bytes.ptr) < (ssize_t)encodeState->stringBuffer.bytes.length);
NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length);
if(JK_EXPECT_F(utf8String[utf8Idx] >= 0x80U)) { encodeState->atIndex = startingAtIndex; goto slowUTF8Path; }
if(JK_EXPECT_F(utf8String[utf8Idx] < 0x20U)) {
switch(utf8String[utf8Idx]) {
case '\b': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'b'; break;
case '\f': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'f'; break;
case '\n': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'n'; break;
case '\r': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'r'; break;
case '\t': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 't'; break;
default: if(JK_EXPECT_F(jk_encode_printf(encodeState, NULL, 0UL, NULL, "\\u%4.4x", utf8String[utf8Idx]))) { return(1); } break;
}
} else {
if(JK_EXPECT_F(utf8String[utf8Idx] == '\"') || JK_EXPECT_F(utf8String[utf8Idx] == '\\') || (JK_EXPECT_F(encodeState->serializeOptionFlags & JKSerializeOptionEscapeForwardSlashes) && JK_EXPECT_F(utf8String[utf8Idx] == '/'))) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; }
encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = utf8String[utf8Idx];
}
}
NSCParameterAssert((encodeState->atIndex + 1UL) < encodeState->stringBuffer.bytes.length);
if(JK_EXPECT_T((encodeState->encodeOption & JKEncodeOptionStringObjTrimQuotes) == 0UL)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\"'; }
jk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, encodeCacheObject);
return(0);
}
}
slowUTF8Path:
{
CFIndex stringLength = CFStringGetLength((CFStringRef)object);
CFIndex maxStringUTF8Length = CFStringGetMaximumSizeForEncoding(stringLength, kCFStringEncodingUTF8) + 32L;
if(JK_EXPECT_F((size_t)maxStringUTF8Length > encodeState->utf8ConversionBuffer.bytes.length) && JK_EXPECT_F(jk_managedBuffer_resize(&encodeState->utf8ConversionBuffer, maxStringUTF8Length + 1024UL) == NULL)) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); }
CFIndex usedBytes = 0L, convertedCount = 0L;
convertedCount = CFStringGetBytes((CFStringRef)object, CFRangeMake(0L, stringLength), kCFStringEncodingUTF8, '?', NO, encodeState->utf8ConversionBuffer.bytes.ptr, encodeState->utf8ConversionBuffer.bytes.length - 16L, &usedBytes);
if(JK_EXPECT_F(convertedCount != stringLength) || JK_EXPECT_F(usedBytes < 0L)) { jk_encode_error(encodeState, @"An error occurred converting the contents of a NSString to UTF8."); return(1); }
if(JK_EXPECT_F((encodeState->atIndex + (maxStringUTF8Length * 2UL) + 256UL) > encodeState->stringBuffer.bytes.length) && JK_EXPECT_F(jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + (maxStringUTF8Length * 2UL) + 1024UL) == NULL)) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); }
const unsigned char *utf8String = encodeState->utf8ConversionBuffer.bytes.ptr;
size_t utf8Idx = 0UL;
if(JK_EXPECT_T((encodeState->encodeOption & JKEncodeOptionStringObjTrimQuotes) == 0UL)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\"'; }
for(utf8Idx = 0UL; utf8Idx < (size_t)usedBytes; utf8Idx++) {
NSCParameterAssert(((&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex]) - encodeState->stringBuffer.bytes.ptr) < (ssize_t)encodeState->stringBuffer.bytes.length);
NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length);
NSCParameterAssert((CFIndex)utf8Idx < usedBytes);
if(JK_EXPECT_F(utf8String[utf8Idx] < 0x20U)) {
switch(utf8String[utf8Idx]) {
case '\b': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'b'; break;
case '\f': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'f'; break;
case '\n': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'n'; break;
case '\r': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'r'; break;
case '\t': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 't'; break;
default: if(JK_EXPECT_F(jk_encode_printf(encodeState, NULL, 0UL, NULL, "\\u%4.4x", utf8String[utf8Idx]))) { return(1); } break;
}
} else {
if(JK_EXPECT_F(utf8String[utf8Idx] >= 0x80U) && (encodeState->serializeOptionFlags & JKSerializeOptionEscapeUnicode)) {
const unsigned char *nextValidCharacter = NULL;
UTF32 u32ch = 0U;
ConversionResult result;
if(JK_EXPECT_F((result = ConvertSingleCodePointInUTF8(&utf8String[utf8Idx], &utf8String[usedBytes], (UTF8 const **)&nextValidCharacter, &u32ch)) != conversionOK)) { jk_encode_error(encodeState, @"Error converting UTF8."); return(1); }
else {
utf8Idx = (nextValidCharacter - utf8String) - 1UL;
if(JK_EXPECT_T(u32ch <= 0xffffU)) { if(JK_EXPECT_F(jk_encode_printf(encodeState, NULL, 0UL, NULL, "\\u%4.4x", u32ch))) { return(1); } }
else { if(JK_EXPECT_F(jk_encode_printf(encodeState, NULL, 0UL, NULL, "\\u%4.4x\\u%4.4x", (0xd7c0U + (u32ch >> 10)), (0xdc00U + (u32ch & 0x3ffU))))) { return(1); } }
}
} else {
if(JK_EXPECT_F(utf8String[utf8Idx] == '\"') || JK_EXPECT_F(utf8String[utf8Idx] == '\\') || (JK_EXPECT_F(encodeState->serializeOptionFlags & JKSerializeOptionEscapeForwardSlashes) && JK_EXPECT_F(utf8String[utf8Idx] == '/'))) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; }
encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = utf8String[utf8Idx];
}
}
}
NSCParameterAssert((encodeState->atIndex + 1UL) < encodeState->stringBuffer.bytes.length);
if(JK_EXPECT_T((encodeState->encodeOption & JKEncodeOptionStringObjTrimQuotes) == 0UL)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\"'; }
jk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, encodeCacheObject);
return(0);
}
}
break;
case JKClassNumber:
{
if(object == (id)kCFBooleanTrue) { return(jk_encode_writen(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, "true", 4UL)); }
else if(object == (id)kCFBooleanFalse) { return(jk_encode_writen(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, "false", 5UL)); }
const char *objCType = [object objCType];
char anum[256], *aptr = &anum[255];
int isNegative = 0;
unsigned long long ullv;
long long llv;
if(JK_EXPECT_F(objCType == NULL) || JK_EXPECT_F(objCType[0] == 0) || JK_EXPECT_F(objCType[1] != 0)) { jk_encode_error(encodeState, @"NSNumber conversion error, unknown type. Type: '%s'", (objCType == NULL) ? "" : objCType); return(1); }
switch(objCType[0]) {
case 'c': case 'i': case 's': case 'l': case 'q':
if(JK_EXPECT_T(CFNumberGetValue((CFNumberRef)object, kCFNumberLongLongType, &llv))) {
if(llv < 0LL) { ullv = -llv; isNegative = 1; } else { ullv = llv; isNegative = 0; }
goto convertNumber;
} else { jk_encode_error(encodeState, @"Unable to get scalar value from number object."); return(1); }
break;
case 'C': case 'I': case 'S': case 'L': case 'Q': case 'B':
if(JK_EXPECT_T(CFNumberGetValue((CFNumberRef)object, kCFNumberLongLongType, &ullv))) {
convertNumber:
if(JK_EXPECT_F(ullv < 10ULL)) { *--aptr = ullv + '0'; } else { while(JK_EXPECT_T(ullv > 0ULL)) { *--aptr = (ullv % 10ULL) + '0'; ullv /= 10ULL; NSCParameterAssert(aptr > anum); } }
if(isNegative) { *--aptr = '-'; }
NSCParameterAssert(aptr > anum);
return(jk_encode_writen(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, aptr, &anum[255] - aptr));
} else { jk_encode_error(encodeState, @"Unable to get scalar value from number object."); return(1); }
break;
case 'f': case 'd':
{
double dv;
if(JK_EXPECT_T(CFNumberGetValue((CFNumberRef)object, kCFNumberDoubleType, &dv))) {
if(JK_EXPECT_F(!isfinite(dv))) { jk_encode_error(encodeState, @"Floating point values must be finite. JSON does not support NaN or Infinity."); return(1); }
return(jk_encode_printf(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, "%.17g", dv));
} else { jk_encode_error(encodeState, @"Unable to get floating point value from number object."); return(1); }
}
break;
default: jk_encode_error(encodeState, @"NSNumber conversion error, unknown type. Type: '%c' / 0x%2.2x", objCType[0], objCType[0]); return(1); break;
}
}
break;
case JKClassArray:
{
int printComma = 0;
CFIndex arrayCount = CFArrayGetCount((CFArrayRef)object), idx = 0L;
if(JK_EXPECT_F(jk_encode_write1(encodeState, 1L, "["))) { return(1); }
if(JK_EXPECT_F(arrayCount > 1020L)) {
for(id arrayObject in object) { if(JK_EXPECT_T(printComma)) { if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ","))) { return(1); } } printComma = 1; if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, arrayObject))) { return(1); } }
} else {
void *objects[1024];
CFArrayGetValues((CFArrayRef)object, CFRangeMake(0L, arrayCount), (const void **)objects);
for(idx = 0L; idx < arrayCount; idx++) { if(JK_EXPECT_T(printComma)) { if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ","))) { return(1); } } printComma = 1; if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, objects[idx]))) { return(1); } }
}
return(jk_encode_write1(encodeState, -1L, "]"));
}
break;
case JKClassDictionary:
{
int printComma = 0;
CFIndex dictionaryCount = CFDictionaryGetCount((CFDictionaryRef)object), idx = 0L;
id enumerateObject = JK_EXPECT_F(_jk_encode_prettyPrint) ? [[object allKeys] sortedArrayUsingSelector:@selector(compare:)] : object;
if(JK_EXPECT_F(jk_encode_write1(encodeState, 1L, "{"))) { return(1); }
if(JK_EXPECT_F(_jk_encode_prettyPrint) || JK_EXPECT_F(dictionaryCount > 1020L)) {
for(id keyObject in enumerateObject) {
if(JK_EXPECT_T(printComma)) { if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ","))) { return(1); } }
printComma = 1;
if(JK_EXPECT_F((object_getClass(keyObject) != encodeState->fastClassLookup.stringClass)) && JK_EXPECT_F(([keyObject isKindOfClass:[NSString class]] == NO))) { jk_encode_error(encodeState, @"Key must be a string object."); return(1); }
if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, keyObject))) { return(1); }
if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ":"))) { return(1); }
if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, (void *)CFDictionaryGetValue((CFDictionaryRef)object, keyObject)))) { return(1); }
}
} else {
void *keys[1024], *objects[1024];
CFDictionaryGetKeysAndValues((CFDictionaryRef)object, (const void **)keys, (const void **)objects);
for(idx = 0L; idx < dictionaryCount; idx++) {
if(JK_EXPECT_T(printComma)) { if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ","))) { return(1); } }
printComma = 1;
if(JK_EXPECT_F(object_getClass((id)keys[idx]) != encodeState->fastClassLookup.stringClass) && JK_EXPECT_F([(id)keys[idx] isKindOfClass:[NSString class]] == NO)) { jk_encode_error(encodeState, @"Key must be a string object."); return(1); }
if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, keys[idx]))) { return(1); }
if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ":"))) { return(1); }
if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, objects[idx]))) { return(1); }
}
}
return(jk_encode_write1(encodeState, -1L, "}"));
}
break;
case JKClassNull: return(jk_encode_writen(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, "null", 4UL)); break;
default: jk_encode_error(encodeState, @"Unable to serialize object class %@.", NSStringFromClass([object class])); return(1); break;
}
return(0);
}
@implementation JKSerializer
+ (id)serializeObject:(id)object options:(JKSerializeOptionFlags)optionFlags encodeOption:(JKEncodeOptionType)encodeOption block:(JKSERIALIZER_BLOCKS_PROTO)block delegate:(id)delegate selector:(SEL)selector error:(NSError **)error
{
return([[[[self alloc] init] autorelease] serializeObject:object options:optionFlags encodeOption:encodeOption block:block delegate:delegate selector:selector error:error]);
}
- (id)serializeObject:(id)object options:(JKSerializeOptionFlags)optionFlags encodeOption:(JKEncodeOptionType)encodeOption block:(JKSERIALIZER_BLOCKS_PROTO)block delegate:(id)delegate selector:(SEL)selector error:(NSError **)error
{
#ifndef __BLOCKS__
#pragma unused(block)
#endif
NSParameterAssert((object != NULL) && (encodeState == NULL) && ((delegate != NULL) ? (block == NULL) : 1) && ((block != NULL) ? (delegate == NULL) : 1) &&
(((encodeOption & JKEncodeOptionCollectionObj) != 0UL) ? (((encodeOption & JKEncodeOptionStringObj) == 0UL) && ((encodeOption & JKEncodeOptionStringObjTrimQuotes) == 0UL)) : 1) &&
(((encodeOption & JKEncodeOptionStringObj) != 0UL) ? ((encodeOption & JKEncodeOptionCollectionObj) == 0UL) : 1));
id returnObject = NULL;
if(encodeState != NULL) { [self releaseState]; }
if((encodeState = (struct JKEncodeState *)calloc(1UL, sizeof(JKEncodeState))) == NULL) { [NSException raise:NSMallocException format:@"Unable to allocate state structure."]; return(NULL); }
if((error != NULL) && (*error != NULL)) { *error = NULL; }
if(delegate != NULL) {
if(selector == NULL) { [NSException raise:NSInvalidArgumentException format:@"The delegate argument is not NULL, but the selector argument is NULL."]; }
if([delegate respondsToSelector:selector] == NO) { [NSException raise:NSInvalidArgumentException format:@"The serializeUnsupportedClassesUsingDelegate: delegate does not respond to the selector argument."]; }
encodeState->classFormatterDelegate = delegate;
encodeState->classFormatterSelector = selector;
encodeState->classFormatterIMP = (JKClassFormatterIMP)[delegate methodForSelector:selector];
NSCParameterAssert(encodeState->classFormatterIMP != NULL);
}
#ifdef __BLOCKS__
encodeState->classFormatterBlock = block;
#endif
encodeState->serializeOptionFlags = optionFlags;
encodeState->encodeOption = encodeOption;
encodeState->stringBuffer.roundSizeUpToMultipleOf = (1024UL * 32UL);
encodeState->utf8ConversionBuffer.roundSizeUpToMultipleOf = 4096UL;
unsigned char stackJSONBuffer[JK_JSONBUFFER_SIZE] JK_ALIGNED(64);
jk_managedBuffer_setToStackBuffer(&encodeState->stringBuffer, stackJSONBuffer, sizeof(stackJSONBuffer));
unsigned char stackUTF8Buffer[JK_UTF8BUFFER_SIZE] JK_ALIGNED(64);
jk_managedBuffer_setToStackBuffer(&encodeState->utf8ConversionBuffer, stackUTF8Buffer, sizeof(stackUTF8Buffer));
if(((encodeOption & JKEncodeOptionCollectionObj) != 0UL) && (([object isKindOfClass:[NSArray class]] == NO) && ([object isKindOfClass:[NSDictionary class]] == NO))) { jk_encode_error(encodeState, @"Unable to serialize object class %@, expected a NSArray or NSDictionary.", NSStringFromClass([object class])); goto errorExit; }
if(((encodeOption & JKEncodeOptionStringObj) != 0UL) && ([object isKindOfClass:[NSString class]] == NO)) { jk_encode_error(encodeState, @"Unable to serialize object class %@, expected a NSString.", NSStringFromClass([object class])); goto errorExit; }
if(jk_encode_add_atom_to_buffer(encodeState, object) == 0) {
BOOL stackBuffer = ((encodeState->stringBuffer.flags & JKManagedBufferMustFree) == 0UL) ? YES : NO;
if((encodeState->atIndex < 2UL))
if((stackBuffer == NO) && ((encodeState->stringBuffer.bytes.ptr = (unsigned char *)reallocf(encodeState->stringBuffer.bytes.ptr, encodeState->atIndex + 16UL)) == NULL)) { jk_encode_error(encodeState, @"Unable to realloc buffer"); goto errorExit; }
switch((encodeOption & JKEncodeOptionAsTypeMask)) {
case JKEncodeOptionAsData:
if(stackBuffer == YES) { if((returnObject = [(id)CFDataCreate( NULL, encodeState->stringBuffer.bytes.ptr, (CFIndex)encodeState->atIndex) autorelease]) == NULL) { jk_encode_error(encodeState, @"Unable to create NSData object"); } }
else { if((returnObject = [(id)CFDataCreateWithBytesNoCopy( NULL, encodeState->stringBuffer.bytes.ptr, (CFIndex)encodeState->atIndex, NULL) autorelease]) == NULL) { jk_encode_error(encodeState, @"Unable to create NSData object"); } }
break;
case JKEncodeOptionAsString:
if(stackBuffer == YES) { if((returnObject = [(id)CFStringCreateWithBytes( NULL, (const UInt8 *)encodeState->stringBuffer.bytes.ptr, (CFIndex)encodeState->atIndex, kCFStringEncodingUTF8, NO) autorelease]) == NULL) { jk_encode_error(encodeState, @"Unable to create NSString object"); } }
else { if((returnObject = [(id)CFStringCreateWithBytesNoCopy(NULL, (const UInt8 *)encodeState->stringBuffer.bytes.ptr, (CFIndex)encodeState->atIndex, kCFStringEncodingUTF8, NO, NULL) autorelease]) == NULL) { jk_encode_error(encodeState, @"Unable to create NSString object"); } }
break;
default: jk_encode_error(encodeState, @"Unknown encode as type."); break;
}
if((returnObject != NULL) && (stackBuffer == NO)) { encodeState->stringBuffer.flags &= ~JKManagedBufferMustFree; encodeState->stringBuffer.bytes.ptr = NULL; encodeState->stringBuffer.bytes.length = 0UL; }
}
errorExit:
if((encodeState != NULL) && (error != NULL) && (encodeState->error != NULL)) { *error = encodeState->error; encodeState->error = NULL; }
[self releaseState];
return(returnObject);
}
- (void)releaseState
{
if(encodeState != NULL) {
jk_managedBuffer_release(&encodeState->stringBuffer);
jk_managedBuffer_release(&encodeState->utf8ConversionBuffer);
free(encodeState); encodeState = NULL;
}
}
- (void)dealloc
{
[self releaseState];
[super dealloc];
}
@end
@implementation NSString (JSONKitSerializing)
////////////
#pragma mark Methods for serializing a single NSString.
////////////
// Useful for those who need to serialize just a NSString. Otherwise you would have to do something like [NSArray arrayWithObject:stringToBeJSONSerialized], serializing the array, and then chopping of the extra ^\[.*\]$ square brackets.
// NSData returning methods...
- (NSData *)JSONData
{
return([self JSONDataWithOptions:JKSerializeOptionNone includeQuotes:YES error:NULL]);
}
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error
{
return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsData | ((includeQuotes == NO) ? JKEncodeOptionStringObjTrimQuotes : 0UL) | JKEncodeOptionStringObj) block:NULL delegate:NULL selector:NULL error:error]);
}
// NSString returning methods...
- (NSString *)JSONString
{
return([self JSONStringWithOptions:JKSerializeOptionNone includeQuotes:YES error:NULL]);
}
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error
{
return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsString | ((includeQuotes == NO) ? JKEncodeOptionStringObjTrimQuotes : 0UL) | JKEncodeOptionStringObj) block:NULL delegate:NULL selector:NULL error:error]);
}
@end
@implementation NSArray (JSONKitSerializing)
// NSData returning methods...
- (NSData *)JSONData
{
return([JKSerializer serializeObject:self options:JKSerializeOptionNone encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:NULL]);
}
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error
{
return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:error]);
}
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error
{
return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:NULL delegate:delegate selector:selector error:error]);
}
// NSString returning methods...
- (NSString *)JSONString
{
return([JKSerializer serializeObject:self options:JKSerializeOptionNone encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:NULL]);
}
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error
{
return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:error]);
}
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error
{
return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:NULL delegate:delegate selector:selector error:error]);
}
@end
@implementation NSDictionary (JSONKitSerializing)
// NSData returning methods...
- (NSData *)JSONData
{
return([JKSerializer serializeObject:self options:JKSerializeOptionNone encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:NULL]);
}
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error
{
return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:error]);
}
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error
{
return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:NULL delegate:delegate selector:selector error:error]);
}
// NSString returning methods...
- (NSString *)JSONString
{
return([JKSerializer serializeObject:self options:JKSerializeOptionNone encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:NULL]);
}
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error
{
return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:error]);
}
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error
{
return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:NULL delegate:delegate selector:selector error:error]);
}
@end
#ifdef __BLOCKS__
@implementation NSArray (JSONKitSerializingBlockAdditions)
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error
{
return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:block delegate:NULL selector:NULL error:error]);
}
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error
{
return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:block delegate:NULL selector:NULL error:error]);
}
@end
@implementation NSDictionary (JSONKitSerializingBlockAdditions)
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error
{
return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:block delegate:NULL selector:NULL error:error]);
}
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error
{
return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:block delegate:NULL selector:NULL error:error]);
}
@end
#endif // __BLOCKS__
================================================
FILE: Slate/JSOperation.h
================================================
//
// JSOperation.h
// Slate
//
// Created by Jigish Patel on 1/22/13.
// Copyright 2013 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
#import
#import "Operation.h"
@interface JSOperation : Operation {
WebScriptObject *function;
}
@property (strong) WebScriptObject *function;
- (id)initWithFunction:(WebScriptObject *)_function;
+ (id)jsOperationWithFunction:(WebScriptObject *)function;
@end
================================================
FILE: Slate/JSOperation.m
================================================
//
// JSOperation.m
// Slate
//
// Created by Jigish Patel on 1/22/13.
// Copyright 2013 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "JSOperation.h"
#import "JSController.h"
#import "AccessibilityWrapper.h"
#import "ScreenWrapper.h"
#import "SlateLogger.h"
#import "JSWindowWrapper.h"
#import "JSInfoWrapper.h"
@implementation JSOperation
@synthesize function;
- (id)init {
self = [super init];
if (self) {
[self setOpName:@"js"];
}
return self;
}
- (id)initWithFunction:(WebScriptObject *)_function {
self = [super init];
if (self) {
[self setOpName:@"js"];
[self setFunction:_function];
}
return self;
}
- (BOOL)doOperationWithAccessibilityWrapper:(AccessibilityWrapper *)aw screenWrapper:(ScreenWrapper *)sw {
BOOL success = YES;
[self evalOptionsWithAccessibilityWrapper:aw screenWrapper:sw];
[[JSInfoWrapper getInstance] setSw:sw];
[[JSInfoWrapper getInstance] setAw:aw];
JSWindowWrapper *window = nil;
if (aw != nil && [aw inited]) window = [[JSWindowWrapper alloc] initWithAccessibilityWrapper:aw screenWrapper:sw];
[[JSController getInstance] runFunction:[self function] withArg:window];
return success;
}
- (BOOL)doOperation {
SlateLogger(@"----------------- Begin JS Operation -----------------");
AccessibilityWrapper *aw = [[AccessibilityWrapper alloc] init];
ScreenWrapper *sw = [[ScreenWrapper alloc] init];
BOOL success = NO;
if (![aw inited]) aw = nil;
success = [self doOperationWithAccessibilityWrapper:aw screenWrapper:sw];
SlateLogger(@"----------------- End JS Operation -----------------");
return success;
}
- (BOOL)testOperation {
return function != nil && [@"function" isEqualToString:[[JSController getInstance] jsTypeof:function]];
}
+ (JSOperation *)jsOperationWithFunction:(WebScriptObject*)function {
return [[JSOperation alloc] initWithFunction:function];
}
@end
================================================
FILE: Slate/JSOperationWrapper.h
================================================
//
// JSOperationWrapper.h
// Slate
//
// Created by Jigish Patel on 1/25/13.
// Copyright 2013 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
#import
@class AccessibilityWrapper;
@class ScreenWrapper;
@class Operation;
@interface JSOperationWrapper : NSObject {
Operation *op;
}
@property (strong) Operation *op;
- (BOOL)run;
- (JSOperationWrapper *)dup:(WebScriptObject *)opts;
- (BOOL)doOperation;
- (BOOL)doOperationWithAccessibilityWrapper:(AccessibilityWrapper *)aw screenWrapper:(ScreenWrapper *)sw;
+ (JSOperationWrapper *)operation:(NSString*)name options:(WebScriptObject *)opts;
+ (JSOperationWrapper *)operationFromString:(NSString *)opString;
@end
================================================
FILE: Slate/JSOperationWrapper.m
================================================
//
// JSOperationWrapper.m
// Slate
//
// Created by Jigish Patel on 1/25/13.
// Copyright 2013 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "JSOperationWrapper.h"
#import "Operation.h"
#import "JSController.h"
#import "SlateLogger.h"
#import "SlateConfig.h"
@implementation JSOperationWrapper
static NSDictionary *jsowJsMethods;
@synthesize op;
- (id)init {
self = [super init];
if (self) {
[JSOperationWrapper setJsMethods];
}
return self;
}
- (id)initWithOperation:(Operation *)_op {
self = [self init];
if (self) {
@try {
[self setOp:_op];
if ([self op] == nil) { return nil; }
} @catch (NSException *ex) {
SlateLogger(@" ERROR %@",[ex name]);
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Quit", @"Skip", nil]];
[alert setMessageText:[ex name]];
[alert setInformativeText:[ex reason]];
if ([alert runModal] == NSAlertFirstButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
return nil;
}
}
return self;
}
- (id)initWithName:(NSString*)name options:(WebScriptObject *)opts {
self = [self init];
if (self) {
@try {
[self setOp:[Operation operationWithName:name options:[[JSController getInstance] unmarshall:opts]]];
if ([self op] == nil) { return nil; }
} @catch (NSException *ex) {
SlateLogger(@" ERROR %@",[ex name]);
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Quit", @"Skip", nil]];
[alert setMessageText:[ex name]];
[alert setInformativeText:[ex reason]];
if ([alert runModal] == NSAlertFirstButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
return nil;
}
}
return self;
}
- (id)initWithString:(NSString *)str {
self = [self init];
if (self) {
@try {
[self setOp:[Operation operationFromString:str]];
if ([self op] == nil) { return nil; }
} @catch (NSException *ex) {
SlateLogger(@" ERROR %@",[ex name]);
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Quit", @"Skip", nil]];
[alert setMessageText:[ex name]];
[alert setInformativeText:[ex reason]];
if ([alert runModal] == NSAlertFirstButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
return nil;
}
}
return self;
}
- (BOOL)run {
return [[self op] doOperation];
}
- (BOOL)doOperation {
return [[self op] doOperation];
}
- (BOOL)doOperationWithAccessibilityWrapper:(AccessibilityWrapper *)aw screenWrapper:(ScreenWrapper *)sw {
return [[self op] doOperationWithAccessibilityWrapper:aw screenWrapper:sw];
}
- (JSOperationWrapper *)dup:(WebScriptObject *)opts {
NSString *type = [[JSController getInstance] jsTypeof:opts];
if (![@"object" isEqualToString:type]) {
SlateLogger(@" ERROR operation.dup parameter must be a hash");
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Quit", @"Skip", nil]];
[alert setMessageText:@"operation.dup parameter must be a hash"];
[alert setInformativeText:[NSString stringWithFormat:@"was: %@", type]];
if ([alert runModal] == NSAlertFirstButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
return nil;
}
NSDictionary *optDict = [[JSController getInstance] unmarshall:opts];
return [[JSOperationWrapper alloc] initWithOperation:[[self op] dup:optDict]];
}
+ (JSOperationWrapper *)operation:(NSString*)name options:(WebScriptObject *)opts {
return [[JSOperationWrapper alloc] initWithName:name options:opts];
}
+ (JSOperationWrapper *)operationFromString:(NSString *)opString {
return [[JSOperationWrapper alloc] initWithString:opString];
}
+ (void)setJsMethods {
if (jsowJsMethods == nil) {
jsowJsMethods = @{
NSStringFromSelector(@selector(run)): @"run",
NSStringFromSelector(@selector(dup:)): @"dup",
};
}
}
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)sel {
return [jsowJsMethods objectForKey:NSStringFromSelector(sel)] == NULL;
}
+ (NSString *)webScriptNameForSelector:(SEL)sel {
return [jsowJsMethods objectForKey:NSStringFromSelector(sel)];
}
@end
================================================
FILE: Slate/JSScreenWrapper.h
================================================
//
// JSScreenWrapper.h
// Slate
//
// Created by Jigish Patel on 1/21/13.
// Copyright 2013 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@class AccessibilityWrapper;
@class ScreenWrapper;
@interface JSScreenWrapper : NSObject {
ScreenWrapper *sw;
NSInteger screenId;
}
@property (strong) ScreenWrapper *sw;
@property NSInteger screenId;
- (id)initWithScreenId:(NSInteger)_id screenWrapper:(ScreenWrapper *)_sw;
- (NSString *)toString;
@end
================================================
FILE: Slate/JSScreenWrapper.m
================================================
//
// JSScreenWrapper.m
// Slate
//
// Created by Jigish Patel on 1/21/13.
// Copyright 2013 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "JSScreenWrapper.h"
#import "ScreenWrapper.h"
#import "JSController.h"
@implementation JSScreenWrapper
static NSDictionary *jsswJsMethods = nil;
@synthesize screenId;
@synthesize sw;
- (id)init {
self = [super init];
if (self) {
[self setScreenId:0];
[self setSw:[[ScreenWrapper alloc] init]];
[JSScreenWrapper setJsMethods];
}
return self;
}
- (id)initWithScreenId:(NSInteger)_id screenWrapper:(ScreenWrapper *)_sw {
self = [super init];
if (self) {
[self setScreenId:_id];
[self setSw:_sw];
[JSScreenWrapper setJsMethods];
}
return self;
}
- (id)rect {
NSRect rect = [sw getScreenRectForRef:[self screenId]];
return [[JSController getInstance] marshall:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInteger:rect.size.width],
@"width",
[NSNumber numberWithInteger:rect.size.height],
@"height",
[NSNumber numberWithInteger:rect.origin.x],
@"x",
[NSNumber numberWithInteger:rect.origin.y],
@"y", nil]];
}
- (id)vrect { return [self visibleRect]; }
- (id)visibleRect {
NSRect rect = [sw getScreenVisibleRectForRef:[self screenId]];
return [[JSController getInstance] marshall:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInteger:rect.size.width],
@"width",
[NSNumber numberWithInteger:rect.size.height],
@"height",
[NSNumber numberWithInteger:rect.origin.x],
@"x",
[NSNumber numberWithInteger:rect.origin.y],
@"y", nil]];
}
- (NSString *)toString {
return [NSString stringWithFormat:@"%ld", [self screenId]];
}
- (BOOL)main { return [self isMain]; }
- (BOOL)isMain {
return [sw isMainScreenRef:[self screenId]];
}
+ (void)setJsMethods {
if (jsswJsMethods == nil) {
jsswJsMethods = @{
NSStringFromSelector(@selector(screenId)): @"id",
NSStringFromSelector(@selector(rect)): @"rect",
NSStringFromSelector(@selector(visibleRect)): @"visibleRect",
NSStringFromSelector(@selector(vrect)): @"vrect",
NSStringFromSelector(@selector(isMain)): @"isMain",
NSStringFromSelector(@selector(main)): @"main",
};
}
}
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)sel {
return [jsswJsMethods objectForKey:NSStringFromSelector(sel)] == NULL;
}
+ (NSString *)webScriptNameForSelector:(SEL)sel {
return [jsswJsMethods objectForKey:NSStringFromSelector(sel)];
}
@end
================================================
FILE: Slate/JSWindowWrapper.h
================================================
//
// JSWindowWrapper.h
// Slate
//
// Created by Jigish Patel on 1/21/13.
// Copyright 2013 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@class AccessibilityWrapper;
@class ScreenWrapper;
@interface JSWindowWrapper : NSObject {
ScreenWrapper *sw;
AccessibilityWrapper *aw;
}
@property (strong) ScreenWrapper *sw;
@property (strong) AccessibilityWrapper *aw;
- (id)initWithAccessibilityWrapper:(AccessibilityWrapper *)_aw screenWrapper:(ScreenWrapper *)_sw;
@end
================================================
FILE: Slate/JSWindowWrapper.m
================================================
//
// JSWindowWrapper.m
// Slate
//
// Created by Jigish Patel on 1/21/13.
// Copyright 2013 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "JSWindowWrapper.h"
#import "AccessibilityWrapper.h"
#import "ScreenWrapper.h"
#import "JSController.h"
#import "ExpressionPoint.h"
#import "JSScreenWrapper.h"
#import "JSWrapperUtils.h"
#import "JSApplicationWrapper.h"
#import "JSOperationWrapper.h"
@implementation JSWindowWrapper
static NSDictionary *jswwJsMethods;
@synthesize aw;
@synthesize sw;
- (id)init {
self = [super init];
if (self) {
[self setAw:[[AccessibilityWrapper alloc] init]];
[self setSw:[[ScreenWrapper alloc] init]];
[JSWindowWrapper setJsMethods];
}
return self;
}
- (id)initWithAccessibilityWrapper:(AccessibilityWrapper *)_aw screenWrapper:(ScreenWrapper *)_sw {
self = [super init];
if (self) {
[self setAw:_aw];
[self setSw:_sw];
[JSWindowWrapper setJsMethods];
}
return self;
}
- (NSString *)title {
return [aw getTitle];
}
- (id)rect {
NSPoint tl = [aw getCurrentTopLeft];
NSSize s = [aw getCurrentSize];
return [[JSController getInstance] marshall:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInteger:tl.x],
@"x",
[NSNumber numberWithInteger:tl.y],
@"y",
[NSNumber numberWithInteger:s.width],
@"width",
[NSNumber numberWithInteger:s.height],
@"height", nil]];
}
- (id)tl { return [self topLeft]; }
- (id)topLeft {
NSPoint tl = [aw getCurrentTopLeft];
return [[JSController getInstance] marshall:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInteger:tl.x],
@"x",
[NSNumber numberWithInteger:tl.y],
@"y", nil]];
}
- (id)size {
NSSize s = [aw getCurrentSize];
return [[JSController getInstance] marshall:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInteger:s.width],
@"width",
[NSNumber numberWithInteger:s.height],
@"height", nil]];
}
- (pid_t)pid {
return [aw processIdentifier];
}
- (BOOL)focus {
return [aw focus];
}
- (BOOL)hidden { return [self isMinimizedOrHidden]; }
- (BOOL)isMinimizedOrHidden {
return [aw isMinimizedOrHidden];
}
- (BOOL)movable { return [self isMovable]; }
- (BOOL)isMovable {
return [aw isMovable];
}
- (BOOL)resizable { return [self isResizable]; }
- (BOOL)isResizable {
return [aw isResizable];
}
- (BOOL)main { return [self isMain]; }
- (BOOL)isMain {
return [AccessibilityWrapper isMainWindow:[aw window]];
}
- (BOOL)move:(id)point {
id pointDict = [[JSController getInstance] unmarshall:point];
NSValue *p = [JSWrapperUtils pointFromDict:pointDict aw:aw sw:sw];
if (p == nil) { return NO; }
return [aw moveWindow:[p pointValue]];
}
- (BOOL)resize:(id)size {
id sizeDict = [[JSController getInstance] unmarshall:size];
NSValue *s = [JSWrapperUtils sizeFromDict:sizeDict aw:aw sw:sw];
if (s == nil) { return NO; }
return [aw resizeWindow:[s sizeValue]];
}
- (JSScreenWrapper *)screen {
NSPoint tl = [aw getCurrentTopLeft];
NSSize size = [aw getCurrentSize];
NSRect wRect = NSMakeRect(tl.x, tl.y, size.width, size.height);
return [[JSScreenWrapper alloc] initWithScreenId:[sw getScreenRefIdForRect:wRect] screenWrapper:sw];
}
- (JSApplicationWrapper *)app {
return [[JSApplicationWrapper alloc] initWithAccessibilityWrapper:aw screenWrapper:sw];
}
- (BOOL)doop:(id)op options:(id)opts { return [self doOperation:op options:opts]; }
- (BOOL)doOperation:(id)op options:(id)opts {
if ([op isKindOfClass:[JSOperationWrapper class]]) {
return [op doOperationWithAccessibilityWrapper:aw screenWrapper:sw];
} else if ([op isKindOfClass:[NSString class]]) {
id options = [[JSController getInstance] unmarshall:opts];
if ([options isKindOfClass:[NSDictionary class]]) {
return [Operation doOperation:op options:options aw:[self aw] sw:[self sw]];
}
}
return NO;
}
+ (void)setJsMethods {
if (jswwJsMethods == nil) {
jswwJsMethods = @{
NSStringFromSelector(@selector(title)): @"title",
NSStringFromSelector(@selector(topLeft)): @"topLeft",
NSStringFromSelector(@selector(tl)): @"tl",
NSStringFromSelector(@selector(size)): @"size",
NSStringFromSelector(@selector(rect)): @"rect",
NSStringFromSelector(@selector(pid)): @"pid",
NSStringFromSelector(@selector(focus)): @"focus",
NSStringFromSelector(@selector(isMinimizedOrHidden)): @"isMinimizedOrHidden",
NSStringFromSelector(@selector(hidden)): @"hidden",
NSStringFromSelector(@selector(isMovable)): @"isMovable",
NSStringFromSelector(@selector(movable)): @"movable",
NSStringFromSelector(@selector(isResizable)): @"isResizable",
NSStringFromSelector(@selector(resizable)): @"resizable",
NSStringFromSelector(@selector(isMain)): @"isMain",
NSStringFromSelector(@selector(main)): @"main",
NSStringFromSelector(@selector(move:)): @"move",
NSStringFromSelector(@selector(resize:)): @"resize",
NSStringFromSelector(@selector(screen)): @"screen",
NSStringFromSelector(@selector(doOperation:options:)): @"doOperation",
NSStringFromSelector(@selector(doop:options:)): @"doop",
NSStringFromSelector(@selector(app)): @"app",
};
}
}
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)sel {
return [jswwJsMethods objectForKey:NSStringFromSelector(sel)] == NULL;
}
+ (NSString *)webScriptNameForSelector:(SEL)sel {
return [jswwJsMethods objectForKey:NSStringFromSelector(sel)];
}
@end
================================================
FILE: Slate/JSWrapperUtils.h
================================================
//
// JSWrapperUtils.h
// Slate
//
// Created by Jigish Patel on 1/22/13.
// Copyright 2013 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@class AccessibilityWrapper;
@class ScreenWrapper;
@interface JSWrapperUtils : NSObject
+ (NSDictionary *)screenAndWindowValues:(NSString *)screen aw:(AccessibilityWrapper *)aw sw:(ScreenWrapper *)sw;
+ (NSValue *)pointFromDict:(id)dict aw:(AccessibilityWrapper *)aw sw:(ScreenWrapper *)sw;
+ (NSValue *)sizeFromDict:(id)dict aw:(AccessibilityWrapper *)aw sw:(ScreenWrapper *)sw;
+ (NSValue *)rectFromDict:(id)dict aw:(AccessibilityWrapper *)aw sw:(ScreenWrapper *)sw;
@end
================================================
FILE: Slate/JSWrapperUtils.m
================================================
//
// JSWrapperUtils.m
// Slate
//
// Created by Jigish Patel on 1/22/13.
// Copyright 2013 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "JSWrapperUtils.h"
#import "ExpressionPoint.h"
#import "AccessibilityWrapper.h"
#import "ScreenWrapper.h"
@implementation JSWrapperUtils
+ (NSDictionary *)screenAndWindowValues:(NSString *)screen aw:(AccessibilityWrapper *)aw sw:(ScreenWrapper *)sw {
NSInteger screenId = 0;
NSPoint wTL = [aw getCurrentTopLeft];
NSSize wSize = [aw getCurrentSize];
NSRect wRect = NSMakeRect(wTL.x, wTL.y, wSize.width, wSize.height);
if (screen != nil) {
screenId = [sw getScreenId:screen windowRect:wRect];
} else {
screenId = [sw getScreenIdForRect:wRect];
}
return [sw getScreenAndWindowValues:screenId window:wRect newSize:wRect.size];
}
+ (NSValue *)pointFromDict:(id)dict aw:(AccessibilityWrapper *)aw sw:(ScreenWrapper *)sw {
if (![dict isKindOfClass:[NSDictionary class]]) { return nil; }
if ([dict objectForKey:@"x"] == nil) { return nil; }
if ([dict objectForKey:@"y"] == nil) { return nil; }
NSDictionary *values = nil;
float x = 0;
if ([[dict objectForKey:@"x"] isKindOfClass:[NSString class]]) {
values = [JSWrapperUtils screenAndWindowValues:[dict objectForKey:@"screen"] aw:aw sw:sw];
x = [ExpressionPoint expToFloat:[dict objectForKey:@"x"] withDict:values];
} else if ([[dict objectForKey:@"x"] isKindOfClass:[NSNumber class]] ||
[[dict objectForKey:@"x"] isKindOfClass:[NSValue class]]) {
x = [[dict objectForKey:@"x"] floatValue];
} else {
return nil;
}
float y = 0;
if ([[dict objectForKey:@"y"] isKindOfClass:[NSString class]]) {
if (values == nil) {
values = [JSWrapperUtils screenAndWindowValues:[dict objectForKey:@"screen"] aw:aw sw:sw];
}
y = [ExpressionPoint expToFloat:[dict objectForKey:@"y"] withDict:values];
} else if ([[dict objectForKey:@"y"] isKindOfClass:[NSNumber class]] ||
[[dict objectForKey:@"y"] isKindOfClass:[NSValue class]]) {
y = [[dict objectForKey:@"y"] floatValue];
} else {
return nil;
}
return [NSValue valueWithPoint:NSMakePoint(x, y)];
}
+ (NSValue *)sizeFromDict:(id)dict aw:(AccessibilityWrapper *)aw sw:(ScreenWrapper *)sw {
if (![dict isKindOfClass:[NSDictionary class]]) { return nil; }
if ([dict objectForKey:@"width"] == nil) { return nil; }
if ([dict objectForKey:@"height"] == nil) { return nil; }
NSDictionary *values = nil;
float width = 0;
if ([[dict objectForKey:@"width"] isKindOfClass:[NSString class]]) {
values = [JSWrapperUtils screenAndWindowValues:[dict objectForKey:@"screen"] aw:aw sw:sw];
width = [ExpressionPoint expToFloat:[dict objectForKey:@"width"] withDict:values];
} else if ([[dict objectForKey:@"width"] isKindOfClass:[NSNumber class]] ||
[[dict objectForKey:@"width"] isKindOfClass:[NSValue class]]) {
width = [[dict objectForKey:@"width"] floatValue];
} else {
return nil;
}
float height = 0;
if ([[dict objectForKey:@"height"] isKindOfClass:[NSString class]]) {
if (values == nil) {
values = [JSWrapperUtils screenAndWindowValues:[dict objectForKey:@"screen"] aw:aw sw:sw];
}
height = [ExpressionPoint expToFloat:[dict objectForKey:@"height"] withDict:values];
} else if ([[dict objectForKey:@"height"] isKindOfClass:[NSNumber class]] ||
[[dict objectForKey:@"height"] isKindOfClass:[NSValue class]]) {
height = [[dict objectForKey:@"height"] floatValue];
} else {
return nil;
}
return [NSValue valueWithSize:NSMakeSize(width, height)];
}
+ (NSValue *)rectFromDict:(id)dict aw:(AccessibilityWrapper *)aw sw:(ScreenWrapper *)sw {
NSValue *pVal = [JSWrapperUtils pointFromDict:dict aw:aw sw:sw];
if (pVal == nil) { return nil; }
NSPoint p = [pVal pointValue];
NSValue *sVal = [JSWrapperUtils sizeFromDict:dict aw:aw sw:sw];
if (sVal == nil) { return nil; }
NSSize s = [sVal sizeValue];
return [NSValue valueWithRect:NSMakeRect(p.x, p.y, s.width, s.height)];
}
@end
================================================
FILE: Slate/Layout.h
================================================
//
// Layout.h
// Slate
//
// Created by Jigish Patel on 6/13/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface Layout : NSObject {
@private
NSString *name;
NSMutableDictionary *appStates;
NSMutableDictionary *appOptions;
NSMutableArray *appOrder;
NSMutableArray *before;
NSMutableArray *after;
}
@property (copy) NSString *name;
@property NSMutableDictionary *appStates;
@property NSMutableDictionary *appOptions;
@property NSMutableArray *appOrder;
@property NSMutableArray *before;
@property NSMutableArray *after;
- (id)initWithString:(NSString *)layout;
- (id)initWithName:(NSString *)name dict:(NSDictionary *)dict;
- (void)addWithString:(NSString *)layout;
@end
================================================
FILE: Slate/Layout.m
================================================
//
// Layout.m
// Slate
//
// Created by Jigish Patel on 6/13/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "ApplicationOptions.h"
#import "Constants.h"
#import "Layout.h"
#import "Operation.h"
#import "StringTokenizer.h"
#import "SlateLogger.h"
@implementation Layout
@synthesize name;
@synthesize appStates;
@synthesize appOptions;
@synthesize appOrder;
@synthesize before, after;
- (id)init {
self = [super init];
if (self) {
appStates = [NSMutableDictionary dictionary];
appOptions = [NSMutableDictionary dictionary];
appOrder = [NSMutableArray array];
before = [NSMutableArray array];
after = [NSMutableArray array];
}
return self;
}
- (id)initWithString:(NSString *)layout {
self = [self init];
if (self) {
[self addWithString:layout];
}
return self;
}
- (id)initWithName:(NSString *)_name dict:(NSDictionary *)dict {
self = [self init];
if (self) {
if (_name == nil) { return nil; }
[self setName:_name];
for (NSString *appName in [dict allKeys]) {
id appDict = [dict objectForKey:appName];
if (appDict == nil || ![appDict isKindOfClass:[NSDictionary class]]) continue;
id _ops = [appDict objectForKey:OPT_OPERATIONS];
if (_ops == nil) continue;
NSMutableArray *ops = nil;
if ([_ops isKindOfClass:[NSArray class]]) ops = [_ops mutableCopy];
else ops = [NSMutableArray arrayWithObject:_ops];
if (ops == nil || ![ops isKindOfClass:[NSArray class]]) continue;
if ([OPT_BEFORE isEqualToString:appName]) {
[self setBefore:ops];
} else if ([OPT_AFTER isEqualToString:appName]) {
[self setAfter:ops];
} else {
[[self appOrder] addObject:appName];
[[self appStates] setObject:ops forKey:appName];
ApplicationOptions *appOpts = [[ApplicationOptions alloc] init];
if ([appDict objectForKey:OPT_IGNORE_FAIL] != nil && [[appDict objectForKey:OPT_IGNORE_FAIL] boolValue]) {
[appOpts setIgnoreFail:YES];
}
if ([appDict objectForKey:OPT_REPEAT] != nil && [[appDict objectForKey:OPT_REPEAT] boolValue]) {
[appOpts setRepeat:YES];
}
if ([appDict objectForKey:OPT_REPEAT_LAST] != nil && [[appDict objectForKey:OPT_REPEAT_LAST] boolValue]) {
[appOpts setRepeatLast:YES];
}
if ([appDict objectForKey:OPT_MAIN_FIRST] != nil && [[appDict objectForKey:OPT_MAIN_FIRST] boolValue]) {
[appOpts setMainFirst:YES];
}
if ([appDict objectForKey:OPT_MAIN_LAST] != nil && [[appDict objectForKey:OPT_MAIN_LAST] boolValue]) {
[appOpts setMainLast:YES];
}
if ([appDict objectForKey:OPT_SORT_TITLE] != nil && [[appDict objectForKey:OPT_SORT_TITLE] boolValue]) {
[appOpts setSortTitle:YES];
}
if ([appDict objectForKey:OPT_TITLE_ORDER] != nil && [[appDict objectForKey:OPT_TITLE_ORDER] isKindOfClass:[NSArray class]]) {
[appOpts setTitleOrder:[appDict objectForKey:OPT_TITLE_ORDER]];
}
if ([appDict objectForKey:OPT_TITLE_ORDER_REGEX] != nil && [[appDict objectForKey:OPT_TITLE_ORDER_REGEX] isKindOfClass:[NSArray class]]) {
[appOpts setTitleOrderRegex:[appDict objectForKey:OPT_TITLE_ORDER_REGEX]];
}
[appOptions setObject:appOpts forKey:appName];
}
}
}
return self;
}
- (void)addWithString:(NSString *)layout {
// layout (| )*
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:layout into:tokens maxTokens:4 quoteChars:[NSCharacterSet characterSetWithCharactersInString:QUOTES]];
if ([tokens count] <=3) {
@throw([NSException exceptionWithName:@"Unrecognized Layout" reason:layout userInfo:nil]);
}
[self setName:[tokens objectAtIndex:1]];
NSArray *appNameAndOptions = [[tokens objectAtIndex:2] componentsSeparatedByString:COLON];
NSString *appName = [appNameAndOptions objectAtIndex:0];
if ([APP_NAME_BEFORE isEqualToString:appName]) {
NSString *opsString = [tokens objectAtIndex:3];
Operation *op = [Operation operationFromString:opsString];
if (op != nil) {
[before addObject:op];
} else {
SlateLogger(@"ERROR: Invalid Operation in before: '%@'", opsString);
@throw([NSException exceptionWithName:@"Invalid Operation in before" reason:[NSString stringWithFormat:@"Invalid operation '%@' in chain.", opsString] userInfo:nil]);
}
} else if([APP_NAME_AFTER isEqualToString:appName]) {
NSString *opsString = [tokens objectAtIndex:3];
Operation *op = [Operation operationFromString:opsString];
if (op != nil) {
[after addObject:op];
} else {
SlateLogger(@"ERROR: Invalid Operation in after: '%@'", opsString);
@throw([NSException exceptionWithName:@"Invalid Operation in after" reason:[NSString stringWithFormat:@"Invalid operation '%@' in chain.", opsString] userInfo:nil]);
}
} else {
if ([appOptions objectForKey:appName] != nil) [appOrder removeObject:appName];
[appOrder addObject:appName];
if ([appNameAndOptions count] > 1) {
NSString *options = [appNameAndOptions objectAtIndex:1];
ApplicationOptions *appOpts = [[ApplicationOptions alloc] init];
NSArray *optArr = [options componentsSeparatedByString:COMMA];
for (NSInteger i = 0; i < [optArr count]; i++) {
NSString *option = [optArr objectAtIndex:i];
if ([option isEqualToString:IGNORE_FAIL]) {
[appOpts setIgnoreFail:YES];
} else if ([option isEqualToString:REPEAT]) {
[appOpts setRepeat:YES];
} else if ([option isEqualToString:REPEAT_LAST]) {
[appOpts setRepeatLast:YES];
} else if ([option isEqualToString:MAIN_FIRST]) {
[appOpts setMainFirst:YES];
} else if ([option isEqualToString:MAIN_LAST]) {
[appOpts setMainLast:YES];
} else if ([option isEqualToString:SORT_TITLE]) {
[appOpts setSortTitle:YES];
} else if ([option rangeOfString:TITLE_ORDER].length > 0) {
[appOpts setTitleOrder:[[[option componentsSeparatedByString:EQUALS] objectAtIndex:1] componentsSeparatedByString:SEMICOLON]];
} else if ([option rangeOfString:TITLE_ORDER_REGEX].length > 0) {
[appOpts setTitleOrderRegex:[[[option componentsSeparatedByString:EQUALS] objectAtIndex:1] componentsSeparatedByString:SEMICOLON]];
}
}
[appOptions setObject:appOpts forKey:appName];
} else {
[appOptions setObject:[[ApplicationOptions alloc] init] forKey:appName];
}
NSString *opsString = [tokens objectAtIndex:3];
NSArray *ops = [opsString componentsSeparatedByString:PIPE_PADDED];
NSMutableArray *opArray = [[NSMutableArray alloc] initWithCapacity:10];
for (NSInteger i = 0; i < [ops count]; i++) {
Operation *op = [Operation operationFromString:[ops objectAtIndex:i]];
if (op != nil) {
[opArray addObject:op];
} else {
SlateLogger(@"ERROR: Invalid Operation in Chain: '%@'", [ops objectAtIndex:i]);
@throw([NSException exceptionWithName:@"Invalid Operation in Chain" reason:[NSString stringWithFormat:@"Invalid operation '%@' in chain.", [ops objectAtIndex:i]] userInfo:nil]);
}
}
[[self appStates] setObject:opArray forKey:appName];
}
}
@end
================================================
FILE: Slate/LayoutOperation.h
================================================
//
// LayoutOperation.h
// Slate
//
// Created by Jigish Patel on 6/14/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
#import "Operation.h"
@interface LayoutOperation : Operation {
@private
NSString *name;
}
@property (copy) NSString *name;
- (id)initWithName:(NSString *)theName;
- (BOOL)testOperation:(Operation *)op;
+ (BOOL)activateLayout:(NSString *)name;
+ (BOOL)activateLayout:(NSString *)name screenWrapper:(ScreenWrapper *)sw;
+ (id)layoutOperation;
+ (id)layoutOperationFromString:(NSString *)layoutOperation;
@end
================================================
FILE: Slate/LayoutOperation.m
================================================
//
// LayoutOperation.m
// Slate
//
// Created by Jigish Patel on 6/14/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "ApplicationOptions.h"
#import "Constants.h"
#import "Layout.h"
#import "LayoutOperation.h"
#import "SlateConfig.h"
#import "StringTokenizer.h"
#import "SlateLogger.h"
#import "RunningApplications.h"
@implementation LayoutOperation
@synthesize name;
- (id)init {
self = [super init];
if (self) {
[self setName:nil];
}
return self;
}
- (id)initWithName:(NSString *)theName {
self = [self init];
if (self) {
[self setName:theName];
}
return self;
}
- (BOOL)doOperation {
SlateLogger(@"----------------- Begin Layout Operation -----------------");
ScreenWrapper *sw = [[ScreenWrapper alloc] init];
// We don't use the passed in AccessibilityWrapper so it is nil. No need to waste time creating one here
BOOL success = [self doOperationWithAccessibilityWrapper:nil screenWrapper:sw];
SlateLogger(@"----------------- End Layout Operation -----------------");
return success;
}
// Note that the AccessibilityWrapper is never used because layouts use multiple applications
- (BOOL)doOperationWithAccessibilityWrapper:(AccessibilityWrapper *)iamnil screenWrapper:(ScreenWrapper *)sw {
[self evalOptionsWithAccessibilityWrapper:iamnil screenWrapper:sw];
return [LayoutOperation activateLayout:[self name] screenWrapper:sw];
}
- (BOOL)testOperation:(Operation *)op {
BOOL success = [op testOperation];
return success;
}
- (BOOL)testOperation {
BOOL success = YES;
Layout *layout = [[[SlateConfig getInstance] layouts] objectForKey:[self name]];
if (layout == nil) {
@throw([NSException exceptionWithName:@"Unrecognized Layout" reason:[self name] userInfo:nil]);
}
NSArray *apps = [[layout appStates] allKeys];
for (NSInteger i = 0; i < [apps count]; i++) {
NSArray *ops = [[layout appStates] objectForKey:[apps objectAtIndex:i]];
for (NSInteger op = 0; op < [ops count]; op++) {
success = [self testOperation:[ops objectAtIndex:op]] && success;
}
}
return success;
}
- (NSArray *)requiredOptions {
return [NSArray arrayWithObjects:OPT_NAME, nil];
}
- (void)parseOption:(NSString *)_name value:(id)value {
if (value == nil) { return; }
if ([_name isEqualToString:OPT_NAME]) {
if (![value isKindOfClass:[NSString class]]) {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", _name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", _name, value] userInfo:nil]);
return;
}
[self setName:value];
}
}
+ (BOOL)activateLayout:(NSString *)name screenWrapper:(ScreenWrapper *)sw {
Layout *layout = [[[SlateConfig getInstance] layouts] objectForKey:name];
if (layout == nil) {
@throw([NSException exceptionWithName:@"Unrecognized Layout" reason:name userInfo:nil]);
}
BOOL success = YES;
id appsArray = nil;
if ([[SlateConfig getInstance] getBoolConfig:LAYOUT_FOCUS_ON_ACTIVATE]) {
appsArray = [NSMutableArray array];
for (NSString *appName in [layout appOrder]) {
NSRunningApplication *appToAdd = [[[RunningApplications getInstance] appNameToApp] objectForKey:appName];
if (appToAdd != nil) [appsArray addObject:appToAdd];
}
} else {
appsArray = [RunningApplications getInstance];
}
// do before
for (Operation *op in [layout before]) {
[op doOperation];
}
for (NSRunningApplication *app in appsArray) {
NSString *appName = [app localizedName];
pid_t appPID = [app processIdentifier];
SlateLogger(@"I see application '%@' with pid '%d'", appName, appPID);
NSArray *operations = [[layout appStates] objectForKey:appName];
if (operations == nil) {
continue;
}
// Yes, I am aware that the following blocks are inefficient. Deal with it.
AXUIElementRef appRef = AXUIElementCreateApplication(appPID);
CFArrayRef windowsArrRef = [AccessibilityWrapper windowsInApp:appRef];
if (!windowsArrRef || CFArrayGetCount(windowsArrRef) == 0) continue;
CFMutableArrayRef windowsArr = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, windowsArrRef);
CFMutableArrayRef windows = CFArrayCreateMutable(kCFAllocatorDefault, CFArrayGetCount(windowsArr), &kCFTypeArrayCallBacks);
CFMutableArrayRef windowsAppend = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
// First Pass for main window if needed
if ([(ApplicationOptions *)[[layout appOptions] objectForKey:appName] mainFirst]) {
SlateLogger(@"Main First");
for (NSInteger i = 0; i < CFArrayGetCount(windowsArr); i++) {
if([AccessibilityWrapper isMainWindow:CFArrayGetValueAtIndex(windowsArr, i)]) {
SlateLogger(@" Found Main");
CFArrayAppendValue(windows, CFArrayGetValueAtIndex(windowsArr, i));
CFArrayRemoveValueAtIndex(windowsArr, i);
break;
}
}
} else if ([(ApplicationOptions *)[[layout appOptions] objectForKey:appName] mainLast]) {
SlateLogger(@"Main Last");
CFRelease(windowsAppend);
windowsAppend = CFArrayCreateMutable(kCFAllocatorDefault, 1, &kCFTypeArrayCallBacks);
for (NSInteger i = 0; i < CFArrayGetCount(windowsArr); i++) {
if([AccessibilityWrapper isMainWindow:CFArrayGetValueAtIndex(windowsArr, i)]) {
SlateLogger(@" Found Main");
CFArrayAppendValue(windowsAppend, CFArrayGetValueAtIndex(windowsArr, i));
CFArrayRemoveValueAtIndex(windowsArr, i);
break;
}
}
}
// Second Pass for title order if needed
if ([(ApplicationOptions *)[[layout appOptions] objectForKey:appName] titleOrder] != nil) {
NSMutableArray *titleOrder = [NSMutableArray arrayWithArray:[(ApplicationOptions *)[[layout appOptions] objectForKey:appName] titleOrder]];
SlateLogger(@"Title Order: %@", titleOrder);
for (NSInteger j = 0; j < [titleOrder count]; j++) {
for (NSInteger i = 0; i < CFArrayGetCount(windowsArr); i++) {
SlateLogger(@" Checking Title: %@", [AccessibilityWrapper getTitle:CFArrayGetValueAtIndex(windowsArr, i)]);
if([[AccessibilityWrapper getTitle:CFArrayGetValueAtIndex(windowsArr, i)] isEqualToString:[titleOrder objectAtIndex:j]]) {
SlateLogger(@" Found Title: %@", [titleOrder objectAtIndex:j]);
CFArrayAppendValue(windows, CFArrayGetValueAtIndex(windowsArr, i));
CFArrayRemoveValueAtIndex(windowsArr, i);
break;
}
}
}
}
// Third Pass for title order regex if needed
if ([(ApplicationOptions *)[[layout appOptions] objectForKey:appName] titleOrderRegex] != nil) {
NSMutableArray *titleOrder = [NSMutableArray arrayWithArray:[(ApplicationOptions *)[[layout appOptions] objectForKey:appName] titleOrderRegex]];
SlateLogger(@"Title Order Regex: %@", titleOrder);
for (NSInteger j = 0; j < [titleOrder count]; j++) {
for (NSInteger i = 0; i < CFArrayGetCount(windowsArr); i++) {
SlateLogger(@" Checking Title: %@", [AccessibilityWrapper getTitle:CFArrayGetValueAtIndex(windowsArr, i)]);
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:[titleOrder objectAtIndex:j] options:0 error:&error];
if (error != NULL && error != nil) {
continue;
}
NSString *currWinTitle = [AccessibilityWrapper getTitle:CFArrayGetValueAtIndex(windowsArr, i)];
NSUInteger numMatches = [regex numberOfMatchesInString:currWinTitle options:0 range:NSMakeRange(0, [currWinTitle length])];
if(numMatches > 0) {
SlateLogger(@" Found Title: %@", currWinTitle);
CFArrayAppendValue(windows, CFArrayGetValueAtIndex(windowsArr, i));
CFArrayRemoveValueAtIndex(windowsArr, i);
break;
}
}
}
}
// Fourth Pass for sort
if ([(ApplicationOptions *)[[layout appOptions] objectForKey:appName] sortTitle]) {
SlateLogger(@"Sort By Title");
while (CFArrayGetCount(windowsArr) > 0) {
NSString *title = nil;
NSInteger index = 0;
for (NSInteger i = 0; i < CFArrayGetCount(windowsArr); i++) {
NSString *currTitle = [AccessibilityWrapper getTitle:CFArrayGetValueAtIndex(windowsArr, i)];
if (title == nil) {
title = currTitle;
index = i;
continue;
}
if ([title compare:currTitle] == NSOrderedDescending) {
title = currTitle;
index = i;
}
}
CFArrayAppendValue(windows, CFArrayGetValueAtIndex(windowsArr, index));
CFArrayRemoveValueAtIndex(windowsArr, index);
}
} else {
SlateLogger(@"No Sort");
CFArrayAppendArray(windows, windowsArr, CFRangeMake(0, CFArrayGetCount(windowsArr)));
}
CFArrayAppendArray(windows, windowsAppend, CFRangeMake(0, CFArrayGetCount(windowsAppend)));
NSInteger failedWindows = 0;
BOOL appSuccess = YES;
if ([(ApplicationOptions *)[[layout appOptions] objectForKey:appName] repeat]) {
for (NSInteger i = 0; i < CFArrayGetCount(windows); i++) {
AccessibilityWrapper *aw = [[AccessibilityWrapper alloc] initWithApp:appRef window:CFArrayGetValueAtIndex(windows, i)];
appSuccess = [[operations objectAtIndex:((i-failedWindows) % [operations count])] doOperationWithAccessibilityWrapper:aw screenWrapper:sw] && appSuccess;
if ([[SlateConfig getInstance] getBoolConfig:LAYOUT_FOCUS_ON_ACTIVATE]) { [aw focus]; }
if (![(ApplicationOptions *)[[layout appOptions] objectForKey:appName] ignoreFail] && !appSuccess)
failedWindows++;
}
} else if ([(ApplicationOptions *)[[layout appOptions] objectForKey:appName] repeatLast]) {
for (NSInteger i = 0; i < CFArrayGetCount(windows); i++) {
AccessibilityWrapper *aw = [[AccessibilityWrapper alloc] initWithApp:appRef window:CFArrayGetValueAtIndex(windows, i)];
NSInteger opIndex = ((i-failedWindows) % [operations count]);
if (i-failedWindows >= [operations count]) {
opIndex = [operations count] - 1;
}
appSuccess = [[operations objectAtIndex:opIndex] doOperationWithAccessibilityWrapper:aw screenWrapper:sw] && appSuccess;
if ([[SlateConfig getInstance] getBoolConfig:LAYOUT_FOCUS_ON_ACTIVATE]) { [aw focus]; }
if (![(ApplicationOptions *)[[layout appOptions] objectForKey:appName] ignoreFail] && !appSuccess)
failedWindows++;
}
} else {
for (NSInteger i = 0; i < CFArrayGetCount(windows) && i-failedWindows < [operations count]; i++) {
AccessibilityWrapper *aw = [[AccessibilityWrapper alloc] initWithApp:appRef window:CFArrayGetValueAtIndex(windows, i)];
appSuccess = [[operations objectAtIndex:(i-failedWindows)] doOperationWithAccessibilityWrapper:aw screenWrapper:sw] && appSuccess;
if ([[SlateConfig getInstance] getBoolConfig:LAYOUT_FOCUS_ON_ACTIVATE]) { [aw focus]; }
if (![(ApplicationOptions *)[[layout appOptions] objectForKey:appName] ignoreFail] && !appSuccess)
failedWindows++;
}
}
success = appSuccess && success;
CFRelease(windows);
CFRelease(windowsArr);
CFRelease(windowsAppend);
}
// do after
for (Operation *op in [layout after]) {
[op doOperation];
}
return success;
}
+ (BOOL)activateLayout:(NSString *)name {
ScreenWrapper *sw = [[ScreenWrapper alloc] init];
return [LayoutOperation activateLayout:name screenWrapper:sw];
}
+ (id)layoutOperation {
return [[LayoutOperation alloc] init];
}
+ (id)layoutOperationFromString:(NSString *)layoutOperation {
// layout
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:layoutOperation into:tokens maxTokens:2];
if ([tokens count] < 2) {
SlateLogger(@"ERROR: Invalid Parameters '%@'", layoutOperation);
@throw([NSException exceptionWithName:@"Invalid Parameters" reason:[NSString stringWithFormat:@"Invalid Parameters in '%@'. Layout operations require the following format: 'layout '", layoutOperation] userInfo:nil]);
}
Operation *op = [[LayoutOperation alloc] initWithName:[tokens objectAtIndex:1]];
return op;
}
@end
================================================
FILE: Slate/MathUtils.h
================================================
//
// MathUtils.h
// Slate
//
// Created by Jigish Patel on 6/22/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface MathUtils : NSObject {}
+ (BOOL)isRect:(NSRect)rect1 biggerThan:(NSRect)rect2;
+ (NSRect)flipYCoordinateOfRect:(NSRect)original withReference:(NSRect)reference;
+ (NSRect)scaleRect:(NSRect)rect factor:(double)factor;
+ (NSRect)weightedIntersectionOf:(NSRect)rect1 and:(NSRect)rect2 weight:(double)weight;
@end
================================================
FILE: Slate/MathUtils.m
================================================
//
// MathUtils.m
// Slate
//
// Created by Jigish Patel on 6/22/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "MathUtils.h"
@implementation MathUtils
+ (BOOL)isRect:(NSRect)rect1 biggerThan:(NSRect)rect2 {
return rect1.size.width*rect1.size.height > rect2.size.width*rect2.size.height;
}
// I understand that the following method is stupidly written. Apple apparently enjoys keeping
// multiple types of coordinate spaces. NSScreen.origin returns bottom-left while we need
// top-left for window moving. Go figure.
+ (NSRect)flipYCoordinateOfRect:(NSRect)original withReference:(NSRect)reference {
return NSMakeRect(original.origin.x,
reference.size.height - (reference.origin.y + original.origin.y + original.size.height),
original.size.width,
original.size.height);
}
+ (NSRect)scaleRect:(NSRect)rect factor:(double)factor {
return NSMakeRect(rect.origin.x, rect.origin.y, rect.size.width*factor, rect.size.height*factor);
}
+ (NSRect)weightedIntersectionOf:(NSRect)rect1 and:(NSRect)rect2 weight:(double)weight {
NSRect intersection = NSIntersectionRect(rect1, rect2);
if (NSEqualRects(intersection, NSZeroRect)) return NSZeroRect;
return [MathUtils scaleRect:intersection factor:weight];
}
@end
================================================
FILE: Slate/MoveOperation.h
================================================
//
// MoveOperation.h
// Slate
//
// Created by Jigish Patel on 5/18/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
#import "Operation.h"
#import "ExpressionPoint.h"
@interface MoveOperation : Operation {
@private
ExpressionPoint *topLeft;
ExpressionPoint *dimensions;
NSString *monitor;
NSInteger screenId;
}
@property ExpressionPoint *topLeft;
@property ExpressionPoint *dimensions;
@property NSString *monitor;
@property NSInteger screenId;
- (id)initWithTopLeft:(NSString *)tl dimensions:(NSString *)dim monitor:(NSString *)mon;
- (id)initWithTopLeftEP:(ExpressionPoint *)tl dimensionsEP:(ExpressionPoint *)dim screenId:(NSInteger)myScreenId;
- (NSPoint)getTopLeftWithCurrentWindowRect:(NSRect)cWindowRect newSize:(NSSize)nSize screenWrapper:(ScreenWrapper *)sw;
- (NSSize)getDimensionsWithCurrentWindowRect:(NSRect)cWindowRect screenWrapper:(ScreenWrapper *)sw;
+ (id)moveOperation;
+ (id)moveOperationFromString:(NSString *)moveOperation;
@end
================================================
FILE: Slate/MoveOperation.m
================================================
//
// MoveOperation.m
// Slate
//
// Created by Jigish Patel on 5/18/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "AccessibilityWrapper.h"
#import "Constants.h"
#import "MoveOperation.h"
#import "ScreenWrapper.h"
#import "SlateConfig.h"
#import "StringTokenizer.h"
#import "SlateLogger.h"
@implementation MoveOperation
@synthesize topLeft;
@synthesize dimensions;
@synthesize monitor;
@synthesize screenId;
- (id)init {
self = [super init];
if (self) {
[self setTopLeft:[[ExpressionPoint alloc] initWithX:@"" y:@""]];
[self setDimensions:[[ExpressionPoint alloc] initWithX:@"" y:@""]];
[self setMonitor:REF_CURRENT_SCREEN];
[self setScreenId:-1];
}
return self;
}
- (id)initWithTopLeft:(NSString *)tl dimensions:(NSString *)dim monitor:(NSString *)mon {
self = [self init];
if (self) {
NSArray *tlTokens = [tl componentsSeparatedByString:SEMICOLON];
if ([tlTokens count] == 2) {
[self setTopLeft:[[ExpressionPoint alloc] initWithX:[tlTokens objectAtIndex:0] y:[tlTokens objectAtIndex:1]]];
} else {
tlTokens = [tl componentsSeparatedByString:COMMA];
if ([tlTokens count] == 2) {
[self setTopLeft:[[ExpressionPoint alloc] initWithX:[tlTokens objectAtIndex:0] y:[tlTokens objectAtIndex:1]]];
} else {
return nil;
}
}
NSArray *dimTokens = [dim componentsSeparatedByString:SEMICOLON];
if ([dimTokens count] == 2) {
[self setDimensions:[[ExpressionPoint alloc] initWithX:[dimTokens objectAtIndex:0] y:[dimTokens objectAtIndex:1]]];
} else {
dimTokens = [dim componentsSeparatedByString:COMMA];
if ([dimTokens count] == 2) {
[self setDimensions:[[ExpressionPoint alloc] initWithX:[dimTokens objectAtIndex:0] y:[dimTokens objectAtIndex:1]] ];
} else {
return nil;
}
}
[self setMonitor:mon];
[self setScreenId:-1];
}
return self;
}
- (id)initWithTopLeftEP:(ExpressionPoint *)tl dimensionsEP:(ExpressionPoint *)dim screenId:(NSInteger)myScreenId {
self = [self init];
if (self) {
[self setTopLeft:tl];
[self setDimensions:dim];
[self setScreenId:myScreenId];
[self setMonitor:nil];
}
return self;
}
- (BOOL)doOperationWithAccessibilityWrapper:(AccessibilityWrapper *)aw screenWrapper:(ScreenWrapper *)sw {
BOOL success = NO;
[self evalOptionsWithAccessibilityWrapper:aw screenWrapper:sw];
NSPoint cTopLeft = [aw getCurrentTopLeft];
NSSize cSize = [aw getCurrentSize];
NSRect cWindowRect = NSMakeRect(cTopLeft.x, cTopLeft.y, cSize.width, cSize.height);
NSSize nSize = [self getDimensionsWithCurrentWindowRect:cWindowRect screenWrapper:sw];
success = [aw resizeWindow:nSize];
NSSize realNewSize = [aw getCurrentSize];
NSPoint nTopLeft = [self getTopLeftWithCurrentWindowRect:cWindowRect newSize:realNewSize screenWrapper:sw];
success = [aw moveWindow:nTopLeft] && success;
success = [aw resizeWindow:nSize] && success;
return success;
}
- (BOOL)doOperation {
SlateLogger(@"----------------- Begin Move Operation -----------------");
AccessibilityWrapper *aw = [[AccessibilityWrapper alloc] init];
ScreenWrapper *sw = [[ScreenWrapper alloc] init];
BOOL success = NO;
if ([aw inited]) success = [self doOperationWithAccessibilityWrapper:aw screenWrapper:sw];
SlateLogger(@"----------------- End Move Operation -----------------");
return success;
}
- (BOOL)testOperation {
ScreenWrapper *sw = [[ScreenWrapper alloc] init];
NSPoint cTopLeft = NSMakePoint(0, 0);
NSSize cSize = NSMakeSize(1000, 1000);
NSRect cWindowRect = NSMakeRect(cTopLeft.x, cTopLeft.y, cSize.width, cSize.height);
NSSize nSize = [self getDimensionsWithCurrentWindowRect:cWindowRect screenWrapper:sw];
[self getTopLeftWithCurrentWindowRect:cWindowRect newSize:nSize screenWrapper:sw];
return YES;
}
- (NSPoint)getTopLeftWithCurrentWindowRect:(NSRect)cWindowRect newSize:(NSSize)nSize screenWrapper:(ScreenWrapper *)sw {
// If monitor does not exist send back the same origin
if (monitor != nil) {
[self setScreenId:[sw getScreenId:monitor windowRect:cWindowRect]];
}
if (![sw screenExists:[self screenId]]) return cWindowRect.origin;
NSDictionary *values = [sw getScreenAndWindowValues:screenId window:cWindowRect newSize:nSize];
return [topLeft getPointWithDict:values];
}
- (NSSize)getDimensionsWithCurrentWindowRect:(NSRect)cWindowRect screenWrapper:(ScreenWrapper *)sw {
// If monitor does not exist send back the same size
if (monitor != nil) {
[self setScreenId:[sw getScreenId:monitor windowRect:cWindowRect]];
}
if (![sw screenExists:[self screenId]]) return cWindowRect.size;
NSDictionary *values = [sw getScreenAndWindowValues:screenId window:cWindowRect newSize:cWindowRect.size];
return [dimensions getSizeWithDict:values];
}
- (NSArray *)requiredOptions {
return [NSArray arrayWithObjects:OPT_X, OPT_Y, OPT_WIDTH, OPT_HEIGHT, nil];
}
- (void)parseOption:(NSString *)name value:(id)val {
// all options should be strings
if (val == nil) { return; }
NSString *value = nil;
if ([val isKindOfClass:[NSString class]]) {
value = val;
} else if ([val isKindOfClass:[NSNumber class]]) {
value = [val stringValue];
} else {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", name, val] userInfo:nil]);
return;
}
[[self options] setValue:value forKey:name];
if ([name isEqualToString:OPT_X]) {
[[self topLeft] setX:value];
} else if ([name isEqualToString:OPT_Y]) {
[[self topLeft] setY:value];
} else if ([name isEqualToString:OPT_SCREEN]) {
[self setMonitor:value];
} else if ([name isEqualToString:OPT_WIDTH]) {
[[self dimensions] setX:value];
} else if ([name isEqualToString:OPT_HEIGHT]) {
[[self dimensions] setY:value];
}
}
+ (id)moveOperation {
return [[MoveOperation alloc] init];
}
+ (id)moveOperationFromString:(NSString *)moveOperation {
// move
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:moveOperation into:tokens];
if ([tokens count] < 3) {
SlateLogger(@"ERROR: Invalid Parameters '%@'", moveOperation);
@throw([NSException exceptionWithName:@"Invalid Parameters" reason:[NSString stringWithFormat:@"Invalid Parameters in '%@'. Move operations require the following format: 'move topLeftX;topLeftY width;height [optional:screemNumber]'", moveOperation] userInfo:nil]);
}
Operation *op = nil;
op = [[MoveOperation alloc] initWithTopLeft:[tokens objectAtIndex:1] dimensions:[tokens objectAtIndex:2] monitor:([tokens count] >=4 ? [tokens objectAtIndex:3] : REF_CURRENT_SCREEN)];
return op;
}
@end
================================================
FILE: Slate/NSFileManager+ApplicationSupport.h
================================================
//
// NSFileManager+ApplicationSupport.h
// Slate
//
// Created by Jigish Patel on 4/23/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface NSFileManager (ApplicationSupport)
- (NSURL *)applicationSupportDirectory;
- (NSURL *)findOrCreateDirectory:(NSSearchPathDirectory)searchPathDirectory inDomain:(NSSearchPathDomainMask)domainMask append:(NSString *)appendComponent error:(NSError **)errorOut;
@end
================================================
FILE: Slate/NSFileManager+ApplicationSupport.m
================================================
//
// NSFileManager+ApplicationSupport.m
// Slate
//
// Created by Jigish Patel on 4/23/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "NSFileManager+ApplicationSupport.h"
@implementation NSFileManager (ApplicationSupport)
- (NSURL *)applicationSupportDirectory {
return [self findOrCreateDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask append:[[NSBundle mainBundle] bundleIdentifier] error:nil];
}
- (NSURL *)findOrCreateDirectory:(NSSearchPathDirectory)searchPathDirectory inDomain:(NSSearchPathDomainMask)domainMask append:(NSString *)appendComponent error:(NSError **)errorOut {
NSArray *urls = [self URLsForDirectory:searchPathDirectory inDomains:domainMask];
if ([urls count] == 0) return nil;
NSURL *url = [urls objectAtIndex:0];
if (appendComponent) {
url = [url URLByAppendingPathComponent:appendComponent];
}
NSError *error;
SInt32 major, minor, bugfix;
Gestalt(gestaltSystemVersionMajor, &major);
Gestalt(gestaltSystemVersionMinor, &minor);
Gestalt(gestaltSystemVersionBugFix, &bugfix);
BOOL success = NO;
// need to use AtPath if < 10.7 because AtURL is 10.7 only
if (major >= 10 && minor >= 7) {
success = [self createDirectoryAtURL:url withIntermediateDirectories:YES attributes:nil error:&error];
} else {
success = [self createDirectoryAtPath:[url path] withIntermediateDirectories:YES attributes:nil error:&error];
}
if (!success) {
if (errorOut) *errorOut = error;
return nil;
}
if (errorOut) *errorOut = nil;
return url;
}
@end
================================================
FILE: Slate/NSString+Indicies.h
================================================
//
// NSString+Indicies.h
// Slate
//
// Created by Jigish Patel on 10/1/12.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface NSString (Indicies)
- (NSInteger)indexOfString:(NSString *)str;
- (NSInteger)indexOfChar:(const unichar)c;
@end
================================================
FILE: Slate/NSString+Indicies.m
================================================
//
// NSString+Indicies.m
// Slate
//
// Created by Jigish Patel on 10/1/12.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "NSString+Indicies.h"
@implementation NSString (Indicies)
- (NSInteger)indexOfString:(NSString *)str {
NSRange range = [self rangeOfString:str];
if ( range.length > 0 ) {
return range.location;
} else {
return -1;
}
}
- (NSInteger)indexOfChar:(const unichar) c {
NSRange range = [self rangeOfString:[NSString stringWithCharacters:&c length:1]];
if ( range.length > 0 ) {
return range.location;
} else {
return -1;
}
}
@end
================================================
FILE: Slate/NSString+Levenshtein.h
================================================
//
// NSString+Levenshtein.h
// Slate
//
// Created by Jigish Patel on 3/1/12.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface NSString (Levenshtein)
- (float) levenshteinDistance: (NSString *) stringB;
- (float) sequentialDistance: (NSString *) stringB;
- (int) smallestOf: (int) a andOf: (int) b andOf: (int) c;
@end
================================================
FILE: Slate/NSString+Levenshtein.m
================================================
//
// NSString+Levenshtein.m
// Slate
//
// Created by Jigish Patel on 3/1/12.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "NSString+Levenshtein.h"
@implementation NSString (Levenshtein)
- (float) levenshteinDistance:(NSString *)stringB {
// normalize strings
NSString * stringA = [NSString stringWithString: self];
[stringA stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[stringB stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
stringA = [stringA lowercaseString];
stringB = [stringB lowercaseString];
// Step 1
int k, i, j, cost, * d, distance;
NSUInteger n = [stringA length];
NSUInteger m = [stringB length];
if( n++ != 0 && m++ != 0 ) {
d = malloc( sizeof(int) * m * n );
// Step 2
for( k = 0; k < n; k++)
d[k] = k;
for( k = 0; k < m; k++)
d[ k * n ] = k;
// Step 3 and 4
for( i = 1; i < n; i++ )
for( j = 1; j < m; j++ ) {
// Step 5
if( [stringA characterAtIndex: i-1] ==
[stringB characterAtIndex: j-1] )
cost = 0;
else
cost = 1;
// Step 6
d[ j * n + i ] = [self smallestOf: d [ (j - 1) * n + i ] + 1
andOf: d[ j * n + i - 1 ] + 1
andOf: d[ (j - 1) * n + i -1 ] + cost ];
}
distance = d[ n * m - 1 ];
free( d );
return distance;
}
return 0.0;
}
- (float) sequentialDistance:(NSString *)stringB {
NSString * stringA = [NSString stringWithString: self];
[stringA stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[stringB stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
stringA = [stringA lowercaseString];
stringB = [stringB lowercaseString];
float distance = 0.0;
for (NSInteger i = 0; i < MIN([stringA length], [stringB length]); i++) {
if ([stringA characterAtIndex:i] == [stringB characterAtIndex:i]) distance++;
else break;
}
return distance;
}
// return the minimum of a, b and c
- (int) smallestOf:(int)a andOf:(int)b andOf:(int)c {
int min = a;
if ( b < min )
min = b;
if( c < min )
min = c;
return min;
}
@end
================================================
FILE: Slate/NudgeOperation.h
================================================
//
// NudgeOperation.h
// Slate
//
// Created by Jigish Patel on 1/20/13.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "MoveOperation.h"
@interface NudgeOperation : MoveOperation
+ (id)nudgeOperation;
+ (id)nudgeOperationFromString:(NSString *)nudgeOperation;
@end
================================================
FILE: Slate/NudgeOperation.m
================================================
//
// NudgeOperation.m
// Slate
//
// Created by Jigish Patel on 1/20/13.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "NudgeOperation.h"
#import "Constants.h"
#import "StringTokenizer.h"
#import "SlateLogger.h"
#import "SlateConfig.h"
@implementation NudgeOperation
- (NSArray *)requiredOptions {
return [NSArray arrayWithObjects:OPT_X, OPT_Y, nil];
}
- (void)beforeInitOptions {
[self setDimensions:[[ExpressionPoint alloc] initWithX:@"windowSizeX" y:@"windowSizeY"]];
[self setMonitor:REF_CURRENT_SCREEN];
}
- (void)parseOption:(NSString *)name value:(NSString *)value {
// all options should be strings
if (value == nil) { return; }
if (![value isKindOfClass:[NSString class]]) {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", name, value] userInfo:nil]);
return;
}
NSString *nudgePercentOf = [[SlateConfig getInstance] getConfig:NUDGE_PERCENT_OF];
if ([name isEqualToString:OPT_X]) {
NSString *tlX = WINDOW_TOP_LEFT_X;
if ([value hasSuffix:PERCENT]) {
// % Nudge
tlX = [tlX stringByAppendingString:[value stringByReplacingOccurrencesOfString:PERCENT withString:[NSString stringWithFormat:@"*%@X/100",nudgePercentOf]]];
} else {
// Hard Nudge
tlX = [tlX stringByAppendingString:value];
}
[[self topLeft] setX:tlX];
} else if ([name isEqualToString:OPT_Y]) {
NSString *tlY = WINDOW_TOP_LEFT_Y;
if ([value hasSuffix:PERCENT]) {
// % Nudge
tlY = [tlY stringByAppendingString:[value stringByReplacingOccurrencesOfString:PERCENT withString:[NSString stringWithFormat:@"*%@Y/100",nudgePercentOf]]];
} else {
// Hard Nudge
tlY = [tlY stringByAppendingString:value];
}
[[self topLeft] setY:tlY];
}
}
+ (id)nudgeOperation {
return [[NudgeOperation alloc] init];
}
+ (id)nudgeOperationFromString:(NSString *)nudgeOperation {
// nudge x y
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:nudgeOperation into:tokens];
if ([tokens count] < 2) {
SlateLogger(@"ERROR: Invalid Parameters '%@'", nudgeOperation);
@throw([NSException exceptionWithName:@"Invalid Parameters" reason:[NSString stringWithFormat:@"Invalid Parameters in '%@'. Nudge operations require the following format: 'nudge x y'", nudgeOperation] userInfo:nil]);
}
NSString *tlX = WINDOW_TOP_LEFT_X;
NSString *x = [tokens objectAtIndex:1];
NSString *nudgePercentOf = [[SlateConfig getInstance] getConfig:NUDGE_PERCENT_OF];
if ([x hasSuffix:PERCENT]) {
// % Nudge
tlX = [tlX stringByAppendingString:[x stringByReplacingOccurrencesOfString:PERCENT withString:[NSString stringWithFormat:@"*%@X/100",nudgePercentOf]]];
} else {
// Hard Nudge
tlX = [tlX stringByAppendingString:x];
}
NSString *tlY = WINDOW_TOP_LEFT_Y;
NSString *y = [tokens objectAtIndex:2];
if ([y hasSuffix:PERCENT]) {
// % Nudge
tlY = [tlY stringByAppendingString:[y stringByReplacingOccurrencesOfString:PERCENT withString:[NSString stringWithFormat:@"*%@Y/100",nudgePercentOf]]];
} else {
// Hard Nudge
tlY = [tlY stringByAppendingString:y];
}
Operation *op = [[MoveOperation alloc] initWithTopLeft:[[tlX stringByAppendingString:SEMICOLON] stringByAppendingString:tlY] dimensions:@"windowSizeX;windowSizeY" monitor:REF_CURRENT_SCREEN];
return op;
}
@end
================================================
FILE: Slate/Operation.h
================================================
//
// Operation.h
// Slate
//
// Created by Jigish Patel on 5/18/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
#import "AccessibilityWrapper.h"
#import "ScreenWrapper.h"
@interface Operation : NSObject {
NSString *opName;
NSMutableDictionary *options;
NSMutableDictionary *dynamicOptions;
}
@property NSString *opName;
@property NSMutableDictionary *options;
@property NSMutableDictionary *dynamicOptions;
- (BOOL)doOperation;
- (BOOL)doOperationWithAccessibilityWrapper:(AccessibilityWrapper *)aw screenWrapper:(ScreenWrapper *)sw;
- (BOOL)testOperation;
- (BOOL)testOperationWithAccessibilityWrapper:(AccessibilityWrapper *)aw screenWrapper:(ScreenWrapper *)sw;
- (BOOL)shouldTakeUndoSnapshot;
- (NSArray *)requiredOptions;
- (NSString *)checkRequiredOptions:(NSDictionary *)_options;
- (void)beforeInitOptions;
- (void)initOptions:(NSDictionary *)_options;
- (void)parseOption:(NSString *)name value:(id)value;
- (void)evalOptionsWithAccessibilityWrapper:(AccessibilityWrapper *)aw screenWrapper:(ScreenWrapper *)sw;
- (void)afterEvalOptions;
- (id)dup:(NSDictionary *)_options;
+ (id)operationFromString:(NSString *)opString;
+ (id)operationWithName:(NSString *)op options:(NSDictionary *)options;
+ (BOOL)isRepeatOnHoldOp:(NSString *)op;
+ (BOOL)doOperation:(NSString *)op options:(NSDictionary *)options aw:(AccessibilityWrapper *)aw sw:(ScreenWrapper *)sw;
@end
================================================
FILE: Slate/Operation.m
================================================
//
// Operation.m
// Slate
//
// Created by Jigish Patel on 5/18/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "Operation.h"
#import "MoveOperation.h"
#import "ResizeOperation.h"
#import "ChainOperation.h"
#import "LayoutOperation.h"
#import "FocusOperation.h"
#import "SnapshotOperation.h"
#import "ActivateSnapshotOperation.h"
#import "DeleteSnapshotOperation.h"
#import "HintOperation.h"
#import "StringTokenizer.h"
#import "Constants.h"
#import "SlateLogger.h"
#import "SwitchOperation.h"
#import "GridOperation.h"
#import "SequenceOperation.h"
#import "VisibilityOperation.h"
#import "RelaunchOperation.h"
#import "ShellOperation.h"
#import "UndoOperation.h"
#import "SlateConfig.h"
#import
#import "JSController.h"
#import "CornerOperation.h"
#import "ThrowOperation.h"
#import "NudgeOperation.h"
#import "PushOperation.h"
#import "JSInfoWrapper.h"
@implementation Operation
@synthesize opName;
@synthesize options;
@synthesize dynamicOptions;
- (id)init {
self = [super init];
if (self) {
[self setOpName:nil];
[self setOptions:[NSMutableDictionary dictionary]];
[self setDynamicOptions:[NSMutableDictionary dictionary]];
}
return self;
}
- (BOOL)doOperation {
return YES;
}
- (BOOL)doOperationWithAccessibilityWrapper:(AccessibilityWrapper *)aw screenWrapper:(ScreenWrapper *)sw {
return YES;
}
- (BOOL)testOperation {
return YES;
}
- (BOOL)shouldTakeUndoSnapshot {
return [[[SlateConfig getInstance] getConfig:UNDO_OPS] rangeOfString:[self opName]].location != NSNotFound;
}
- (NSArray *)requiredOptions {
return [NSArray array];
}
- (NSString *)checkRequiredOptions:(NSDictionary *)_options {
for (NSString *key in [self requiredOptions]) {
id opt = [_options objectForKey:key];
if (opt == nil) {
return key;
}
}
return nil;
}
- (void)initOptions:(NSDictionary *)_options {
NSString *missing = [self checkRequiredOptions:_options];
if (missing != nil) {
SlateLogger(@"ERROR: Missing Option in %@", [self opName]);
@throw([NSException exceptionWithName:@"Missing Option" reason:[NSString stringWithFormat:@"Missing option '%@' in '%@'", missing, [self opName]] userInfo:nil]);
return;
}
[self beforeInitOptions];
for (NSString *key in [_options allKeys]) {
id opt = [_options objectForKey:key];
if (opt == nil) { continue; }
if ([opt isKindOfClass:[NSString class]] || [opt isKindOfClass:[NSValue class]] || [opt isKindOfClass:[NSNumber class]] ||
[opt isKindOfClass:[NSDictionary class]] || [opt isKindOfClass:[NSArray class]]) {
[self.options setObject:opt forKey:key];
[self parseOption:key value:[[self options] objectForKey:key]];
} else if ([opt isKindOfClass:[WebScriptObject class]]) {
// assume this is a function (otherwise it would have been converted)
NSString *jsKey = [[JSController getInstance] addCallableFunction:opt];
[self.dynamicOptions setObject:jsKey forKey:key];
} else {
[self.options setObject:[NSString stringWithFormat:@"%@", opt] forKey:key];
[self parseOption:key value:[[self options] objectForKey:key]];
}
}
if ([[self dynamicOptions] count] == 0) { [self afterEvalOptions]; };
}
- (void)beforeInitOptions {
// OVERRIDE - runs before any options are set
}
- (void)parseOption:(NSString *)name value:(id)value {
// OVERRIDE - runs while setting options (both normal and dynamic)
}
- (void)afterEvalOptions {
// OVERRIDE - runs after all options are set
}
- (BOOL)testOperationWithAccessibilityWrapper:(AccessibilityWrapper *)aw screenWrapper:(ScreenWrapper *)sw {
[self evalOptionsWithAccessibilityWrapper:aw screenWrapper:sw];
return [self testOperation];
}
- (void)evalOptionsWithAccessibilityWrapper:(AccessibilityWrapper *)aw screenWrapper:(ScreenWrapper *)sw {
if ([[self dynamicOptions] count] == 0) { return; }
if (aw == nil) {
[[JSInfoWrapper getInstance] setAw:[[AccessibilityWrapper alloc] init]];
} else {
[[JSInfoWrapper getInstance] setAw:aw];
}
if (sw == nil) {
[[JSInfoWrapper getInstance] setSw:[[ScreenWrapper alloc] init]];
} else {
[[JSInfoWrapper getInstance] setSw:sw];
}
for (NSString *key in [[self dynamicOptions] allKeys]) {
id result = [[JSController getInstance] runCallableFunction:[[self dynamicOptions] objectForKey:key]];
if (result == nil) { continue; }
[self.options setObject:result forKey:key];
[self parseOption:key value:[[self options] objectForKey:key]];
}
[self afterEvalOptions];
}
- (id)dup:(NSDictionary *)_options {
NSMutableDictionary *newOptions = [NSMutableDictionary dictionaryWithDictionary:[self options]];
[newOptions addEntriesFromDictionary:[self dynamicOptions]];
[newOptions addEntriesFromDictionary:_options];
return [Operation operationWithName:[self opName] options:newOptions];
}
+ (BOOL)doOperation:(NSString *)op options:(NSDictionary *)options aw:(AccessibilityWrapper *)aw sw:(ScreenWrapper *)sw {
Operation *operation = [Operation operationWithName:op options:options];
BOOL success = NO;
if (operation != nil) {
[operation doOperationWithAccessibilityWrapper:aw screenWrapper:sw];
operation = nil; // force release of operation object
}
return success;
}
+ (id)operationFromString:(NSString *)opString {
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:opString into:tokens maxTokens:2];
NSString *op = [tokens objectAtIndex:0];
Operation *operation = nil;
if ([op isEqualToString:MOVE]) {
operation = [MoveOperation moveOperationFromString:opString];
} else if ([op isEqualToString:RESIZE]) {
operation = [ResizeOperation resizeOperationFromString:opString];
} else if ([op isEqualToString:PUSH]) {
operation = [PushOperation pushOperationFromString:opString];
} else if ([op isEqualToString:NUDGE]) {
operation = [NudgeOperation nudgeOperationFromString:opString];
} else if ([op isEqualToString:THROW]) {
operation = [ThrowOperation throwOperationFromString:opString];
} else if ([op isEqualToString:CORNER]) {
operation = [CornerOperation cornerOperationFromString:opString];
} else if ([op isEqualToString:CHAIN]) {
operation = [ChainOperation chainOperationFromString:opString];
} else if ([op isEqualToString:LAYOUT]) {
operation = [LayoutOperation layoutOperationFromString:opString];
} else if ([op isEqualToString:FOCUS]) {
operation = [FocusOperation focusOperationFromString:opString];
} else if ([op isEqualToString:SNAPSHOT]) {
operation = [SnapshotOperation snapshotOperationFromString:opString];
} else if ([op isEqualToString:ACTIVATE_SNAPSHOT]) {
operation = [ActivateSnapshotOperation activateSnapshotOperationFromString:opString];
} else if ([op isEqualToString:DELETE_SNAPSHOT]) {
operation = [DeleteSnapshotOperation deleteSnapshotOperationFromString:opString];
} else if ([op isEqualToString:HINT]) {
operation = [HintOperation hintOperationFromString:opString];
} else if ([op isEqualToString:SWITCH]) {
operation = [SwitchOperation switchOperationFromString:opString];
} else if ([op isEqualToString:GRID]) {
operation = [GridOperation gridOperationFromString:opString];
} else if ([op isEqualToString:SEQUENCE]) {
operation = [SequenceOperation sequenceOperationFromString:opString];
} else if ([op isEqualToString:TOGGLE] || [op isEqualToString:SHOW] || [op isEqualToString:HIDE]) {
operation = [VisibilityOperation visibilityOperationFromString:opString];
} else if ([op isEqualToString:RELAUNCH]) {
operation = [RelaunchOperation relaunchOperationFromString:opString];
} else if ([op isEqualToString:SHELL]) {
operation = [ShellOperation shellOperationFromString:opString];
} else if ([op isEqualToString:UNDO]) {
operation = [UndoOperation undoOperationFromString:opString];
} else {
SlateLogger(@"ERROR: Unrecognized operation '%@'", opString);
@throw([NSException exceptionWithName:@"Unrecognized Operation" reason:[NSString stringWithFormat:@"Unrecognized operation '%@' in '%@'", op, opString] userInfo:nil]);
}
if (operation != nil) { [operation setOpName:op]; }
return operation;
}
+ (id)operationWithName:(NSString *)op options:(NSDictionary *)options {
Operation *operation = nil;
if ([op isEqualToString:MOVE]) {
operation = [MoveOperation moveOperation];
} else if ([op isEqualToString:RESIZE]) {
operation = [ResizeOperation resizeOperation];
} else if ([op isEqualToString:PUSH]) {
operation = [PushOperation pushOperation];
} else if ([op isEqualToString:NUDGE]) {
operation = [NudgeOperation nudgeOperation];
} else if ([op isEqualToString:THROW]) {
operation = [ThrowOperation throwOperation];
} else if ([op isEqualToString:CORNER]) {
operation = [CornerOperation cornerOperation];
} else if ([op isEqualToString:CHAIN]) {
operation = [ChainOperation chainOperation];
} else if ([op isEqualToString:LAYOUT]) {
operation = [LayoutOperation layoutOperation];
} else if ([op isEqualToString:FOCUS]) {
operation = [FocusOperation focusOperation];
} else if ([op isEqualToString:SNAPSHOT]) {
operation = [SnapshotOperation snapshotOperation];
} else if ([op isEqualToString:ACTIVATE_SNAPSHOT]) {
operation = [ActivateSnapshotOperation activateSnapshotOperation];
} else if ([op isEqualToString:DELETE_SNAPSHOT]) {
operation = [DeleteSnapshotOperation deleteSnapshotOperation];
} else if ([op isEqualToString:HINT]) {
operation = [HintOperation hintOperation];
} else if ([op isEqualToString:SWITCH]) {
operation = [SwitchOperation switchOperation];
} else if ([op isEqualToString:GRID]) {
operation = [GridOperation gridOperation];
} else if ([op isEqualToString:SEQUENCE]) {
operation = [SequenceOperation sequenceOperation];
} else if ([op isEqualToString:TOGGLE] || [op isEqualToString:SHOW] || [op isEqualToString:HIDE]) {
operation = [VisibilityOperation visibilityOperation];
} else if ([op isEqualToString:RELAUNCH]) {
operation = [RelaunchOperation relaunchOperation];
} else if ([op isEqualToString:SHELL]) {
operation = [ShellOperation shellOperation];
} else if ([op isEqualToString:UNDO]) {
operation = [UndoOperation undoOperation];
} else {
SlateLogger(@"ERROR: Unrecognized operation '%@'", op);
@throw([NSException exceptionWithName:@"Unrecognized Operation" reason:[NSString stringWithFormat:@"Unrecognized operation '%@'", op] userInfo:nil]);
}
if (operation != nil) {
[operation setOpName:op];
[operation initOptions:options];
}
return operation;
}
+ (BOOL)isRepeatOnHoldOp:(NSString *)op {
NSArray *repeatOps = [[[SlateConfig getInstance] getConfig:REPEAT_ON_HOLD_OPS] componentsSeparatedByString:COMMA];
for (NSInteger i = 0; i < [repeatOps count]; i++) {
if ([op isEqualToString:[repeatOps objectAtIndex:i]]) {
return YES;
}
}
return NO;
}
@end
================================================
FILE: Slate/PushOperation.h
================================================
//
// PushOperation.h
// Slate
//
// Created by Jigish Patel on 1/20/13.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "MoveOperation.h"
@interface PushOperation : MoveOperation
+ (id)pushOperation;
+ (id)pushOperationFromString:(NSString *)pushOperation;
@end
================================================
FILE: Slate/PushOperation.m
================================================
//
// PushOperation.m
// Slate
//
// Created by Jigish Patel on 1/20/13.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "PushOperation.h"
#import "StringTokenizer.h"
#import "SlateLogger.h"
#import "Constants.h"
@implementation PushOperation
- (NSArray *)requiredOptions {
return [NSArray arrayWithObjects:OPT_DIRECTION, nil];
}
- (void)afterEvalOptions {
NSString *direction = [[self options] objectForKey:OPT_DIRECTION];
NSString *style = [[self options] objectForKey:OPT_STYLE];
if (style == nil) { style = NONE; }
NSString *screen = [[self options] objectForKey:OPT_SCREEN];
if (screen == nil) { screen = REF_CURRENT_SCREEN; }
[self setMonitor:screen];
[self setDimensions:[[ExpressionPoint alloc] initWithX:@"windowSizeX" y:@"windowSizeY"]];
if ([direction isEqualToString:TOP] || [direction isEqualToString:UP]) {
if ([style isEqualToString:CENTER]) {
[self setTopLeft:[[ExpressionPoint alloc] initWithX:@"screenOriginX+(screenSizeX-windowSizeX)/2" y:@"screenOriginY"]];
} else if ([style isEqualToString:BAR]) {
[self setTopLeft:[[ExpressionPoint alloc] initWithX:@"screenOriginX" y:@"screenOriginY"]];
[self setDimensions:[[ExpressionPoint alloc] initWithX:@"screenSizeX" y:@"windowSizeY"]];
} else if ([style hasPrefix:BAR_RESIZE_WITH_VALUE]) {
NSString *resizeExpression = [[style componentsSeparatedByString:COLON] objectAtIndex:1];
[self setTopLeft:[[ExpressionPoint alloc] initWithX:@"screenOriginX" y:@"screenOriginY"]];
[self setDimensions:[[ExpressionPoint alloc] initWithX:@"screenSizeX" y:resizeExpression]];
} else if ([style isEqualToString:NONE]) {
[self setTopLeft:[[ExpressionPoint alloc] initWithX:@"windowTopLeftX" y:@"screenOriginY"]];
} else {
SlateLogger(@"ERROR: Unrecognized style '%@'", style);
@throw([NSException exceptionWithName:@"Unrecognized Style" reason:[NSString stringWithFormat:@"Unrecognized style '%@'", style] userInfo:nil]);
}
} else if ([direction isEqualToString:BOTTOM] || [direction isEqualToString:DOWN]) {
if ([style isEqualToString:CENTER]) {
[self setTopLeft:[[ExpressionPoint alloc] initWithX:@"screenOriginX+(screenSizeX-windowSizeX)/2" y:@"screenOriginY+screenSizeY-windowSizeY"]];
} else if ([style isEqualToString:BAR]) {
[self setTopLeft:[[ExpressionPoint alloc] initWithX:@"screenOriginX" y:@"screenOriginY+screenSizeY-windowSizeY"]];
[self setDimensions:[[ExpressionPoint alloc] initWithX:@"screenSizeX" y:@"windowSizeY"]];
} else if ([style hasPrefix:BAR_RESIZE_WITH_VALUE]) {
NSString *resizeExpression = [[style componentsSeparatedByString:COLON] objectAtIndex:1];
[self setTopLeft:[[ExpressionPoint alloc] initWithX:@"screenOriginX" y:[NSString stringWithFormat:@"screenOriginY+screenSizeY-(%@)", resizeExpression]]];
[self setDimensions:[[ExpressionPoint alloc] initWithX:@"screenSizeX" y:resizeExpression]];
} else if ([style isEqualToString:NONE]) {
[self setTopLeft:[[ExpressionPoint alloc] initWithX:@"windowTopLeftX" y:@"screenOriginY+screenSizeY-windowSizeY"]];
} else {
SlateLogger(@"ERROR: Unrecognized style '%@'", style);
@throw([NSException exceptionWithName:@"Unrecognized Style" reason:[NSString stringWithFormat:@"Unrecognized style '%@'", style] userInfo:nil]);
}
} else if ([direction isEqualToString:LEFT]) {
if ([style isEqualToString:CENTER]) {
[self setTopLeft:[[ExpressionPoint alloc] initWithX:@"screenOriginX" y:@"screenOriginY+(screenSizeY-windowSizeY)/2"]];
} else if ([style isEqualToString:BAR]) {
[self setTopLeft:[[ExpressionPoint alloc] initWithX:@"screenOriginX" y:@"screenOriginY"]];
[self setDimensions:[[ExpressionPoint alloc] initWithX:@"windowSizeX" y:@"screenSizeY"]];
} else if ([style hasPrefix:BAR_RESIZE_WITH_VALUE]) {
NSString *resizeExpression = [[style componentsSeparatedByString:COLON] objectAtIndex:1];
[self setTopLeft:[[ExpressionPoint alloc] initWithX:@"screenOriginX" y:@"screenOriginY"]];
[self setDimensions:[[ExpressionPoint alloc] initWithX:resizeExpression y:@"screenSizeY"]];
} else if ([style isEqualToString:NONE]) {
[self setTopLeft:[[ExpressionPoint alloc] initWithX:@"screenOriginX" y:@"windowTopLeftY"]];
} else {
SlateLogger(@"ERROR: Unrecognized style '%@'", style);
@throw([NSException exceptionWithName:@"Unrecognized Style" reason:[NSString stringWithFormat:@"Unrecognized style '%@'", style] userInfo:nil]);
}
} else if ([direction isEqualToString:RIGHT]) {
if ([style isEqualToString:CENTER]) {
[self setTopLeft:[[ExpressionPoint alloc] initWithX:@"screenOriginX+screenSizeX-windowSizeX" y:@"screenOriginY+(screenSizeY-windowSizeY)/2"]];
} else if ([style isEqualToString:BAR]) {
[self setTopLeft:[[ExpressionPoint alloc] initWithX:@"screenOriginX+screenSizeX-windowSizeX" y:@"screenOriginY"]];
[self setDimensions:[[ExpressionPoint alloc] initWithX:@"windowSizeX" y:@"screenSizeY"]];
} else if ([style hasPrefix:BAR_RESIZE_WITH_VALUE]) {
NSString *resizeExpression = [[style componentsSeparatedByString:COLON] objectAtIndex:1];
[self setTopLeft:[[ExpressionPoint alloc] initWithX:[NSString stringWithFormat:@"screenOriginX+screenSizeX-(%@)", resizeExpression] y:@"screenOriginY"]];
[self setDimensions:[[ExpressionPoint alloc] initWithX:resizeExpression y:@"screenSizeY"]];
} else if ([style isEqualToString:NONE]) {
[self setTopLeft:[[ExpressionPoint alloc] initWithX:@"screenOriginX+screenSizeX-windowSizeX" y:@"windowTopLeftY"]];
} else {
SlateLogger(@"ERROR: Unrecognized style '%@'", style);
@throw([NSException exceptionWithName:@"Unrecognized Style" reason:[NSString stringWithFormat:@"Unrecognized style '%@'", style] userInfo:nil]);
}
} else {
SlateLogger(@"ERROR: Unrecognized direction '%@'", direction);
@throw([NSException exceptionWithName:@"Unrecognized Direction" reason:[NSString stringWithFormat:@"Unrecognized direction '%@'", direction] userInfo:nil]);
}
}
+ (id)pushOperation {
return [[PushOperation alloc] init];
}
+ (id)pushOperationFromString:(NSString *)pushOperation {
// push
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:pushOperation into:tokens];
if ([tokens count] < 2) {
SlateLogger(@"ERROR: Invalid Parameters '%@'", pushOperation);
@throw([NSException exceptionWithName:@"Invalid Parameters" reason:[NSString stringWithFormat:@"Invalid Parameters in '%@'. Push operations require the following format: 'push direction [optional:style]'", pushOperation] userInfo:nil]);
}
NSString *direction = [tokens objectAtIndex:1];
NSString *dimensions = @"windowSizeX;windowSizeY";
NSString *topLeft = nil;
NSString *style = NONE;
if ([tokens count] >= 3) {
style = [tokens objectAtIndex:2];
}
if ([direction isEqualToString:TOP] || [direction isEqualToString:UP]) {
if ([style isEqualToString:CENTER]) {
topLeft = @"screenOriginX+(screenSizeX-windowSizeX)/2;screenOriginY";
} else if ([style isEqualToString:BAR]) {
topLeft = @"screenOriginX;screenOriginY";
dimensions = @"screenSizeX;windowSizeY";
} else if ([style hasPrefix:BAR_RESIZE_WITH_VALUE]) {
NSString *resizeExpression = [[style componentsSeparatedByString:COLON] objectAtIndex:1];
topLeft = @"screenOriginX;screenOriginY";
dimensions = [@"screenSizeX;" stringByAppendingString:resizeExpression];
} else if ([style isEqualToString:NONE]) {
topLeft = @"windowTopLeftX;screenOriginY";
} else {
SlateLogger(@"ERROR: Unrecognized style '%@'", style);
@throw([NSException exceptionWithName:@"Unrecognized Style" reason:[NSString stringWithFormat:@"Unrecognized style '%@' in '%@'", style, pushOperation] userInfo:nil]);
}
} else if ([direction isEqualToString:BOTTOM] || [direction isEqualToString:DOWN]) {
if ([style isEqualToString:CENTER]) {
topLeft = @"screenOriginX+(screenSizeX-windowSizeX)/2;screenOriginY+screenSizeY-windowSizeY";
} else if ([style isEqualToString:BAR]) {
topLeft = @"screenOriginX;screenOriginY+screenSizeY-windowSizeY";
dimensions = @"screenSizeX;windowSizeY";
} else if ([style hasPrefix:BAR_RESIZE_WITH_VALUE]) {
NSString *resizeExpression = [[style componentsSeparatedByString:COLON] objectAtIndex:1];
topLeft = [@"screenOriginX;screenOriginY+screenSizeY-" stringByAppendingString:resizeExpression];
dimensions = [@"screenSizeX;" stringByAppendingString:resizeExpression];
} else if ([style isEqualToString:NONE]) {
topLeft = @"windowTopLeftX;screenOriginY+screenSizeY-windowSizeY";
} else {
SlateLogger(@"ERROR: Unrecognized style '%@'", style);
@throw([NSException exceptionWithName:@"Unrecognized Style" reason:[NSString stringWithFormat:@"Unrecognized style '%@' in '%@'", style, pushOperation] userInfo:nil]);
}
} else if ([direction isEqualToString:LEFT]) {
if ([style isEqualToString:CENTER]) {
topLeft = @"screenOriginX;screenOriginY+(screenSizeY-windowSizeY)/2";
} else if ([style isEqualToString:BAR]) {
topLeft = @"screenOriginX;screenOriginY";
dimensions = @"windowSizeX;screenSizeY";
} else if ([style hasPrefix:BAR_RESIZE_WITH_VALUE]) {
NSString *resizeExpression = [[style componentsSeparatedByString:COLON] objectAtIndex:1];
topLeft = @"screenOriginX;screenOriginY";
dimensions = [resizeExpression stringByAppendingString:@",screenSizeY"];
} else if ([style isEqualToString:NONE]) {
topLeft = @"screenOriginX;windowTopLeftY";
} else {
SlateLogger(@"ERROR: Unrecognized style '%@'", style);
@throw([NSException exceptionWithName:@"Unrecognized Style" reason:[NSString stringWithFormat:@"Unrecognized style '%@' in '%@'", style, pushOperation] userInfo:nil]);
}
} else if ([direction isEqualToString:RIGHT]) {
if ([style isEqualToString:CENTER]) {
topLeft = @"screenOriginX+screenSizeX-windowSizeX;screenOriginY+(screenSizeY-windowSizeY)/2";
} else if ([style isEqualToString:BAR]) {
topLeft = @"screenOriginX+screenSizeX-windowSizeX;screenOriginY";
dimensions = @"windowSizeX;screenSizeY";
} else if ([style hasPrefix:BAR_RESIZE_WITH_VALUE]) {
NSString *resizeExpression = [[style componentsSeparatedByString:COLON] objectAtIndex:1];
topLeft = [[@"screenOriginX+screenSizeX-" stringByAppendingString:resizeExpression] stringByAppendingString:@";screenOriginY"];
dimensions = [resizeExpression stringByAppendingString:@";screenSizeY"];
} else if ([style isEqualToString:NONE]) {
topLeft = @"screenOriginX+screenSizeX-windowSizeX;windowTopLeftY";
} else {
SlateLogger(@"ERROR: Unrecognized style '%@'", style);
@throw([NSException exceptionWithName:@"Unrecognized Style" reason:[NSString stringWithFormat:@"Unrecognized style '%@' in '%@'", style, pushOperation] userInfo:nil]);
}
} else {
SlateLogger(@"ERROR: Unrecognized direction '%@'", direction);
@throw([NSException exceptionWithName:@"Unrecognized Direction" reason:[NSString stringWithFormat:@"Unrecognized direction '%@' in '%@'", direction, pushOperation] userInfo:nil]);
}
Operation *op = [[MoveOperation alloc] initWithTopLeft:topLeft dimensions:dimensions monitor:([tokens count] >=4 ? [tokens objectAtIndex:3] : REF_CURRENT_SCREEN)];
return op;
}
@end
================================================
FILE: Slate/RelaunchOperation.h
================================================
//
// RelaunchOperation.h
// Slate
//
// Created by Jigish Patel on 10/11/12.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "Operation.h"
@interface RelaunchOperation : Operation
+ (id)relaunchOperation;
+ (id)relaunchOperationFromString:(NSString *)unused;
@end
================================================
FILE: Slate/RelaunchOperation.m
================================================
//
// RelaunchOperation.m
// Slate
//
// Created by Jigish Patel on 10/11/12.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "RelaunchOperation.h"
#import "SlateLogger.h"
#import "SlateAppDelegate.h"
@implementation RelaunchOperation
- (id)init {
self = [super init];
return self;
}
- (BOOL)doOperation {
SlateLogger(@"----------------- Begin Relaunch Operation -----------------");
// We don't use the passed in AccessibilityWrapper or ScreenWrapper so they are nil. No need to waste time creating them here.
BOOL success = [self doOperationWithAccessibilityWrapper:nil screenWrapper:nil];
SlateLogger(@"----------------- End Relaunch Operation -----------------");
return success;
}
- (BOOL)doOperationWithAccessibilityWrapper:(AccessibilityWrapper *)iamnil screenWrapper:(ScreenWrapper *)iamalsonil {
[(SlateAppDelegate *)[NSApp delegate] relaunch];
return YES;
}
- (BOOL)testOperation {
return YES;
}
+ (id)relaunchOperation {
return [[RelaunchOperation alloc] init];
}
+ (id)relaunchOperationFromString:(NSString *)unused {
return [RelaunchOperation relaunchOperation];
}
@end
================================================
FILE: Slate/ResizeOperation.h
================================================
//
// ResizeOperation.h
// Slate
//
// Created by Jigish Patel on 5/26/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
#import "Operation.h"
@interface ResizeOperation : Operation {
@private
NSInteger anchor;
NSString *xResize;
NSString *yResize;
}
@property (assign) NSInteger anchor;
@property (copy) NSString *xResize;
@property (copy) NSString *yResize;
- (id)initWithAnchor:(NSString *)a xResize:(NSString *)x yResize:(NSString *)y;
- (NSPoint)getTopLeftWithCurrentWindow:(NSRect)cWindowRect newSize:(NSSize)nSize;
- (NSSize)getDimensionsWithCurrentWindow:(NSRect)cWindowRect screenWrapper:(ScreenWrapper *)sw;
- (NSInteger)resizeStringToInt:(NSString *)resize withValue:(NSInteger) val;
+ (id)resizeOperation;
+ (id)resizeOperationFromString:(NSString *)resizeOperation;
@end
================================================
FILE: Slate/ResizeOperation.m
================================================
//
// ResizeOperation.m
// Slate
//
// Created by Jigish Patel on 5/26/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "AccessibilityWrapper.h"
#import "Constants.h"
#import "ResizeOperation.h"
#import "ScreenWrapper.h"
#import "SlateConfig.h"
#import "StringTokenizer.h"
#import "SlateLogger.h"
@implementation ResizeOperation
@synthesize anchor;
@synthesize xResize;
@synthesize yResize;
- (id)init {
self = [super init];
if (self) {
[self setAnchor:ANCHOR_TOP_LEFT];
[self setXResize:@"+0"];
[self setYResize:@"+0"];
}
return self;
}
- (id)initWithAnchor:(NSString *)a xResize:(NSString *)x yResize:(NSString *)y {
self = [self init];
if (self) {
if ([a isEqualToString:TOP_LEFT]) {
[self setAnchor:ANCHOR_TOP_LEFT];
} else if ([a isEqualToString:TOP_RIGHT]) {
[self setAnchor:ANCHOR_TOP_RIGHT];
} else if ([a isEqualToString:BOTTOM_LEFT]) {
[self setAnchor:ANCHOR_BOTTOM_LEFT];
} else if ([a isEqualToString:BOTTOM_RIGHT]) {
[self setAnchor:ANCHOR_BOTTOM_RIGHT];
} else {
SlateLogger(@"ERROR: Unrecognized anchor '%@'", a);
return nil;
}
[self setXResize:x];
[self setYResize:y];
}
return self;
}
- (BOOL)doOperationWithAccessibilityWrapper:(AccessibilityWrapper *)aw screenWrapper:(ScreenWrapper *)sw {
BOOL success = NO;
[self evalOptionsWithAccessibilityWrapper:aw screenWrapper:sw];
NSPoint cTopLeft = [aw getCurrentTopLeft];
NSSize cSize = [aw getCurrentSize];
NSRect cWindowRect = NSMakeRect(cTopLeft.x, cTopLeft.y, cSize.width, cSize.height);
NSSize nSize = [self getDimensionsWithCurrentWindow:cWindowRect screenWrapper:sw];
if (!NSEqualSizes(cSize, nSize)) {
success = [aw resizeWindow:nSize];
NSSize realNewSize = [aw getCurrentSize];
NSPoint nTopLeft = [self getTopLeftWithCurrentWindow:cWindowRect newSize:realNewSize];
success = [aw moveWindow:nTopLeft] && success;
}
return success;
}
- (BOOL)doOperation {
SlateLogger(@"----------------- Begin Resize Operation -----------------");
AccessibilityWrapper *aw = [[AccessibilityWrapper alloc] init];
ScreenWrapper *sw = [[ScreenWrapper alloc] init];
BOOL success = NO;
if ([aw inited]) success = [self doOperationWithAccessibilityWrapper:aw screenWrapper:sw];
SlateLogger(@"----------------- End Resize Operation -----------------");
return success;
}
- (BOOL)testOperation {
ScreenWrapper *sw = [[ScreenWrapper alloc] init];
NSRect cWindowRect = NSMakeRect(0, 0, 1000, 1000);
NSSize nSize = [self getDimensionsWithCurrentWindow:cWindowRect screenWrapper:sw];
[self getTopLeftWithCurrentWindow:cWindowRect newSize:nSize];
return YES;
}
- (NSPoint)getTopLeftWithCurrentWindow:(NSRect)cWindowRect newSize:(NSSize)nSize {
NSPoint cTopLeft = cWindowRect.origin;
NSSize cSize = cWindowRect.size;
if (anchor == ANCHOR_TOP_LEFT) {
return cTopLeft;
} else if (anchor == ANCHOR_TOP_RIGHT) {
NSInteger x = cTopLeft.x + cSize.width - nSize.width;
NSInteger y = cTopLeft.y;
return NSMakePoint(x,y);
} else if (anchor == ANCHOR_BOTTOM_LEFT) {
NSInteger x = cTopLeft.x;
NSInteger y = cTopLeft.y + cSize.height - nSize.height;
return NSMakePoint(x,y);
} else if (anchor == ANCHOR_BOTTOM_RIGHT) {
NSInteger x = cTopLeft.x + cSize.width - nSize.width;
NSInteger y = cTopLeft.y + cSize.height - nSize.height;
return NSMakePoint(x,y);
}
return NSMakePoint(0,0);
}
// Assumes well-formed resize +100 or -10%
- (NSInteger)resizeStringToInt:(NSString *)resize withValue:(NSInteger) val {
NSInteger sign = [resize hasPrefix:MINUS] ? -1 : 1;
NSString *magnitude = [resize stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:EMPTY];
if ([magnitude hasSuffix:PERCENT]) {
magnitude = [magnitude stringByReplacingOccurrencesOfString:PERCENT withString:EMPTY];
return (sign * val * [magnitude integerValue] / 100);
} else {
return (sign * [magnitude integerValue]);
}
}
- (NSSize)getDimensionsWithCurrentWindow:(NSRect)cWindowRect screenWrapper:(ScreenWrapper *)sw {
NSSize cSize = cWindowRect.size;
NSInteger sizeX = cSize.width;
NSInteger sizeY = cSize.height;
NSString *resizePercentOf = [[SlateConfig getInstance] getConfig:RESIZE_PERCENT_OF];
if ([resizePercentOf isEqualToString:SCREEN_SIZE]) {
NSInteger screenId = [sw getScreenId:REF_CURRENT_SCREEN windowRect:cWindowRect];
if (![sw screenExists:screenId]) {
return cSize;
}
NSSize screenSize = [sw convertScreenVisibleRectToWindowCoords:screenId].size;
sizeX = screenSize.width;
sizeY = screenSize.height;
}
NSInteger dimX = cSize.width + [self resizeStringToInt:xResize withValue:sizeX];
NSInteger dimY = cSize.height + [self resizeStringToInt:yResize withValue:sizeY];
return NSMakeSize(dimX,dimY);
}
- (void)parseOption:(NSString *)name value:(NSString *)value {
// all options should be strings
if (value == nil) { return; }
if (![value isKindOfClass:[NSString class]]) {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", name, value] userInfo:nil]);
}
if ([name isEqualToString:OPT_WIDTH]) {
[self setXResize:value];
} else if ([name isEqualToString:OPT_HEIGHT]) {
[self setYResize:value];
} else if ([name isEqualToString:OPT_ANCHOR]) {
if ([value isEqualToString:TOP_LEFT]) {
[self setAnchor:ANCHOR_TOP_LEFT];
} else if ([value isEqualToString:TOP_RIGHT]) {
[self setAnchor:ANCHOR_TOP_RIGHT];
} else if ([value isEqualToString:BOTTOM_LEFT]) {
[self setAnchor:ANCHOR_BOTTOM_LEFT];
} else if ([value isEqualToString:BOTTOM_RIGHT]) {
[self setAnchor:ANCHOR_BOTTOM_RIGHT];
} else {
SlateLogger(@"ERROR: Unrecognized anchor '%@'", value);
@throw([NSException exceptionWithName:@"Unrecognized Anchor" reason:[NSString stringWithFormat:@"ERROR: Unrecognized anchor '%@'", value] userInfo:nil]);
}
}
}
+ (id)resizeOperation {
return [[ResizeOperation alloc] init];
}
+ (id)resizeOperationFromString:(NSString *)resizeOperation {
// resize
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:resizeOperation into:tokens];
if ([tokens count] < 3) {
SlateLogger(@"ERROR: Invalid Parameters '%@'", resizeOperation);
@throw([NSException exceptionWithName:@"Invalid Parameters" reason:[NSString stringWithFormat:@"Invalid Parameters in '%@'. Resize operations require the following format: 'resize resizeX resizeY [optional:anchor]'", resizeOperation] userInfo:nil]);
}
NSString *anchor = TOP_LEFT;
if ([tokens count] >= 4) {
anchor = [tokens objectAtIndex:3];
}
Operation *op = [[ResizeOperation alloc] initWithAnchor:anchor xResize:[tokens objectAtIndex:1] yResize:[tokens objectAtIndex:2]];
return op;
}
@end
================================================
FILE: Slate/RunningApplications.h
================================================
//
// RunningApplications.h
// Slate
//
// Created by Jigish Patel on 3/22/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface RunningApplications : NSObject {
NSMutableArray *apps;
NSMutableDictionary *appNameToApp;
NSMutableArray *windows;
NSMutableDictionary *appToWindows;
NSMutableDictionary *titleToWindow;
NSInteger nextWindowNumber;
NSMutableArray *unusedWindowNumbers;
NSMutableDictionary *pidToObserver;
}
@property NSMutableArray *apps;
@property NSMutableDictionary *appNameToApp;
@property NSMutableArray *windows;
@property NSMutableDictionary *appToWindows;
@property NSMutableDictionary *titleToWindow;
@property (assign) NSInteger nextWindowNumber;
@property NSMutableArray *unusedWindowNumbers;
@property NSMutableDictionary *pidToObserver;
+ (RunningApplications *)getInstance;
+ (BOOL)isAppSelectable:(NSRunningApplication *)app;
+ (NSRunningApplication *)focusedApp;
- (void)pruneWindows;
- (NSArray *)windowIdsForTitle:(NSString *)title;
@end
================================================
FILE: Slate/RunningApplications.m
================================================
//
// RunningApplications.m
// Slate
//
// Created by Jigish Patel on 3/22/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "RunningApplications.h"
#import "SlateLogger.h"
#import "AccessibilityWrapper.h"
#import "Constants.h"
#import "JSController.h"
#import "JSWindowWrapper.h"
#import "JSApplicationWrapper.h"
#import "SlateConfig.h"
@implementation RunningApplications
@synthesize apps, appNameToApp, windows, appToWindows, titleToWindow, unusedWindowNumbers, nextWindowNumber, pidToObserver;
static RunningApplications *_instance = nil;
+ (RunningApplications *)getInstance {
@synchronized([RunningApplications class]) {
if (!_instance)
_instance = [[[RunningApplications class] alloc] init];
return _instance;
}
}
+ (BOOL)isAppSelectable:(NSRunningApplication *)app {
return [app activationPolicy] == NSApplicationActivationPolicyRegular;
}
static NSDictionary *eventNameDict = nil;
static NSString *prettyifyEventName(NSString *event) {
if (eventNameDict == nil) {
eventNameDict = [NSDictionary dictionaryWithObjectsAndKeys:@"windowClosed", [NSString stringWithFormat:@"%@", kAXUIElementDestroyedNotification],
@"windowMoved", [NSString stringWithFormat:@"%@", kAXMovedNotification],
@"windowResized", [NSString stringWithFormat:@"%@", kAXResizedNotification],
@"windowOpened", [NSString stringWithFormat:@"%@", kAXWindowCreatedNotification],
@"windowFocused", [NSString stringWithFormat:@"%@", kAXFocusedWindowChangedNotification],
@"windowTitleChanged", [NSString stringWithFormat:@"%@", kAXTitleChangedNotification],
@"appClosed", NSWorkspaceDidTerminateApplicationNotification,
@"appOpened", NSWorkspaceDidLaunchApplicationNotification,
@"appHidden", NSWorkspaceDidHideApplicationNotification,
@"appUnhidden", NSWorkspaceDidUnhideApplicationNotification,
@"appActivated", NSWorkspaceDidActivateApplicationNotification,
@"appDeactivated", NSWorkspaceDidDeactivateApplicationNotification, nil];
}
return [eventNameDict objectForKey:event];
}
static void runWindowJSCallbacks(AXUIElementRef element, CFStringRef notification) {
// Run any js callbacks
AccessibilityWrapper *openedWindow = [[AccessibilityWrapper alloc] initWithApp:[AccessibilityWrapper applicationForElement:element] window:element];
NSString *eventName = prettyifyEventName([NSString stringWithFormat:@"%@", notification]);
[[JSController getInstance] runCallbacks:eventName
payload:[[JSWindowWrapper alloc] initWithAccessibilityWrapper:openedWindow screenWrapper:[[ScreenWrapper alloc] init]]];
}
static void windowChanged(AXObserverRef observer, AXUIElementRef element, CFStringRef notification, void *refcon) {
RunningApplications *ref = (__bridge RunningApplications *)refcon;
SlateLogger(@">> WINDOW CHANGED, %@ <<", notification);
if (CFStringCompare(notification, kAXUIElementDestroyedNotification, 0) == kCFCompareEqualTo) {
SlateLogger(@">> WINDOW DESTROYED <<");
AXObserverRemoveNotification(observer, element, kAXUIElementDestroyedNotification);
AXObserverRemoveNotification(observer, element, kAXMovedNotification);
AXObserverRemoveNotification(observer, element, kAXResizedNotification);
[ref pruneWindows];
}
NSString *eventName = prettyifyEventName([NSString stringWithFormat:@"%@", notification]);
[[JSController getInstance] runCallbacks:eventName
payload:[[JSApplicationWrapper alloc] initWithRunningApplication:[NSRunningApplication runningApplicationWithProcessIdentifier:[AccessibilityWrapper processIdentifierOfUIElement:element]] screenWrapper:[[ScreenWrapper alloc] init]]];
}
static void registerForWindowDeath(AXUIElementRef element, RunningApplications *ref) {
// register for death event
AXError err;
AXObserverRef observer;
err = AXObserverCreate([AccessibilityWrapper processIdentifierOfUIElement:element], windowChanged, &observer);
err = AXObserverAddNotification(observer, element, kAXUIElementDestroyedNotification, (__bridge void *)ref);
if (err != kAXErrorSuccess) {
AXObserverRemoveNotification(observer, element, kAXUIElementDestroyedNotification);
return;
}
if ([[SlateConfig getInstance] getBoolConfig:JS_RECEIVE_MOVE_EVENT]) {
err = AXObserverAddNotification(observer, element, kAXMovedNotification, (__bridge void *)ref);
if (err != kAXErrorSuccess) {
AXObserverRemoveNotification(observer, element, kAXMovedNotification);
return;
}
}
if ([[SlateConfig getInstance] getBoolConfig:JS_RECEIVE_RESIZE_EVENT]) {
err = AXObserverAddNotification(observer, element, kAXResizedNotification, (__bridge void *)ref);
if (err != kAXErrorSuccess) {
AXObserverRemoveNotification(observer, element, kAXResizedNotification);
return;
}
}
CFRunLoopAddSource ([[NSRunLoop currentRunLoop] getCFRunLoop], AXObserverGetRunLoopSource(observer), kCFRunLoopDefaultMode);
}
// WINDOW INFO:
// 0 = title
// 1 = NSRunningApplication
// 2 = window number
static void windowCreated(pid_t currPID, AXUIElementRef element, RunningApplications *ref) {
SlateLogger(@">> WINDOW CREATED <<");
[ref pruneWindows];
NSString *title = [AccessibilityWrapper getTitle:element];
if (title == nil || [EMPTY isEqualToString:title]) return; // skip empty title windows because they are invisible
NSMutableArray *windowInfo = [NSMutableArray array];
[windowInfo addObject:title];
[windowInfo addObject:[NSRunningApplication runningApplicationWithProcessIdentifier:currPID]];
if ([[ref unusedWindowNumbers] count] > 0) {
[windowInfo addObject:[[ref unusedWindowNumbers] objectAtIndex:0]];
[[ref unusedWindowNumbers] removeObjectAtIndex:0];
} else {
[windowInfo addObject:[NSNumber numberWithInteger:[ref nextWindowNumber]]];
[ref setNextWindowNumber:[ref nextWindowNumber]+1];
}
[[ref windows] insertObject:windowInfo atIndex:0];
if ([[ref titleToWindow] objectForKey:title]) {
[[[ref titleToWindow] objectForKey:title] addObject:windowInfo];
} else {
[[ref titleToWindow] setObject:[NSMutableArray arrayWithObject:windowInfo] forKey:title];
}
[[[ref appToWindows] objectForKey:[NSNumber numberWithInteger:currPID]] addObject:windowInfo];
registerForWindowDeath(element, ref);
}
+ (NSRunningApplication *)focusedApp {
NSWorkspace *sharedWorkspace = [NSWorkspace sharedWorkspace];
if ([sharedWorkspace respondsToSelector:@selector(frontmostApplication)]) {
return [sharedWorkspace frontmostApplication];
} else {
AccessibilityWrapper *aw = [[AccessibilityWrapper alloc] init];
return [NSRunningApplication runningApplicationWithProcessIdentifier:[aw processIdentifier]];
}
}
static void windowCallback(AXObserverRef observer, AXUIElementRef element, CFStringRef notification, void *refcon) {
SlateLogger(@">>> %@ for %@", notification, [AccessibilityWrapper getRole:element]);
if (![AccessibilityWrapper isWindow:element]) return;
RunningApplications *ref = (__bridge RunningApplications *)refcon;
pid_t currPID = [AccessibilityWrapper processIdentifierOfUIElement:element];
// Title Changed, update windows
if (CFStringCompare(notification, kAXTitleChangedNotification, 0) == kCFCompareEqualTo) {
SlateLogger(@">>> TITLE CHANGED TO %@", [AccessibilityWrapper getTitle:element]);
NSNumber *appPID = [NSNumber numberWithInteger:[AccessibilityWrapper processIdentifierOfUIElement:element]];
NSMutableArray *oldWindowsInApp = [[[ref appToWindows] objectForKey:appPID] mutableCopy];
CFArrayRef windowsArr = [AccessibilityWrapper windowsInApp:AXUIElementCreateApplication([appPID intValue])];
// Remove all windows if app has no windows
if (!windowsArr || CFArrayGetCount(windowsArr) == 0) return;
if (oldWindowsInApp == nil || [oldWindowsInApp count] == 0) {
windowCreated(currPID, element, ref);
runWindowJSCallbacks(element, notification);
return;
}
// set up title counts
NSMutableDictionary *tmpTitleToCount = [NSMutableDictionary dictionary];
for (NSMutableArray *windowInfo in oldWindowsInApp) {
NSString *title = [windowInfo objectAtIndex:0];
if ([tmpTitleToCount objectForKey:title]) {
[tmpTitleToCount setObject:[NSNumber numberWithInteger:([[tmpTitleToCount objectForKey:title] integerValue]+1)] forKey:title];
} else {
[tmpTitleToCount setObject:[NSNumber numberWithInteger:1] forKey:title];
}
}
// figure out which title is new
NSString *newTitle = nil;
for (NSInteger i = 0; i < CFArrayGetCount(windowsArr); i++) {
AXUIElementRef window = CFArrayGetValueAtIndex(windowsArr, i);
NSString *currTitle = [AccessibilityWrapper getTitle:window];
NSNumber *currCount = [tmpTitleToCount objectForKey:currTitle];
if (currCount == nil || [currCount integerValue] == 0) {
newTitle = currTitle;
} else {
[tmpTitleToCount setObject:[NSNumber numberWithInteger:([currCount integerValue]-1)] forKey:currTitle];
}
}
// figure out which title is old
NSString *oldTitle = nil;
for (NSString *currTitle in [tmpTitleToCount allKeys]) {
NSNumber *currCount = [tmpTitleToCount objectForKey:currTitle];
if (currCount != nil && [currCount integerValue] > 0) {
oldTitle = currTitle;
break;
}
}
// out with the old and in with the new
if (oldTitle != nil && newTitle != nil) {
for (NSMutableArray *windowInfo in oldWindowsInApp) {
if ([[windowInfo objectAtIndex:0] isEqualToString:oldTitle]) {
[windowInfo removeObjectAtIndex:0];
[windowInfo insertObject:[AccessibilityWrapper getTitle:element] atIndex:0];
NSMutableArray *windowsForTitle = [[ref titleToWindow] objectForKey:oldTitle];
if (!windowsForTitle || [windowsForTitle count] == 0) continue;
if ([windowsForTitle count] == 1) {
[[ref titleToWindow] removeObjectForKey:oldTitle];
} else {
[windowsForTitle removeObject:windowInfo];
}
windowsForTitle = [[ref titleToWindow] objectForKey:[windowInfo objectAtIndex:0]];
if (!windowsForTitle) {
[[ref titleToWindow] setObject:[NSMutableArray arrayWithObject:windowInfo] forKey:[windowInfo objectAtIndex:0]];
} else {
[windowsForTitle insertObject:windowInfo atIndex:0];
[[ref titleToWindow] setObject:windowsForTitle forKey:[windowInfo objectAtIndex:0]];
}
[ref pruneWindows];
runWindowJSCallbacks(element, notification);
return;
}
}
}
[ref pruneWindows];
runWindowJSCallbacks(element, notification);
return;
}
// Focus Changed, update windows
if (CFStringCompare(notification, kAXFocusedWindowChangedNotification, 0) == kCFCompareEqualTo) {
[ref pruneWindows];
runWindowJSCallbacks(element, notification);
return;
}
// Window created, add to windows
windowCreated(currPID, element, ref);
runWindowJSCallbacks(element, notification);
SlateLogger(@">>> END %@ for %@", notification, [AccessibilityWrapper getRole:element]);
}
- (id)init {
self = [super init];
if (self) {
unusedWindowNumbers = [NSMutableArray array];
nextWindowNumber = 0;
apps = [NSMutableArray array];
appNameToApp = [NSMutableDictionary dictionary];
windows = [NSMutableArray array];
appToWindows = [NSMutableDictionary dictionary];
pidToObserver = [NSMutableDictionary dictionary];
titleToWindow = [NSMutableDictionary dictionary];
SlateLogger(@"------------------ Checking Running Applications ------------------");
NSArray *appsArr = [[NSWorkspace sharedWorkspace] runningApplications];
NSRunningApplication *currentApp = [NSRunningApplication currentApplication];
for (NSRunningApplication *app in appsArr) {
if ([RunningApplications isAppSelectable:app]) {
SlateLogger(@" Selectable: %@", [app localizedName]);
[apps addObject:app];
[appNameToApp setObject:app forKey:[app localizedName]];
SlateLogger(@" I see application '%@'", [app localizedName]);
// check for windows
NSNumber *appPID = [NSNumber numberWithInteger:[app processIdentifier]];
[appToWindows setObject:[NSMutableArray array] forKey:appPID];
AXUIElementRef appRef = AXUIElementCreateApplication([app processIdentifier]);
CFArrayRef windowsArr = [AccessibilityWrapper windowsInApp:appRef];
if (windowsArr && CFArrayGetCount(windowsArr) > 0) {
SlateLogger(@" Has Windows: %@", [app localizedName]);
for (NSInteger i = 0; i < CFArrayGetCount(windowsArr); i++) {
NSMutableArray *windowInfo = [NSMutableArray array];
AXUIElementRef window = CFArrayGetValueAtIndex(windowsArr, i);
NSString *title = [AccessibilityWrapper getTitle:window];
if (title == nil || [EMPTY isEqualToString:title]) continue; // skip empty title windows because they are invisible
SlateLogger(@" Title: %@", title);
[windowInfo addObject:title];
[windowInfo addObject:app];
[windowInfo addObject:[NSNumber numberWithInteger:nextWindowNumber]];
if ([self isCurrentApplication:app] && [AccessibilityWrapper isMainWindow:window]) {
[windows insertObject:windowInfo atIndex:0];
[[appToWindows objectForKey:appPID] insertObject:windowInfo atIndex:0];
} else if ([AccessibilityWrapper isMainWindow:window]) {
[windows addObject:windowInfo];
[[appToWindows objectForKey:appPID] insertObject:windowInfo atIndex:0];
} else {
[windows addObject:windowInfo];
[[appToWindows objectForKey:appPID] addObject:windowInfo];
}
NSMutableArray *windowsForTitle = [titleToWindow objectForKey:title];
if (!windowsForTitle) {
[titleToWindow setObject:[NSMutableArray arrayWithObject:windowInfo] forKey:title];
} else {
[windowsForTitle addObject:windowInfo];
[titleToWindow setObject:windowsForTitle forKey:title];
}
registerForWindowDeath(window, self);
nextWindowNumber++;
}
}
if ([self isCurrentApplication:app]) {
currentApp = app;
}
AXError err;
AXUIElementRef sendingApp = AXUIElementCreateApplication([app processIdentifier]);
AXObserverRef observer;
err = AXObserverCreate([app processIdentifier], windowCallback, &observer);
err = AXObserverAddNotification(observer, sendingApp, kAXWindowCreatedNotification, (__bridge void *)self);
err = AXObserverAddNotification(observer, sendingApp, kAXFocusedWindowChangedNotification, (__bridge void *)self);
err = AXObserverAddNotification(observer, sendingApp, kAXTitleChangedNotification, (__bridge void *)self);
if (err != kAXErrorSuccess) {
AXObserverRemoveNotification(observer, sendingApp, kAXWindowCreatedNotification);
AXObserverRemoveNotification(observer, sendingApp, kAXFocusedWindowChangedNotification);
AXObserverRemoveNotification(observer, sendingApp, kAXTitleChangedNotification);
} else {
CFRunLoopAddSource ([[NSRunLoop currentRunLoop] getCFRunLoop], AXObserverGetRunLoopSource(observer), kCFRunLoopDefaultMode);
[pidToObserver setObject:[NSValue valueWithPointer:observer] forKey:[NSNumber numberWithInteger:[app processIdentifier]]];
}
}
}
SlateLogger(@"CURRENT APP = '%@'", [currentApp localizedName]);
[self bringAppToFront:currentApp];
SlateLogger(@"------------------ Done Checking Running Applications ------------------");
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(applicationKilled:) name:NSWorkspaceDidTerminateApplicationNotification object:nil];
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(applicationLaunched:) name:NSWorkspaceDidLaunchApplicationNotification object:nil];
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(applicationDeactivated:) name:NSWorkspaceDidHideApplicationNotification object:nil];
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(applicationActivated:) name:NSWorkspaceDidUnhideApplicationNotification object:nil];
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(applicationDeactivated:) name:NSWorkspaceDidDeactivateApplicationNotification object:nil];
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(applicationActivated:) name:NSWorkspaceDidActivateApplicationNotification object:nil];
}
return self;
}
- (NSRunningApplication *)currentApplication {
// ownsMenuBar is 10.7+ only
if ([[NSRunningApplication currentApplication] respondsToSelector:@selector(ownsMenuBar)]) {
NSArray *appsArr = [[NSWorkspace sharedWorkspace] runningApplications];
for (NSRunningApplication *app in appsArr) {
if ([RunningApplications isAppSelectable:app]) {
if ([app ownsMenuBar]) return app;
}
}
}
return [NSRunningApplication currentApplication];
}
- (BOOL)isCurrentApplication:(NSRunningApplication *)app {
// ownsMenuBar is 10.7+ only
if ([[NSRunningApplication currentApplication] respondsToSelector:@selector(ownsMenuBar)]) {
return [app ownsMenuBar];
}
return app == [NSRunningApplication currentApplication];
}
- (void)pruneWindows {
NSArray *windowsCopy = [windows copy];
for (NSArray *windowInfo in windowsCopy) {
if (![apps containsObject:[windowInfo objectAtIndex:1]]) {
[self removeWindow:windowInfo];
SlateLogger(@" PRUNE Because app died");
}
}
for (NSRunningApplication *app in apps) {
NSNumber *appPID = [NSNumber numberWithInteger:[app processIdentifier]];
NSMutableArray *oldWindowsInApp = [[appToWindows objectForKey:appPID] mutableCopy];
CFArrayRef windowsArr = [AccessibilityWrapper windowsInRunningApp:app];
// Remove all windows if app has no windows
if (!windowsArr || CFArrayGetCount(windowsArr) == 0) {
NSArray *windowsToRemove = [[appToWindows objectForKey:appPID] copy];
for (NSArray *windowInfo in windowsToRemove) {
[self removeWindow:windowInfo];
SlateLogger(@" PRUNE Because app has no windows");
}
continue;
}
// Remove windows that are no longer open. No need to add windows that we havn't seen because technically our callback should catch it.
for (NSArray *windowInfo in oldWindowsInApp) {
BOOL found = NO;
for (NSInteger i = 0; i < CFArrayGetCount(windowsArr); i++) {
AXUIElementRef window = CFArrayGetValueAtIndex(windowsArr, i);
if ([[windowInfo objectAtIndex:0] isEqualToString:[AccessibilityWrapper getTitle:window]]) {
found = YES;
}
}
if (!found) {
[self removeWindow:windowInfo];
SlateLogger(@" PRUNE Because title mismatch");
}
}
}
}
- (void)removeWindow:(NSArray *)windowInfo {
[unusedWindowNumbers addObject:[windowInfo objectAtIndex:2]];
[windows removeObject:windowInfo];
NSNumber *appPID = [NSNumber numberWithInteger:[[windowInfo objectAtIndex:1] processIdentifier]];
if ([appToWindows objectForKey:appPID] != nil) {
[[appToWindows objectForKey:appPID] removeObject:windowInfo];
}
NSMutableArray *windowsForTitle = [titleToWindow objectForKey:[windowInfo objectAtIndex:0]];
if (!windowsForTitle || [windowsForTitle count] <= 1) {
[titleToWindow removeObjectForKey:[windowInfo objectAtIndex:0]];
} else {
[windowsForTitle removeObject:windowInfo];
[titleToWindow setObject:windowsForTitle forKey:[windowInfo objectAtIndex:0]];
}
}
/*- (void)notificationRecieved:(id)notification {
SlateLogger(@"NOTE RECIEVED: %@", [notification name]);
}*/
- (void)applicationActivated:(id)notification {
SlateLogger(@"Activated: %@", [notification name]);
NSRunningApplication *activatedApp = [[notification userInfo] objectForKey:NSWorkspaceApplicationKey];
if ([[activatedApp localizedName] isEqualToString:@"Slate"]) return;
[self bringAppToFront:activatedApp];
[self pruneWindows];
NSString *eventName = prettyifyEventName([notification name]);
[[JSController getInstance] runCallbacks:eventName
payload:[[JSApplicationWrapper alloc] initWithRunningApplication:activatedApp screenWrapper:[[ScreenWrapper alloc] init]]];
}
- (void)applicationDeactivated:(id)notification {
SlateLogger(@"Deactivated: %@", [notification name]);
NSRunningApplication *deactivatedApp = [[notification userInfo] objectForKey:NSWorkspaceApplicationKey];
NSString *eventName = prettyifyEventName([notification name]);
[[JSController getInstance] runCallbacks:eventName
payload:[[JSApplicationWrapper alloc] initWithRunningApplication:deactivatedApp screenWrapper:[[ScreenWrapper alloc] init]]];
}
- (void)applicationLaunched:(id)notification {
SlateLogger(@"Launched: %@", [notification name]);
NSRunningApplication *launchedApp = [[notification userInfo] objectForKey:NSWorkspaceApplicationKey];
if ([[launchedApp localizedName] isEqualToString:@"Slate"]) return;
NSNumber *appPID = [NSNumber numberWithInteger:[launchedApp processIdentifier]];
[appToWindows setObject:[NSMutableArray array] forKey:appPID];
AXError err;
AXUIElementRef sendingApp = AXUIElementCreateApplication([launchedApp processIdentifier]);
AXObserverRef observer;
err = AXObserverCreate([launchedApp processIdentifier], windowCallback, &observer);
if (err != kAXErrorSuccess) return;
err = AXObserverAddNotification(observer, sendingApp, kAXWindowCreatedNotification, (__bridge void *)self);
err = AXObserverAddNotification(observer, sendingApp, kAXFocusedWindowChangedNotification, (__bridge void *)self);
err = AXObserverAddNotification(observer, sendingApp, kAXTitleChangedNotification, (__bridge void *)self);
if (err != kAXErrorSuccess) {
AXObserverRemoveNotification(observer, AXUIElementCreateApplication([launchedApp processIdentifier]), kAXWindowCreatedNotification);
AXObserverRemoveNotification(observer, AXUIElementCreateApplication([launchedApp processIdentifier]), kAXFocusedWindowChangedNotification);
AXObserverRemoveNotification(observer, AXUIElementCreateApplication([launchedApp processIdentifier]), kAXTitleChangedNotification);
return;
}
CFRunLoopAddSource ([[NSRunLoop currentRunLoop] getCFRunLoop], AXObserverGetRunLoopSource(observer), kCFRunLoopDefaultMode);
[pidToObserver setObject:[NSValue valueWithPointer:observer] forKey:[NSNumber numberWithInteger:[launchedApp processIdentifier]]];
// add already created windows
CFArrayRef windowsArr = [AccessibilityWrapper windowsInApp:AXUIElementCreateApplication([launchedApp processIdentifier])];
if (windowsArr != NULL) {
for (NSInteger i = 0; i < CFArrayGetCount(windowsArr); i++) {
AXUIElementRef element = CFArrayGetValueAtIndex(windowsArr, i);
NSString *title = [AccessibilityWrapper getTitle:element];
if (title == nil || [EMPTY isEqualToString:title]) continue; // skip empty title windows because they are invisible
NSMutableArray *windowInfo = [NSMutableArray array];
[windowInfo addObject:title];
[windowInfo addObject:[NSRunningApplication runningApplicationWithProcessIdentifier:[launchedApp processIdentifier]]];
if ([[self unusedWindowNumbers] count] > 0) {
[windowInfo addObject:[[self unusedWindowNumbers] objectAtIndex:0]];
[[self unusedWindowNumbers] removeObjectAtIndex:0];
} else {
[windowInfo addObject:[NSNumber numberWithInteger:[self nextWindowNumber]]];
[self setNextWindowNumber:[self nextWindowNumber]+1];
}
[[self windows] insertObject:windowInfo atIndex:0];
NSMutableArray *windowsForTitle = [[self titleToWindow] objectForKey:title];
if (!windowsForTitle) {
[[self titleToWindow] setObject:[NSMutableArray arrayWithObject:windowInfo] forKey:title];
} else {
[windowsForTitle addObject:windowInfo];
[[self titleToWindow] setObject:windowsForTitle forKey:title];
}
[[[self appToWindows] objectForKey:[NSNumber numberWithInteger:[launchedApp processIdentifier]]] addObject:windowInfo];
}
}
[self bringAppToFront:launchedApp];
[self pruneWindows];
NSString *eventName = prettyifyEventName([notification name]);
[[JSController getInstance] runCallbacks:eventName
payload:[[JSApplicationWrapper alloc] initWithRunningApplication:launchedApp screenWrapper:[[ScreenWrapper alloc] init]]];
}
- (void)applicationKilled:(id)notification {
SlateLogger(@"Killed: %@", [notification name]);
NSRunningApplication *app = [[notification userInfo] objectForKey:NSWorkspaceApplicationKey];
[apps removeObject:app];
[appNameToApp removeObjectForKey:[app localizedName]];
NSNumber *appPID = [NSNumber numberWithInteger:[app processIdentifier]];
[appToWindows removeObjectForKey:appPID];
AXObserverRemoveNotification([[pidToObserver objectForKey:[NSNumber numberWithInteger:[app processIdentifier]]] pointerValue], AXUIElementCreateApplication([app processIdentifier]), kAXWindowCreatedNotification);
AXObserverRemoveNotification([[pidToObserver objectForKey:[NSNumber numberWithInteger:[app processIdentifier]]] pointerValue], AXUIElementCreateApplication([app processIdentifier]), kAXFocusedWindowChangedNotification);
AXObserverRemoveNotification([[pidToObserver objectForKey:[NSNumber numberWithInteger:[app processIdentifier]]] pointerValue], AXUIElementCreateApplication([app processIdentifier]), kAXTitleChangedNotification);
[pidToObserver removeObjectForKey:[NSNumber numberWithInteger:[app processIdentifier]]];
[self pruneWindows];
[self bringAppToFront:[self currentApplication]];
NSString *eventName = prettyifyEventName([notification name]);
[[JSController getInstance] runCallbacks:eventName
payload:[[JSApplicationWrapper alloc] initWithRunningApplication:app screenWrapper:[[ScreenWrapper alloc] init]]];
}
- (void)bringAppToFront:(NSRunningApplication *)app {
if (![RunningApplications isAppSelectable:app]) return;
[apps removeObject:app];
[apps insertObject:app atIndex:0];
#ifdef DEBUG
SlateLogger(@" New App Order:");
for (NSRunningApplication *app in apps) {
SlateLogger(@" %@", [app localizedName]);
}
#endif
}
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id *)stackbuf count:(NSUInteger)len {
return [apps countByEnumeratingWithState:state objects:stackbuf count:len];
}
- (NSArray *)windowIdsForTitle:(NSString *)title {
NSArray *windowsForTitle = [titleToWindow objectForKey:title];
if (windowsForTitle == nil || [windowsForTitle count] == 0) return nil;
NSMutableArray *windowIdsForTitle = [NSMutableArray array];
for(NSArray *windowInfo in windowsForTitle) {
[windowIdsForTitle addObject:[windowInfo objectAtIndex:2]];
}
return windowIdsForTitle;
}
@end
================================================
FILE: Slate/ScreenState.h
================================================
//
// ScreenState.h
// Slate
//
// Created by Jigish Patel on 6/19/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface ScreenState : NSObject {
@private
id layout;
NSInteger type;
NSInteger count;
NSMutableArray *resolutions;
}
@property id layout;
@property (assign) NSInteger type;
@property (assign) NSInteger count;
@property NSMutableArray *resolutions;
- (id)initWithString:(NSString *)state;
- (id)initWithConfig:(id)screenConfig layout:(id)_layout;
@end
================================================
FILE: Slate/ScreenState.m
================================================
//
// ScreenState.m
// Slate
//
// Created by Jigish Patel on 6/19/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "Constants.h"
#import "ScreenState.h"
#import "StringTokenizer.h"
@implementation ScreenState
@synthesize layout;
@synthesize type;
@synthesize count;
@synthesize resolutions;
- (id)init {
self = [super init];
if (self) {
[self setLayout:nil];
[self setType:TYPE_UNKNOWN];
[self setCount:TYPE_UNKNOWN];
[self setResolutions:nil];
}
return self;
}
- (id)initWithString:(NSString *)state {
// defaultLayout
self = [self init];
if (self) {
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:state into:tokens];
if ([tokens count] < 3) {
return nil;
}
[self setLayout:[tokens objectAtIndex:1]];
// Parse screen-setup
// count:NUMBER
// resolutions:WIDTHxHEIGHT;...
NSArray *screenSetup = [[tokens objectAtIndex:2] componentsSeparatedByString:COLON];
if ([screenSetup count] < 2) {
return nil;
}
if ([[screenSetup objectAtIndex:0] isEqualToString:COUNT]) {
[self setCount:[[screenSetup objectAtIndex:1] integerValue]];
[self setType:TYPE_COUNT];
} else if ([[screenSetup objectAtIndex:0] isEqualToString:RESOLUTIONS]) {
[self setResolutions:[NSMutableArray arrayWithArray:[[screenSetup objectAtIndex:1] componentsSeparatedByCharactersInSet:
[NSCharacterSet characterSetWithCharactersInString:[SEMICOLON stringByAppendingString:COMMA]]]]];
[resolutions sortUsingSelector:@selector(compare:)];
[self setType:TYPE_RESOLUTIONS];
} else {
return nil;
}
}
return self;
}
- (id)initWithConfig:(id)screenConfig layout:(id)_layout {
self = [self init];
if (self) {
[self setLayout:_layout];
if ([screenConfig isKindOfClass:[NSValue class]] || [screenConfig isKindOfClass:[NSNumber class]]) {
[self setCount:[screenConfig integerValue]];
[self setType:TYPE_COUNT];
} else if ([screenConfig isKindOfClass:[NSArray class]]) {
[self setResolutions:[screenConfig mutableCopy]];
[resolutions sortUsingSelector:@selector(compare:)];
[self setType:TYPE_RESOLUTIONS];
} else {
return nil;
}
}
return self;
}
@end
================================================
FILE: Slate/ScreenWrapper.h
================================================
//
// ScreenWrapper.h
// Slate
//
// Created by Jigish Patel on 6/17/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface ScreenWrapper : NSObject {
NSArray *screens;
}
@property NSArray *screens;
+ (void)updateLeftToRightToDefault;
+ (void)updateLeftToRightToDefault:(NSArray *)theScreens;
+ (void)updateScreenResolutions;
+ (void)updateScreenResolutions:(NSArray *)theScreens;
+ (void)updateStatics;
+ (BOOL)hasScreenConfigChanged;
- (id)initWithScreens:(NSArray *)theScreens; // Used for testing
- (NSInteger)getScreenCount;
- (NSRect)getScreenVisibleRect:(NSInteger)screenId;
- (NSRect)getScreenVisibleRectForRef:(NSInteger)screenRefId;
- (NSRect)getScreenRect:(NSInteger)screenId;
- (NSRect)getScreenRectForRef:(NSInteger)screenRefId;
- (NSInteger)convertDefaultOrderToLeftToRightOrder:(NSInteger)screenId;
- (NSInteger)convertDefaultOrderToLeftToRightOrderIfNeeded:(NSInteger)screenId;
- (void)getScreenResolutionStrings:(NSMutableArray *)strings;
- (NSInteger)getScreenRefId:(NSString *)screenRef windowRect:(NSRect)window;
- (NSInteger)getScreenId:(NSString *)screenRef windowRect:(NSRect)window;
- (NSInteger)getScreenRefIdForRect:(NSRect)rect;
- (NSInteger)getScreenIdForRect:(NSRect)rect;
- (NSInteger)getScreenRefIdForPoint:(NSPoint)point;
- (NSInteger)getScreenIdForPoint:(NSPoint)point;
- (BOOL)isMainScreen:(NSInteger)screenID;
- (BOOL)isMainScreenRef:(NSInteger)screenID;
- (BOOL)isRectOffScreen:(NSRect)rect;
- (BOOL)screenExists:(NSInteger)screenId;
- (NSDictionary *)getScreenAndWindowValues:(NSInteger)screenId window:(NSRect)cWindowRect newSize:(NSSize)nSize;
- (NSRect)convertScreenRectToWindowCoords:(NSInteger)screenId;
+ (NSRect)convertScreenRectToWindowCoords:(NSScreen *)screen withReference:(NSScreen *)refScreen;
- (NSRect)convertScreenVisibleRectToWindowCoords:(NSInteger)screenId;
- (NSPoint)convertTopLeftToScreenRelative:(NSPoint)topLeft screen:(NSInteger)screenId;
@end
================================================
FILE: Slate/ScreenWrapper.m
================================================
//
// ScreenWrapper.m
// Slate
//
// Created by Jigish Patel on 6/17/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "Constants.h"
#import "MathUtils.h"
#import "ScreenWrapper.h"
#import "SlateConfig.h"
#import "SlateLogger.h"
static NSMutableArray *leftToRightToDefault = nil;
static NSString *resolutions = nil;
@implementation ScreenWrapper
@synthesize screens;
+ (void)initialize {
if (!leftToRightToDefault) {
leftToRightToDefault = [[NSMutableArray alloc] init];
[ScreenWrapper updateStatics];
}
}
+ (void)updateLeftToRightToDefault {
[ScreenWrapper updateLeftToRightToDefault:[NSScreen screens]];
}
+ (void)updateLeftToRightToDefault:(NSArray *)theScreens {
leftToRightToDefault = [[NSMutableArray alloc] initWithCapacity:[theScreens count]];
NSArray *sortedByXThenY = [theScreens sortedArrayUsingComparator: ^(id screen1, id screen2) {
NSRect screen1Rect = [ScreenWrapper convertScreenRectToWindowCoords:screen1 withReference:[theScreens objectAtIndex:ID_MAIN_SCREEN]];
NSRect screen2Rect = [ScreenWrapper convertScreenRectToWindowCoords:screen2 withReference:[theScreens objectAtIndex:ID_MAIN_SCREEN]];
if (screen1Rect.origin.x > screen2Rect.origin.x) {
return (NSComparisonResult)NSOrderedDescending;
}
if (screen1Rect.origin.x < screen2Rect.origin.x) {
return (NSComparisonResult)NSOrderedAscending;
}
if (screen1Rect.origin.y < screen2Rect.origin.y) {
return (NSComparisonResult)NSOrderedDescending;
}
if (screen1Rect.origin.y > screen2Rect.origin.y) {
return (NSComparisonResult)NSOrderedAscending;
}
return (NSComparisonResult)NSOrderedSame;
}];
for (NSInteger i = 0; i < [sortedByXThenY count]; i++) {
NSNumber *defaultId = nil;
for (NSInteger j = 0; j < [theScreens count]; j++) {
if ([sortedByXThenY objectAtIndex:i] == [theScreens objectAtIndex:j]) {
defaultId = [NSNumber numberWithInteger:j];
break;
}
}
[leftToRightToDefault addObject:defaultId];
}
}
+ (void)updateScreenResolutions {
[ScreenWrapper updateScreenResolutions:[NSScreen screens]];
}
+ (void)updateScreenResolutions:(NSArray *)theScreens {
resolutions = @"";
for (NSScreen *screen in theScreens) {
NSRect screenRect =[screen frame];
resolutions = [resolutions stringByAppendingFormat:@"%i%@%i,",(int)screenRect.size.width,X,(int)screenRect.size.height];
}
}
+ (void)updateStatics {
SlateLogger(@"-- updateStatics");
[ScreenWrapper updateLeftToRightToDefault];
[ScreenWrapper updateScreenResolutions];
}
+ (BOOL)hasScreenConfigChanged {
NSString *oldResolutions = [NSString stringWithString:resolutions];
NSArray *oldLeftToRight = [NSArray arrayWithArray:leftToRightToDefault];
[ScreenWrapper updateStatics];
if (![oldResolutions isEqualToString:resolutions]) return YES;
if ([oldLeftToRight count] != [leftToRightToDefault count]) return YES;
for (NSInteger i = 0; i < [oldLeftToRight count]; i++) {
if ([[oldLeftToRight objectAtIndex:i] integerValue] != [[leftToRightToDefault objectAtIndex:i] integerValue]) return YES;
}
return NO;
}
- (id)init {
return [self initWithScreens:[NSScreen screens]];
}
- (id)initWithScreens:(NSArray *)theScreens {
self = [super init];
if (self) {
[self setScreens:theScreens];
[ScreenWrapper updateLeftToRightToDefault:theScreens];
}
return self;
}
- (NSInteger)getScreenCount {
return [screens count];
}
- (void)getScreenResolutionStrings:(NSMutableArray *)strings {
for (NSInteger i = 0; i < [screens count]; i++) {
NSRect screenRect = [self convertScreenRectToWindowCoords:i];
NSString *resolution = [NSString stringWithFormat:@"%i%@%i",(int)screenRect.size.width,X,(int)screenRect.size.height];
SlateLogger(@"Adding resolution: %@",resolution);
[strings addObject:resolution];
}
}
- (NSRect)getScreenVisibleRect:(NSInteger)screenId {
return [self convertScreenVisibleRectToWindowCoords:screenId];
}
- (NSRect)getScreenVisibleRectForRef:(NSInteger)screenRefId {
NSInteger screenId = [[SlateConfig getInstance] getBoolConfig:ORDER_SCREENS_LEFT_TO_RIGHT] ? [[leftToRightToDefault objectAtIndex:screenRefId] integerValue] : screenRefId;
return [self getScreenVisibleRect:screenId];
}
- (NSRect)getScreenRect:(NSInteger)screenId {
return [self convertScreenRectToWindowCoords:screenId];
}
- (NSRect)getScreenRectForRef:(NSInteger)screenRefId {
NSInteger screenId = [[SlateConfig getInstance] getBoolConfig:ORDER_SCREENS_LEFT_TO_RIGHT] ? [[leftToRightToDefault objectAtIndex:screenRefId] integerValue] : screenRefId;
return [self getScreenRect:screenId];
}
- (NSInteger)convertDefaultOrderToLeftToRightOrder:(NSInteger)screenId {
return [leftToRightToDefault indexOfObject:[NSNumber numberWithInteger:screenId]];
}
- (NSInteger)convertDefaultOrderToLeftToRightOrderIfNeeded:(NSInteger)screenId {
return [[SlateConfig getInstance] getBoolConfig:ORDER_SCREENS_LEFT_TO_RIGHT] ? [self convertDefaultOrderToLeftToRightOrder:screenId] : screenId;
}
- (NSInteger)getScreenRefId:(NSString *)screenRef windowRect:(NSRect)window {
// returns the external (not default ordering) screen ID
if ([[SlateConfig getInstance] getBoolConfig:ORDER_SCREENS_LEFT_TO_RIGHT]) {
return [self convertDefaultOrderToLeftToRightOrder:[self getScreenId:screenRef windowRect:window]];
} else {
return [self getScreenId:screenRef windowRect:window];
}
}
- (NSInteger)getScreenId:(NSString *)screenRef windowRect:(NSRect)window {
NSInteger screenId = ID_IGNORE_SCREEN;
NSInteger currentScreenId = [self getScreenIdForRect:window];
NSRect screenRect = [self convertScreenRectToWindowCoords:currentScreenId];
if ([screenRef rangeOfString:RIGHT].length > 0) { // Orientation Based
NSRect testRect = NSMakeRect(screenRect.origin.x+screenRect.size.width, screenRect.origin.y, 1, screenRect.size.height);
screenId = [self getScreenIdForRect:testRect];
} else if ([screenRef rangeOfString:LEFT].length > 0) {
NSRect testRect = NSMakeRect(screenRect.origin.x-1, screenRect.origin.y, 1, screenRect.size.height);
screenId = [self getScreenIdForRect:testRect];
} else if ([screenRef rangeOfString:ABOVE].length > 0 || [screenRef rangeOfString:UP].length > 0) {
NSRect testRect = NSMakeRect(screenRect.origin.x, screenRect.origin.y - 1, screenRect.size.width, 1);
screenId = [self getScreenIdForRect:testRect];
} else if ([screenRef rangeOfString:BELOW].length > 0 || [screenRef rangeOfString:UP].length > 0) {
NSRect testRect = NSMakeRect(screenRect.origin.x, screenRect.origin.y + screenRect.size.height, screenRect.size.width, 1);
screenId = [self getScreenIdForRect:testRect];
} else if ([screenRef rangeOfString:NEXT].length > 0) {
if (currentScreenId == [screens count] - 1)
screenId = ID_MAIN_SCREEN;
else
screenId = currentScreenId+1;
} else if ([screenRef rangeOfString:PREVIOUS].length > 0 || [screenRef rangeOfString:PREV].length > 0) {
if (currentScreenId == ID_MAIN_SCREEN)
screenId = [screens count] - 1;
else
screenId = currentScreenId-1;
} else if ([screenRef rangeOfString:X].length > 0) { // Resolution Based
NSArray *tokens = [screenRef componentsSeparatedByString:X];
if ([tokens count] < 2) return ID_IGNORE_SCREEN;
NSInteger width = [[tokens objectAtIndex:0] integerValue];
NSInteger height = [[tokens objectAtIndex:1] integerValue];
for (NSUInteger i = 0; i < [screens count]; i++) {
NSSize size = [self convertScreenRectToWindowCoords:i].size;
if (size.width == width && size.height == height) return i;
}
screenId = [[SlateConfig getInstance] getBoolConfig:DEFAULT_TO_CURRENT_SCREEN] ? ID_CURRENT_SCREEN : ID_IGNORE_SCREEN;
} else if ([screenRef rangeOfString:ORDERED].length > 0) { // Explicitly Ordered
NSArray *tokens = [screenRef componentsSeparatedByString:COLON];
if ([tokens count] < 2) return ID_IGNORE_SCREEN;
NSInteger leftToRightId = [[tokens objectAtIndex:1] integerValue];
screenId = (leftToRightId < ID_MAIN_SCREEN || leftToRightId > [screens count]) ? leftToRightId : [[leftToRightToDefault objectAtIndex:leftToRightId] integerValue];
} else {
NSInteger screenRefInt = [screenRef integerValue];
if (screenRefInt < ID_MAIN_SCREEN || screenRefInt >= [screens count]) {
screenId = screenRefInt;
} else {
screenId = [[SlateConfig getInstance] getBoolConfig:ORDER_SCREENS_LEFT_TO_RIGHT] ? [[leftToRightToDefault objectAtIndex:screenRefInt] integerValue] : screenRefInt;
}
}
SlateLogger(@"getScreenId for ref=[%@] current=[%ld] screen=[%ld]", screenRef, (long)currentScreenId, (long)screenId);
if (screenId == ID_CURRENT_SCREEN) {
return currentScreenId;
} else if (screenId < ID_MAIN_SCREEN) {
return ID_IGNORE_SCREEN;
} else if (ID_MAIN_SCREEN <= screenId && screenId < [screens count]) {
return screenId;
} else if ([[SlateConfig getInstance] getBoolConfig:DEFAULT_TO_CURRENT_SCREEN]) {
return currentScreenId;
} else {
return ID_IGNORE_SCREEN;
}
}
- (NSInteger)getScreenRefIdForRect:(NSRect)rect {
return [self convertDefaultOrderToLeftToRightOrder:[self getScreenIdForRect:rect]];
}
- (NSInteger)getScreenIdForRect:(NSRect)rect {
NSRect largestIntersection = NSZeroRect;
NSInteger screenIndex = ID_IGNORE_SCREEN;
for (NSInteger i = 0; i < [screens count]; i++) {
NSRect currentIntersection = NSIntersectionRect([self convertScreenRectToWindowCoords:i], rect);
if ([MathUtils isRect:currentIntersection biggerThan:largestIntersection]) {
largestIntersection = currentIntersection;
screenIndex = i;
}
}
return screenIndex;
}
- (NSInteger)getScreenRefIdForPoint:(NSPoint)point {
return [self convertDefaultOrderToLeftToRightOrder:[self getScreenIdForPoint:point]];
}
- (NSInteger)getScreenIdForPoint:(NSPoint)point {
for (NSInteger i = 0; i < [screens count]; i++) {
NSRect screen = [self convertScreenRectToWindowCoords:i];
if (screen.origin.x <= point.x && screen.origin.x+screen.size.width >= point.x &&
screen.origin.y <= point.y && screen.origin.y+screen.size.height >= point.y)
return i;
}
return -1;
}
- (BOOL)isMainScreen:(NSInteger)screenID {
return screenID == ID_MAIN_SCREEN;
}
- (BOOL)isMainScreenRef:(NSInteger)screenRefId {
NSInteger screenId = [[SlateConfig getInstance] getBoolConfig:ORDER_SCREENS_LEFT_TO_RIGHT] ? [[leftToRightToDefault objectAtIndex:screenRefId] integerValue] : screenRefId;
return [self isMainScreen:screenId];
}
- (BOOL)isRectOffScreen:(NSRect)rect {
// Check all corners to see if they are on a screen
// Top-left
if ([self getScreenIdForPoint:NSMakePoint(rect.origin.x, rect.origin.y)] < 0) return YES;
// Top-right
if ([self getScreenIdForPoint:NSMakePoint(rect.origin.x+rect.size.width, rect.origin.y)] < 0) return YES;
// Bottom-left
if ([self getScreenIdForPoint:NSMakePoint(rect.origin.x, rect.origin.y+rect.size.height)] < 0) return YES;
// Bottom-right
if ([self getScreenIdForPoint:NSMakePoint(rect.origin.x+rect.size.width, rect.origin.y+rect.size.height)] < 0) return YES;
return NO;
}
- (BOOL)screenExists:(NSInteger)screenId {
NSInteger count = ((NSInteger)[[NSScreen screens] count]);
return (ID_MAIN_SCREEN <= screenId && screenId < count) ? YES : NO;
}
- (NSDictionary *)getScreenAndWindowValues:(NSInteger)screenId window:(NSRect)cWindowRect newSize:(NSSize)nSize {
NSInteger originX = 0;
NSInteger originY = 0;
NSInteger sizeX = 0;
NSInteger sizeY = 0;
if ([self screenExists:screenId]) {
NSRect screenRect = [self convertScreenVisibleRectToWindowCoords:screenId];
sizeX = screenRect.size.width;
sizeY = screenRect.size.height;
originX = screenRect.origin.x;
originY = screenRect.origin.y;
} else {
return [NSDictionary dictionary];
}
NSSize cSize = cWindowRect.size;
NSPoint cTopLeft = cWindowRect.origin;
SlateLogger(@"screenOrigin:(%ld,%ld), screenSize:(%ld,%ld), windowSize:(%f,%f), windowTopLeft:(%f,%f)",(long)originX,
(long)originY,
(long)sizeX,
(long)sizeY,
cSize.width,
cSize.height,
cTopLeft.x,
cTopLeft.y);
return [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInteger:originX], SCREEN_ORIGIN_X,
[NSNumber numberWithInteger:originY], SCREEN_ORIGIN_Y,
[NSNumber numberWithInteger:sizeX], SCREEN_SIZE_X,
[NSNumber numberWithInteger:sizeY], SCREEN_SIZE_Y,
[NSNumber numberWithInteger:(NSInteger)cSize.width], WINDOW_SIZE_X,
[NSNumber numberWithInteger:(NSInteger)cSize.height], WINDOW_SIZE_Y,
[NSNumber numberWithInteger:(NSInteger)nSize.width], NEW_WINDOW_SIZE_X,
[NSNumber numberWithInteger:(NSInteger)nSize.height], NEW_WINDOW_SIZE_Y,
[NSNumber numberWithInteger:(NSInteger)cTopLeft.x], WINDOW_TOP_LEFT_X,
[NSNumber numberWithInteger:(NSInteger)cTopLeft.y], WINDOW_TOP_LEFT_Y, nil];
}
// The following three methods are the only methods that should contain the frame/visibleFrame calls. All other methods should fetch the frame
// and/or visibleFrame using these methods. This is due to the comment above flipYCoordinateOfRect.
- (NSRect)convertScreenRectToWindowCoords:(NSInteger)screenId {
if (screenId == ID_MAIN_SCREEN) {
return [[screens objectAtIndex:screenId] frame];
} else if ([self screenExists:screenId]) {
return [MathUtils flipYCoordinateOfRect:[[screens objectAtIndex:screenId] frame] withReference:[[screens objectAtIndex:ID_MAIN_SCREEN] frame]];
}
return NSZeroRect;
}
+ (NSRect)convertScreenRectToWindowCoords:(NSScreen *)screen withReference:(NSScreen *)refScreen {
if (screen) {
return [MathUtils flipYCoordinateOfRect:[screen frame] withReference:[refScreen frame]];
}
return NSZeroRect;
}
- (NSRect)convertScreenVisibleRectToWindowCoords:(NSInteger)screenId {
if (screenId == ID_MAIN_SCREEN) {
return [MathUtils flipYCoordinateOfRect:[[screens objectAtIndex:ID_MAIN_SCREEN] visibleFrame] withReference:[[screens objectAtIndex:ID_MAIN_SCREEN] frame]];
} else if ([self screenExists:screenId]) {
return [MathUtils flipYCoordinateOfRect:[[screens objectAtIndex:screenId] visibleFrame] withReference:[[screens objectAtIndex:ID_MAIN_SCREEN] frame]];
}
return NSZeroRect;
}
- (NSPoint)convertTopLeftToScreenRelative:(NSPoint)topLeft screen:(NSInteger)screenId {
NSRect screenRect = [self convertScreenVisibleRectToWindowCoords:screenId];
return NSMakePoint(topLeft.x-screenRect.origin.x, topLeft.y-screenRect.origin.y);
}
@end
================================================
FILE: Slate/SequenceOperation.h
================================================
//
// SequenceOperation.h
// Slate
//
// Created by Jigish Patel on 10/5/12.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "Operation.h"
@interface SequenceOperation : Operation {
NSArray *operations; // Array of arrays
}
@property NSArray *operations;
- (id)initWithArray:(NSArray *)opArray;
+ (id)sequenceOperation;
+ (id)sequenceOperationFromString:(NSString *)sequenceOperation;
@end
================================================
FILE: Slate/SequenceOperation.m
================================================
//
// SequenceOperation.m
// Slate
//
// Created by Jigish Patel on 10/5/12.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "SequenceOperation.h"
#import "SlateLogger.h"
#import "StringTokenizer.h"
#import "Constants.h"
#import "JSController.h"
#import
#import "JSOperation.h"
@implementation SequenceOperation
@synthesize operations;
- (id)init {
self = [super init];
if (self) {
[self setOperations:[NSArray array]];
}
return self;
}
- (id)initWithArray:(NSArray *)opArray {
self = [self init];
if (self) {
[self setOperations:opArray];
}
return self;
}
- (BOOL)doOperation {
SlateLogger(@"----------------- Begin Sequence Operation -----------------");
ScreenWrapper *sw = [[ScreenWrapper alloc] init];
BOOL success = [self doOperationWithAccessibilityWrapper:nil screenWrapper:sw];
SlateLogger(@"----------------- End Sequence Operation -----------------");
return success;
}
- (BOOL) doOperationWithAccessibilityWrapper:(AccessibilityWrapper *)iamnil screenWrapper:(ScreenWrapper *)sw {
for (NSInteger i = 0; i < [[self operations] count]; i++) {
AccessibilityWrapper *aw = [[AccessibilityWrapper alloc] init];
if (![aw inited]) return NO;
for (NSInteger j = 0; j < [[[self operations] objectAtIndex:i] count]; j++) {
[[[[self operations] objectAtIndex:i] objectAtIndex:j] doOperationWithAccessibilityWrapper:aw screenWrapper:sw];
}
}
return YES;
}
- (BOOL)testOperation {
BOOL success = YES;
for (NSInteger i = 0; i < [operations count]; i++) {
for (NSInteger op = 0; op < [[operations objectAtIndex:i] count]; op++) {
[[[operations objectAtIndex:i] objectAtIndex:op] testOperation];
}
}
return success;
}
- (NSArray *)requiredOptions {
return [NSArray arrayWithObject:OPT_OPERATIONS];
}
- (void)parseOption:(NSString *)_name value:(id)value {
if (value == nil) { return; }
if ([_name isEqualToString:OPT_OPERATIONS]) {
if (![value isKindOfClass:[NSArray class]]) {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", _name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", _name, value] userInfo:nil]);
return;
}
NSMutableArray *ops = [NSMutableArray array];
for (id key in value) {
if (![key isKindOfClass:[Operation class]] && ![key isKindOfClass:[NSArray class]] && ![key isKindOfClass:[WebScriptObject class]]) {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", _name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", _name, value] userInfo:nil]);
continue;
}
NSMutableArray *innerOps = [NSMutableArray array];
if ([key isKindOfClass:[WebScriptObject class]]) {
Operation *op = [JSOperation jsOperationWithFunction:key];
if (op == nil) {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", _name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", _name, value] userInfo:nil]);
continue;
}
[innerOps addObject:op];
} else if ([key isKindOfClass:[Operation class]]) {
[innerOps addObject:key];
} else if ([key isKindOfClass:[NSArray class]]) {
for (id innerKey in key) {
Operation *op = nil;
if ([innerKey isKindOfClass:[WebScriptObject class]]) {
op = [JSOperation jsOperationWithFunction:innerKey];
} else if ([innerKey isKindOfClass:[Operation class]]) {
op = innerKey;
}
if (op == nil) {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", _name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", _name, value] userInfo:nil]);
continue;
}
[innerOps addObject:op];
}
}
[ops addObject:innerOps];
}
[self setOperations:ops];
}
}
+ (id)sequenceOperation {
return [[SequenceOperation alloc] init];
}
+ (id)sequenceOperationFromString:(NSString *)sequenceOperation {
// sequence op[ (\||>) op]+
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:sequenceOperation into:tokens maxTokens:2];
if ([tokens count] < 2) {
SlateLogger(@"ERROR: Invalid Parameters '%@'", sequenceOperation);
@throw([NSException exceptionWithName:@"Invalid Parameters" reason:[NSString stringWithFormat:@"Invalid Parameters in '%@'. Sequence operations require the following format: 'chain op [(\\||>) op]+'", sequenceOperation] userInfo:nil]);
}
NSString *opsString = [tokens objectAtIndex:1];
NSArray *ops = [opsString componentsSeparatedByString:PIPE_PADDED];
NSMutableArray *opArray = [[NSMutableArray alloc] initWithCapacity:10];
for (NSInteger i = 0; i < [ops count]; i++) {
NSArray *sameWindowOps = [[ops objectAtIndex:i] componentsSeparatedByString:GREATER_THAN_PADDED];
NSMutableArray *sameWindowOpArray = [[NSMutableArray alloc] initWithCapacity:10];
for (NSInteger j = 0; j < [sameWindowOps count]; j++) {
Operation *op = [Operation operationFromString:[sameWindowOps objectAtIndex:j]];
if (op != nil) {
[sameWindowOpArray addObject:op];
} else {
SlateLogger(@"ERROR: Invalid Operation in Sequence: '%@'", [sameWindowOps objectAtIndex:j]);
@throw([NSException exceptionWithName:@"Invalid Operation in Sequence" reason:[NSString stringWithFormat:@"Invalid operation '%@' in sequence.", [sameWindowOps objectAtIndex:j]] userInfo:nil]);
}
}
[opArray addObject:sameWindowOpArray];
}
Operation *op = [[SequenceOperation alloc] initWithArray:opArray];
return op;
}
@end
================================================
FILE: Slate/ShellOperation.h
================================================
//
// ShellOperation.h
// Slate
//
// Created by Jigish Patel on 10/17/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "Operation.h"
@interface ShellOperation : Operation {
NSString *command;
NSArray *args;
NSString *currentPath;
BOOL waitForExit;
}
@property NSString *command;
@property NSArray *args;
@property NSString *currentPath;
@property BOOL waitForExit;
- (id)initWithCommand:(NSString *)theCommand args:(NSArray *)theArgs waitForExit:(BOOL)theWaitForExit currentPath:(NSString *)theCurrentPath;
+ (id)shellOperation;
+ (id)shellOperationFromString:(NSString *)shellOperation;
@end
================================================
FILE: Slate/ShellOperation.m
================================================
//
// ShellOperation.m
// Slate
//
// Created by Jigish Patel on 10/17/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "ShellOperation.h"
#import "Constants.h"
#import "SlateLogger.h"
#import "StringTokenizer.h"
#import "ShellUtils.h"
@implementation ShellOperation
@synthesize command, args, waitForExit, currentPath;
- (id)init {
self = [super init];
if (self) {
[self setCommand:@""];
[self setArgs:[NSArray array]];
[self setWaitForExit:NO];
[self setCurrentPath:nil];
}
return self;
}
- (id)initWithCommand:(NSString *)theCommand args:(NSArray *)theArgs waitForExit:(BOOL)theWaitForExit currentPath:(NSString *)theCurrentPath {
self = [super init];
if (self) {
[self setCommand:theCommand];
[self setArgs:theArgs];
[self setWaitForExit:theWaitForExit];
[self setCurrentPath:theCurrentPath];
}
return self;
}
- (BOOL)doOperation {
SlateLogger(@"----------------- Begin Shell Operation -----------------");
// We don't use the passed in AccessibilityWrapper or ScreenWrapper so they are nil. No need to waste time creating them here.
BOOL success = [self doOperationWithAccessibilityWrapper:nil screenWrapper:nil];
SlateLogger(@"----------------- End Shell Operation -----------------");
return success;
}
- (BOOL)doOperationWithAccessibilityWrapper:(AccessibilityWrapper *)iamnil screenWrapper:(ScreenWrapper *)iamalsonil {
[self evalOptionsWithAccessibilityWrapper:iamnil screenWrapper:iamalsonil];
NSTask *task = [ShellUtils run:[self command] args:[self args] wait:[self waitForExit] path:[self currentPath]];
return task != nil;
}
- (BOOL)testOperation {
return [ShellUtils commandExists:[self command]];
}
- (NSArray *)requiredOptions {
return [NSArray arrayWithObjects:OPT_COMMAND, nil];
}
- (void)parseOption:(NSString *)name value:(NSString *)value {
// all options should be strings
if (value == nil) { return; }
if ([name isEqualToString:OPT_COMMAND]) {
if (![value isKindOfClass:[NSString class]]) {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", name, value] userInfo:nil]);
}
NSMutableArray *commandAndArgsTokens = [NSMutableArray array];
[StringTokenizer tokenize:value into:commandAndArgsTokens];
if ([commandAndArgsTokens count] < 1) {
SlateLogger(@"ERROR: Invalid Shell Parameter '%@'", value);
@throw([NSException exceptionWithName:@"Invalid Shell Parameter" reason:[NSString stringWithFormat:@"Invalid Shell Parameter '%@'.", value] userInfo:nil]);
}
NSString *cmd = [commandAndArgsTokens objectAtIndex:0];
NSMutableArray *ars = [NSMutableArray array];
for (NSInteger i = 1; i < [commandAndArgsTokens count]; i++) {
[ars addObject:[commandAndArgsTokens objectAtIndex:i]];
}
[self setCommand:cmd];
[self setArgs:ars];
} else if ([name isEqualToString:OPT_WAIT]) {
if (![value isKindOfClass:[NSString class]] && ![value isKindOfClass:[NSValue class]] && ![value isKindOfClass:[NSNumber class]]) {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", name, value] userInfo:nil]);
}
[self setWaitForExit:[value boolValue]];
} else if ([name isEqualToString:OPT_PATH]) {
if (![value isKindOfClass:[NSString class]]) {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", name, value] userInfo:nil]);
}
[self setCurrentPath:[value stringByExpandingTildeInPath]];
}
}
+ (id)shellOperation {
return [[ShellOperation alloc] init];
}
+ (id)shellOperationFromString:(NSString *)shellOperation {
// shell [wait] 'command'
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:shellOperation into:tokens quoteChars:[NSCharacterSet characterSetWithCharactersInString:QUOTES]];
if ([tokens count] < 2) {
SlateLogger(@"ERROR: Invalid Parameters '%@'", shellOperation);
@throw([NSException exceptionWithName:@"Invalid Parameters" reason:[NSString stringWithFormat:@"Invalid Parameters in '%@'. Shell operations require the following format: shell [wait] 'command'", shellOperation] userInfo:nil]);
}
BOOL waitForExit = NO;
NSString *currentPath = nil;
for (NSInteger i = 1; i < [tokens count] - 1; i++) {
if ([[tokens objectAtIndex:i] isEqualToString:WAIT]) {
waitForExit = YES;
} else if ([[tokens objectAtIndex:i] hasPrefix:PATH]) {
currentPath = [[tokens objectAtIndex:i] stringByReplacingOccurrencesOfString:PATH withString:EMPTY];
if ([currentPath hasPrefix:TILDA]) {
currentPath = [currentPath stringByExpandingTildeInPath];
}
}
}
NSString *commandAndArgs = [tokens lastObject];
NSMutableArray *commandAndArgsTokens = [NSMutableArray array];
[StringTokenizer tokenize:commandAndArgs into:commandAndArgsTokens];
if ([commandAndArgsTokens count] < 1) {
SlateLogger(@"ERROR: Invalid Parameters '%@'", shellOperation);
@throw([NSException exceptionWithName:@"Invalid Parameters" reason:[NSString stringWithFormat:@"Invalid Parameters in '%@'. Shell operations require the following format: shell [wait] 'command'", shellOperation] userInfo:nil]);
}
NSString *command = [commandAndArgsTokens objectAtIndex:0];
NSMutableArray *args = [NSMutableArray array];
for (NSInteger i = 1; i < [commandAndArgsTokens count]; i++) {
[args addObject:[commandAndArgsTokens objectAtIndex:i]];
}
Operation *op = [[ShellOperation alloc] initWithCommand:command args:args waitForExit:waitForExit currentPath:currentPath];
return op;
}
@end
================================================
FILE: Slate/ShellUtils.h
================================================
//
// ShellUtils.h
// Slate
//
// Created by Jigish Patel on 10/17/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface ShellUtils : NSObject
+ (BOOL)commandExists:(NSString *)command;
+ (NSTask *)run:(NSString *)command args:(NSArray *)args wait:(BOOL)wait path:(NSString *)path;
+ (NSString *)run:(NSString *)commandAndArgs wait:(BOOL)wait path:(NSString *)path;
@end
================================================
FILE: Slate/ShellUtils.m
================================================
//
// ShellUtils.m
// Slate
//
// Created by Jigish Patel on 10/17/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "ShellUtils.h"
#import "Constants.h"
#import "SlateLogger.h"
#import "StringTokenizer.h"
@implementation ShellUtils
+ (BOOL)commandExists:(NSString *)command {
if (command == nil || [EMPTY isEqualToString:command]) return NO;
@try {
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath:@"/usr/bin/command"];
NSArray *arguments;
arguments = [NSArray arrayWithObjects:@"-v", command, nil];
[task setArguments:arguments];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput:pipe];
[task setStandardInput:[NSPipe pipe]];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
NSData *data;
data = [file readDataToEndOfFile];
NSString *string;
string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
if ([string isEqualToString:EMPTY]) {
return NO;
}
return YES;
} @catch (id ex) {
return NO;
}
}
+ (NSTask *)run:(NSString *)command args:(NSArray *)args wait:(BOOL)wait path:(NSString *)path {
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath:command];
[task setArguments:args];
if (path != nil) [task setCurrentDirectoryPath:path];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput:pipe];
[task setStandardError:pipe];
[task setStandardInput:[NSPipe pipe]];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
if (wait){
[task waitUntilExit];
SlateLogger(@"SHELL RESULT:");
SlateLogger([[NSString alloc] initWithData:[file readDataToEndOfFile] encoding:NSUTF8StringEncoding]);
}
return task;
}
+ (NSString *)run:(NSString *)commandAndArgs wait:(BOOL)wait path:(NSString *)path {
NSMutableArray *commandAndArgsTokens = [NSMutableArray array];
[StringTokenizer tokenize:commandAndArgs into:commandAndArgsTokens quoteChars:[NSCharacterSet characterSetWithCharactersInString:@"'\""]];
if ([commandAndArgsTokens count] < 1) {
SlateLogger(@"ERROR: Invalid Shell Parameter '%@'", commandAndArgs);
@throw([NSException exceptionWithName:@"Invalid Shell Parameter" reason:[NSString stringWithFormat:@"Invalid Shell Parameter '%@'.", commandAndArgs] userInfo:nil]);
}
NSString *command = [commandAndArgsTokens objectAtIndex:0];
NSMutableArray *args = [NSMutableArray array];
for (NSInteger i = 1; i < [commandAndArgsTokens count]; i++) {
[args addObject:[commandAndArgsTokens objectAtIndex:i]];
}
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath:[command stringByExpandingTildeInPath]];
[task setArguments:args];
if (path != nil) [task setCurrentDirectoryPath:[path stringByExpandingTildeInPath]];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput:pipe];
[task setStandardError:pipe];
[task setStandardInput:[NSPipe pipe]];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
if (!wait) return nil;
[task waitUntilExit];
NSData *data = [file readDataToEndOfFile];
NSString *res = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
SlateLogger(@"SHELL RESULT:");
SlateLogger(res);
return res;
}
@end
================================================
FILE: Slate/Slate-Info.plist
================================================
CFBundleDevelopmentRegionenCFBundleExecutable${EXECUTABLE_NAME}CFBundleIconFileicon.icnsCFBundleIdentifiercom.slate.${PRODUCT_NAME:rfc1034identifier}CFBundleInfoDictionaryVersion6.0CFBundleName${PRODUCT_NAME}CFBundlePackageTypeAPPLCFBundleShortVersionString1.0CFBundleSignature????CFBundleVersion1.0.25LSApplicationCategoryTypepublic.app-category.utilitiesLSBackgroundOnlyLSMinimumSystemVersion${MACOSX_DEPLOYMENT_TARGET}LSUIElementNSMainNibFileMainMenuNSPrincipalClassNSApplicationSUFeedURLhttps://www.ninjamonkeysoftware.com/slate/appcast.xml
================================================
FILE: Slate/Slate-Prefix.pch
================================================
//
// Prefix header for all source files of the 'Slate' target in the 'Slate' project
//
#ifdef __OBJC__
#import
#endif
================================================
FILE: Slate/SlateAppDelegate.h
================================================
//
// SlateAppDelegate.h
// Slate
//
// Created by Jigish Patel on 5/18/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
#import
@class SwitchOperation;
@class SnapshotOperation;
@class ActivateSnapshotOperation;
@class DeleteSnapshotOperation;
@class HintOperation;
@class Binding;
@class GridOperation;
@interface SlateAppDelegate : NSObject {
@private
IBOutlet NSMenu *statusMenu;
NSMenuItem *activateSnapshotItem;
NSMenuItem *launchOnLoginItem;
IBOutlet NSWindow *windowInfo;
IBOutlet NSWindow *configHelper;
IBOutlet NSTextView *configHelperTextView;
NSStatusItem *statusItem;
NSWindowController *windowInfoController;
NSWindowController *configHelperController;
HintOperation *currentHintOperation;
GridOperation *currentGridOperation;
Binding *currentSwitchBinding;
SnapshotOperation *menuSnapshotOperation;
SnapshotOperation *undoSnapshotOperation;
ActivateSnapshotOperation *menuActivateSnapshotOperation;
DeleteSnapshotOperation *undoDeleteSnapshotOperation;
NSInteger cmdTabBinding;
NSInteger cmdShiftTabBinding;
NSMutableDictionary *modalHotKeyRefs;
NSMutableDictionary *modalIdToKey;
NSString *currentModalKey;
NSMutableArray *currentModalHotKeyRefs;
BOOL hasUndoOperation;
}
@property HintOperation *currentHintOperation;
@property GridOperation *currentGridOperation;
@property Binding *currentSwitchBinding;
@property SnapshotOperation *menuSnapshotOperation;
@property SnapshotOperation *undoSnapshotOperation;
@property ActivateSnapshotOperation *menuActivateSnapshotOperation;
@property DeleteSnapshotOperation *undoDeleteSnapshotOperation;
@property (assign) NSInteger cmdTabBinding;
@property (assign) NSInteger cmdShiftTabBinding;
@property NSMutableDictionary *modalHotKeyRefs;
@property NSMutableDictionary *modalIdToKey;
@property NSString *currentModalKey;
@property NSMutableArray *currentModalHotKeyRefs;
@property (assign) BOOL hasUndoOperation;
- (IBAction)updateLaunchState;
- (IBAction)relaunch;
- (IBAction)currentWindowInfo;
- (IBAction)configurationHelper;
- (IBAction)aboutWindow;
- (void)loadConfig;
- (void)registerHotKeys;
- (void)createSnapshotOperations;
- (IBAction)takeSnapshot;
- (IBAction)activateSnapshot;
- (OSStatus)timerActivateBinding:(NSTimer *)timer;
- (void)resetModalKey;
- (OSStatus)activateBinding:(EventHotKeyID)hkCom isRepeat:(BOOL)isRepeat;
- (BOOL)isInLoginItems;
- (void)addToLoginItems;
- (void)deleteFromLoginItems;
- (void)setLaunchOnLoginItemStatus;
OSStatus OnHotKeyEvent(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData);
OSStatus OnHotKeyReleasedEvent(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData);
OSStatus OnModifiersChangedEvent(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData);
@end
================================================
FILE: Slate/SlateAppDelegate.m
================================================
//
// SlateAppDelegate.m
// Slate
//
// Created by Jigish Patel on 5/18/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "Constants.h"
#import "SlateAppDelegate.h"
#import "SlateConfig.h"
#import "JSController.h"
#import "Binding.h"
#import "HintOperation.h"
#import "SlateLogger.h"
#import "SnapshotList.h"
#import "SnapshotOperation.h"
#import "ActivateSnapshotOperation.h"
#import "DeleteSnapshotOperation.h"
#import "SwitchOperation.h"
#import "RunningApplications.h"
#import "GridOperation.h"
#import
@implementation SlateAppDelegate
@synthesize currentHintOperation, currentGridOperation, currentSwitchBinding, menuSnapshotOperation;
@synthesize menuActivateSnapshotOperation, cmdTabBinding, cmdShiftTabBinding, modalHotKeyRefs, modalIdToKey;
@synthesize currentModalKey, currentModalHotKeyRefs, undoSnapshotOperation, undoDeleteSnapshotOperation, hasUndoOperation;
static NSObject *timerLock = nil;
static NSObject *keyUpLock = nil;
static NSTimer *currentTimer = nil;
static EventHotKeyID currentHotKey;
static SlateAppDelegate *selfRef = nil;
static EventHandlerRef modifiersEvent;
- (IBAction)updateLaunchState {
if ([launchOnLoginItem state] == NSOnState) {
// currently on
[self deleteFromLoginItems];
[self setLaunchOnLoginItemStatus];
} else {
// currently off
[self addToLoginItems];
[self setLaunchOnLoginItemStatus];
}
}
- (IBAction)relaunch {
NSString *launcherSource = [[NSBundle bundleForClass:[SUUpdater class]] pathForResource:@"relaunch" ofType:@""];
NSString *launcherTarget = [NSTemporaryDirectory() stringByAppendingPathComponent:[launcherSource lastPathComponent]];
NSString *appPath = [[NSBundle mainBundle] bundlePath];
NSString *processID = [NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]];
[[NSFileManager defaultManager] removeItemAtPath:launcherTarget error:NULL];
[[NSFileManager defaultManager] copyItemAtPath:launcherSource toPath:launcherTarget error:NULL];
[NSTask launchedTaskWithLaunchPath:launcherTarget arguments:[NSArray arrayWithObjects:appPath, processID, nil]];
[NSApp terminate:self];
}
- (IBAction)currentWindowInfo {
[windowInfoController showWindow:windowInfo];
[windowInfo makeKeyAndOrderFront:NSApp];
[windowInfo setLevel:(NSScreenSaverWindowLevel - 1)];
}
- (IBAction)configurationHelper {
NSString *configFile = [@"~/.slate" stringByExpandingTildeInPath];
[configHelperTextView setFont:[NSFont fontWithName:@"Menlo" size:11]];
[configHelperTextView setString:[NSString stringWithContentsOfFile:[configFile stringByExpandingTildeInPath] encoding:NSUTF8StringEncoding error:nil]];
[configHelperController showWindow:configHelper];
[configHelper makeKeyAndOrderFront:NSApp];
[configHelper setLevel:(NSScreenSaverWindowLevel - 1)];
}
- (void)loadConfig {
[self setHasUndoOperation:NO];
[[SlateConfig getInstance] load];
}
- (void)registerHotKeys {
SlateLogger(@"Registering HotKeys...");
EventTypeSpec eventType;
EventTypeSpec eventReleasedType;
eventType.eventClass = kEventClassKeyboard;
eventType.eventKind = kEventHotKeyPressed;
eventReleasedType.eventClass = kEventClassKeyboard;
eventReleasedType.eventKind = kEventHotKeyReleased;
InstallEventHandler(GetEventMonitorTarget(), &OnHotKeyEvent, 1, &eventType, (__bridge void *)self, NULL);
InstallEventHandler(GetEventMonitorTarget(), &OnHotKeyReleasedEvent, 1, &eventReleasedType, (__bridge void *)self, NULL);
NSMutableArray *bindings = [[SlateConfig getInstance] bindings];
for (NSInteger i = 0; i < [bindings count]; i++) {
Binding *binding = [bindings objectAtIndex:i];
SlateLogger(@"REGISTERING KEY: %u, MODIFIERS: %u", [binding keyCode], [binding modifiers]);
if ([binding keyCode] == 48 && [binding modifiers] == cmdKey) {
cmdTabBinding = i;
SlateLogger(@"Found CMD+Tab binding!");
} else if ([binding keyCode] == 48 && [binding modifiers] == (cmdKey + shiftKey)) {
cmdShiftTabBinding = i;
SlateLogger(@"Found CMD+Shift+Tab binding!");
}
EventHotKeyID myHotKeyID;
EventHotKeyRef myHotKeyRef;
myHotKeyID.signature = *[[NSString stringWithFormat:@"hotkey%li",i] cStringUsingEncoding:NSASCIIStringEncoding];
myHotKeyID.id = (UInt32)i;
RegisterEventHotKey([binding keyCode], [binding modifiers], myHotKeyID, GetEventMonitorTarget(), 0, &myHotKeyRef);
[binding setHotKeyRef:myHotKeyRef];
}
NSArray *modalKeys = [[[SlateConfig getInstance] modalBindings] allKeys];
NSInteger i = MODAL_BEGIN_ID;
for (NSString *modalHashKey in modalKeys) {
SlateLogger(@"REGISTERING MODAL KEY: %@", modalHashKey);
NSArray *modalKeyArr = [Binding modalHashKeyToKeyAndModifiers:modalHashKey];
if (modalKeyArr == nil) continue;
EventHotKeyID myHotKeyID;
EventHotKeyRef myHotKeyRef;
myHotKeyID.signature = *[[NSString stringWithFormat:@"hotkey%li",i] cStringUsingEncoding:NSASCIIStringEncoding];
myHotKeyID.id = (UInt32)i;
RegisterEventHotKey([[modalKeyArr objectAtIndex:0] unsignedIntValue], [[modalKeyArr objectAtIndex:1] unsignedIntValue], myHotKeyID, GetEventMonitorTarget(), 0, &myHotKeyRef);
[[self modalHotKeyRefs] setObject:[NSValue valueWithPointer:myHotKeyRef] forKey:modalHashKey];
[[self modalIdToKey] setObject:modalHashKey forKey:[NSNumber numberWithInteger:i]];
i++;
}
SlateLogger(@"HotKeys registered.");
}
- (void)createSnapshotOperations {
SnapshotOperation *undoSnapOp = [SnapshotOperation operationFromString:[NSString stringWithFormat:@"snapshot %@ save-to-disk;stack", UNDO_SNAPSHOT]];
[undoSnapOp setStackSize:[[SlateConfig getInstance] getIntegerConfig:UNDO_MAX_STACK_SIZE]];
[self setUndoSnapshotOperation:undoSnapOp];
[self setUndoDeleteSnapshotOperation:[DeleteSnapshotOperation operationFromString:[NSString stringWithFormat:@"delete-snapshot %@ all", UNDO_SNAPSHOT]]];
[self setMenuSnapshotOperation:[SnapshotOperation operationFromString:[NSString stringWithFormat:@"snapshot %@ save-to-disk", MENU_SNAPSHOT]]];
[self setMenuActivateSnapshotOperation:[ActivateSnapshotOperation operationFromString:[NSString stringWithFormat:@"activate-snapshot %@", MENU_SNAPSHOT]]];
[[self undoDeleteSnapshotOperation] doOperation];
}
- (IBAction)takeSnapshot {
[menuSnapshotOperation doOperation];
}
- (IBAction)activateSnapshot {
[menuActivateSnapshotOperation doOperation];
}
- (IBAction)aboutWindow {
[NSApp orderFrontStandardAboutPanel:self];
NSArray *windows = [NSApp windows];
for (NSWindow *window in windows) {
[window setLevel:(NSScreenSaverWindowLevel - 1)];
}
}
- (OSStatus)timerActivateBinding:(NSTimer *)timer {
return [self activateBinding:currentHotKey isRepeat:YES];
}
- (void)resetModalKey {
// clear out bindings
for (NSValue *hotKeyRef in [self currentModalHotKeyRefs]) {
UnregisterEventHotKey([hotKeyRef pointerValue]);
}
// reset status image
[statusItem setImage:[NSImage imageNamed:@"status"]];
currentModalKey = nil;
}
- (OSStatus)activateBinding:(EventHotKeyID)hkCom isRepeat:(BOOL)isRepeat {
SlateLogger(@"ACTIVATING BINDING: %u", hkCom.id);
// check if there is a currently open binding
HintOperation *hintop = [self currentHintOperation];
GridOperation *gridop = [self currentGridOperation];
Binding *switchop = [self currentSwitchBinding];
if (hintop != nil) {
[hintop activateHintKey:hkCom.id];
return noErr;
}
if (gridop != nil) {
[gridop activateGridKey:hkCom.id];
return noErr;
}
if (switchop != nil) {
[(SwitchOperation *)[switchop op] activateSwitchKey:hkCom isRepeat:isRepeat];
@synchronized(timerLock) {
if (currentTimer != nil) {
[currentTimer invalidate];
currentTimer = nil;
}
// Setup timer to repeat operation
currentHotKey = hkCom;
currentTimer = [NSTimer scheduledTimerWithTimeInterval:[[SlateConfig getInstance] getDoubleConfig:(isRepeat ? SWITCH_SECONDS_BETWEEN_REPEAT : SWITCH_SECONDS_BEFORE_REPEAT)]
target:selfRef
selector:@selector(timerActivateBinding:)
userInfo:nil
repeats:NO];
}
return noErr;
}
// check modal stuffs
NSNumber *hkId = [NSNumber numberWithInteger:hkCom.id];
NSString *modalKey = [[self modalIdToKey] objectForKey:hkId];
if (modalKey != nil) {
if (currentModalKey != nil && [modalKey isEqualToString:currentModalKey]) {
[self resetModalKey];
return noErr;
} else if (currentModalKey == nil) {
SlateLogger(@"FOUND MODAL KEY BINDING, REGISTERING!");
// register all these bindings
[[self currentModalHotKeyRefs] removeAllObjects];
NSArray *modalOperations = [[[SlateConfig getInstance] modalBindings] objectForKey:modalKey];
NSInteger i = CURRENT_MODAL_BEGIN_ID;
for (Binding *binding in modalOperations) {
EventHotKeyID myHotKeyID;
EventHotKeyRef myHotKeyRef;
myHotKeyID.signature = *[[NSString stringWithFormat:@"hotkey%li",i] cStringUsingEncoding:NSASCIIStringEncoding];
myHotKeyID.id = (UInt32)i;
RegisterEventHotKey([binding keyCode], 0, myHotKeyID, GetEventMonitorTarget(), 0, &myHotKeyRef);
[binding setHotKeyRef:myHotKeyRef];
[[self currentModalHotKeyRefs] addObject:[NSValue valueWithPointer:myHotKeyRef]];
i++;
}
if (![EMPTY isEqualToString:[[SlateConfig getInstance] getConfig:MODAL_ESCAPE_KEY]]) {
EventHotKeyID myHotKeyID;
EventHotKeyRef myHotKeyRef;
myHotKeyID.signature = *[[NSString stringWithFormat:@"hotkey%li",MODAL_ESCAPE_ID] cStringUsingEncoding:NSASCIIStringEncoding];
myHotKeyID.id = (UInt32)MODAL_ESCAPE_ID;
NSArray *keyarr = [Binding getKeystrokeFromString:[[SlateConfig getInstance] getConfig:MODAL_ESCAPE_KEY]];
RegisterEventHotKey([[keyarr objectAtIndex:0] unsignedIntValue], [[keyarr objectAtIndex:1] unsignedIntValue], myHotKeyID, GetEventMonitorTarget(), 0, &myHotKeyRef);
[[self currentModalHotKeyRefs] addObject:[NSValue valueWithPointer:myHotKeyRef]];
}
[self setCurrentModalKey:modalKey];
// change status image
[statusItem setImage:[NSImage imageNamed:@"statusActive"]];
return noErr;
}
}
if (hkCom.id >= [[[SlateConfig getInstance] bindings] count]) {
if (currentModalKey != nil) {
if (hkCom.id == MODAL_ESCAPE_ID) {
[self resetModalKey];
} else {
NSInteger potentialId = hkCom.id - CURRENT_MODAL_BEGIN_ID;
if (potentialId >= 0 && potentialId < [[[[SlateConfig getInstance] modalBindings] objectForKey:currentModalKey] count]) {
Binding *binding = [[[[SlateConfig getInstance] modalBindings] objectForKey:currentModalKey] objectAtIndex:potentialId];
[binding doOperation];
if (![binding toggle]) {
// clear out bindings
[self resetModalKey];
}
}
}
}
return noErr;
}
Binding *binding = [[[SlateConfig getInstance] bindings] objectAtIndex:hkCom.id];
if (binding) {
SlateLogger(@"Running Operation %@", [[[SlateConfig getInstance] bindings] objectAtIndex:hkCom.id]);
if ([[binding op] isKindOfClass:[SwitchOperation class]]) {
// makes sure that if switch is called immediately after opening slate we don't run into issues
NSArray *currentApps = [[RunningApplications getInstance] apps];
if ([currentApps count] > 0) {
[AccessibilityWrapper focusApp:[currentApps objectAtIndex:0]];
}
[self setCurrentSwitchBinding:binding];
EventTypeSpec modifiersChangedType;
modifiersChangedType.eventClass = kEventClassKeyboard;
modifiersChangedType.eventKind = kEventRawKeyModifiersChanged;
InstallEventHandler(GetEventMonitorTarget(), &OnModifiersChangedEvent, 1, &modifiersChangedType, (__bridge void *)self, &modifiersEvent);
}
[binding doOperation];
if (!(cmdTabBinding > 0 && [[[SlateConfig getInstance] bindings] objectAtIndex:cmdTabBinding] == binding) &&
!(cmdShiftTabBinding > 0 && [[[SlateConfig getInstance] bindings] objectAtIndex:cmdShiftTabBinding] == binding) &&
([binding repeat] || [[binding op] isKindOfClass:[SwitchOperation class]])) {
@synchronized(timerLock) {
if (currentTimer != nil) {
[currentTimer invalidate];
currentTimer = nil;
}
// Setup timer to repeat operation
currentHotKey = hkCom;
if (![[binding op] isKindOfClass:[SwitchOperation class]] || !keyUpSeen) {
currentTimer = [NSTimer scheduledTimerWithTimeInterval:[[SlateConfig getInstance] getDoubleConfig:([[binding op] isKindOfClass:[SwitchOperation class]] ?
(isRepeat ? SWITCH_SECONDS_BETWEEN_REPEAT : SWITCH_SECONDS_BEFORE_REPEAT) :
(isRepeat ? SECONDS_BETWEEN_REPEAT : SECONDS_BEFORE_REPEAT))]
target:selfRef
selector:@selector(timerActivateBinding:)
userInfo:nil
repeats:NO];
}
}
}
}
return noErr;
}
// Quartz Event Tap for reserved key bindings (CMD+Tab or CMD+Shift+Tab)
static const NSTimeInterval KEY_UP_BUFFER = -0.020;
static BOOL keyUpSeen = YES;
static NSDate *keyUpTime = nil;
CGEventRef EatAppSwitcherCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
@synchronized(keyUpLock) {
CGEventFlags flags = CGEventGetFlags(event);
int64_t keyCode = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);
SlateLogger(@"KEY DOWN - FLAGS: %llu, KEYCODE: %lld", (uint64_t)flags, keyCode);
// 48 is tab key code
if (keyCode == 48 &&
((flags & kCGEventFlagMaskCommand) == kCGEventFlagMaskCommand) &&
((flags & kCGEventFlagMaskAlternate) != kCGEventFlagMaskAlternate) &&
((flags & kCGEventFlagMaskControl) != kCGEventFlagMaskControl) &&
((flags & kCGEventFlagMaskAlphaShift) != kCGEventFlagMaskAlphaShift) &&
((flags & kCGEventFlagMaskHelp) != kCGEventFlagMaskHelp) &&
((flags & kCGEventFlagMaskNumericPad) != kCGEventFlagMaskNumericPad) &&
((flags & kCGEventFlagMaskSecondaryFn) != kCGEventFlagMaskSecondaryFn)) {
SlateLogger(@" IS CMD+TAB");
if (keyUpSeen) {
SlateLogger(@" KEY UP SEEN: %f", [keyUpTime timeIntervalSinceNow]);
if (keyUpTime && [keyUpTime timeIntervalSinceNow] > KEY_UP_BUFFER) {
return NULL;
}
@synchronized(timerLock) {
if (currentTimer != nil) {
[currentTimer invalidate];
currentTimer = nil;
}
}
keyUpSeen = NO;
keyUpTime = nil;
SlateAppDelegate *del = (__bridge SlateAppDelegate *)refcon;
EventHotKeyID myHotKeyID;
NSInteger hotkeyID = ((flags & kCGEventFlagMaskShift) == kCGEventFlagMaskShift) ? [del cmdShiftTabBinding] : [del cmdTabBinding];
if (hotkeyID < 0) return NULL;
myHotKeyID.signature = *[[NSString stringWithFormat:@"hotkey%li",hotkeyID] cStringUsingEncoding:NSASCIIStringEncoding];
myHotKeyID.id = (UInt32)hotkeyID;
[del activateBinding:myHotKeyID isRepeat:NO];
} else {
SlateLogger(@" KEY UP NOT SEEN");
@synchronized(timerLock) {
if (currentTimer != nil) {
[currentTimer invalidate];
currentTimer = nil;
}
}
SlateAppDelegate *del = (__bridge SlateAppDelegate *)refcon;
EventHotKeyID myHotKeyID;
NSInteger hotkeyID = ((flags & kCGEventFlagMaskShift) == kCGEventFlagMaskShift) ? [del cmdShiftTabBinding] : [del cmdTabBinding];
if (hotkeyID < 0) return NULL;
myHotKeyID.signature = *[[NSString stringWithFormat:@"hotkey%li",hotkeyID] cStringUsingEncoding:NSASCIIStringEncoding];
myHotKeyID.id = (UInt32)hotkeyID;
[del activateBinding:myHotKeyID isRepeat:YES];
}
return NULL;
}
return event;
}
}
CGEventRef EatAppSwitcherResetCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
@synchronized(keyUpLock) {
SlateLogger(@"KEY UP");
@synchronized(timerLock) {
if (currentTimer != nil) {
[currentTimer invalidate];
currentTimer = nil;
}
}
keyUpSeen = YES;
keyUpTime = [NSDate date];
return event;
}
}
OSStatus OnHotKeyEvent(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData) {
@synchronized(timerLock) {
if (currentTimer != nil) {
[currentTimer invalidate];
currentTimer = nil;
}
}
if (![(__bridge id)userData isKindOfClass:[SlateAppDelegate class]]) return noErr;
EventHotKeyID hkCom;
GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(hkCom), NULL, &hkCom);
return [(__bridge SlateAppDelegate *)userData activateBinding:hkCom isRepeat:NO];
}
OSStatus OnHotKeyReleasedEvent(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData) {
@synchronized(timerLock) {
if (currentTimer != nil) {
[currentTimer invalidate];
currentTimer = nil;
}
}
return noErr;
}
OSStatus OnModifiersChangedEvent(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData) {
SlateLogger(@"Modifiers changed");
@synchronized(timerLock) {
if (currentTimer != nil) {
[currentTimer invalidate];
currentTimer = nil;
}
}
Binding *currSwitch = [(__bridge SlateAppDelegate *)userData currentSwitchBinding];
UInt32 modifiers;
GetEventParameter(theEvent, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(modifiers), NULL, &modifiers);
if (currSwitch != nil) {
if ([(SwitchOperation *)[currSwitch op] modifiersChanged:[currSwitch modifiers] new:modifiers]) {
[(__bridge SlateAppDelegate *)userData setCurrentSwitchBinding:nil];
RemoveEventHandler(modifiersEvent);
keyUpSeen = YES;
}
}
return noErr;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
if (cmdTabBinding > 0 || cmdShiftTabBinding > 0) {
CFMachPortRef keyDownEventTap;
CFRunLoopSourceRef keyDownRunLoopSource;
keyDownEventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, 0, CGEventMaskBit(kCGEventKeyDown), EatAppSwitcherCallback, (__bridge void *)self);
keyDownRunLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, keyDownEventTap, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), keyDownRunLoopSource, kCFRunLoopCommonModes);
CGEventTapEnable(keyDownEventTap, true);
CFMachPortRef keyUpEventTap;
CFRunLoopSourceRef keyUpRunLoopSource;
keyUpEventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, 0, CGEventMaskBit(kCGEventKeyUp), EatAppSwitcherResetCallback, (__bridge void *)self);
keyUpRunLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, keyUpEventTap, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), keyUpRunLoopSource, kCFRunLoopCommonModes);
CGEventTapEnable(keyUpEventTap, true);
}
}
- (void)setLaunchOnLoginItemStatus {
if ([self isInLoginItems]) [launchOnLoginItem setState:NSOnState];
else [launchOnLoginItem setState:NSOffState];
}
- (void)awakeFromNib {
cmdTabBinding = -1;
cmdShiftTabBinding = -1;
currentHintOperation = nil;
currentGridOperation = nil;
[self setModalHotKeyRefs:[NSMutableDictionary dictionary]];
[self setModalIdToKey:[NSMutableDictionary dictionary]];
[self setCurrentModalHotKeyRefs:[NSMutableArray array]];
windowInfoController = [[NSWindowController alloc] initWithWindow:windowInfo];
configHelperController = [[NSWindowController alloc] initWithWindow:configHelper];
NSMenuItem *aboutItem = [statusMenu insertItemWithTitle:@"About Slate" action:@selector(aboutWindow) keyEquivalent:@"" atIndex:0];
[aboutItem setTarget:self];
NSMenuItem *takeSnapshotItem = [statusMenu insertItemWithTitle:@"Take Snapshot" action:@selector(takeSnapshot) keyEquivalent:@"" atIndex:3];
[takeSnapshotItem setTarget:self];
activateSnapshotItem = [statusMenu insertItemWithTitle:@"Activate Snapshot" action:@selector(activateSnapshot) keyEquivalent:@"" atIndex:4];
[activateSnapshotItem setTarget:self];
NSMenuItem *loadConfigItem = [statusMenu insertItemWithTitle:@"Relaunch and Load Config" action:@selector(relaunch) keyEquivalent:@"" atIndex:1];
[loadConfigItem setTarget:self];
launchOnLoginItem = [statusMenu insertItemWithTitle:@"Launch Slate on Login" action:@selector(updateLaunchState) keyEquivalent:@"" atIndex:2];
[self setLaunchOnLoginItemStatus];
[launchOnLoginItem setTarget:self];
NSMenuItem *windowInfoItem = [statusMenu insertItemWithTitle:@"Current Window Info" action:@selector(currentWindowInfo) keyEquivalent:@"" atIndex:4];
[windowInfoItem setTarget:self];
//NSMenuItem *configInfoItem = [statusMenu insertItemWithTitle:@"Configuration Helper" action:@selector(configurationHelper) keyEquivalent:@"" atIndex:2];
//[configInfoItem setTarget:self];
statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength: NSVariableStatusItemLength];
[statusItem setMenu:statusMenu];
NSImage *statusImage = [NSImage imageNamed:@"status"];
[statusImage setTemplate:YES];
[statusItem setImage:statusImage];
[statusItem setHighlightMode:YES];
// Ensure no timer exists
@synchronized(timerLock) {
currentTimer = nil;
timerLock = [[NSObject alloc] init];
}
@synchronized(keyUpLock) {
keyUpLock = [[NSObject alloc] init];
}
// Check if Accessibility API is enabled
if (!AXAPIEnabled()) {
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Enable", @"Quit", nil]];
[alert setMessageText:[NSString stringWithFormat:@"Slate cannot run without \"Access for assistive devices\". Would you like to enable it?"]];
[alert setInformativeText:[NSString stringWithFormat:@"You may be prompted for your administrator password."]];
[alert setAlertStyle:NSCriticalAlertStyle];
NSInteger alertIndex = [alert runModal];
if (alertIndex == NSAlertFirstButtonReturn) {
SlateLogger(@"User wants to enable Access for assistive devices");
NSDictionary* errorDictionary;
NSAppleScript* applescript = [[NSAppleScript alloc] initWithSource:@"tell application \"System Events\" to set UI elements enabled to true"];
[applescript executeAndReturnError:&errorDictionary];
}
else if (alertIndex == NSAlertSecondButtonReturn) {
SlateLogger(@"User selected quit");
[NSApp terminate:nil];
}
}
// Read Config
[self loadConfig];
// Register Hot Keys
[self registerHotKeys];
[self createSnapshotOperations];
// Setup App list
NSArray *rapps = [[RunningApplications getInstance] apps];
if ([rapps count] > 0) [AccessibilityWrapper focusMainWindow:[rapps objectAtIndex:0]];
selfRef = self;
}
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
if (menuItem == activateSnapshotItem) {
SnapshotList *menuSnapshots = [[[SlateConfig getInstance] snapshots] objectForKey:@"menuSnapshot"];
if (menuSnapshots == nil || [[menuSnapshots snapshots] count] <= 0) {
return NO;
}
}
return YES;
}
- (BOOL)isInLoginItems {
NSString * appPath = [[NSBundle mainBundle] bundlePath];
// This will retrieve the path for the application
// For example, /Applications/test.app
CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:appPath];
// Create a reference to the shared file list.
LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItems) {
UInt32 seedValue;
// Retrieve the list of Login Items and cast them to
// a NSArray so that it will be easier to iterate.
NSArray *loginItemsArray = (__bridge NSArray *)LSSharedFileListCopySnapshot(loginItems, &seedValue);
for(NSInteger i = 0; i < [loginItemsArray count]; i++){
LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)[loginItemsArray objectAtIndex:i];
//Resolve the item with URL
if (LSSharedFileListItemResolve(itemRef, 0, (CFURLRef*) &url, NULL) == noErr) {
NSString * urlPath = [(__bridge NSURL*)url path];
if ([urlPath compare:appPath] == NSOrderedSame) {
return YES;
}
}
}
}
return NO;
}
- (void)addToLoginItems {
NSString * appPath = [[NSBundle mainBundle] bundlePath];
// This will retrieve the path for the application
// For example, /Applications/test.app
CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:appPath];
// Create a reference to the shared file list.
// We are adding it to the current user only.
// If we want to add it all users, use
// kLSSharedFileListGlobalLoginItems instead of
// kLSSharedFileListSessionLoginItems
LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItems) {
//Insert an item to the list.
LSSharedFileListItemRef item = LSSharedFileListInsertItemURL(loginItems,
kLSSharedFileListItemLast, NULL, NULL,
url, NULL, NULL);
if (item){
CFRelease(item);
}
}
CFRelease(loginItems);
}
- (void)deleteFromLoginItems {
NSString * appPath = [[NSBundle mainBundle] bundlePath];
// This will retrieve the path for the application
// For example, /Applications/test.app
CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:appPath];
// Create a reference to the shared file list.
LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItems) {
UInt32 seedValue;
// Retrieve the list of Login Items and cast them to
// a NSArray so that it will be easier to iterate.
NSArray *loginItemsArray = (__bridge NSArray *)LSSharedFileListCopySnapshot(loginItems, &seedValue);
for(NSInteger i = 0; i < [loginItemsArray count]; i++){
LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)[loginItemsArray objectAtIndex:i];
//Resolve the item with URL
if (LSSharedFileListItemResolve(itemRef, 0, (CFURLRef*) &url, NULL) == noErr) {
NSString * urlPath = [(__bridge NSURL*)url path];
if ([urlPath compare:appPath] == NSOrderedSame) {
LSSharedFileListItemRemove(loginItems,itemRef);
}
}
}
}
}
@end
================================================
FILE: Slate/SlateConfig.h
================================================
//
// SlateConfig.h
// Slate
//
// Created by Jigish Patel on 5/18/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
#import "Snapshot.h"
@class Binding;
@interface SlateConfig : NSObject {
@private
NSMutableDictionary *configs;
NSMutableDictionary *configDefaults;
NSMutableArray *bindings;
NSMutableDictionary *modalBindings;
NSMutableDictionary *layouts;
NSMutableArray *defaultLayouts;
NSMutableDictionary *aliases;
NSMutableDictionary *snapshots;
}
@property NSMutableDictionary *configs;
@property NSMutableDictionary *configDefaults;
@property NSMutableDictionary *appConfigs; // two layer map
@property NSMutableArray *bindings;
@property NSMutableDictionary *modalBindings;
@property NSMutableDictionary *layouts;
@property NSMutableArray *defaultLayouts;
@property NSMutableDictionary *aliases;
@property NSMutableDictionary *snapshots;
+ (SlateConfig *)getInstance;
+ (NSAlert *)warningAlertWithKeyEquivalents:(NSArray *)titles;
- (BOOL)load;
- (BOOL)loadConfigFileWithPath:(NSString *)file;
- (BOOL)addLayout:(NSString *)name dict:(NSDictionary *)dict;
- (void)addDefault:(id)screenConfig layout:(NSString *)layout;
- (void)addBinding:(Binding *)bind;
- (BOOL)append:(NSString *)configString;
- (BOOL)loadSnapshots;
- (BOOL)getBoolConfig:(NSString *)key;
- (void)setConfig:(NSString *)key to:(NSString *)value;
- (NSInteger)getIntegerConfig:(NSString *)key;
- (double)getDoubleConfig:(NSString *)key;
- (float)getFloatConfig:(NSString *)key;
- (NSString *)getConfig:(NSString *)key;
- (NSString *)getConfigDefault:(NSString *)key;
- (NSString *)getConfig:(NSString *)key app:(NSString *)app;
- (NSArray *)getArrayConfig:(NSString *)key;
- (void)addAlias:(NSString *)line;
- (NSString *)replaceAliases:(NSString *)line;
- (void)onScreenChange:(id)notification;
- (void)setupDefaultConfigs;
- (void)addSnapshot:(Snapshot *)snapshot name:(NSString *)name saveToDisk:(BOOL)saveToDisk isStack:(BOOL)isStack stackSize:(NSUInteger)stackSize;
- (void)deleteSnapshot:(NSString *)name pop:(BOOL)pop;
- (Snapshot *)popSnapshot:(NSString *)name remove:(BOOL)remove;
- (void)activateLayoutOrSnapshot:(NSString *)name;
- (NSString *)stripComments:(NSString *)line;
//- (void)processNotification:(id)notification;
+ (NSURL *)snapshotsFile;
@end
void onDisplayReconfiguration (CGDirectDisplayID display, CGDisplayChangeSummaryFlags flags, void *userInfo);
================================================
FILE: Slate/SlateConfig.m
================================================
//
// SlateConfig.m
// Slate
//
// Created by Jigish Patel on 5/18/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "Binding.h"
#import "Constants.h"
#import "Layout.h"
#import "LayoutOperation.h"
#import "ScreenState.h"
#import "ScreenWrapper.h"
#import "SlateConfig.h"
#import "StringTokenizer.h"
#import "Snapshot.h"
#import "SnapshotList.h"
#import "JSONKit.h"
#import "SlateLogger.h"
#import "NSFileManager+ApplicationSupport.h"
#import "NSString+Indicies.h"
#import "ActivateSnapshotOperation.h"
#import "JSController.h"
#import "Operation.h"
#import "JSScreenWrapper.h"
@implementation SlateConfig
@synthesize configs;
@synthesize configDefaults;
@synthesize bindings;
@synthesize modalBindings;
@synthesize layouts;
@synthesize defaultLayouts;
@synthesize aliases;
@synthesize snapshots;
@synthesize appConfigs;
static SlateConfig *_instance = nil;
+ (SlateConfig *)getInstance {
@synchronized([SlateConfig class]) {
if (!_instance) {
[ScreenWrapper updateStatics];
_instance = [[[SlateConfig class] alloc] init];
}
return _instance;
}
}
- (id)init {
self = [super init];
if (self) {
[self setupDefaultConfigs];
[self setBindings:[NSMutableArray arrayWithCapacity:10]];
[self setModalBindings:[NSMutableDictionary dictionary]];
[self setLayouts:[NSMutableDictionary dictionary]];
[self setDefaultLayouts:[NSMutableArray array]];
[self setAliases:[NSMutableDictionary dictionary]];
[self setSnapshots:[NSMutableDictionary dictionary]];
// Listen for screen change notifications with Quartz
CGDisplayRegisterReconfigurationCallback(onDisplayReconfiguration, (__bridge void *)(self));
//[nc addObserver:self selector:@selector(processNotification:) name:nil object:nil];
}
return self;
}
- (BOOL)getBoolConfig:(NSString *)key {
return [[self getConfig:key] boolValue];
}
- (NSInteger)getIntegerConfig:(NSString *)key {
return [[self getConfig:key] integerValue];
}
- (double)getDoubleConfig:(NSString *)key {
return [[self getConfig:key] doubleValue];
}
- (float)getFloatConfig:(NSString *)key {
return [[self getConfig:key] floatValue];
}
- (NSArray *)getArrayConfig:(NSString *)key {
return [[self getConfig:key] componentsSeparatedByString:SEMICOLON];
}
- (NSString *)getConfig:(NSString *)key {
NSString *c = [configs objectForKey:key];
if ([c hasPrefix:@"_javascript_::"]) {
NSString *fkey = [c stringByReplacingOccurrencesOfString:@"_javascript_::" withString:@""];
id ret = [[JSController getInstance] runCallableFunction:fkey];
return [NSString stringWithFormat:@"%@", ret];
}
return c;
}
- (void)setConfig:(NSString *)key to:(NSString *)value {
[configs setObject:value forKey:key];
}
- (NSString *)getConfigDefault:(NSString *)key {
return [configDefaults objectForKey:key];
}
- (NSString *)getConfig:(NSString *)key app:(NSString *)app {
NSMutableDictionary *configsForApp = [appConfigs objectForKey:app];
if (configsForApp == nil) return [self getConfigDefault:key];
NSString *config = [configsForApp objectForKey:key];
if (config == nil) return [self getConfigDefault:key];
return config;
}
+ (NSAlert *)warningAlertWithKeyEquivalents:(NSArray *)titles {
NSAlert *alert = [[NSAlert alloc] init];
[alert setAlertStyle:NSWarningAlertStyle];
for (NSString *title in titles) {
[[alert addButtonWithTitle:title] setKeyEquivalent:[[title substringToIndex: 1] lowercaseString]];
}
return alert;
}
- (BOOL)load {
SlateLogger(@"Loading config...");
// Reset configs and bindings in case we are calling from menu
[self setupDefaultConfigs];
[self setBindings:[[NSMutableArray alloc] initWithCapacity:10]];
[self setLayouts:[[NSMutableDictionary alloc] init]];
[self setDefaultLayouts:[[NSMutableArray alloc] init]];
[self setAliases:[[NSMutableDictionary alloc] init]];
BOOL loadedDefault = [self loadConfigFileWithPath:@"~/.slate"];
BOOL loadedJS = [self loadConfigFileWithPath:@"~/.slate.js"];
if (!loadedDefault && !loadedJS) {
SlateLogger(@" ERROR Could not load ~/.slate or ~/.slate.js");
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Continue", @"Quit", nil]];
[alert setMessageText:@"Could not load ~/.slate or ~/.slate.js"];
[alert setInformativeText:@"The default configuration will be used. You can find the default .slate file at https://github.com/jigish/slate/blob/master/Slate/default.slate"];
if ([alert runModal] == NSAlertSecondButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
return [self loadConfigFileWithPath:[[NSBundle mainBundle] pathForResource:@"default" ofType:@"slate"]];
}
if (![self loadSnapshots]) {
SlateLogger(@" ERROR Could not load %@", SNAPSHOTS_FILE);
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Quit", @"Skip", nil]];
[alert setMessageText:[NSString stringWithFormat:@"ERROR Could not load %@", SNAPSHOTS_FILE]];
[alert setInformativeText:[NSString stringWithFormat:@"I dunno. Figure it out. Maybe try deleting %@", SNAPSHOTS_FILE]];
if ([alert runModal] == NSAlertFirstButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
return NO;
}
if ([[SlateConfig getInstance] getBoolConfig:CHECK_DEFAULTS_ON_LOAD]) {
SlateLogger(@"Config loaded. Checking defaults...");
[self checkDefaults];
SlateLogger(@"Defaults loaded.");
} else {
SlateLogger(@"Config loaded.");
}
return YES;
}
- (BOOL)loadConfigFileWithPath:(NSString *)file {
if (file == nil) return NO;
NSString *configFile = file;
if ([file rangeOfString:SLASH].location != 0 && [file rangeOfString:TILDA].location != 0)
configFile = [NSString stringWithFormat:@"~/%@", file];
if ([configFile hasSuffix:EXT_JS]) {
return [[JSController getInstance] loadConfigFileWithPath:configFile];
}
NSError *err;
NSString *fileString = [NSString stringWithContentsOfFile:[configFile stringByExpandingTildeInPath] encoding:NSUTF8StringEncoding error:&err];
if (err == nil && fileString != nil && fileString != NULL) { return [self append:fileString]; }
return NO;
}
- (NSString *)stripComments:(NSString *)line {
if (line == nil) { return nil; }
NSString *theLine = [line stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:WHITESPACE]];
if ([theLine length] == 0 || [theLine characterAtIndex:0] == COMMENT_CHARACTER) {
return nil;
}
NSInteger idx = [theLine indexOfChar:COMMENT_CHARACTER];
if (idx < 0) { return theLine; }
return [[theLine substringToIndex:idx] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:WHITESPACE]];
}
- (BOOL)addLayout:(NSString *)name dict:(NSDictionary *)dict {
Layout *l = [[Layout alloc] initWithName:(NSString *)name dict:dict];
if (l == nil) { return NO; }
[[self layouts] setObject:l forKey:name];
return YES;
}
- (void)addBinding:(Binding *)bind {
if ([bind modalKey] != nil) {
NSMutableArray *theBindings = [modalBindings objectForKey:[bind modalHashKey]];
if (theBindings == nil) theBindings = [NSMutableArray array];
[theBindings addObject:bind];
[modalBindings setObject:theBindings forKey:[bind modalHashKey]];
} else {
[bindings addObject:bind];
}
}
- (BOOL)append:(NSString *)configString {
if (configString == nil)
return NO;
NSArray *lines = [configString componentsSeparatedByString:@"\n"];
NSEnumerator *e = [lines objectEnumerator];
NSString *line = [e nextObject];
while (line) {
line = [self stripComments:line];
if (line == nil || [line length] == 0) { line = [e nextObject]; continue; }
@try {
line = [self replaceAliases:line];
} @catch (NSException *ex) {
SlateLogger(@" ERROR %@",[ex name]);
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Quit", @"Skip", nil]];
[alert setMessageText:[ex name]];
[alert setInformativeText:[ex reason]];
if ([alert runModal] == NSAlertFirstButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
}
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:line into:tokens];
if ([tokens count] >= 3 && [[tokens objectAtIndex:0] isEqualToString:CONFIG]) {
// config [:]
SlateLogger(@" LoadingC: %@",line);
NSArray *splitKey = [[tokens objectAtIndex:1] componentsSeparatedByString:@":"];
NSString *key = [splitKey count] > 1 ? [splitKey objectAtIndex:0] : [tokens objectAtIndex:1];
if ([configs objectForKey:key] == nil) {
SlateLogger(@" ERROR Unrecognized config '%@'",[tokens objectAtIndex:1]);
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Quit", @"Skip", nil]];
[alert setMessageText:[NSString stringWithFormat:@"Unrecognized Config '%@'",[tokens objectAtIndex:1]]];
[alert setInformativeText:line];
if ([alert runModal] == NSAlertFirstButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
} else {
if ([splitKey count] > 1 && [[splitKey objectAtIndex:1] length] > 2) {
NSString *appName = [[splitKey objectAtIndex:1] substringWithRange:NSMakeRange(1, [[splitKey objectAtIndex:1] length] - 2)];
SlateLogger(@" Found App Config for App: '%@' Key: %@", appName, key);
NSMutableDictionary *configsForApp = [appConfigs objectForKey:appName];
if (configsForApp == nil) { configsForApp = [NSMutableDictionary dictionary]; }
[configsForApp setObject:[tokens objectAtIndex:2] forKey:key];
[appConfigs setObject:configsForApp forKey:appName];
} else {
[configs setObject:[tokens objectAtIndex:2] forKey:[tokens objectAtIndex:1]];
}
}
} else if ([tokens count] >= 3 && [[tokens objectAtIndex:0] isEqualToString:BIND]) {
// bind
@try {
SlateLogger(@" LoadingB: %@",line);
Binding *bind = [[Binding alloc] initWithString:line];
[self addBinding:bind];
} @catch (NSException *ex) {
SlateLogger(@" ERROR %@",[ex name]);
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Quit", @"Skip", nil]];
[alert setMessageText:[ex name]];
[alert setInformativeText:[ex reason]];
if ([alert runModal] == NSAlertFirstButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
}
} else if ([tokens count] >= 4 && [[tokens objectAtIndex:0] isEqualToString:LAYOUT]) {
// layout (| )*
@try {
if ([layouts objectForKey:[tokens objectAtIndex:1]] == nil) {
Layout *layout = [[Layout alloc] initWithString:line];
SlateLogger(@" LoadingL: %@",line);
[layouts setObject:layout forKey:[layout name]];
} else {
Layout *layout = [layouts objectForKey:[tokens objectAtIndex:1]];
[layout addWithString:line];
SlateLogger(@" LoadingL: %@",line);
}
} @catch (NSException *ex) {
SlateLogger(@" ERROR %@",[ex name]);
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Quit", @"Skip", nil]];
[alert setMessageText:[ex name]];
[alert setInformativeText:[ex reason]];
if ([alert runModal] == NSAlertFirstButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
}
} else if ([tokens count] >= 3 && [[tokens objectAtIndex:0] isEqualToString:DEFAULT]) {
// default
@try {
ScreenState *state = [[ScreenState alloc] initWithString:line];
if (state == nil) {
SlateLogger(@" ERROR Loading default layout");
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Quit", @"Skip", nil]];
[alert setMessageText:@"Error loading default layout"];
[alert setInformativeText:line];
if ([alert runModal] == NSAlertFirstButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
} else {
[defaultLayouts addObject:state];
SlateLogger(@" LoadingDL: %@",line);
}
} @catch (NSException *ex) {
SlateLogger(@" ERROR %@",[ex name]);
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Quit", @"Skip", nil]];
[alert setMessageText:[ex name]];
[alert setInformativeText:[ex reason]];
if ([alert runModal] == NSAlertFirstButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
}
} else if ([tokens count] >= 3 && [[tokens objectAtIndex:0] isEqualToString:ALIAS]) {
// alias
@try {
[self addAlias:line];
SlateLogger(@" LoadingA: %@",line);
} @catch (NSException *ex) {
SlateLogger(@" ERROR %@",[ex name]);
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Quit", @"Skip", nil]];
[alert setMessageText:[ex name]];
[alert setInformativeText:[ex reason]];
if ([alert runModal] == NSAlertFirstButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
}
} else if ([tokens count] >= 2 && [[tokens objectAtIndex:0] isEqualToString:SOURCE]) {
// source filename optional:if_exists
SlateLogger(@" LoadingS: %@",line);
if (![self loadConfigFileWithPath:[tokens objectAtIndex:1]]) {
if ([tokens count] >= 3 && [[tokens objectAtIndex:2] isEqualToString:IF_EXISTS]) {
SlateLogger(@" Could not find file '%@' but that's ok. User specified if_exists.",[tokens objectAtIndex:1]);
} else {
SlateLogger(@" ERROR Sourcing file '%@'",[tokens objectAtIndex:1]);
NSAlert *alert = [SlateConfig warningAlertWithKeyEquivalents: [NSArray arrayWithObjects:@"Quit", @"Skip", nil]];
[alert setMessageText:[NSString stringWithFormat:@"ERROR Sourcing file '%@'",[tokens objectAtIndex:1]]];
[alert setInformativeText:@"I dunno. Figure it out."];
if ([alert runModal] == NSAlertFirstButtonReturn) {
SlateLogger(@"User selected exit");
[NSApp terminate:nil];
}
}
}
}
line = [e nextObject];
}
return YES;
}
- (void)addDefault:(id)screenConfig layout:(id)layout {
ScreenState *state = [[ScreenState alloc] initWithConfig:screenConfig layout:layout];
if (state == nil) return;
[defaultLayouts addObject:state];
}
- (void)snapshotsFromDictionary:(NSDictionary *)snapshotsDict {
NSArray *keys = [snapshotsDict allKeys];
for (NSString *name in keys) {
SnapshotList *list = [SnapshotList snapshotListFromDictionary:[snapshotsDict objectForKey:name]];
[snapshots setObject:list forKey:[list name]];
}
}
- (BOOL)loadSnapshots {
NSString *fileString = [NSString stringWithContentsOfURL:[SlateConfig snapshotsFile] encoding:NSUTF8StringEncoding error:nil];
if (fileString == nil || [fileString isEqualToString:EMPTY])
return YES;
id iShouldBeADictionary = [fileString objectFromJSONString];
if (![iShouldBeADictionary isKindOfClass:[NSDictionary class]]) return NO;
NSDictionary *snapshotsDict = iShouldBeADictionary;
[self snapshotsFromDictionary:snapshotsDict];
return YES;
}
- (void)addAlias:(NSString *)line {
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:line into:tokens maxTokens:3];
[aliases setObject:[tokens objectAtIndex:2] forKey:[NSString stringWithFormat:@"${%@}",[tokens objectAtIndex:1]]];
}
- (NSString *)replaceAliases:(NSString *)line {
NSArray *aliasNames = [aliases allKeys];
for (NSInteger i = 0; i < [aliasNames count]; i++) {
line = [line stringByReplacingOccurrencesOfString:[aliasNames objectAtIndex:i] withString:[aliases objectForKey:[aliasNames objectAtIndex:i]]];
}
if ([line rangeOfString:@"${"].length > 0) {
@throw([NSException exceptionWithName:@"Unrecognized Alias" reason:[NSString stringWithFormat:@"Unrecognized alias in '%@'", line] userInfo:nil]);
}
return line;
}
/*- (void)processNotification:(id)notification {
SlateLogger(@"Notification: %@", notification);
SlateLogger(@"Notification Name: <%@>", [notification name]);
}*/
- (void)activateLayoutOrSnapshot:(id)name {
if ([name isKindOfClass:[Operation class]]) {
[name doOperation];
} else if ([layouts objectForKey:name] != nil) {
[LayoutOperation activateLayout:name];
} else if ([snapshots objectForKey:name] != nil) {
[ActivateSnapshotOperation activateSnapshot:name remove:NO];
}
}
- (void)checkDefaults {
ScreenWrapper *sw = [[ScreenWrapper alloc] init];
NSInteger screenCount = [sw getScreenCount];
NSMutableArray *resolutions = [[NSMutableArray alloc] initWithCapacity:10];
[sw getScreenResolutionStrings:resolutions];
[resolutions sortUsingSelector:@selector(compare:)];
for (NSInteger i = 0; i < [defaultLayouts count]; i++) {
ScreenState *state = [defaultLayouts objectAtIndex:i];
// Check count
if ([state type] == TYPE_COUNT && [state count] == screenCount) {
SlateLogger(@"onScreenChange count found");
[self activateLayoutOrSnapshot:[state layout]];
break;
}
// Check resolutions
if ([resolutions count] == [[state resolutions] count]) {
SlateLogger(@"onScreenChange resolution counts equal. Check %@ vs %@",resolutions,[state resolutions]);
BOOL isEqual = YES;
for (NSInteger j = 0; j < [resolutions count]; j++) {
if (![[resolutions objectAtIndex:j] isEqualToString:[[state resolutions] objectAtIndex:j]]) {
isEqual = NO;
break;
}
}
if (isEqual) {
SlateLogger(@"onScreenChange resolution found");
[self activateLayoutOrSnapshot:[state layout]];
break;
}
}
}
}
- (void)onScreenChange:(id)notification {
SlateLogger(@"onScreenChange");
if (![ScreenWrapper hasScreenConfigChanged]) return;
[self checkDefaults];
[[JSController getInstance] runCallbacks:@"screenConfigurationChanged" payload:nil];
}
- (NSDictionary *)snapshotsToDictionary {
NSMutableDictionary *snapshotDict = [NSMutableDictionary dictionary];
NSArray *keys = [snapshots allKeys];
for (NSString *name in keys) {
SnapshotList *list = [snapshots objectForKey:name];
if (![list saveToDisk]) continue;
[snapshotDict setObject:[list toDictionary] forKey:name];
}
return snapshotDict;
}
- (void)saveSnapshots {
// Build NSDictionary with snapshots
NSDictionary *snapshotDict = [self snapshotsToDictionary];
// Get NSData from NSDictionary
NSData *jsonData = [snapshotDict JSONData];
// Save NSData to file
[jsonData writeToURL:[SlateConfig snapshotsFile] atomically:YES];
}
- (void)addSnapshot:(Snapshot *)snapshot name:(NSString *)name saveToDisk:(BOOL)saveToDisk isStack:(BOOL)isStack stackSize:(NSUInteger)stackSize {
SnapshotList *list = [snapshots objectForKey:name];
if (list == nil) {
list = [[SnapshotList alloc] initWithName:name saveToDisk:saveToDisk isStack:isStack];
} else {
[list setName:name];
[list setSaveToDisk:saveToDisk];
[list setIsStack:isStack];
}
[list addSnapshot:snapshot];
[snapshots setObject:list forKey:name];
[self saveSnapshots];
}
- (void)deleteSnapshot:(NSString *)name pop:(BOOL)pop {
if (pop) {
SnapshotList *list = [snapshots objectForKey:name];
if (list) {
[list popSnapshot:YES];
}
} else {
[snapshots removeObjectForKey:name];
}
[self saveSnapshots];
}
- (Snapshot *)popSnapshot:(NSString *)name remove:(BOOL)remove {
SnapshotList *list = [snapshots objectForKey:name];
Snapshot *snapshot = nil;
if (list) {
snapshot = [list popSnapshot:remove];
}
[self saveSnapshots];
return snapshot;
}
- (void)setupDefaultConfigs {
[self setConfigDefaults:[NSMutableDictionary dictionaryWithCapacity:10]];
[configDefaults setObject:DEFAULT_TO_CURRENT_SCREEN_DEFAULT forKey:DEFAULT_TO_CURRENT_SCREEN];
[configDefaults setObject:NUDGE_PERCENT_OF_DEFAULT forKey:NUDGE_PERCENT_OF];
[configDefaults setObject:RESIZE_PERCENT_OF_DEFAULT forKey:RESIZE_PERCENT_OF];
[configDefaults setObject:REPEAT_ON_HOLD_OPS_DEFAULT forKey:REPEAT_ON_HOLD_OPS];
[configDefaults setObject:SECONDS_BEFORE_REPEAT_DEFAULT forKey:SECONDS_BEFORE_REPEAT];
[configDefaults setObject:SECONDS_BETWEEN_REPEAT_DEFAULT forKey:SECONDS_BETWEEN_REPEAT];
[configDefaults setObject:CHECK_DEFAULTS_ON_LOAD_DEFAULT forKey:CHECK_DEFAULTS_ON_LOAD];
[configDefaults setObject:FOCUS_CHECK_WIDTH_DEFAULT forKey:FOCUS_CHECK_WIDTH];
[configDefaults setObject:FOCUS_CHECK_WIDTH_MAX_DEFAULT forKey:FOCUS_CHECK_WIDTH_MAX];
[configDefaults setObject:FOCUS_PREFER_SAME_APP_DEFAULT forKey:FOCUS_PREFER_SAME_APP];
[configDefaults setObject:ORDER_SCREENS_LEFT_TO_RIGHT_DEFAULT forKey:ORDER_SCREENS_LEFT_TO_RIGHT];
[configDefaults setObject:WINDOW_HINTS_BACKGROUND_COLOR_DEFAULT forKey:WINDOW_HINTS_BACKGROUND_COLOR];
[configDefaults setObject:WINDOW_HINTS_FONT_COLOR_DEFAULT forKey:WINDOW_HINTS_FONT_COLOR];
[configDefaults setObject:WINDOW_HINTS_FONT_NAME_DEFAULT forKey:WINDOW_HINTS_FONT_NAME];
[configDefaults setObject:WINDOW_HINTS_FONT_SIZE_DEFAULT forKey:WINDOW_HINTS_FONT_SIZE];
[configDefaults setObject:WINDOW_HINTS_HEIGHT_DEFAULT forKey:WINDOW_HINTS_HEIGHT];
[configDefaults setObject:WINDOW_HINTS_WIDTH_DEFAULT forKey:WINDOW_HINTS_WIDTH];
[configDefaults setObject:WINDOW_HINTS_DURATION_DEFAULT forKey:WINDOW_HINTS_DURATION];
[configDefaults setObject:WINDOW_HINTS_ROUNDED_CORNER_SIZE_DEFAULT forKey:WINDOW_HINTS_ROUNDED_CORNER_SIZE];
[configDefaults setObject:WINDOW_HINTS_IGNORE_HIDDEN_WINDOWS_DEFAULT forKey:WINDOW_HINTS_IGNORE_HIDDEN_WINDOWS];
[configDefaults setObject:WINDOW_HINTS_TOP_LEFT_X_DEFAULT forKey:WINDOW_HINTS_TOP_LEFT_X];
[configDefaults setObject:WINDOW_HINTS_TOP_LEFT_Y_DEFAULT forKey:WINDOW_HINTS_TOP_LEFT_Y];
[configDefaults setObject:WINDOW_HINTS_ORDER_DEFAULT forKey:WINDOW_HINTS_ORDER];
[configDefaults setObject:WINDOW_HINTS_SHOW_ICONS_DEFAULT forKey:WINDOW_HINTS_SHOW_ICONS];
[configDefaults setObject:WINDOW_HINTS_ICON_ALPHA_DEFAULT forKey:WINDOW_HINTS_ICON_ALPHA];
[configDefaults setObject:WINDOW_HINTS_SPREAD_DEFAULT forKey:WINDOW_HINTS_SPREAD];
[configDefaults setObject:WINDOW_HINTS_SPREAD_SEARCH_WIDTH_DEFAULT forKey:WINDOW_HINTS_SPREAD_SEARCH_WIDTH];
[configDefaults setObject:WINDOW_HINTS_SPREAD_SEARCH_HEIGHT_DEFAULT forKey:WINDOW_HINTS_SPREAD_SEARCH_HEIGHT];
[configDefaults setObject:WINDOW_HINTS_SPREAD_PADDING_DEFAULT forKey:WINDOW_HINTS_SPREAD_PADDING];
[configDefaults setObject:SWITCH_ICON_SIZE_DEFAULT forKey:SWITCH_ICON_SIZE];
[configDefaults setObject:SWITCH_ICON_PADDING_DEFAULT forKey:SWITCH_ICON_PADDING];
[configDefaults setObject:SWITCH_BACKGROUND_COLOR_DEFAULT forKey:SWITCH_BACKGROUND_COLOR];
[configDefaults setObject:SWITCH_SELECTED_BACKGROUND_COLOR_DEFAULT forKey:SWITCH_SELECTED_BACKGROUND_COLOR];
[configDefaults setObject:SWITCH_SELECTED_BORDER_COLOR_DEFAULT forKey:SWITCH_SELECTED_BORDER_COLOR];
[configDefaults setObject:SWITCH_SELECTED_BORDER_SIZE_DEFAULT forKey:SWITCH_SELECTED_BORDER_SIZE];
[configDefaults setObject:SWITCH_ROUNDED_CORNER_SIZE_DEFAULT forKey:SWITCH_ROUNDED_CORNER_SIZE];
[configDefaults setObject:SWITCH_ORIENTATION_DEFAULT forKey:SWITCH_ORIENTATION];
[configDefaults setObject:SWITCH_SECONDS_BETWEEN_REPEAT_DEFAULT forKey:SWITCH_SECONDS_BETWEEN_REPEAT];
[configDefaults setObject:SWITCH_SECONDS_BEFORE_REPEAT_DEFAULT forKey:SWITCH_SECONDS_BEFORE_REPEAT];
[configDefaults setObject:SWITCH_STOP_REPEAT_AT_EDGE_DEFAULT forKey:SWITCH_STOP_REPEAT_AT_EDGE];
[configDefaults setObject:SWITCH_ONLY_FOCUS_MAIN_WINDOW_DEFAULT forKey:SWITCH_ONLY_FOCUS_MAIN_WINDOW];
[configDefaults setObject:SWITCH_FONT_COLOR_DEFAULT forKey:SWITCH_FONT_COLOR];
[configDefaults setObject:SWITCH_FONT_SIZE_DEFAULT forKey:SWITCH_FONT_SIZE];
[configDefaults setObject:SWITCH_FONT_NAME_DEFAULT forKey:SWITCH_FONT_NAME];
[configDefaults setObject:SWITCH_SHOW_TITLES_DEFAULT forKey:SWITCH_SHOW_TITLES];
[configDefaults setObject:SWITCH_TYPE_DEFAULT forKey:SWITCH_TYPE];
[configDefaults setObject:SWITCH_SELECTED_PADDING_DEFAULT forKey:SWITCH_SELECTED_PADDING];
[configDefaults setObject:KEYBOARD_LAYOUT_DEFAULT forKey:KEYBOARD_LAYOUT];
[configDefaults setObject:SNAPSHOT_TITLE_MATCH_DEFAULT forKey:SNAPSHOT_TITLE_MATCH];
[configDefaults setObject:GRID_BACKGROUND_COLOR_DEFAULT forKey:GRID_BACKGROUND_COLOR];
[configDefaults setObject:GRID_ROUNDED_CORNER_SIZE_DEFAULT forKey:GRID_ROUNDED_CORNER_SIZE];
[configDefaults setObject:GRID_CELL_BACKGROUND_COLOR_DEFAULT forKey:GRID_CELL_BACKGROUND_COLOR];
[configDefaults setObject:GRID_CELL_SELECTED_COLOR_DEFAULT forKey:GRID_CELL_SELECTED_COLOR];
[configDefaults setObject:GRID_CELL_ROUNDED_CORNER_SIZE_DEFAULT forKey:GRID_CELL_ROUNDED_CORNER_SIZE];
[configDefaults setObject:LAYOUT_FOCUS_ON_ACTIVATE_DEFAULT forKey:LAYOUT_FOCUS_ON_ACTIVATE];
[configDefaults setObject:SNAPSHOT_MAX_STACK_SIZE_DEFAULT forKey:SNAPSHOT_MAX_STACK_SIZE];
[configDefaults setObject:UNDO_MAX_STACK_SIZE_DEFAULT forKey:UNDO_MAX_STACK_SIZE];
[configDefaults setObject:UNDO_OPS_DEFAULT forKey:UNDO_OPS];
[configDefaults setObject:MODAL_ESCAPE_KEY_DEFAULT forKey:MODAL_ESCAPE_KEY];
[configDefaults setObject:JS_RECEIVE_MOVE_EVENT_DEFAULT forKey:JS_RECEIVE_MOVE_EVENT];
[configDefaults setObject:JS_RECEIVE_RESIZE_EVENT_DEFAULT forKey:JS_RECEIVE_RESIZE_EVENT];
[self setConfigs:[NSMutableDictionary dictionary]];
[self setAppConfigs:[NSMutableDictionary dictionary]];
[configs setValuesForKeysWithDictionary:configDefaults];
}
+ (NSURL *)snapshotsFile {
NSFileManager *sharedFM = [NSFileManager defaultManager];
NSURL *appSupportDir = [sharedFM applicationSupportDirectory];
NSURL *snapshotsFile = nil;
if (appSupportDir) {
snapshotsFile = [appSupportDir URLByAppendingPathComponent:SNAPSHOTS_FILE];
}
SlateLogger(@"TEST ------------- %@", [snapshotsFile absoluteString]);
return snapshotsFile;
}
@end
void onDisplayReconfiguration (CGDirectDisplayID display, CGDisplayChangeSummaryFlags flags, void *userInfo) {
SlateLogger(@"onDisplayReconfiguration");
[(__bridge id)userInfo onScreenChange:nil];
}
================================================
FILE: Slate/SlateLogger.h
================================================
//
// SlateLogger.h
// Slate
//
// Created by Jigish Patel on 3/5/12.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#ifndef Slate_SlateLogger_h
#define Slate_SlateLogger_h
#ifdef DEBUG
#define SlateLogger(...) NSLog(__VA_ARGS__)
#else
#define SlateLogger(...)
#endif
#endif
================================================
FILE: Slate/Snapshot.h
================================================
//
// Snapshot.h
// Slate
//
// Created by Jigish Patel on 2/28/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@class WindowSnapshot;
@interface Snapshot : NSObject {
@private
NSMutableDictionary *apps;
}
@property NSMutableDictionary *apps;
- (void)addWindow:(WindowSnapshot *)windowSnapshot app:(NSString *)appName;
- (NSDictionary *)toDictionary;
+ (Snapshot *)snapshotFromDictionary:(NSDictionary *)dict;
@end
================================================
FILE: Slate/Snapshot.m
================================================
//
// Snapshot.m
// Slate
//
// Created by Jigish Patel on 2/28/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "Snapshot.h"
#import "WindowSnapshot.h"
#import "Constants.h"
@implementation Snapshot
@synthesize apps;
- (id)init {
self = [super init];
if (self) {
[self setApps:[NSMutableDictionary dictionary]];
}
return self;
}
- (void)addWindow:(WindowSnapshot *)windowSnapshot app:(NSString *)appName {
NSMutableArray *app = [apps objectForKey:appName];
if (app == nil) {
app = [NSMutableArray array];
}
[app addObject:windowSnapshot];
[apps setObject:app forKey:appName];
}
- (NSDictionary *)toDictionary {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
NSMutableDictionary *appsDict = [NSMutableDictionary dictionary];
NSArray *appNames = [apps allKeys];
for (NSString *appName in appNames) {
NSArray *windows = [apps objectForKey:appName];
NSMutableArray *windowDicts = [NSMutableArray array];
for (WindowSnapshot *windowSnap in windows) {
[windowDicts addObject:[windowSnap toDictionary]];
}
[appsDict setObject:windowDicts forKey:appName];
}
[dict setObject:appsDict forKey:APPS];
return dict;
}
+ (Snapshot *)snapshotFromDictionary:(NSDictionary *)dict {
Snapshot *s = [[Snapshot alloc] init];
NSDictionary *appsDict = [dict objectForKey:APPS];
NSArray *appNames = [appsDict allKeys];
for (NSString *appName in appNames) {
NSArray *windowDicts = [appsDict objectForKey:appName];
for (NSDictionary *windowDict in windowDicts) {
[s addWindow:[WindowSnapshot windowSnapshotFromDictionary:windowDict] app:appName];
}
}
return s;
}
@end
================================================
FILE: Slate/SnapshotList.h
================================================
//
// SnapshotList.h
// Slate
//
// Created by Jigish Patel on 2/28/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@class Snapshot;
@interface SnapshotList : NSObject {
@private
NSMutableArray *snapshots;
NSString *name;
BOOL saveToDisk;
BOOL isStack;
NSInteger stackSize;
}
@property NSMutableArray *snapshots;
@property NSString *name;
@property (assign) BOOL saveToDisk;
@property (assign) BOOL isStack;
@property (assign) NSInteger stackSize;
- (id)initWithName:(NSString *)theName saveToDisk:(BOOL)theSaveToDisk isStack:(BOOL)theIsStack;
- (void)addSnapshot:(Snapshot *)snapshot;
- (Snapshot *)popSnapshot:(BOOL)remove;
- (NSDictionary *)toDictionary;
+ (SnapshotList *)snapshotListFromDictionary:(NSDictionary *)dict;
@end
================================================
FILE: Slate/SnapshotList.m
================================================
//
// SnapshotList.m
// Slate
//
// Created by Jigish Patel on 2/28/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "SnapshotList.h"
#import "Snapshot.h"
#import "Constants.h"
#import "SlateConfig.h"
@implementation SnapshotList
@synthesize snapshots, name, saveToDisk, isStack, stackSize;
- (id)init {
self = [super init];
if (self) {
[self setSnapshots:[NSMutableArray array]];
}
return self;
}
- (id)initWithName:(NSString *)theName saveToDisk:(BOOL)theSaveToDisk isStack:(BOOL)theIsStack {
self = [self init];
if (self) {
[self setName:theName];
saveToDisk = theSaveToDisk;
isStack = theIsStack;
stackSize = [[SlateConfig getInstance] getIntegerConfig:SNAPSHOT_MAX_STACK_SIZE]; // 0 means unlimited
}
return self;
}
- (id)initWithName:(NSString *)theName saveToDisk:(BOOL)theSaveToDisk isStack:(BOOL)theIsStack stackSize:(NSUInteger)theStackSize {
self = [self init];
if (self) {
[self setName:theName];
saveToDisk = theSaveToDisk;
isStack = theIsStack;
stackSize = theStackSize;
}
return self;
}
- (void)addSnapshot:(Snapshot *)snapshot {
if (isStack) {
[snapshots addObject:snapshot];
while (stackSize > 0 && [snapshots count] > stackSize) {
[snapshots removeObjectAtIndex:0];
}
} else {
[snapshots removeAllObjects];
[snapshots addObject:snapshot];
}
}
- (Snapshot *)popSnapshot:(BOOL)remove {
if ([snapshots count] <= 0) return nil;
Snapshot *returnVal = [snapshots lastObject];
if (remove) [snapshots removeLastObject];
return returnVal;
}
- (NSDictionary *)toDictionary {
NSMutableDictionary *snapshotList = [NSMutableDictionary dictionary];
NSMutableArray *snapshotsArray = [NSMutableArray array];
[snapshotList setObject:[self name] forKey:NAME];
[snapshotList setObject:[NSNumber numberWithBool:[self saveToDisk]] forKey:SAVE_TO_DISK];
[snapshotList setObject:[NSNumber numberWithBool:[self isStack]] forKey:STACK];
for (Snapshot *snap in [self snapshots]) {
[snapshotsArray addObject:[snap toDictionary]];
}
[snapshotList setObject:snapshotsArray forKey:SNAPSHOTS];
return snapshotList;
}
+ (SnapshotList *)snapshotListFromDictionary:(NSDictionary *)dict {
SnapshotList *sl = [[SnapshotList alloc] initWithName:[dict objectForKey:NAME] saveToDisk:[[dict objectForKey:SAVE_TO_DISK] boolValue] isStack:[[dict objectForKey:STACK] boolValue]];
NSArray *snapshotsArray = [dict objectForKey:SNAPSHOTS];
for (NSDictionary *snap in snapshotsArray) {
[sl addSnapshot:[Snapshot snapshotFromDictionary:snap]];
}
return sl;
}
@end
================================================
FILE: Slate/SnapshotOperation.h
================================================
//
// SnapshotOperation.h
// Slate
//
// Created by Jigish Patel on 2/28/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "Operation.h"
@interface SnapshotOperation : Operation {
@private
NSString *name;
BOOL saveToDisk;
BOOL isStack;
NSInteger stackSize;
}
@property NSString *name;
@property (assign) BOOL saveToDisk;
@property (assign) BOOL isStack;
@property (assign) NSInteger stackSize;
- (id)initWithName:(NSString *)theName options:(NSString *)options;
+ (id)snapshotOperation;
+ (id)snapshotOperationFromString:(NSString *)snapshotOperation;
@end
================================================
FILE: Slate/SnapshotOperation.m
================================================
//
// SnapshotOperation.m
// Slate
//
// Created by Jigish Patel on 2/28/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "SnapshotOperation.h"
#import "Constants.h"
#import "Snapshot.h"
#import "WindowSnapshot.h"
#import "SlateConfig.h"
#import "StringTokenizer.h"
#import "SlateLogger.h"
#import "RunningApplications.h"
@implementation SnapshotOperation
@synthesize name, saveToDisk, isStack, stackSize;
- (id)init {
self = [super init];
if (self) {
saveToDisk = NO;
isStack = NO;
stackSize = [[SlateConfig getInstance] getIntegerConfig:SNAPSHOT_MAX_STACK_SIZE];
[self setName:nil];
}
return self;
}
- (id)initWithName:(NSString *)theName options:(NSString *)_options {
self = [self init];
if (self) {
[self setStackSize:[[SlateConfig getInstance] getIntegerConfig:SNAPSHOT_MAX_STACK_SIZE]];
[self setName:theName];
if (_options) {
NSArray *optionsTokens = [_options componentsSeparatedByString:SEMICOLON];
for (NSInteger i = 0; i < [optionsTokens count]; i++) {
NSString *option = [optionsTokens objectAtIndex:i];
if ([SAVE_TO_DISK isEqualToString:option]) {
saveToDisk = YES;
} else if ([STACK isEqualToString:option]) {
isStack = YES;
}
}
}
}
return self;
}
- (BOOL)doOperation {
SlateLogger(@"----------------- Begin Snapshot Operation -----------------");
BOOL success = [self doOperationWithAccessibilityWrapper:nil screenWrapper:nil];
SlateLogger(@"----------------- End Snapshot Operation -----------------");
return success;
}
- (BOOL)doOperationWithAccessibilityWrapper:(AccessibilityWrapper *)iamnil screenWrapper:(ScreenWrapper *)iamalsonil {
[self evalOptionsWithAccessibilityWrapper:iamnil screenWrapper:iamalsonil];
Snapshot *snapshot = [[Snapshot alloc] init];
for (NSRunningApplication *app in [RunningApplications getInstance]) {
NSString *appName = [app localizedName];
pid_t appPID = [app processIdentifier];
SlateLogger(@"I see application '%@' with pid '%d'", appName, appPID);
AXUIElementRef appRef = AXUIElementCreateApplication(appPID);
CFArrayRef windowsArrRef = [AccessibilityWrapper windowsInApp:appRef];
if (!windowsArrRef || CFArrayGetCount(windowsArrRef) == 0) continue;
CFMutableArrayRef windowsArr = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, windowsArrRef);
for (NSInteger i = 0; i < CFArrayGetCount(windowsArr); i++) {
SlateLogger(@" Printing Window: %@", [AccessibilityWrapper getTitle:CFArrayGetValueAtIndex(windowsArr, i)]);
NSString *title = [AccessibilityWrapper getTitle:CFArrayGetValueAtIndex(windowsArr, i)];
if ([title isEqualToString:@""]) continue;
AccessibilityWrapper *aw = [[AccessibilityWrapper alloc] initWithApp:appRef window:CFArrayGetValueAtIndex(windowsArr, i)];
NSSize size = [aw getCurrentSize];
NSPoint tl = [aw getCurrentTopLeft];
[snapshot addWindow:[[WindowSnapshot alloc] initWithAppName:appName title:title topLeft:tl size:size] app:appName];
}
}
[[SlateConfig getInstance] addSnapshot:snapshot name:name saveToDisk:saveToDisk isStack:isStack stackSize:stackSize];
return YES;
}
- (BOOL)testOperation {
return YES;
}
- (NSArray *)requiredOptions {
return [NSArray arrayWithObject:OPT_NAME];
}
- (void)parseOption:(NSString *)_name value:(id)value {
if (value == nil) { return; }
if ([_name isEqualToString:OPT_NAME]) {
if (![value isKindOfClass:[NSString class]]) {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", _name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", _name, value] userInfo:nil]);
return;
}
[self setName:_name];
} else if ([_name isEqualToString:OPT_SAVE]) {
if (![value isKindOfClass:[NSValue class]] && ![value isKindOfClass:[NSString class]] && ![value isKindOfClass:[NSNumber class]]) {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", _name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", _name, value] userInfo:nil]);
return;
}
[self setSaveToDisk:[value boolValue]];
} else if ([_name isEqualToString:OPT_STACK]) {
if (![value isKindOfClass:[NSValue class]] && ![value isKindOfClass:[NSString class]] && ![value isKindOfClass:[NSNumber class]]) {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", _name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", _name, value] userInfo:nil]);
return;
}
[self setIsStack:[value boolValue]];
}
}
+ (id)snapshotOperation {
return [[SnapshotOperation alloc] init];
}
+ (id)snapshotOperationFromString:(NSString *)snapshotOperation {
// snapshot name options
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:snapshotOperation into:tokens maxTokens:3];
if ([tokens count] < 2) {
SlateLogger(@"ERROR: Invalid Parameters '%@'", snapshotOperation);
@throw([NSException exceptionWithName:@"Invalid Parameters" reason:[NSString stringWithFormat:@"Invalid Parameters in '%@'. Snapshot operations require the following format: 'snapshot name options'", snapshotOperation] userInfo:nil]);
}
Operation *op = [[SnapshotOperation alloc] initWithName:[tokens objectAtIndex:1] options:([tokens count] > 2 ? [tokens objectAtIndex:2] : nil)];
return op;
}
@end
================================================
FILE: Slate/StringTokenizer.h
================================================
//
// StringTokenizer.h
// Slate
//
// Created by Jigish Patel on 5/26/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface StringTokenizer : NSObject {}
+ (BOOL)isSpaceChar:(unichar)c;
+ (BOOL)isQuoteChar:(unichar)c quoteChars:(NSCharacterSet *)quoteChars;
+ (void)tokenize:(NSString *)s into:(NSMutableArray *)array;
+ (void)tokenize:(NSString *)s into:(NSMutableArray *)array quoteChars:(NSCharacterSet *)quotes;
+ (void)tokenize:(NSString *)s into:(NSMutableArray *)array maxTokens:(NSInteger)maxTokens;
+ (void)tokenize:(NSString *)s into:(NSMutableArray *)array maxTokens:(NSInteger)maxTokens quoteChars:(NSCharacterSet *)quotes;
+ (void)firstToken:(NSString *)s into:(NSMutableString *)token;
+ (NSString *)removeQuotes:(NSString *)s quoteChars:(NSCharacterSet *)quoteChars;
@end
================================================
FILE: Slate/StringTokenizer.m
================================================
//
// StringTokenizer.m
// Slate
//
// Created by Jigish Patel on 5/26/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "StringTokenizer.h"
@implementation StringTokenizer
+ (BOOL)isSpaceChar:(unichar)c {
return [[NSCharacterSet whitespaceCharacterSet] characterIsMember:c];
}
+ (BOOL)isQuoteChar:(unichar)c quoteChars:(NSCharacterSet *)quotes {
return [quotes characterIsMember:c];
}
+ (void)tokenize:(NSString *)s into:(NSMutableArray *)array {
NSMutableString *token = [[NSMutableString alloc] initWithCapacity:10];
for (NSInteger i = 0; i < [s length]; i++) {
if ([self isSpaceChar:[s characterAtIndex:i]]) {
if (![token isEqualToString:@""]) {
[array addObject:[NSString stringWithString:token]];
token = [[NSMutableString alloc] initWithCapacity:10];
}
} else {
[token appendFormat:@"%C", [s characterAtIndex:i]];
}
}
if (![token isEqualToString:@""]) {
[array addObject:[NSString stringWithString:token]];
}
}
+ (void)tokenize:(NSString *)s into:(NSMutableArray *)array quoteChars:(NSCharacterSet *)quotes {
NSMutableString *token = [[NSMutableString alloc] initWithCapacity:10];
BOOL quoteSeen = NO;
char quoteChar = '.';
for (NSInteger i = 0; i < [s length]; i++) {
if ([self isSpaceChar:[s characterAtIndex:i]]) {
if (![token isEqualToString:@""] && !quoteSeen) {
[array addObject:[NSString stringWithString:token]];
token = [[NSMutableString alloc] initWithCapacity:10];
} else if (quoteSeen) {
[token appendFormat:@"%C", [s characterAtIndex:i]];
}
} else if ([self isQuoteChar:[s characterAtIndex:i] quoteChars:quotes]) {
if (!quoteSeen) {
quoteSeen = !quoteSeen;
quoteChar = [s characterAtIndex:i];
} else if (quoteSeen && [s characterAtIndex:i] == quoteChar) {
quoteSeen = !quoteSeen;
quoteChar = '.';
} else {
[token appendFormat:@"%C", [s characterAtIndex:i]];
}
} else {
[token appendFormat:@"%C", [s characterAtIndex:i]];
}
}
if (![[NSString stringWithString:token] isEqualToString:@""]) {
[array addObject:[NSString stringWithString:token]];
}
}
+ (void)tokenize:(NSString *)s into:(NSMutableArray *)array maxTokens:(NSInteger)maxTokens {
if (maxTokens <=1) {
[array addObject:s];
return;
}
NSInteger numTokens = 0;
NSMutableString *token = [[NSMutableString alloc] initWithCapacity:10];
for (NSInteger i = 0; i < [s length]; i++) {
if ([self isSpaceChar:[s characterAtIndex:i]]) {
if (![token isEqualToString:@""] && numTokens < (maxTokens-1)) {
numTokens++;
[array addObject:[NSString stringWithString:token]];
token = [[NSMutableString alloc] initWithCapacity:10];
} else if (numTokens >= (maxTokens-1)) {
if ([token isEqualToString:@""] && [self isSpaceChar:[s characterAtIndex:i]]) continue;
[token appendFormat:@"%C", [s characterAtIndex:i]];
}
} else {
[token appendFormat:@"%C", [s characterAtIndex:i]];
}
}
if (![[NSString stringWithString:token] isEqualToString:@""]) {
[array addObject:[NSString stringWithString:token]];
}
}
+ (void)tokenize:(NSString *)s into:(NSMutableArray *)array maxTokens:(NSInteger)maxTokens quoteChars:(NSCharacterSet *)quotes {
if (maxTokens <=1) {
[array addObject:s];
return;
}
NSInteger numTokens = 0;
NSMutableString *token = [[NSMutableString alloc] initWithCapacity:10];
BOOL quoteSeen = NO;
unichar quoteChar = '.';
for (NSInteger i = 0; i < [s length]; i++) {
if ([self isSpaceChar:[s characterAtIndex:i]]) {
if (![token isEqualToString:@""] && numTokens < (maxTokens-1) && !quoteSeen) {
numTokens++;
[array addObject:[NSString stringWithString:token]];
token = [[NSMutableString alloc] initWithCapacity:10];
} else if (numTokens >= (maxTokens-1) || quoteSeen) {
[token appendFormat:@"%C", [s characterAtIndex:i]];
}
} else if (numTokens >= (maxTokens-1)) {
[token appendFormat:@"%C", [s characterAtIndex:i]];
} else if ([self isQuoteChar:[s characterAtIndex:i] quoteChars:quotes]) {
if (!quoteSeen) {
quoteSeen = !quoteSeen;
quoteChar = [s characterAtIndex:i];
} else if (quoteSeen && [s characterAtIndex:i] == quoteChar) {
quoteSeen = !quoteSeen;
quoteChar = '.';
} else {
[token appendFormat:@"%C", [s characterAtIndex:i]];
}
} else {
[token appendFormat:@"%C", [s characterAtIndex:i]];
}
}
if (![[NSString stringWithString:token] isEqualToString:@""]) {
[array addObject:[NSString stringWithString:token]];
}
}
+ (void)firstToken:(NSString *)s into:(NSMutableString *)token {
for (NSInteger i = 0; i < [s length]; i++) {
if ([self isSpaceChar:[s characterAtIndex:i]]) {
if (![token isEqualToString:@""]) {
return;
}
} else {
[token appendFormat:@"%C", [s characterAtIndex:i]];
}
}
}
+ (NSString *)removeQuotes:(NSString *)s quoteChars:(NSCharacterSet *)quoteChars {
if ([StringTokenizer isQuoteChar:[s characterAtIndex:0] quoteChars:quoteChars] &&
[StringTokenizer isQuoteChar:[s characterAtIndex:([s length] - 1)] quoteChars:quoteChars]) {
return [s substringWithRange:NSMakeRange(1, [s length]-2)];
}
return s;
}
@end
================================================
FILE: Slate/SwitchAppQuittingOverlayView.h
================================================
//
// SwitchAppQuittingOverlayView.h
// Slate
//
// Created by Jigish Patel on 3/22/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface SwitchAppQuittingOverlayView : NSView {
BOOL force;
}
@property BOOL force;
@end
================================================
FILE: Slate/SwitchAppQuittingOverlayView.m
================================================
//
// SwitchAppQuittingOverlayView.m
// Slate
//
// Created by Jigish Patel on 3/22/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "SwitchAppQuittingOverlayView.h"
@implementation SwitchAppQuittingOverlayView
@synthesize force;
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setWantsLayer:YES];
force = NO;
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
[[NSColor redColor] set];
NSBezierPath *path = [NSBezierPath bezierPath];
[path moveToPoint:NSMakePoint(self.bounds.origin.x, self.bounds.origin.y)];
[path lineToPoint:NSMakePoint(self.bounds.origin.x, self.bounds.origin.y+5)];
[path lineToPoint:NSMakePoint(self.bounds.origin.x+self.bounds.size.width-5,
self.bounds.origin.y+self.bounds.size.height)];
[path lineToPoint:NSMakePoint(self.bounds.origin.x+self.bounds.size.width,
self.bounds.origin.y+self.bounds.size.height)];
[path lineToPoint:NSMakePoint(self.bounds.origin.x+self.bounds.size.width,
self.bounds.origin.y+self.bounds.size.height-5)];
[path lineToPoint:NSMakePoint(self.bounds.origin.x+5, self.bounds.origin.y)];
[path closePath];
[path fill];
if (force) {
NSBezierPath *path2 = [NSBezierPath bezierPath];
[path2 moveToPoint:NSMakePoint(self.bounds.origin.x+self.bounds.size.width, self.bounds.origin.y)];
[path2 lineToPoint:NSMakePoint(self.bounds.origin.x+self.bounds.size.width-5, self.bounds.origin.y)];
[path2 lineToPoint:NSMakePoint(self.bounds.origin.x, self.bounds.origin.y+self.bounds.size.height-5)];
[path2 lineToPoint:NSMakePoint(self.bounds.origin.x, self.bounds.origin.y+self.bounds.size.height)];
[path2 lineToPoint:NSMakePoint(self.bounds.origin.x+5, self.bounds.origin.y+self.bounds.size.height)];
[path2 lineToPoint:NSMakePoint(self.bounds.origin.x+self.bounds.size.width, self.bounds.origin.y+5)];
[path2 closePath];
[path2 fill];
}
}
@end
================================================
FILE: Slate/SwitchAppView.h
================================================
//
// SwitchAppView.h
// Slate
//
// Created by Jigish Patel on 3/22/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@class SwitchAppQuittingOverlayView;
@interface SwitchAppView : NSView {
BOOL selected;
BOOL hidden;
BOOL quitting;
BOOL forceQuitting;
NSRunningApplication *app;
NSImageView *iconView;
NSTextField *textField;
SwitchAppQuittingOverlayView *quittingView;
}
@property (assign) BOOL selected;
@property (assign) BOOL hidden;
@property (assign) BOOL quitting;
@property (assign) BOOL forceQuitting;
@property NSRunningApplication *app;
@property NSImageView *iconView;
@property NSTextField *textField;
@property SwitchAppQuittingOverlayView *quittingView;
- (void)updateSelected:(BOOL)theSelected;
- (void)updateApp:(NSRunningApplication *)theApp;
- (void)updateHidden:(BOOL)theHidden;
- (void)updateQuitting:(BOOL)theQuitting;
- (void)updateForceQuitting:(BOOL)theForceQuitting;
@end
================================================
FILE: Slate/SwitchAppView.m
================================================
//
// SwitchAppView.m
// Slate
//
// Created by Jigish Patel on 3/22/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "SwitchAppView.h"
#import "SlateConfig.h"
#import "Constants.h"
#import "SwitchAppQuittingOverlayView.h"
@implementation SwitchAppView
static const float HIDDEN_ALPHA = 0.2;
static const float SHOWN_ALPHA = 1.0;
static const float STROKE_WIDTH = -5;
@synthesize selected, hidden, quitting, forceQuitting, app, iconView, textField, quittingView;
static NSColor *switchFontColor = nil;
static NSFont *switchFont = nil;
static float iconSize = -1;
static float iconPadding = -1;
static float switchFontHeight = -1;
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
selected = NO;
hidden = NO;
quitting = NO;
forceQuitting = NO;
iconView = nil;
quittingView = nil;
[self setWantsLayer:YES];
if (switchFontColor == nil) {
NSArray *fColorArr = [[SlateConfig getInstance] getArrayConfig:SWITCH_FONT_COLOR];
if ([fColorArr count] < 4) fColorArr = [SWITCH_FONT_COLOR_DEFAULT componentsSeparatedByString:SEMICOLON];
switchFontColor = [NSColor colorWithDeviceRed:[[fColorArr objectAtIndex:0] floatValue]/255.0
green:[[fColorArr objectAtIndex:1] floatValue]/255.0
blue:[[fColorArr objectAtIndex:2] floatValue]/255.0
alpha:[[fColorArr objectAtIndex:3] floatValue]];
}
if (switchFont == nil) {
switchFont = [NSFont fontWithName:[[SlateConfig getInstance] getConfig:SWITCH_FONT_NAME] size:[[SlateConfig getInstance] getFloatConfig:SWITCH_FONT_SIZE]];
}
if (iconSize < 0) {
iconSize = [[SlateConfig getInstance] getFloatConfig:SWITCH_ICON_SIZE];
}
if (iconPadding < 0) {
iconPadding = [[SlateConfig getInstance] getFloatConfig:SWITCH_ICON_PADDING];
}
if (switchFontHeight < 0) {
NSString *testString = @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont fontWithName:[[SlateConfig getInstance] getConfig:SWITCH_FONT_NAME]
size:[[SlateConfig getInstance] getFloatConfig:SWITCH_FONT_SIZE]],
NSFontAttributeName, nil];
NSSize size = [testString sizeWithAttributes:attributes];
switchFontHeight = size.height;
}
}
return self;
}
- (void)updateSelected:(BOOL)theSelected {
[self setSelected:theSelected];
[self setNeedsDisplay:YES];
}
- (void)updateApp:(NSRunningApplication *)theApp {
[self setApp:theApp];
[self setHidden:[theApp isHidden]];
if (iconView != nil) {
[iconView removeFromSuperview];
}
if ([[SlateConfig getInstance] getBoolConfig:SWITCH_SHOW_TITLES] && [[[SlateConfig getInstance] getConfig:SWITCH_ORIENTATION] isEqualToString:SWITCH_ORIENTATION_HORIZONTAL]) {
iconView = [[NSImageView alloc] initWithFrame:NSMakeRect(iconPadding, self.frame.size.height - iconSize - iconPadding*2, iconSize, iconSize)];
} else {
iconView = [[NSImageView alloc] initWithFrame:NSMakeRect(iconPadding, iconPadding, iconSize, iconSize)];
}
NSImage *icon = [app icon];
[icon setScalesWhenResized:YES];
[icon setSize:NSMakeSize(iconSize, iconSize)];
[iconView setImage:icon];
[iconView setAlphaValue:(hidden ? HIDDEN_ALPHA : SHOWN_ALPHA)];
[self addSubview:iconView];
if ([[SlateConfig getInstance] getBoolConfig:SWITCH_SHOW_TITLES]) {
if ([[[SlateConfig getInstance] getConfig:SWITCH_ORIENTATION] isEqualToString:SWITCH_ORIENTATION_VERTICAL]) {
textField = [[NSTextField alloc] initWithFrame:NSMakeRect(iconSize+iconPadding*2, self.frame.size.height/2 - switchFontHeight/2, self.frame.size.width - iconSize - iconPadding*2, switchFontHeight)];
} else {
textField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, iconPadding, iconSize+iconPadding*2, self.frame.size.height - iconSize - iconPadding*3)];
}
NSMutableAttributedString *theTitle = [[NSMutableAttributedString alloc] initWithString:[theApp localizedName]];
NSRange everything = NSMakeRange(0, [theTitle length]);
[theTitle addAttribute:NSStrokeWidthAttributeName value:[NSNumber numberWithFloat:STROKE_WIDTH] range:everything];
[theTitle setAlignment:NSCenterTextAlignment range:everything];
[textField setAttributedStringValue:theTitle];
[textField setSelectable:NO];
[textField setEditable:NO];
[textField setBezeled:NO];
[textField setBordered:NO];
[textField setAlignment:NSCenterTextAlignment];
[textField setBackgroundColor:[NSColor clearColor]];
[textField setFont:switchFont];
[textField setTextColor:switchFontColor];
[self addSubview:textField];
}
[self setNeedsDisplay:YES];
}
- (void)updateHidden:(BOOL)theHidden {
[self setHidden:theHidden];
[iconView setAlphaValue:(hidden ? HIDDEN_ALPHA : SHOWN_ALPHA)];
[self setNeedsDisplay:YES];
}
- (void)updateQuitting:(BOOL)theQuitting {
[self setQuitting:theQuitting];
if (quitting) {
quittingView = [[SwitchAppQuittingOverlayView alloc] initWithFrame:NSMakeRect(0, 0, [self frame].size.width, [self frame].size.height)];
[self addSubview:quittingView];
} else {
[quittingView removeFromSuperview];
}
[self setNeedsDisplay:YES];
}
- (void)updateForceQuitting:(BOOL)theForceQuitting {
[self setForceQuitting:theForceQuitting];
if (forceQuitting) {
quittingView = [[SwitchAppQuittingOverlayView alloc] initWithFrame:NSMakeRect(0, 0, [self frame].size.width, [self frame].size.height)];
[quittingView setForce:YES];
[self addSubview:quittingView];
} else {
[quittingView removeFromSuperview];
}
[self setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)dirtyRect {
NSColor *backgroundColor = [NSColor clearColor];
NSColor *borderColor = [NSColor clearColor];
if (selected) {
NSArray *bgColorArr = [[SlateConfig getInstance] getArrayConfig:SWITCH_SELECTED_BACKGROUND_COLOR];
if ([bgColorArr count] < 4) bgColorArr = [SWITCH_SELECTED_BACKGROUND_COLOR_DEFAULT componentsSeparatedByString:SEMICOLON];
backgroundColor = [NSColor colorWithDeviceRed:[[bgColorArr objectAtIndex:0] floatValue]/255.0
green:[[bgColorArr objectAtIndex:1] floatValue]/255.0
blue:[[bgColorArr objectAtIndex:2] floatValue]/255.0
alpha:[[bgColorArr objectAtIndex:3] floatValue]];
NSArray *borderColorArr = [[SlateConfig getInstance] getArrayConfig:SWITCH_SELECTED_BORDER_COLOR];
if ([borderColorArr count] < 4) bgColorArr = [SWITCH_SELECTED_BORDER_COLOR_DEFAULT componentsSeparatedByString:SEMICOLON];
borderColor = [NSColor colorWithDeviceRed:[[borderColorArr objectAtIndex:0] floatValue]/255.0
green:[[borderColorArr objectAtIndex:1] floatValue]/255.0
blue:[[borderColorArr objectAtIndex:2] floatValue]/255.0
alpha:[[borderColorArr objectAtIndex:3] floatValue]];
}
float borderSize = [[SlateConfig getInstance] getFloatConfig:SWITCH_SELECTED_BORDER_SIZE];
float cornerSize = [[SlateConfig getInstance] getFloatConfig:SWITCH_ROUNDED_CORNER_SIZE];
[[NSGraphicsContext currentContext] saveGraphicsState];
[[NSGraphicsContext currentContext] setShouldAntialias:YES];
if (borderSize > 0 && selected) {
[backgroundColor set];
[NSBezierPath setDefaultLineWidth:1.0];
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:NSMakeRect([self bounds].origin.x+borderSize/2,
[self bounds].origin.y+borderSize/2,
[self bounds].size.width-borderSize,
[self bounds].size.height-borderSize)
xRadius:cornerSize
yRadius:cornerSize];
[path fill];
[borderColor set];
[NSBezierPath setDefaultLineWidth:borderSize];
path = [NSBezierPath bezierPathWithRoundedRect:NSMakeRect([self bounds].origin.x+borderSize/2,
[self bounds].origin.y+borderSize/2,
[self bounds].size.width-borderSize,
[self bounds].size.height-borderSize)
xRadius:cornerSize
yRadius:cornerSize];
[path stroke];
} else {
[backgroundColor set];
[NSBezierPath setDefaultLineWidth:1.0];
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:[self bounds] xRadius:cornerSize yRadius:cornerSize];
[path fill];
}
/*if (borderSize > 0 && selected) {
[borderColor set];
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:[self bounds] xRadius:cornerSize yRadius:cornerSize];
[path fill];
[backgroundColor set];
path = [NSBezierPath bezierPathWithRoundedRect:NSMakeRect([self bounds].origin.x+borderSize,
[self bounds].origin.y+borderSize,
[self bounds].size.width-borderSize*2,
[self bounds].size.height-borderSize*2)
xRadius:cornerSize
yRadius:cornerSize];
[path fill];
} else {
[backgroundColor set];
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:[self bounds] xRadius:cornerSize yRadius:cornerSize];
[path fill];
}*/
[[NSGraphicsContext currentContext] restoreGraphicsState];
}
@end
================================================
FILE: Slate/SwitchOperation.h
================================================
//
// SwitchOperation.h
// Slate
//
// Created by Jigish Patel on 3/9/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "Operation.h"
#import
@interface SwitchOperation : Operation {
NSMutableArray *switchers;
NSMutableArray *switchersToViews;
UInt32 modifiers;
UInt32 backKeyCode;
EventHotKeyRef backHotKeyRef;
UInt32 quitKeyCode;
EventHotKeyRef quitHotKeyRef;
UInt32 fquitKeyCode;
EventHotKeyRef fquitHotKeyRef;
UInt32 hideKeyCode;
EventHotKeyRef hideHotKeyRef;
}
@property (assign) UInt32 modifiers;
- (void)activateSwitchKey:(EventHotKeyID)key isRepeat:(BOOL)isRepeat;
- (BOOL)modifiersChanged:(UInt32)was new:(UInt32)new;
+ (id)switchOperation;
+ (id)switchOperationFromString:(NSString *)switchOperation;
@end
================================================
FILE: Slate/SwitchOperation.m
================================================
//
// SwitchOperation.m
// Slate
//
// Created by Jigish Patel on 3/9/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "SwitchOperation.h"
#import "SwitchWindow.h"
#import "SwitchView.h"
#import "SwitchAppView.h"
#import "SlateLogger.h"
#import "SlateConfig.h"
#import "Constants.h"
#import "RunningApplications.h"
#import "Binding.h"
#import "StringTokenizer.h"
@interface SwitchOperation() {
NSArray *apps;
NSMutableArray *appsToQuit;
NSMutableArray *appsToForceQuit;
NSInteger currentApp;
}
@end
@implementation SwitchOperation
static const NSString *DEFAULT_BACK_KEY = @"`";
static const NSString *DEFAULT_QUIT_KEY = @"q";
static const NSString *DEFAULT_FQUIT_KEY = @"f";
static const NSString *DEFAULT_HIDE_KEY = @"h";
@synthesize modifiers;
- (id)init {
self = [super init];
if (self) {
backKeyCode = [[[Binding asciiToCodeDict] objectForKey:DEFAULT_BACK_KEY] unsignedIntValue];
quitKeyCode = [[[Binding asciiToCodeDict] objectForKey:DEFAULT_QUIT_KEY] unsignedIntValue];
fquitKeyCode = [[[Binding asciiToCodeDict] objectForKey:DEFAULT_FQUIT_KEY] unsignedIntValue];
hideKeyCode = [[[Binding asciiToCodeDict] objectForKey:DEFAULT_HIDE_KEY] unsignedIntValue];
appsToQuit = [NSMutableArray array];
appsToForceQuit = [NSMutableArray array];
apps = nil;
currentApp = -1;
switchers = [NSMutableArray array];
switchersToViews = [NSMutableArray array];
}
return self;
}
- (id)initWithOptions:(NSString *)_options {
self = [super init];
if (self) {
backKeyCode = [[[Binding asciiToCodeDict] objectForKey:DEFAULT_BACK_KEY] unsignedIntValue];
quitKeyCode = [[[Binding asciiToCodeDict] objectForKey:DEFAULT_QUIT_KEY] unsignedIntValue];
fquitKeyCode = [[[Binding asciiToCodeDict] objectForKey:DEFAULT_FQUIT_KEY] unsignedIntValue];
hideKeyCode = [[[Binding asciiToCodeDict] objectForKey:DEFAULT_HIDE_KEY] unsignedIntValue];
NSMutableArray *optionsArr = [NSMutableArray array];
[StringTokenizer tokenize:_options into:optionsArr];
for (NSString *option in optionsArr) {
NSArray *optionTokens = [option componentsSeparatedByString:COLON];
if ([optionTokens count] != 2) continue;
NSString *keyName = [optionTokens objectAtIndex:0];
NSString *keyValue = [optionTokens objectAtIndex:1];
NSNumber *keyCode = [[Binding asciiToCodeDict] objectForKey:keyValue];
if (keyCode == nil) continue;
if ([keyName isEqualToString:BACK]) {
backKeyCode = [keyCode unsignedIntValue];
} else if ([keyName isEqualToString:QUIT]) {
quitKeyCode = [keyCode unsignedIntValue];
} else if ([keyName isEqualToString:FORCE_QUIT]) {
fquitKeyCode = [keyCode unsignedIntValue];
} else if ([keyName isEqualToString:HIDE]) {
hideKeyCode = [keyCode unsignedIntValue];
}
}
appsToQuit = [NSMutableArray array];
appsToForceQuit = [NSMutableArray array];
apps = nil;
currentApp = -1;
switchers = [NSMutableArray array];
switchersToViews = [NSMutableArray array];
}
return self;
}
- (BOOL)doOperation {
SlateLogger(@"----------------- Begin Switch Operation -----------------");
ScreenWrapper *sw = [[ScreenWrapper alloc] init];
BOOL success = [self doOperationWithAccessibilityWrapper:nil screenWrapper:sw];
SlateLogger(@"----------------- End Switch Operation -----------------");
return success;
}
- (BOOL)doOperationWithAccessibilityWrapper:(AccessibilityWrapper *)aw screenWrapper:(ScreenWrapper *)sw {
[self evalOptionsWithAccessibilityWrapper:aw screenWrapper:sw];
apps = [NSArray arrayWithArray:[[RunningApplications getInstance] apps]];
for (NSRunningApplication *app in apps) {
[appsToQuit addObject:[NSNumber numberWithBool:NO]];
[appsToForceQuit addObject:[NSNumber numberWithBool:NO]];
}
float iconSize = [[SlateConfig getInstance] getFloatConfig:SWITCH_ICON_SIZE];
float iconPadding = [[SlateConfig getInstance] getFloatConfig:SWITCH_ICON_PADDING];
float iconViewSize = iconSize + 2*iconPadding;
float selectedPadding = [[SlateConfig getInstance] getFloatConfig:SWITCH_SELECTED_PADDING];
NSInteger switcherId = 0;
BOOL showTitle = [[SlateConfig getInstance] getBoolConfig:SWITCH_SHOW_TITLES];
float titleHeight = 0;
float titleWidth = 0;
if (showTitle) {
NSString *testString = @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont fontWithName:[[SlateConfig getInstance] getConfig:SWITCH_FONT_NAME]
size:[[SlateConfig getInstance] getFloatConfig:SWITCH_FONT_SIZE]],
NSFontAttributeName, nil];
NSSize size = [testString sizeWithAttributes:attributes];
titleHeight = size.height + iconPadding;
if ([[[SlateConfig getInstance] getConfig:SWITCH_ORIENTATION] isEqualToString:SWITCH_ORIENTATION_VERTICAL]) {
// loop through all apps and check size to set title width
for (NSRunningApplication *app in apps) {
size = [[app localizedName] sizeWithAttributes:attributes];
if (titleWidth < size.width) titleWidth = size.width;
if (titleHeight < size.height) titleHeight = size.height;
}
}
}
for (NSScreen *screen in [sw screens]) {
NSRect frame;
if ([[[SlateConfig getInstance] getConfig:SWITCH_ORIENTATION] isEqualToString:SWITCH_ORIENTATION_VERTICAL]) {
frame = NSMakeRect([screen frame].size.width/2 - (iconViewSize + titleWidth)/2 - selectedPadding,
[screen frame].size.height/2 - ([apps count]*iconViewSize)/2 - selectedPadding,
iconViewSize + titleWidth + selectedPadding*2,
[apps count]*iconViewSize + selectedPadding*2);
} else {
frame = NSMakeRect([screen frame].size.width/2 - ([apps count]*iconViewSize)/2 - selectedPadding,
[screen frame].size.height/2 - (iconViewSize + titleHeight)/2 - selectedPadding,
[apps count]*iconViewSize + selectedPadding*2,
iconViewSize + titleHeight + selectedPadding*2);
}
NSWindow *window = [[SwitchWindow alloc] initWithContentRect:frame
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO
screen:screen];
[window setReleasedWhenClosed:NO];
[window setOpaque:NO];
[window setBackgroundColor:[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.0]];
[window makeKeyAndOrderFront:NSApp];
[window setLevel:(NSScreenSaverWindowLevel - 1)];
SwitchView *view = [[SwitchView alloc] initWithFrame:frame];
[window setContentView:view];
NSWindowController *wc = [[NSWindowController alloc] initWithWindow:window];
[switchers addObject:wc];
[switchersToViews addObject:[NSMutableArray array]];
NSInteger i = 0;
for (NSRunningApplication *app in apps) {
SwitchAppView *appView;
if ([[[SlateConfig getInstance] getConfig:SWITCH_ORIENTATION] isEqualToString:SWITCH_ORIENTATION_VERTICAL]) {
appView = [[SwitchAppView alloc] initWithFrame:NSMakeRect(selectedPadding, ([apps count] - i - 1)*iconViewSize + selectedPadding, iconViewSize + titleWidth, iconViewSize)];
} else {
appView = [[SwitchAppView alloc] initWithFrame:NSMakeRect(i*iconViewSize + selectedPadding, selectedPadding, iconViewSize, iconViewSize + titleHeight)];
}
[appView updateApp:app];
[(NSView *)[[wc window] contentView] addSubview:appView];
[[switchersToViews objectAtIndex:switcherId] addObject:appView];
i++;
}
currentApp = [apps count] > 0 ? 1 : 0;
[[[switchersToViews objectAtIndex:switcherId] objectAtIndex:currentApp] setSelected:YES];
switcherId++;
}
EventHotKeyID backHotKeyID;
backHotKeyID.signature = *[@"switchKeyBack" cStringUsingEncoding:NSASCIIStringEncoding];
backHotKeyID.id = 1000;
RegisterEventHotKey(backKeyCode, modifiers, backHotKeyID, GetEventMonitorTarget(), 0, &backHotKeyRef);
EventHotKeyID quitHotKeyID;
quitHotKeyID.signature = *[@"switchKeyQuit" cStringUsingEncoding:NSASCIIStringEncoding];
quitHotKeyID.id = 1001;
RegisterEventHotKey(quitKeyCode, modifiers, quitHotKeyID, GetEventMonitorTarget(), 0, &quitHotKeyRef);
EventHotKeyID fquitHotKeyID;
fquitHotKeyID.signature = *[@"switchKeyFQuit" cStringUsingEncoding:NSASCIIStringEncoding];
fquitHotKeyID.id = 1002;
RegisterEventHotKey(fquitKeyCode, modifiers, fquitHotKeyID, GetEventMonitorTarget(), 0, &fquitHotKeyRef);
EventHotKeyID hideHotKeyID;
hideHotKeyID.signature = *[@"switchKeyHide" cStringUsingEncoding:NSASCIIStringEncoding];
hideHotKeyID.id = 1003;
RegisterEventHotKey(hideKeyCode, modifiers, hideHotKeyID, GetEventMonitorTarget(), 0, &hideHotKeyRef);
return YES;
}
- (BOOL)testOperation {
return YES;
}
- (void)activateSwitchKey:(EventHotKeyID)key isRepeat:(BOOL)isRepeat {
SlateLogger(@"Activate Switch Key");
NSInteger selectedApp = currentApp+1;
if (key.id == 1000) {
selectedApp = currentApp == 0 ? (isRepeat && [[SlateConfig getInstance] getBoolConfig:SWITCH_STOP_REPEAT_AT_EDGE] ? 0 : [apps count] - 1) : currentApp - 1;
SlateLogger(@" Back: %ld",selectedApp);
} else if (key.id == 1001) {
if (isRepeat) return;
SlateLogger(@" Quit: %ld",currentApp);
if ([[appsToForceQuit objectAtIndex:currentApp] boolValue]) {
[appsToForceQuit replaceObjectAtIndex:currentApp withObject:[NSNumber numberWithBool:NO]];
for (NSInteger switcherId = 0; switcherId < [switchers count]; switcherId++) {
[[[switchersToViews objectAtIndex:switcherId] objectAtIndex:currentApp] updateForceQuitting:NO];
}
}
[appsToQuit replaceObjectAtIndex:currentApp withObject:[NSNumber numberWithBool:![[appsToQuit objectAtIndex:currentApp] boolValue]]];
for (NSInteger switcherId = 0; switcherId < [switchers count]; switcherId++) {
[[[switchersToViews objectAtIndex:switcherId] objectAtIndex:currentApp] updateQuitting:[[appsToQuit objectAtIndex:currentApp] boolValue]];
}
return;
} else if (key.id == 1002) {
if (isRepeat) return;
SlateLogger(@" Force Quit: %ld",currentApp);
if ([[appsToQuit objectAtIndex:currentApp] boolValue]) {
[appsToQuit replaceObjectAtIndex:currentApp withObject:[NSNumber numberWithBool:NO]];
for (NSInteger switcherId = 0; switcherId < [switchers count]; switcherId++) {
[[[switchersToViews objectAtIndex:switcherId] objectAtIndex:currentApp] updateQuitting:NO];
}
}
[appsToForceQuit replaceObjectAtIndex:currentApp withObject:[NSNumber numberWithBool:![[appsToForceQuit objectAtIndex:currentApp] boolValue]]];
for (NSInteger switcherId = 0; switcherId < [switchers count]; switcherId++) {
[[[switchersToViews objectAtIndex:switcherId] objectAtIndex:currentApp] updateForceQuitting:[[appsToForceQuit objectAtIndex:currentApp] boolValue]];
}
return;
} else if (key.id == 1003) {
if (isRepeat) return;
NSRunningApplication *cApp = [apps objectAtIndex:currentApp];
if ([cApp isHidden]) {
SlateLogger(@" UnHide: %ld",currentApp);
if ([[SlateConfig getInstance] getBoolConfig:SWITCH_ONLY_FOCUS_MAIN_WINDOW]) {
[AccessibilityWrapper focusMainWindow:cApp];
} else {
[AccessibilityWrapper focusApp:cApp];
}
for (NSInteger switcherId = 0; switcherId < [switchers count]; switcherId++) {
[[[switchersToViews objectAtIndex:switcherId] objectAtIndex:currentApp] updateHidden:NO];
}
} else {
SlateLogger(@" Hide: %ld",currentApp);
[cApp hide];
for (NSInteger switcherId = 0; switcherId < [switchers count]; switcherId++) {
[[[switchersToViews objectAtIndex:switcherId] objectAtIndex:currentApp] updateHidden:YES];
}
}
return;
} else if (selectedApp >= [apps count]) {
selectedApp = isRepeat && [[SlateConfig getInstance] getBoolConfig:SWITCH_STOP_REPEAT_AT_EDGE] ? [apps count] - 1 : 0;
}
for (NSInteger switcherId = 0; switcherId < [switchers count]; switcherId++) {
SlateLogger(@"SELECTING %ld,%ld,%ld",switcherId,currentApp,selectedApp);
[[[switchersToViews objectAtIndex:switcherId] objectAtIndex:currentApp] updateSelected:NO];
[[[switchersToViews objectAtIndex:switcherId] objectAtIndex:selectedApp] updateSelected:YES];
}
currentApp = selectedApp;
}
- (BOOL)modifiersChanged:(UInt32)was new:(UInt32)new {
if (was != new) {
[self killSwitchers];
if ([[apps objectAtIndex:currentApp] isHidden]) { [[apps objectAtIndex:currentApp] unhide]; }
if ([[SlateConfig getInstance] getBoolConfig:SWITCH_ONLY_FOCUS_MAIN_WINDOW]) {
[AccessibilityWrapper focusMainWindow:[apps objectAtIndex:currentApp]];
} else {
[AccessibilityWrapper focusApp:[apps objectAtIndex:currentApp]];
}
return YES;
}
return NO;
}
- (void)killSwitchers {
for (NSWindowController *controller in switchers) {
[controller close];
}
for (NSMutableArray *views in switchersToViews) {
for (NSView *view in views) {
[view removeFromSuperview];
}
[views removeAllObjects];
}
UnregisterEventHotKey(backHotKeyRef);
UnregisterEventHotKey(quitHotKeyRef);
UnregisterEventHotKey(fquitHotKeyRef);
UnregisterEventHotKey(hideHotKeyRef);
NSInteger i = 0;
for (NSNumber *appToQuit in appsToQuit) {
if ([appToQuit boolValue]) {
SlateLogger(@"Quitting: '%@'",[[apps objectAtIndex:i] localizedName]);
[(NSRunningApplication *)[apps objectAtIndex:i] terminate];
}
i++;
}
[appsToQuit removeAllObjects];
i = 0;
for (NSNumber *appToForceQuit in appsToForceQuit) {
if ([appToForceQuit boolValue]) {
SlateLogger(@"Force Quitting: '%@'",[[apps objectAtIndex:i] localizedName]);
[(NSRunningApplication *)[apps objectAtIndex:i] forceTerminate];
}
i++;
}
[appsToForceQuit removeAllObjects];
[switchers removeAllObjects];
[switchersToViews removeAllObjects];
}
- (void)parseOption:(NSString *)name value:(id)value {
if (value == nil) { return; }
if (![value isKindOfClass:[NSString class]]) {
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", name, value] userInfo:nil]);
}
if ([name isEqualToString:OPT_BACK]) {
backKeyCode = [[[Binding asciiToCodeDict] objectForKey:value] unsignedIntValue];
} else if ([name isEqualToString:OPT_QUIT]) {
quitKeyCode = [[[Binding asciiToCodeDict] objectForKey:value] unsignedIntValue];
} else if ([name isEqualToString:OPT_FORCE_QUIT]) {
fquitKeyCode = [[[Binding asciiToCodeDict] objectForKey:value] unsignedIntValue];
} else if ([name isEqualToString:OPT_HIDE]) {
hideKeyCode = [[[Binding asciiToCodeDict] objectForKey:value] unsignedIntValue];
}
}
+ (id)switchOperation {
return [[SwitchOperation alloc] init];
}
+ (id)switchOperationFromString:(NSString *)switchOperation {
// switch option+
// options:
// back:[key code]
// quit:[key code]
// force-quit:[key code]
// hide:[key code]
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:switchOperation into:tokens maxTokens:2];
Operation *op = nil;
if ([tokens count] > 1) {
op = [[SwitchOperation alloc] initWithOptions:[tokens objectAtIndex:1]];
} else {
op = [[SwitchOperation alloc] init];
}
return op;
}
@end
================================================
FILE: Slate/SwitchView.h
================================================
//
// SwitchView.h
// Slate
//
// Created by Jigish Patel on 3/9/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface SwitchView : NSView
@end
================================================
FILE: Slate/SwitchView.m
================================================
//
// SwitchView.m
// Slate
//
// Created by Jigish Patel on 3/9/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "SwitchView.h"
#import "SlateConfig.h"
#import "Constants.h"
@implementation SwitchView
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) [self setWantsLayer:YES];
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
NSArray *bgColorArr = [[SlateConfig getInstance] getArrayConfig:SWITCH_BACKGROUND_COLOR];
if ([bgColorArr count] < 4) bgColorArr = [SWITCH_BACKGROUND_COLOR_DEFAULT componentsSeparatedByString:SEMICOLON];
NSColor *backgroundColor = [NSColor colorWithDeviceRed:[[bgColorArr objectAtIndex:0] floatValue]/255.0
green:[[bgColorArr objectAtIndex:1] floatValue]/255.0
blue:[[bgColorArr objectAtIndex:2] floatValue]/255.0
alpha:[[bgColorArr objectAtIndex:3] floatValue]];
[backgroundColor set];
float cornerSize = [[SlateConfig getInstance] getFloatConfig:SWITCH_ROUNDED_CORNER_SIZE];
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:[self bounds] xRadius:cornerSize yRadius:cornerSize];
[path fill];
}
@end
================================================
FILE: Slate/SwitchWindow.h
================================================
//
// SwitchWindow.h
// Slate
//
// Created by Jigish Patel on 3/9/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface SwitchWindow : NSWindow
@end
================================================
FILE: Slate/SwitchWindow.m
================================================
//
// SwitchWindow.m
// Slate
//
// Created by Jigish Patel on 3/9/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "SwitchWindow.h"
@implementation SwitchWindow
- (BOOL)canBecomeKeyWindow {
return YES;
}
@end
================================================
FILE: Slate/ThrowOperation.h
================================================
//
// ThrowOperation.h
// Slate
//
// Created by Jigish Patel on 1/20/13.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "MoveOperation.h"
@interface ThrowOperation : MoveOperation
+ (id)throwOperation;
+ (id)throwOperationFromString:(NSString *)throwOperation;
@end
================================================
FILE: Slate/ThrowOperation.m
================================================
//
// ThrowOperation.m
// Slate
//
// Created by Jigish Patel on 1/20/13.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "ThrowOperation.h"
#import "Constants.h"
#import "StringTokenizer.h"
#import "SlateLogger.h"
@implementation ThrowOperation
- (NSArray *)requiredOptions {
return [NSArray arrayWithObjects:OPT_SCREEN, nil];
}
- (void)beforeInitOptions {
// throw is basically an alias for move with some reasonable defaults for x, y, width, and height
[self setTopLeft:[[ExpressionPoint alloc] initWithX:@"screenOriginX" y:@"screenOriginY"]];
[self setDimensions:[[ExpressionPoint alloc] initWithX:@"windowSizeX" y:@"windowSizeY"]];
}
+ (id)throwOperation {
return [[ThrowOperation alloc] init];
}
+ (id)throwOperationFromString:(NSString *)throwOperation {
// throw
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:throwOperation into:tokens];
if ([tokens count] < 2) {
SlateLogger(@"ERROR: Invalid Parameters '%@'", throwOperation);
@throw([NSException exceptionWithName:@"Invalid Parameters" reason:[NSString stringWithFormat:@"Invalid Parameters in '%@'. Throw operations require the following format: 'throw screen [optional:style]'", throwOperation] userInfo:nil]);
}
NSString *tl = @"screenOriginX;screenOriginY";
NSString *dim = @"windowSizeX;windowSizeY";
if ([tokens count] >= 3) {
NSString *style = [tokens objectAtIndex:2];
if ([style isEqualToString:RESIZE]) {
tl = @"screenOriginX;screenOriginY";
dim = @"screenSizeX;screenSizeY";
} else if ([style hasPrefix:RESIZE_WITH_VALUE]) {
tl = @"screenOriginX;screenOriginY";
dim = [[style componentsSeparatedByString:COLON] objectAtIndex:1];
} else if ([style isEqualToString:NORESIZE]) {
// do nothing
} else {
SlateLogger(@"ERROR: Unrecognized style '%@'", style);
@throw([NSException exceptionWithName:@"Unrecognized Style" reason:[NSString stringWithFormat:@"Unrecognized style '%@' in '%@'", style, throwOperation] userInfo:nil]);
}
}
Operation *op = [[MoveOperation alloc] initWithTopLeft:tl dimensions:dim monitor:[tokens objectAtIndex:1]];
return op;
}
@end
================================================
FILE: Slate/UndoOperation.h
================================================
//
// UndoOperation.h
// Slate
//
// Created by Jigish Patel on 11/27/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "ActivateSnapshotOperation.h"
@interface UndoOperation : ActivateSnapshotOperation
+ (id)undoOperation;
+ (id)undoOperationFromString:(NSString *)unused;
@end
================================================
FILE: Slate/UndoOperation.m
================================================
//
// UndoOperation.m
// Slate
//
// Created by Jigish Patel on 11/27/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "UndoOperation.h"
#import "Constants.h"
#import "SlateAppDelegate.h"
@implementation UndoOperation
- (NSArray *)requiredOptions {
return [NSArray array];
}
+ (id)undoOperation {
[(SlateAppDelegate *)[NSApp delegate] setHasUndoOperation:YES];
return [[UndoOperation alloc] initWithName:UNDO_SNAPSHOT options:DELETE];
}
+ (id)undoOperationFromString:(NSString *)unused {
return [UndoOperation undoOperation];
}
@end
================================================
FILE: Slate/VisibilityOperation.h
================================================
//
// VisibilityOperation.h
// Slate
//
// Created by Jigish Patel on 10/7/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "Operation.h"
typedef enum {
VisibilityOperationTypeUnknown,
VisibilityOperationTypeShow,
VisibilityOperationTypeHide,
VisibilityOperationTypeToggle
} VisibilityOperationType;
@interface VisibilityOperation : Operation {
@private
VisibilityOperationType type;
NSArray *apps;
}
@property (assign) VisibilityOperationType type;
@property NSArray *apps;
- (id)initWithType:(VisibilityOperationType)theType apps:(NSArray *)theApps;
- (void)applyVisibilityToApp:(NSRunningApplication *)app;
+ (id)visibilityOperation;
+ (id)visibilityOperationFromString:(NSString *)visibilityOperation;
@end
================================================
FILE: Slate/VisibilityOperation.m
================================================
//
// VisibilityOperation.m
// Slate
//
// Created by Jigish Patel on 10/7/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "VisibilityOperation.h"
#import "StringTokenizer.h"
#import "SlateLogger.h"
#import "Constants.h"
#import "RunningApplications.h"
@implementation VisibilityOperation
@synthesize type, apps;
- (id)init {
self = [super init];
if (self) {
[self setType:VisibilityOperationTypeUnknown];
[self setApps:nil];
}
return self;
}
- (id)initWithType:(VisibilityOperationType)theType apps:(NSArray *)theApps {
self = [super init];
if (self) {
[self setType:theType];
[self setApps:theApps];
}
return self;
}
- (BOOL)doOperation {
SlateLogger(@"----------------- Begin Visibility Operation -----------------");
// We don't use the passed in AccessibilityWrapper or ScreenWrapper so they are nil. No need to waste time creating them here.
BOOL success = [self doOperationWithAccessibilityWrapper:nil screenWrapper:nil];
SlateLogger(@"----------------- End Visibility Operation -----------------");
return success;
}
- (void)applyVisibilityToApp:(NSRunningApplication *)app {
if ([self type] == VisibilityOperationTypeShow) {
[app unhide];
} else if ([self type] == VisibilityOperationTypeHide) {
[app hide];
} else if ([self type] == VisibilityOperationTypeToggle) {
if ([app isHidden]) {
[app unhide];
} else {
[app hide];
}
}
}
- (BOOL)doOperationWithAccessibilityWrapper:(AccessibilityWrapper *)iamnil screenWrapper:(ScreenWrapper *)iamalsonil {
[self evalOptionsWithAccessibilityWrapper:iamnil screenWrapper:iamalsonil];
for (NSString *appName in [self apps]) {
NSRunningApplication *app = nil;
if ([CURRENT isEqualToString:appName]) {
app = [RunningApplications focusedApp];
} else if ([ALL isEqualToString:appName]) {
// run through ALL THE APPS
for (NSRunningApplication *theApp in [RunningApplications getInstance]) {
[self applyVisibilityToApp:theApp];
}
continue;
} else if ([appName hasPrefix:ALL_BUT]) {
NSString *skipApp = [StringTokenizer removeQuotes:[appName stringByReplacingOccurrencesOfString:ALL_BUT withString:EMPTY] quoteChars:[NSCharacterSet characterSetWithCharactersInString:QUOTES]];
// run through ALL THE APPS
if ([skipApp isEqualToString:CURRENT]) {
skipApp = [[RunningApplications focusedApp] localizedName];
}
for (NSRunningApplication *theApp in [RunningApplications getInstance]) {
if ([skipApp isEqualToString:[theApp localizedName]]) continue;
[self applyVisibilityToApp:theApp];
}
continue;
} else {
app = [[[RunningApplications getInstance] appNameToApp] objectForKey:appName];
}
[self applyVisibilityToApp:app];
}
return YES;
}
- (BOOL)testOperation {
if ([self type] == VisibilityOperationTypeUnknown)
@throw [NSException exceptionWithName:@"Unknown Type" reason:@"type" userInfo:nil];
return YES;
}
- (void)beforeInitOptions {
if ([SHOW isEqualToString:[self opName]]) {
[self setType:VisibilityOperationTypeShow];
} else if ([HIDE isEqualToString:[self opName]]) {
[self setType:VisibilityOperationTypeHide];
} else if ([TOGGLE isEqualToString:[self opName]]) {
[self setType:VisibilityOperationTypeToggle];
} else {
// should never happen
@throw([NSException exceptionWithName:@"Invalid Type" reason:[NSString stringWithFormat:@"Invalid visibility type '%@'", [self opName]] userInfo:nil]);
}
}
- (void)parseOption:(NSString *)name value:(id)value {
if (value == nil) { return; }
if ([name isEqualToString:OPT_APP]) {
if ([value isKindOfClass:[NSString class]]) {
[self setApps:[NSArray arrayWithObject:value]];
} else if ([value isKindOfClass:[NSArray class]]) {
[self setApps:value];
} else { // app should be a string or an array
@throw([NSException exceptionWithName:[NSString stringWithFormat:@"Invalid %@", name] reason:[NSString stringWithFormat:@"Invalid %@ '%@'", name, value] userInfo:nil]);
}
}
}
+ (id)visibilityOperation {
return [[VisibilityOperation alloc] init];
}
+ (id)visibilityOperationFromString:(NSString *)visibilityOperation {
// hide|show|toggle apps
NSMutableArray *tokens = [[NSMutableArray alloc] initWithCapacity:10];
[StringTokenizer tokenize:visibilityOperation into:tokens maxTokens:2];
if ([tokens count] < 2) {
SlateLogger(@"ERROR: Invalid Parameters '%@'", visibilityOperation);
@throw([NSException exceptionWithName:@"Invalid Parameters" reason:[NSString stringWithFormat:@"Invalid Parameters in '%@'. Visibility operations require the following format: 'hide|show|toggle apps'", visibilityOperation] userInfo:nil]);
}
VisibilityOperationType theType = VisibilityOperationTypeUnknown;
NSString *typeStr = [tokens objectAtIndex:0];
if ([SHOW isEqualToString:typeStr]) {
theType = VisibilityOperationTypeShow;
} else if ([HIDE isEqualToString:typeStr]) {
theType = VisibilityOperationTypeHide;
} else if ([TOGGLE isEqualToString:typeStr]) {
theType = VisibilityOperationTypeToggle;
}
NSString *appsStr = [tokens objectAtIndex:1];
NSArray *appsArrayWithQuotes = [appsStr componentsSeparatedByString:COMMA];
NSMutableArray *appsArray = [NSMutableArray array];
for (NSString *appWithQuotes in appsArrayWithQuotes) {
[appsArray addObject:[StringTokenizer removeQuotes:appWithQuotes quoteChars:[NSCharacterSet characterSetWithCharactersInString:QUOTES]]];
}
Operation *op = [[VisibilityOperation alloc] initWithType:theType apps:appsArray];
return op;
}
@end
================================================
FILE: Slate/WindowInfoView.h
================================================
//
// WindowInfoView.h
// Slate
//
// Created by Jigish Patel on 2/27/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface WindowInfoView : NSTextView {
NSDate *lastDraw;
}
- (void)genText;
@end
================================================
FILE: Slate/WindowInfoView.m
================================================
//
// WindowInfoView.m
// Slate
//
// Created by Jigish Patel on 2/27/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "WindowInfoView.h"
#import "AccessibilityWrapper.h"
#import "ScreenWrapper.h"
#import "SlateLogger.h"
#import "RunningApplications.h"
@implementation WindowInfoView
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setSelectable:YES];
[self genText];
lastDraw = [NSDate dateWithTimeIntervalSince1970:0];
}
return self;
}
- (void)viewWillDraw {
NSDate *now = [NSDate date];
if ([now timeIntervalSinceDate:lastDraw] < 5) { return; } // refresh every 5 seconds
lastDraw = now;
[self genText];
}
- (void)genText {
SlateLogger(@"WindowInfoView gen text.");
NSString *text = @"----------------- Screens -----------------\n";
ScreenWrapper *sw = [[ScreenWrapper alloc] init];
NSMutableArray *resolutions = [NSMutableArray array];
[sw getScreenResolutionStrings:resolutions];
for (NSInteger i = 0; i < [resolutions count]; i++) {
text = [text stringByAppendingFormat:@"Left To Right ID: %ld\n OS X ID: %ld\n Resolution: %@\n", [sw convertDefaultOrderToLeftToRightOrder:i], i, [resolutions objectAtIndex:i]];
}
text = [text stringByAppendingString:@"\n----------------- Windows -----------------\n" ];
for (NSRunningApplication *app in [RunningApplications getInstance]) {
NSString *appName = [app localizedName];
pid_t appPID = [app processIdentifier];
SlateLogger(@"I see application '%@' with pid '%d'", appName, appPID);
text = [text stringByAppendingFormat:@"\nApplication: %@\n", appName];
// Yes, I am aware that the following blocks are inefficient. Deal with it.
AXUIElementRef appRef = AXUIElementCreateApplication(appPID);
CFArrayRef windowsArrRef = [AccessibilityWrapper windowsInApp:appRef];
if (!windowsArrRef || CFArrayGetCount(windowsArrRef) == 0) continue;
CFMutableArrayRef windowsArr = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, windowsArrRef);
for (NSInteger i = 0; i < CFArrayGetCount(windowsArr); i++) {
SlateLogger(@" Printing Window: %@", [AccessibilityWrapper getTitle:CFArrayGetValueAtIndex(windowsArr, i)]);
NSString *title = [AccessibilityWrapper getTitle:CFArrayGetValueAtIndex(windowsArr, i)];
if ([title isEqualToString:@""]) continue;
AccessibilityWrapper *aw = [[AccessibilityWrapper alloc] initWithApp:appRef window:CFArrayGetValueAtIndex(windowsArr, i)];
NSSize size = [aw getCurrentSize];
NSPoint badTL = [aw getCurrentTopLeft];
NSInteger badScreenID = [sw getScreenIdForRect:NSMakeRect(badTL.x, badTL.y, size.width, size.height)];
NSInteger screenID = [sw convertDefaultOrderToLeftToRightOrder:badScreenID];
NSPoint tl = [sw convertTopLeftToScreenRelative:badTL screen:badScreenID];
text = [text stringByAppendingFormat:@" Window: '%@'\n Screen ID (Left to Right): %ld\n Size: (%ld, %ld)\n Top Left: (screenOriginX+%ld, screenOriginY+%ld)\n", title, screenID, (NSInteger)size.width, (NSInteger)size.height, (NSInteger)tl.x, (NSInteger)tl.y];
}
}
[self setString:text];
}
@end
================================================
FILE: Slate/WindowSnapshot.h
================================================
//
// WindowSnapshot.h
// Slate
//
// Created by Jigish Patel on 2/28/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface WindowSnapshot : NSObject {
@private
NSString *appName;
NSString *title;
NSPoint topLeft;
NSSize size;
}
@property NSString *appName;
@property NSString *title;
@property (assign) NSPoint topLeft;
@property (assign) NSSize size;
- (id)initWithAppName:(NSString *)theAppName title:(NSString *)theTitle topLeft:(NSPoint)theTopLeft size:(NSSize)theSize;
- (NSDictionary *)toDictionary;
+ (WindowSnapshot *)windowSnapshotFromDictionary:(NSDictionary *)dict;
@end
================================================
FILE: Slate/WindowSnapshot.m
================================================
//
// WindowSnapshot.m
// Slate
//
// Created by Jigish Patel on 2/28/12.
// Copyright 2012 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "WindowSnapshot.h"
#import "Constants.h"
@implementation WindowSnapshot
@synthesize appName, title, topLeft, size;
- (id)init {
self = [super init];
return self;
}
- (id)initWithAppName:(NSString *)theAppName title:(NSString *)theTitle topLeft:(NSPoint)theTopLeft size:(NSSize)theSize {
self = [self init];
if (self) {
[self setAppName:theAppName];
[self setTitle:theTitle];
[self setTopLeft:theTopLeft];
[self setSize:theSize];
}
return self;
}
- (NSDictionary *)toDictionary {
return [NSDictionary dictionaryWithObjectsAndKeys:
appName, APP_NAME,
title, TITLE,
[NSNumber numberWithFloat:topLeft.x], X,
[NSNumber numberWithFloat:topLeft.y], Y,
[NSNumber numberWithFloat:size.width], WIDTH,
[NSNumber numberWithFloat:size.height], HEIGHT, nil];
}
+ (WindowSnapshot *)windowSnapshotFromDictionary:(NSDictionary *)dict {
return [[WindowSnapshot alloc] initWithAppName:[dict objectForKey:APP_NAME]
title:[dict objectForKey:TITLE]
topLeft:NSMakePoint([[dict objectForKey:X] floatValue], [[dict objectForKey:Y] floatValue])
size:NSMakeSize([[dict objectForKey:WIDTH] floatValue], [[dict objectForKey:HEIGHT] floatValue])];
}
@end
================================================
FILE: Slate/WindowState.h
================================================
//
// WindowState.h
// Slate
//
// Created by Jigish Patel on 6/13/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
@interface WindowState : NSObject {
@private
pid_t appPID;
NSSize size;
NSPoint topLeft;
}
@property (assign) pid_t appPID;
@property (assign) NSSize size;
@property (assign) NSPoint topLeft;
- (id)init:(AccessibilityWrapper *)aw;
@end
================================================
FILE: Slate/WindowState.m
================================================
//
// WindowState.m
// Slate
//
// Created by Jigish Patel on 6/13/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import "AccessibilityWrapper.h"
#import "WindowState.h"
@implementation WindowState
@synthesize appPID;
@synthesize size;
@synthesize topLeft;
- (id)init {
self = [super init];
if (self) {
}
return self;
}
- (id)init:(AccessibilityWrapper *)aw {
self = [self init];
if (self && [aw inited]) {
[self setAppPID:[AccessibilityWrapper processIdentifierOfUIElement:[aw window]]];
[self setSize:[aw getCurrentSize]];
[self setTopLeft:[aw getCurrentTopLeft]];
}
return self;
}
- (id)copyWithZone:(NSZone *)zone {
WindowState *other = [[WindowState alloc] init];
[other setAppPID:[self appPID]];
[other setSize:[self size]];
[other setTopLeft:[self topLeft]];
return other;
}
- (BOOL)isEqual:(NSObject *)other {
if ([other isKindOfClass:[WindowState class]]) {
return [(WindowState *)other appPID] == [self appPID] && NSEqualSizes([(WindowState *)other size],[self size]) && NSEqualPoints([(WindowState *)other topLeft], [self topLeft]);
}
return NO;
}
- (NSUInteger)hash {
NSUInteger prime = 31;
NSUInteger result = prime * 1 + [self appPID];
result = prime * result + [self size].width;
result = prime * result + [self size].height;
result = prime * result + [self topLeft].x;
result = prime * result + [self topLeft].y;
return result;
}
@end
================================================
FILE: Slate/default.slate
================================================
# This is the default .slate file.
# If no ~/.slate file exists this is the file that will be used.
config defaultToCurrentScreen true
config nudgePercentOf screenSize
config resizePercentOf screenSize
# Resize Bindings
bind right:alt resize +10% +0
bind left:alt resize -10% +0
bind up:alt resize +0 -10%
bind down:alt resize +0 +10%
bind right:ctrl;alt resize -10% +0 bottom-right
bind left:ctrl;alt resize +10% +0 bottom-right
bind up:ctrl;alt resize +0 +10% bottom-right
bind down:ctrl;alt resize +0 -10% bottom-right
# Push Bindings
bind right:ctrl;cmd push right bar-resize:screenSizeX/3
bind left:ctrl;cmd push left bar-resize:screenSizeX/3
bind up:ctrl;cmd push up bar-resize:screenSizeY/2
bind down:ctrl;cmd push down bar-resize:screenSizeY/2
# Nudge Bindings
bind right:shift;alt nudge +10% +0
bind left:shift;alt nudge -10% +0
bind up:shift;alt nudge +0 -10%
bind down:shift;alt nudge +0 +10%
# Throw Bindings
bind 1:ctrl;alt throw 0 resize
bind 2:ctrl;alt throw 1 resize
bind 3:ctrl;alt throw 2 resize
bind right:ctrl;alt;cmd throw right resize
bind left:ctrl;alt;cmd throw left resize
bind up:ctrl;alt;cmd throw up resize
bind down:ctrl;alt;cmd throw down resize
# Focus Bindings
bind right:cmd focus right
bind left:cmd focus left
bind up:cmd focus up
bind down:cmd focus down
bind up:cmd;alt focus behind
bind down:cmd;alt focus behind
# Window Hints
bind esc:cmd hint
================================================
FILE: Slate/en.lproj/Credits.rtf
================================================
{\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf510
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\vieww10800\viewh8400\viewkind0
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720
\f0\b\fs24 \cf0 Links\
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
{\field{\*\fldinst{HYPERLINK "https://github.com/jigish/slate"}}{\fldrslt
\b0 \cf0 Home Page}}
\b0 \
{\field{\*\fldinst{HYPERLINK "https://github.com/jigish/slate/wiki/JavaScript-Configs"}}{\fldrslt JavaScript Configs}}
\b \
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720
\cf0 \
Development
\b0 \
Jigish Patel & {\field{\*\fldinst{HYPERLINK "https://github.com/jigish/slate/graphs/contributors"}}{\fldrslt Contributors\
\
}}
\b Utilising{\field{\*\fldinst{HYPERLINK "https://github.com/jigish/slate/graphs/contributors"}}{\fldrslt
\b0 \
JSONKit\
Sparkle}}
\b0 \
\
\b Licensing
\b0 \
GPLv3 \'a9 Jigish Patel 2011}
================================================
FILE: Slate/en.lproj/InfoPlist.strings
================================================
/* Localized versions of Info.plist keys */
================================================
FILE: Slate/en.lproj/MainMenu.xib
================================================
107011E5321821138.47569.00YESterminate:554terminate:564delegate495performMiniaturize:37arrangeInFront:39print:86runPageLayout:87clearRecentDocuments:127performClose:193toggleContinuousSpellChecking:222undo:223copy:224checkSpelling:225paste:226stopSpeaking:227cut:228showGuessPanel:230redo:231selectAll:232startSpeaking:233delete:235performZoom:240performFindPanelAction:241centerSelectionInVisibleArea:245toggleGrammarChecking:347toggleSmartInsertDelete:355toggleAutomaticQuoteSubstitution:356toggleAutomaticLinkDetection:357saveDocument:362saveDocumentAs:363revertDocumentToSaved:364runToolbarCustomizationPalette:365toggleToolbarShown:366hide:367hideOtherApplications:368unhideAllApplications:370newDocument:373openDocument:374raiseBaseline:426lowerBaseline:427copyFont:428subscript:429superscript:430tightenKerning:431underline:432orderFrontColorPanel:433useAllLigatures:434loosenKerning:435pasteFont:436unscript:437useStandardKerning:438useStandardLigatures:439turnOffLigatures:440turnOffKerning:441toggleAutomaticSpellingCorrection:456orderFrontSubstitutionsPanel:458toggleAutomaticDashSubstitution:461toggleAutomaticTextReplacement:463uppercaseWord:464capitalizeWord:467lowercaseWord:468pasteAsPlainText:486performFindPanelAction:487performFindPanelAction:488performFindPanelAction:489showHelp:493alignCenter:518pasteRuler:519toggleRuler:520alignRight:521copyRuler:522alignJustified:523alignLeft:524makeBaseWritingDirectionNatural:525makeBaseWritingDirectionLeftToRight:526makeBaseWritingDirectionRightToLeft:527makeTextWritingDirectionNatural:528makeTextWritingDirectionLeftToRight:529makeTextWritingDirectionRightToLeft:530addFontTrait:421addFontTrait:422modifyFont:423orderFrontFontPanel:424modifyFont:425statusMenu547windowInfo567configHelper626configHelperTextView651checkForUpdates:660value: automaticallyChecksForUpdatesvalue: automaticallyChecksForUpdatesvalueautomaticallyChecksForUpdatesNSConditionallySetsEnabled2674YES0YES-2File's Owner-1First Responder-3Application29YES19YES56YES217YES83YES81YES7580787282124YES77737911274125YES126205YES202198207214199203197206215218YES216YES200YES219201204220YES21321022120820957YES134150144129143131YES14513024YES92523923295YES296YES297298211YES212YES195196346348YES349YES350351354375YES376YES377YES388YES389390391392393394395396397YES398YES399YES400401402403404405YES406407408409410411YES412413414415YES416417418419420450YES451YES452453454457459460462465466485490YES491YES492494Slate App Delegate496YES497YES498499500501502503YES504505506507508YES509510511512513514515516517543YES553563236149565YES566YES609YES610611612613YES614YES652YES653YES647YES650649648655657659661665668YESYES-1.IBPluginDependency-2.IBPluginDependency-3.IBPluginDependency112.IBPluginDependency124.IBPluginDependency125.IBPluginDependency126.IBPluginDependency129.IBPluginDependency130.IBPluginDependency131.IBPluginDependency134.IBPluginDependency143.IBPluginDependency144.IBPluginDependency145.IBPluginDependency149.IBPluginDependency150.IBPluginDependency19.IBPluginDependency195.IBPluginDependency196.IBPluginDependency197.IBPluginDependency198.IBPluginDependency199.IBPluginDependency200.IBPluginDependency201.IBPluginDependency202.IBPluginDependency203.IBPluginDependency204.IBPluginDependency205.IBPluginDependency206.IBPluginDependency207.IBPluginDependency208.IBPluginDependency209.IBPluginDependency210.IBPluginDependency211.IBPluginDependency212.IBPluginDependency213.IBPluginDependency214.IBPluginDependency215.IBPluginDependency216.IBPluginDependency217.IBPluginDependency218.IBPluginDependency219.IBPluginDependency220.IBPluginDependency221.IBPluginDependency23.IBPluginDependency236.IBPluginDependency239.IBPluginDependency24.IBPluginDependency29.IBPluginDependency295.IBPluginDependency296.IBPluginDependency297.IBPluginDependency298.IBPluginDependency346.IBPluginDependency348.IBPluginDependency349.IBPluginDependency350.IBPluginDependency351.IBPluginDependency354.IBPluginDependency375.IBPluginDependency376.IBPluginDependency377.IBPluginDependency388.IBPluginDependency389.IBPluginDependency390.IBPluginDependency391.IBPluginDependency392.IBPluginDependency393.IBPluginDependency394.IBPluginDependency395.IBPluginDependency396.IBPluginDependency397.IBPluginDependency398.IBPluginDependency399.IBPluginDependency400.IBPluginDependency401.IBPluginDependency402.IBPluginDependency403.IBPluginDependency404.IBPluginDependency405.IBPluginDependency406.IBPluginDependency407.IBPluginDependency408.IBPluginDependency409.IBPluginDependency410.IBPluginDependency411.IBPluginDependency412.IBPluginDependency413.IBPluginDependency414.IBPluginDependency415.IBPluginDependency416.IBPluginDependency417.IBPluginDependency418.IBPluginDependency419.IBPluginDependency420.IBPluginDependency450.IBPluginDependency451.IBPluginDependency452.IBPluginDependency453.IBPluginDependency454.IBPluginDependency457.IBPluginDependency459.IBPluginDependency460.IBPluginDependency462.IBPluginDependency465.IBPluginDependency466.IBPluginDependency485.IBPluginDependency490.IBPluginDependency491.IBPluginDependency492.IBPluginDependency494.IBPluginDependency496.IBPluginDependency497.IBPluginDependency498.IBPluginDependency499.IBPluginDependency5.IBPluginDependency500.IBPluginDependency501.IBPluginDependency502.IBPluginDependency503.IBPluginDependency504.IBPluginDependency505.IBPluginDependency506.IBPluginDependency507.IBPluginDependency508.IBPluginDependency509.IBPluginDependency510.IBPluginDependency511.IBPluginDependency512.IBPluginDependency513.IBPluginDependency514.IBPluginDependency515.IBPluginDependency516.IBPluginDependency517.IBPluginDependency543.IBPluginDependency553.IBPluginDependency56.IBPluginDependency563.IBPluginDependency565.IBNSWindowAutoPositionCentersHorizontal565.IBNSWindowAutoPositionCentersVertical565.IBPluginDependency565.NSWindowTemplate.visibleAtLaunch566.IBPluginDependency57.IBPluginDependency609.IBPluginDependency610.CustomClassName610.IBPluginDependency611.IBPluginDependency612.IBPluginDependency613.IBNSWindowAutoPositionCentersHorizontal613.IBNSWindowAutoPositionCentersVertical613.IBPluginDependency613.NSWindowTemplate.visibleAtLaunch614.IBPluginDependency647.IBPluginDependency648.IBPluginDependency649.IBPluginDependency650.IBPluginDependency652.IBPluginDependency653.IBPluginDependency655.IBPluginDependency657.IBPluginDependency659.IBPluginDependency661.IBPluginDependency665.IBPluginDependency668.IBPluginDependency72.IBPluginDependency73.IBPluginDependency74.IBPluginDependency75.IBPluginDependency77.IBPluginDependency78.IBPluginDependency79.IBPluginDependency80.IBPluginDependency81.IBPluginDependency82.IBPluginDependency83.IBPluginDependency92.IBPluginDependencyYEScom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPluginWindowInfoViewcom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPlugincom.apple.InterfaceBuilder.CocoaPluginYESYES677YESConfigurationHelperViewNSViewIBProjectSource./Classes/ConfigurationHelperView.hSUUpdaterNSObjectcheckForUpdates:idcheckForUpdates:checkForUpdates:iddelegateiddelegatedelegateidIBProjectSource./Classes/SUUpdater.hSlateAppDelegateNSObjectYESYESconfigHelperconfigHelperTextViewstatusMenuwindowInfoYESNSWindowNSTextViewNSMenuNSWindowYESYESconfigHelperconfigHelperTextViewstatusMenuwindowInfoYESconfigHelperNSWindowconfigHelperTextViewNSTextViewstatusMenuNSMenuwindowInfoNSWindowIBProjectSource./Classes/SlateAppDelegate.hWindowInfoViewNSTextViewIBProjectSource./Classes/WindowInfoView.h0IBCocoaFrameworkcom.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3YES3YESYESNSMenuCheckmarkNSMenuMixedStateYES{11, 11}{10, 3}
================================================
FILE: Slate/initialize.js
================================================
(function(_controller, _info) {
var slate = window.slate = {
log: function() {
var msg = Array.prototype.slice.call(arguments, 0).join(" ");
return _controller.log(msg);
},
config: function(key, callback) {
if (_.isFunction(callback)) {
return _controller.configFunction(key, callback);
} else if (_.isString(callback) || _.isNumber(callback) || _.isBoolean(callback)) {
return _controller.configNative(key, callback);
} else if (_.isArray(callback)) {
return _controller.configNative(key, callback.join(';'));
}
throw "Invalid "+key+" "+callback;
},
configAll: function(configMap) {
for (key in configMap) {
slate.config(key, configMap[key]);
}
},
bind: function(key, callback, repeat) {
if (_.isFunction(callback)) {
return _controller.bindFunction(key, callback, repeat);
} else if (_.isObject(callback)) {
return _controller.bindNative(key, callback, repeat);
}
throw "bind failed, second parameter must be an operation or a function. was: "+callback;
},
bindAll: function(bindMap) {
for(key in bindMap) {
if (_.isArray(bindMap[key]) && _.size(bindMap[key]) >= 2) {
slate.bind(key, bindMap[key][0], bindMap[key][1]);
} else if (_.isArray(bindMap[key]) && _.size(bindMap[key]) == 1) {
slate.bind(key, bindMap[key][0]);
} else {
slate.bind(key, bindMap[key]);
}
}
},
operationFromString: function(opString) {
if (!_.isString(opString)) {
throw "Operation String must be a string. Was: "+opString;
}
return _controller.operationFromString(opString);
},
operation : function(name, opts) {
if (!_.isString(name)) {
throw "Operation name must be a string. Was: "+name;
}
if (opts !== undefined && !_.isObject(opts)) {
throw "Operation options must be undefined or a hash. Was: "+opts;
}
return _controller.operation(name, opts);
},
doOperation : function(name, opts) {
if (!_.isString(name)) {
throw "Operation name must be a string. Was: "+name;
}
if (opts !== undefined && !_.isObject(opts)) {
throw "Operation options must be undefined or a hash. Was: "+opts;
}
return _controller.doOperation(name, opts);
},
source : function(path) {
if (!_.isString(path)) {
throw "Source path must be a string. Was: "+path;
}
return _controller.source(path);
},
layout : function(name, hash) {
if (!_.isString(name)) {
throw "layout name must be a string. Was: "+path;
}
if (!_.isObject(hash)) {
throw "layout app hash should be a hash, was: "+path;
}
return _controller.layout(name, hash);
},
default : function(screenConfig, thething) {
if (!_.isNumber(screenConfig) && !_.isString(screenConfig) && !_.isArray(screenConfig)) {
throw "default screen config should be a number, string, or array, was: "+screenConfig;
}
if (thething !== undefined && thething !== null &&
(_.isObject(thething) || _.isFunction(thething) || _.isString(thething))) {
return _controller.default(screenConfig, thething);
}
throw "default action should be a function, operation, or string, was: "+thething;
},
shell : function(commandAndArgs, wait, path) {
if (!_.isString(commandAndArgs)) {
throw "shell command should be a string, was: "+commandAndArgs;
}
if (path === null) { path = undefined; }
if (path !== undefined && !_.isString(path)) {
throw "path should be undefined or a string, was: "+path;
}
if (wait === null || wait === undefined) { wait = false; }
if (!_.isBoolean(wait)) {
throw "wait should be a boolean, was: "+wait;
}
return _controller.shell(commandAndArgs, wait, path);
},
on : function(what, callback) {
if (!_.isString(what)) {
throw "on failed, first parameter must be a string. was: "+what;
}
if (!_.isFunction(callback)) {
throw "on failed, second parameter must be a function. was: "+callback;
}
return _controller.on(what, callback);
}
};
window.S = window.slate;
window.S.cfg = window.S.config;
window.S.cfga = window.S.configAll;
window.S.bnd = window.S.bind;
window.S.bnda = window.S.bindAll;
window.S.op = window.S.operation;
window.S.doop = window.S.doOperation;
window.S.opstr = window.S.operationFromString;
window.S.src = window.S.source;
window.S.lay = window.S.layout;
window.S.def = window.S.default;
window.S.sh = window.S.shell;
window.S.info = _info;
var methods = window.S.info.jsMethods();
_.each(methods, function(method) {
if (window.S[method] !== undefined) {
throw "OMGWTFBBQ!!!";
}
window.S[method] = _.bind(_info[method], _info);
});
window.S.log("JS INIT FINISHED");
})(window._controller, window._info);
================================================
FILE: Slate/main.m
================================================
//
// main.m
// Slate
//
// Created by Jigish Patel on 5/18/11.
// Copyright 2011 Jigish Patel. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses
#import
int main(int argc, char *argv[]) {
return NSApplicationMain(argc, (const char **)argv);
}
================================================
FILE: Slate/slate-mock.js
================================================
var opNum = 0;
window._controller = {
log : function(msg) {
console.log(msg);
},
bindFunction : function(k, c, r) {
console.log("Mock bind function "+callback+" to "+k+" with repeat "+r);
},
bindNative : function(k, c, r) {
console.log("Mock bind operation "+callback+" to "+k+" with repeat "+r);
},
configFunction : function(k, c) {
console.log("Mock config function "+k+" = "+c);
},
configNative : function(k, c) {
console.log("Mock config native "+k+" = "+c);
},
doOperation : function(op) {
console.log("Mock do operation "+op);
return true;
},
operation : function(op, opts) {
console.log("Mock create operation "+op+" with opts "+opts);
opNum++;
return "javascript:operation["+opNum+"]";
},
operationFromString : function(str) {
opNum++;
return "javascript:operation["+opNum+"]";
}
}
================================================
FILE: Slate/slate-test.html
================================================
================================================
FILE: Slate/underscore.js
================================================
(function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,v=e.reduce,h=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,_=Object.keys,j=i.bind,w=function(n){return n instanceof w?n:this instanceof w?(this._wrapped=n,void 0):new w(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=w),exports._=w):n._=w,w.VERSION="1.4.3";var A=w.each=w.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(w.has(n,a)&&t.call(e,n[a],a,n)===r)return};w.map=w.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e[e.length]=t.call(r,n,u,i)}),e)};var O="Reduce of empty array with no initial value";w.reduce=w.foldl=w.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduce===v)return e&&(t=w.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},w.reduceRight=w.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduceRight===h)return e&&(t=w.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=w.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},w.find=w.detect=function(n,t,r){var e;return E(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},w.filter=w.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&(e[e.length]=n)}),e)},w.reject=function(n,t,r){return w.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},w.every=w.all=function(n,t,e){t||(t=w.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var E=w.some=w.any=function(n,t,e){t||(t=w.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};w.contains=w.include=function(n,t){return null==n?!1:y&&n.indexOf===y?-1!=n.indexOf(t):E(n,function(n){return n===t})},w.invoke=function(n,t){var r=o.call(arguments,2);return w.map(n,function(n){return(w.isFunction(t)?t:n[t]).apply(n,r)})},w.pluck=function(n,t){return w.map(n,function(n){return n[t]})},w.where=function(n,t){return w.isEmpty(t)?[]:w.filter(n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},w.max=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.max.apply(Math,n);if(!t&&w.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>=e.computed&&(e={value:n,computed:a})}),e.value},w.min=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.min.apply(Math,n);if(!t&&w.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;e.computed>a&&(e={value:n,computed:a})}),e.value},w.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=w.random(r++),e[r-1]=e[t],e[t]=n}),e};var F=function(n){return w.isFunction(n)?n:function(t){return t[n]}};w.sortBy=function(n,t,r){var e=F(t);return w.pluck(w.map(n,function(n,t,u){return{value:n,index:t,criteria:e.call(r,n,t,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||void 0===r)return 1;if(e>r||void 0===e)return-1}return n.indexi;){var o=i+a>>>1;u>r.call(e,n[o])?i=o+1:a=o}return i},w.toArray=function(n){return n?w.isArray(n)?o.call(n):n.length===+n.length?w.map(n,w.identity):w.values(n):[]},w.size=function(n){return null==n?0:n.length===+n.length?n.length:w.keys(n).length},w.first=w.head=w.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:o.call(n,0,t)},w.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},w.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},w.rest=w.tail=w.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},w.compact=function(n){return w.filter(n,w.identity)};var R=function(n,t,r){return A(n,function(n){w.isArray(n)?t?a.apply(r,n):R(n,t,r):r.push(n)}),r};w.flatten=function(n,t){return R(n,t,[])},w.without=function(n){return w.difference(n,o.call(arguments,1))},w.uniq=w.unique=function(n,t,r,e){w.isFunction(t)&&(e=r,r=t,t=!1);var u=r?w.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:w.contains(a,r))||(a.push(r),i.push(n[e]))}),i},w.union=function(){return w.uniq(c.apply(e,arguments))},w.intersection=function(n){var t=o.call(arguments,1);return w.filter(w.uniq(n),function(n){return w.every(t,function(t){return w.indexOf(t,n)>=0})})},w.difference=function(n){var t=c.apply(e,o.call(arguments,1));return w.filter(n,function(n){return!w.contains(t,n)})},w.zip=function(){for(var n=o.call(arguments),t=w.max(w.pluck(n,"length")),r=Array(t),e=0;t>e;e++)r[e]=w.pluck(n,""+e);return r},w.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},w.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=w.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},w.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},w.range=function(n,t,r){1>=arguments.length&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=Array(e);e>u;)i[u++]=n,n+=r;return i};var I=function(){};w.bind=function(n,t){var r,e;if(n.bind===j&&j)return j.apply(n,o.call(arguments,1));if(!w.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));I.prototype=n.prototype;var u=new I;I.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},w.bindAll=function(n){var t=o.call(arguments,1);return 0==t.length&&(t=w.functions(n)),A(t,function(t){n[t]=w.bind(n[t],n)}),n},w.memoize=function(n,t){var r={};return t||(t=w.identity),function(){var e=t.apply(this,arguments);return w.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},w.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},w.defer=function(n){return w.delay.apply(w,[n,1].concat(o.call(arguments,1)))},w.throttle=function(n,t){var r,e,u,i,a=0,o=function(){a=new Date,u=null,i=n.apply(r,e)};return function(){var c=new Date,l=t-(c-a);return r=this,e=arguments,0>=l?(clearTimeout(u),u=null,a=c,i=n.apply(r,e)):u||(u=setTimeout(o,l)),i}},w.debounce=function(n,t,r){var e,u;return function(){var i=this,a=arguments,o=function(){e=null,r||(u=n.apply(i,a))},c=r&&!e;return clearTimeout(e),e=setTimeout(o,t),c&&(u=n.apply(i,a)),u}},w.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},w.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},w.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},w.after=function(n,t){return 0>=n?t():function(){return 1>--n?t.apply(this,arguments):void 0}},w.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)w.has(n,r)&&(t[t.length]=r);return t},w.values=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push(n[r]);return t},w.pairs=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push([r,n[r]]);return t},w.invert=function(n){var t={};for(var r in n)w.has(n,r)&&(t[n[r]]=r);return t},w.functions=w.methods=function(n){var t=[];for(var r in n)w.isFunction(n[r])&&t.push(r);return t.sort()},w.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},w.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},w.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)w.contains(r,u)||(t[u]=n[u]);return t},w.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)null==n[r]&&(n[r]=t[r])}),n},w.clone=function(n){return w.isObject(n)?w.isArray(n)?n.slice():w.extend({},n):n},w.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof w&&(n=n._wrapped),t instanceof w&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==t+"";case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;r.push(n),e.push(t);var a=0,o=!0;if("[object Array]"==u){if(a=n.length,o=a==t.length)for(;a--&&(o=S(n[a],t[a],r,e)););}else{var c=n.constructor,f=t.constructor;if(c!==f&&!(w.isFunction(c)&&c instanceof c&&w.isFunction(f)&&f instanceof f))return!1;for(var s in n)if(w.has(n,s)&&(a++,!(o=w.has(t,s)&&S(n[s],t[s],r,e))))break;if(o){for(s in t)if(w.has(t,s)&&!a--)break;o=!a}}return r.pop(),e.pop(),o};w.isEqual=function(n,t){return S(n,t,[],[])},w.isEmpty=function(n){if(null==n)return!0;if(w.isArray(n)||w.isString(n))return 0===n.length;for(var t in n)if(w.has(n,t))return!1;return!0},w.isElement=function(n){return!(!n||1!==n.nodeType)},w.isArray=x||function(n){return"[object Array]"==l.call(n)},w.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){w["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),w.isArguments(arguments)||(w.isArguments=function(n){return!(!n||!w.has(n,"callee"))}),w.isFunction=function(n){return"function"==typeof n},w.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},w.isNaN=function(n){return w.isNumber(n)&&n!=+n},w.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},w.isNull=function(n){return null===n},w.isUndefined=function(n){return void 0===n},w.has=function(n,t){return f.call(n,t)},w.noConflict=function(){return n._=t,this},w.identity=function(n){return n},w.times=function(n,t,r){for(var e=Array(n),u=0;n>u;u++)e[u]=t.call(r,u);return e},w.random=function(n,t){return null==t&&(t=n,n=0),n+(0|Math.random()*(t-n+1))};var T={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};T.unescape=w.invert(T.escape);var M={escape:RegExp("["+w.keys(T.escape).join("")+"]","g"),unescape:RegExp("("+w.keys(T.unescape).join("|")+")","g")};w.each(["escape","unescape"],function(n){w[n]=function(t){return null==t?"":(""+t).replace(M[n],function(t){return T[n][t]})}}),w.result=function(n,t){if(null==n)return null;var r=n[t];return w.isFunction(r)?r.call(n):r},w.mixin=function(n){A(w.functions(n),function(t){var r=w[t]=n[t];w.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(w,n))}})};var N=0;w.uniqueId=function(n){var t=""+ ++N;return n?n+t:t},w.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;w.template=function(n,t,r){r=w.defaults({},r,w.templateSettings);var e=RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,a,o){return i+=n.slice(u,o).replace(D,function(n){return"\\"+B[n]}),r&&(i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(i+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),a&&(i+="';\n"+a+"\n__p+='"),u=o+t.length,t}),i+="';\n",r.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var a=Function(r.variable||"obj","_",i)}catch(o){throw o.source=i,o}if(t)return a(t,w);var c=function(n){return a.call(this,n,w)};return c.source="function("+(r.variable||"obj")+"){\n"+i+"}",c},w.chain=function(n){return w(n).chain()};var z=function(n){return this._chain?w(n).chain():n};w.mixin(w),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];w.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];w.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),w.extend(w.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this);
================================================
FILE: Slate/utils.js
================================================
(function(controller) {
for (key in _) {
window["_"+key+"_"] = _[key];
}
var _typeof_ = window._typeof_ = function(obj) {
if (_.isString(obj)) { return "string"; }
if (_.isArray(obj)) { return "array"; }
if (_.isFunction(obj)) { return "function"; }
if (_.isObject(obj)) { return "object"; }
if (_.isNumber(obj)) { return "number"; }
if (_.isBoolean(obj)) { return "boolean"; }
return "unknown";
}
var _array_ = window._array_ = function() { return []; }
var _array_with_ = window._array_with_ = function() { return Array.prototype.slice.call(arguments, 0); }
var _hash_ = window._hash_ = function() { return {}; }
})(window._controller);
================================================
FILE: Slate.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
3AA7D09116A6ED1200C81A67 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AA7D09016A6ED1200C81A67 /* WebKit.framework */; };
3AA7D09516A6EE0200C81A67 /* JSController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AA7D09416A6EE0200C81A67 /* JSController.m */; };
3AA7D09D16A6FC9B00C81A67 /* initialize.js in Resources */ = {isa = PBXBuildFile; fileRef = 3AA7D09A16A6FAE100C81A67 /* initialize.js */; };
3B1E86EE16B3627A001B9090 /* JSOperationWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B1E86ED16B3627A001B9090 /* JSOperationWrapper.m */; };
3B3335AD14FC1EA900F72638 /* WindowInfoView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B3335AC14FC1EA900F72638 /* WindowInfoView.m */; };
3B34F39115462323004090BB /* NSFileManager+ApplicationSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B34F39015462323004090BB /* NSFileManager+ApplicationSupport.m */; };
3B38E04816AB8BBF00603901 /* underscore.js in Resources */ = {isa = PBXBuildFile; fileRef = 3B38E04616AB8B3500603901 /* underscore.js */; };
3B3C41A01501ADC600C1E927 /* HintOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B3C419F1501ADC600C1E927 /* HintOperation.m */; };
3B3C41A31501BE8600C1E927 /* HintWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B3C41A21501BE8600C1E927 /* HintWindow.m */; };
3B3E241013A5F59E00B0D762 /* WindowState.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B3E240F13A5F59E00B0D762 /* WindowState.m */; };
3B40AC2113A73530006C9A5C /* Layout.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B40AC2013A73530006C9A5C /* Layout.m */; };
3B40AC2413A743B0006C9A5C /* LayoutOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B40AC2313A743AF006C9A5C /* LayoutOperation.m */; };
3B42D72F13A831E7009103A0 /* ApplicationOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B42D72E13A831E6009103A0 /* ApplicationOptions.m */; };
3B4CDD00150ACA6800B819E2 /* SwitchOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B4CDCFF150ACA6800B819E2 /* SwitchOperation.m */; };
3B4CDD03150AD68000B819E2 /* SwitchView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B4CDD02150AD68000B819E2 /* SwitchView.m */; };
3B4CDD06150AD69500B819E2 /* SwitchWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B4CDD05150AD69500B819E2 /* SwitchWindow.m */; };
3B58153C1502FB7D0078D568 /* HintView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B58153B1502FB7D0078D568 /* HintView.m */; };
3B5B1B7C1665871E00D5B1B5 /* UndoOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B5B1B7B1665871D00D5B1B5 /* UndoOperation.m */; };
3B65CF251576CDF10063D298 /* ASCIIToCode_Dvorak.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B65CF241576CDF10063D298 /* ASCIIToCode_Dvorak.plist */; };
3B66A56213ADBDFD0015EDD5 /* ScreenWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B66A56113ADBDFD0015EDD5 /* ScreenWrapper.m */; };
3B7DDCDF16AE1E290087CD12 /* JSInfoWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B7DDCDE16AE1E290087CD12 /* JSInfoWrapper.m */; };
3B7DDCE416AE301D0087CD12 /* JSWindowWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B7DDCE316AE301D0087CD12 /* JSWindowWrapper.m */; };
3B7DDCE716AE35840087CD12 /* JSApplicationWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B7DDCE616AE35840087CD12 /* JSApplicationWrapper.m */; };
3B7DDCEA16AE90FF0087CD12 /* JSScreenWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B7DDCE916AE90FF0087CD12 /* JSScreenWrapper.m */; };
3B7EB3A7138F3F6800EBEC2B /* ResizeOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B7EB3A6138F3F6800EBEC2B /* ResizeOperation.m */; };
3B7EB475138F691800EBEC2B /* Constants.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B7EB474138F691800EBEC2B /* Constants.m */; };
3B7EB4CC138F72D900EBEC2B /* StringTokenizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B7EB4CB138F72D900EBEC2B /* StringTokenizer.m */; };
3B84A552150552DC006E2194 /* ConfigurationHelperView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B84A551150552DC006E2194 /* ConfigurationHelperView.m */; };
3B883A0D1627725200FF3D8C /* RelaunchOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B883A0C1627725200FF3D8C /* RelaunchOperation.m */; };
3B8B5523161A1E5C00E21A6C /* NSString+Indicies.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B8B5522161A1E5C00E21A6C /* NSString+Indicies.m */; };
3B8B5527161A1FDF00E21A6C /* TestNSString+Indicies.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B8B5526161A1FDF00E21A6C /* TestNSString+Indicies.m */; };
3B9B3007151BDD960069D95E /* RunningApplications.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B9B3006151BDD960069D95E /* RunningApplications.m */; };
3B9B301E151BFC330069D95E /* SwitchAppView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B9B301D151BFC330069D95E /* SwitchAppView.m */; };
3B9B3021151C830F0069D95E /* SwitchAppQuittingOverlayView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B9B3020151C830E0069D95E /* SwitchAppQuittingOverlayView.m */; };
3BA1BA58162DD3630026774E /* TestNSString+Levenshtein.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BA1BA57162DD3630026774E /* TestNSString+Levenshtein.m */; };
3BA1BA5C162DD7190026774E /* TestMathUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BA1BA5B162DD7190026774E /* TestMathUtils.m */; };
3BA1BA62162DDFEF0026774E /* TestExpressionPoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BA1BA61162DDFEF0026774E /* TestExpressionPoint.m */; };
3BA1BA65162E01A30026774E /* TestStringTokenizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BA1BA64162E01A20026774E /* TestStringTokenizer.m */; };
3BA1BA68162F3E530026774E /* ShellOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BA1BA67162F3E530026774E /* ShellOperation.m */; };
3BA1BA6B162F4CFF0026774E /* ShellUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BA1BA6A162F4CFF0026774E /* ShellUtils.m */; };
3BA1BA6E162F51760026774E /* TestShellUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BA1BA6D162F51760026774E /* TestShellUtils.m */; };
3BA7485913B1D0F500CFA792 /* MathUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BA7485813B1D0F500CFA792 /* MathUtils.m */; };
3BA85C9A16276DAB00FAAF0B /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BA85C9916276DAB00FAAF0B /* Sparkle.framework */; };
3BA85C9B16276E9E00FAAF0B /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3BA85C9916276DAB00FAAF0B /* Sparkle.framework */; };
3BADA37213A2C1ED009E21D8 /* AccessibilityWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BADA37113A2C1ED009E21D8 /* AccessibilityWrapper.m */; };
3BB5380413AEDA1B0005CFFC /* ScreenState.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BB5380313AEDA190005CFFC /* ScreenState.m */; };
3BBD24BA1580206C00940ABF /* default.slate in Resources */ = {isa = PBXBuildFile; fileRef = 3BBD24B91580206C00940ABF /* default.slate */; };
3BC9E96F15005925002FD1FA /* ActivateSnapshotOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BC9E96E15005925002FD1FA /* ActivateSnapshotOperation.m */; };
3BC9E97315005F8F002FD1FA /* NSString+Levenshtein.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BC9E97215005F8F002FD1FA /* NSString+Levenshtein.m */; };
3BCE40F513B15FF300804615 /* FocusOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BCE40F413B15FF300804615 /* FocusOperation.m */; };
3BD8191D14FDC0CE003D63BC /* DeleteSnapshotOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BD8191C14FDC0CE003D63BC /* DeleteSnapshotOperation.m */; };
3BDC2B5B1391C55400B0D151 /* ChainOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BDC2B5A1391C55400B0D151 /* ChainOperation.m */; };
3BDDA17A14FD95E200829D2A /* SnapshotOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BDDA17914FD95E200829D2A /* SnapshotOperation.m */; };
3BDDA17D14FDA50C00829D2A /* Snapshot.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BDDA17C14FDA50C00829D2A /* Snapshot.m */; };
3BDDA18014FDA55600829D2A /* WindowSnapshot.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BDDA17F14FDA55600829D2A /* WindowSnapshot.m */; };
3BDDA18314FDAA6600829D2A /* SnapshotList.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BDDA18214FDAA6600829D2A /* SnapshotList.m */; };
3BE0736816AF3C9B00D33DB0 /* utils.js in Resources */ = {isa = PBXBuildFile; fileRef = 3BE0736516AF3C3F00D33DB0 /* utils.js */; };
3BE0736B16AF735A00D33DB0 /* JSWrapperUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BE0736A16AF735A00D33DB0 /* JSWrapperUtils.m */; };
3BE0736E16AFB5CA00D33DB0 /* JSOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BE0736D16AFB5CA00D33DB0 /* JSOperation.m */; };
3BE702B3161F9D5000260716 /* SequenceOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BE702B2161F9D5000260716 /* SequenceOperation.m */; };
3BF39E1214FDB16F0083E9C4 /* JSONKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BF39E1114FDB16F0083E9C4 /* JSONKit.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
3BF57C9B1621686900C0F89B /* statusActive.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 3BF57C9A1621686900C0F89B /* statusActive.pdf */; };
3BF57C9E16216C2200C0F89B /* VisibilityOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BF57C9D16216C2200C0F89B /* VisibilityOperation.m */; };
3BFC8A6A15FAAD9D00B16139 /* GridOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BFC8A6915FAAD9D00B16139 /* GridOperation.m */; };
3BFC8A6D15FABC9700B16139 /* GridView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BFC8A6C15FABC9600B16139 /* GridView.m */; };
3BFC8A7015FABCBE00B16139 /* GridWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BFC8A6F15FABCBD00B16139 /* GridWindow.m */; };
3BFC8A7315FE5CD700B16139 /* GridCellView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BFC8A7215FE5CD700B16139 /* GridCellView.m */; };
3BFF7D7116AC784B00A8CB0E /* PushOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BFF7D7016AC784A00A8CB0E /* PushOperation.m */; };
3BFF7D7516AC7B3D00A8CB0E /* NudgeOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BFF7D7416AC7B3C00A8CB0E /* NudgeOperation.m */; };
3BFF7D7816AC7E2900A8CB0E /* ThrowOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BFF7D7716AC7E2800A8CB0E /* ThrowOperation.m */; };
3BFF7D7B16AC7FF600A8CB0E /* CornerOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BFF7D7A16AC7FF600A8CB0E /* CornerOperation.m */; };
4747E1EF13877C160005180C /* icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 4747E1EE13877C150005180C /* icon.icns */; };
4786D53513882381004554B1 /* ASCIIToCode.plist in Resources */ = {isa = PBXBuildFile; fileRef = 4786D53413882381004554B1 /* ASCIIToCode.plist */; };
47C4A2941384E8890066B6DE /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 47C4A2931384E8890066B6DE /* Cocoa.framework */; };
47C4A29E1384E8890066B6DE /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 47C4A29C1384E8890066B6DE /* InfoPlist.strings */; };
47C4A2A11384E8890066B6DE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 47C4A2A01384E8890066B6DE /* main.m */; };
47C4A2A41384E8890066B6DE /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 47C4A2A21384E8890066B6DE /* Credits.rtf */; };
47C4A2A71384E8890066B6DE /* SlateAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 47C4A2A61384E8890066B6DE /* SlateAppDelegate.m */; };
47C4A2AA1384E8890066B6DE /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 47C4A2A81384E8890066B6DE /* MainMenu.xib */; };
47C4A2B11384E8890066B6DE /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 47C4A2931384E8890066B6DE /* Cocoa.framework */; };
47C4A2B91384E8890066B6DE /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 47C4A2B71384E8890066B6DE /* InfoPlist.strings */; };
47C4A2BC1384E8890066B6DE /* SlateTests.h in Resources */ = {isa = PBXBuildFile; fileRef = 47C4A2BB1384E8890066B6DE /* SlateTests.h */; };
47C4A2BE1384E8890066B6DE /* SlateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 47C4A2BD1384E8890066B6DE /* SlateTests.m */; };
47C4A2C81384E8B10066B6DE /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 47C4A2C71384E8B10066B6DE /* Carbon.framework */; };
47C4A2CB1384EA510066B6DE /* SlateConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 47C4A2CA1384EA510066B6DE /* SlateConfig.m */; };
47C4A2CE1384F26A0066B6DE /* Binding.m in Sources */ = {isa = PBXBuildFile; fileRef = 47C4A2CD1384F26A0066B6DE /* Binding.m */; };
47C4A2D11384F3E10066B6DE /* Operation.m in Sources */ = {isa = PBXBuildFile; fileRef = 47C4A2D01384F3E10066B6DE /* Operation.m */; };
47C4A2D41384F4A60066B6DE /* MoveOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 47C4A2D31384F4A60066B6DE /* MoveOperation.m */; };
47C4A2D71384F5DE0066B6DE /* ExpressionPoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 47C4A2D61384F5DE0066B6DE /* ExpressionPoint.m */; };
5EB99A0216A7526400760915 /* ASCIIToCode_Azerty.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5EB99A0116A7526400760915 /* ASCIIToCode_Azerty.plist */; };
7F44A9AA16621A9D00222908 /* ASCIIToCode_Colemak.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7F44A9A916621A9D00222908 /* ASCIIToCode_Colemak.plist */; };
B3D087D0160577FB008A7FC6 /* status.pdf in Resources */ = {isa = PBXBuildFile; fileRef = B3D087CF160577FB008A7FC6 /* status.pdf */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
47C4A2B21384E8890066B6DE /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 47C4A2861384E8890066B6DE /* Project object */;
proxyType = 1;
remoteGlobalIDString = 47C4A28E1384E8890066B6DE;
remoteInfo = Slate;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
3B4D0C3615A3691900B39242 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
3BA85C9B16276E9E00FAAF0B /* Sparkle.framework in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
3AA7D09016A6ED1200C81A67 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };
3AA7D09316A6EE0200C81A67 /* JSController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSController.h; sourceTree = ""; };
3AA7D09416A6EE0200C81A67 /* JSController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSController.m; sourceTree = ""; };
3AA7D09A16A6FAE100C81A67 /* initialize.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = initialize.js; sourceTree = ""; };
3B1E86EC16B3627A001B9090 /* JSOperationWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSOperationWrapper.h; sourceTree = ""; };
3B1E86ED16B3627A001B9090 /* JSOperationWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSOperationWrapper.m; sourceTree = ""; };
3B3335AB14FC1EA900F72638 /* WindowInfoView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowInfoView.h; sourceTree = ""; };
3B3335AC14FC1EA900F72638 /* WindowInfoView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WindowInfoView.m; sourceTree = ""; };
3B34F38F15462323004090BB /* NSFileManager+ApplicationSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSFileManager+ApplicationSupport.h"; sourceTree = ""; };
3B34F39015462323004090BB /* NSFileManager+ApplicationSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSFileManager+ApplicationSupport.m"; sourceTree = ""; };
3B38E04616AB8B3500603901 /* underscore.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = underscore.js; sourceTree = ""; };
3B3C419E1501ADC600C1E927 /* HintOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HintOperation.h; sourceTree = ""; };
3B3C419F1501ADC600C1E927 /* HintOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HintOperation.m; sourceTree = ""; };
3B3C41A11501BE8500C1E927 /* HintWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HintWindow.h; sourceTree = ""; };
3B3C41A21501BE8600C1E927 /* HintWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HintWindow.m; sourceTree = ""; };
3B3E240E13A5F59E00B0D762 /* WindowState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowState.h; sourceTree = ""; };
3B3E240F13A5F59E00B0D762 /* WindowState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WindowState.m; sourceTree = ""; };
3B40AC1F13A73530006C9A5C /* Layout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Layout.h; sourceTree = ""; };
3B40AC2013A73530006C9A5C /* Layout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Layout.m; sourceTree = ""; };
3B40AC2213A743AE006C9A5C /* LayoutOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LayoutOperation.h; sourceTree = ""; };
3B40AC2313A743AF006C9A5C /* LayoutOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LayoutOperation.m; sourceTree = ""; };
3B42D72D13A831E6009103A0 /* ApplicationOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ApplicationOptions.h; sourceTree = ""; };
3B42D72E13A831E6009103A0 /* ApplicationOptions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ApplicationOptions.m; sourceTree = ""; };
3B4CDCFE150ACA6800B819E2 /* SwitchOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SwitchOperation.h; sourceTree = ""; };
3B4CDCFF150ACA6800B819E2 /* SwitchOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SwitchOperation.m; sourceTree = ""; };
3B4CDD01150AD68000B819E2 /* SwitchView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SwitchView.h; sourceTree = ""; };
3B4CDD02150AD68000B819E2 /* SwitchView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SwitchView.m; sourceTree = ""; };
3B4CDD04150AD69500B819E2 /* SwitchWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SwitchWindow.h; sourceTree = ""; };
3B4CDD05150AD69500B819E2 /* SwitchWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SwitchWindow.m; sourceTree = ""; };
3B58153A1502FB7D0078D568 /* HintView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HintView.h; sourceTree = ""; };
3B58153B1502FB7D0078D568 /* HintView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HintView.m; sourceTree = ""; };
3B58154215054EBD0078D568 /* SlateLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SlateLogger.h; sourceTree = ""; };
3B5B1B7A1665871D00D5B1B5 /* UndoOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UndoOperation.h; sourceTree = ""; };
3B5B1B7B1665871D00D5B1B5 /* UndoOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UndoOperation.m; sourceTree = ""; };
3B65CF241576CDF10063D298 /* ASCIIToCode_Dvorak.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = ASCIIToCode_Dvorak.plist; sourceTree = ""; };
3B66A56013ADBDFD0015EDD5 /* ScreenWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScreenWrapper.h; sourceTree = ""; };
3B66A56113ADBDFD0015EDD5 /* ScreenWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ScreenWrapper.m; sourceTree = ""; };
3B7DDCDD16AE1E290087CD12 /* JSInfoWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSInfoWrapper.h; sourceTree = ""; };
3B7DDCDE16AE1E290087CD12 /* JSInfoWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSInfoWrapper.m; sourceTree = ""; };
3B7DDCE216AE301D0087CD12 /* JSWindowWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSWindowWrapper.h; sourceTree = ""; };
3B7DDCE316AE301D0087CD12 /* JSWindowWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSWindowWrapper.m; sourceTree = ""; };
3B7DDCE516AE35840087CD12 /* JSApplicationWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSApplicationWrapper.h; sourceTree = ""; };
3B7DDCE616AE35840087CD12 /* JSApplicationWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSApplicationWrapper.m; sourceTree = ""; };
3B7DDCE816AE90FF0087CD12 /* JSScreenWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSScreenWrapper.h; sourceTree = ""; };
3B7DDCE916AE90FF0087CD12 /* JSScreenWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSScreenWrapper.m; sourceTree = ""; };
3B7EB3A5138F3F6800EBEC2B /* ResizeOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ResizeOperation.h; sourceTree = ""; };
3B7EB3A6138F3F6800EBEC2B /* ResizeOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ResizeOperation.m; sourceTree = ""; };
3B7EB473138F691800EBEC2B /* Constants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = Constants.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
3B7EB474138F691800EBEC2B /* Constants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = Constants.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
3B7EB4CA138F72D900EBEC2B /* StringTokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StringTokenizer.h; sourceTree = ""; };
3B7EB4CB138F72D900EBEC2B /* StringTokenizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StringTokenizer.m; sourceTree = ""; };
3B84A550150552DC006E2194 /* ConfigurationHelperView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConfigurationHelperView.h; sourceTree = ""; };
3B84A551150552DC006E2194 /* ConfigurationHelperView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConfigurationHelperView.m; sourceTree = ""; };
3B883A0B1627725200FF3D8C /* RelaunchOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RelaunchOperation.h; sourceTree = ""; };
3B883A0C1627725200FF3D8C /* RelaunchOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RelaunchOperation.m; sourceTree = ""; };
3B8B5521161A1E5C00E21A6C /* NSString+Indicies.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+Indicies.h"; sourceTree = ""; };
3B8B5522161A1E5C00E21A6C /* NSString+Indicies.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+Indicies.m"; sourceTree = ""; };
3B8B5525161A1FDF00E21A6C /* TestNSString+Indicies.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "TestNSString+Indicies.h"; sourceTree = ""; };
3B8B5526161A1FDF00E21A6C /* TestNSString+Indicies.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "TestNSString+Indicies.m"; sourceTree = ""; };
3B9B3005151BDD960069D95E /* RunningApplications.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RunningApplications.h; sourceTree = ""; };
3B9B3006151BDD960069D95E /* RunningApplications.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RunningApplications.m; sourceTree = ""; };
3B9B301C151BFC330069D95E /* SwitchAppView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SwitchAppView.h; sourceTree = ""; };
3B9B301D151BFC330069D95E /* SwitchAppView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SwitchAppView.m; sourceTree = ""; };
3B9B301F151C830E0069D95E /* SwitchAppQuittingOverlayView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SwitchAppQuittingOverlayView.h; sourceTree = ""; };
3B9B3020151C830E0069D95E /* SwitchAppQuittingOverlayView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SwitchAppQuittingOverlayView.m; sourceTree = ""; };
3BA1BA56162DD3630026774E /* TestNSString+Levenshtein.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "TestNSString+Levenshtein.h"; sourceTree = ""; };
3BA1BA57162DD3630026774E /* TestNSString+Levenshtein.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "TestNSString+Levenshtein.m"; sourceTree = ""; };
3BA1BA5A162DD7190026774E /* TestMathUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestMathUtils.h; sourceTree = ""; };
3BA1BA5B162DD7190026774E /* TestMathUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestMathUtils.m; sourceTree = ""; };
3BA1BA60162DDFEF0026774E /* TestExpressionPoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestExpressionPoint.h; sourceTree = ""; };
3BA1BA61162DDFEF0026774E /* TestExpressionPoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestExpressionPoint.m; sourceTree = ""; };
3BA1BA63162E01A20026774E /* TestStringTokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestStringTokenizer.h; sourceTree = ""; };
3BA1BA64162E01A20026774E /* TestStringTokenizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestStringTokenizer.m; sourceTree = ""; };
3BA1BA66162F3E530026774E /* ShellOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShellOperation.h; sourceTree = ""; };
3BA1BA67162F3E530026774E /* ShellOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShellOperation.m; sourceTree = ""; };
3BA1BA69162F4CFE0026774E /* ShellUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShellUtils.h; sourceTree = ""; };
3BA1BA6A162F4CFF0026774E /* ShellUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShellUtils.m; sourceTree = ""; };
3BA1BA6C162F51760026774E /* TestShellUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestShellUtils.h; sourceTree = ""; };
3BA1BA6D162F51760026774E /* TestShellUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestShellUtils.m; sourceTree = ""; };
3BA7485713B1D0F500CFA792 /* MathUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MathUtils.h; sourceTree = ""; };
3BA7485813B1D0F500CFA792 /* MathUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MathUtils.m; sourceTree = ""; };
3BA85C9916276DAB00FAAF0B /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Sparkle.framework; sourceTree = ""; };
3BADA37013A2C1ED009E21D8 /* AccessibilityWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AccessibilityWrapper.h; sourceTree = ""; };
3BADA37113A2C1ED009E21D8 /* AccessibilityWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = AccessibilityWrapper.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
3BB5380213AEDA190005CFFC /* ScreenState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScreenState.h; sourceTree = ""; };
3BB5380313AEDA190005CFFC /* ScreenState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ScreenState.m; sourceTree = ""; };
3BBD24B91580206C00940ABF /* default.slate */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = default.slate; sourceTree = ""; };
3BC9E96D15005925002FD1FA /* ActivateSnapshotOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ActivateSnapshotOperation.h; sourceTree = ""; };
3BC9E96E15005925002FD1FA /* ActivateSnapshotOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ActivateSnapshotOperation.m; sourceTree = ""; };
3BC9E97115005F8F002FD1FA /* NSString+Levenshtein.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+Levenshtein.h"; sourceTree = ""; };
3BC9E97215005F8F002FD1FA /* NSString+Levenshtein.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+Levenshtein.m"; sourceTree = ""; };
3BCE40F313B15FF300804615 /* FocusOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FocusOperation.h; sourceTree = ""; };
3BCE40F413B15FF300804615 /* FocusOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FocusOperation.m; sourceTree = ""; };
3BD8191B14FDC0CE003D63BC /* DeleteSnapshotOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DeleteSnapshotOperation.h; sourceTree = ""; };
3BD8191C14FDC0CE003D63BC /* DeleteSnapshotOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DeleteSnapshotOperation.m; sourceTree = ""; };
3BDC2B591391C55400B0D151 /* ChainOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ChainOperation.h; sourceTree = ""; };
3BDC2B5A1391C55400B0D151 /* ChainOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ChainOperation.m; sourceTree = ""; };
3BDDA17814FD95E200829D2A /* SnapshotOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SnapshotOperation.h; sourceTree = ""; };
3BDDA17914FD95E200829D2A /* SnapshotOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SnapshotOperation.m; sourceTree = ""; };
3BDDA17B14FDA50C00829D2A /* Snapshot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Snapshot.h; sourceTree = ""; };
3BDDA17C14FDA50C00829D2A /* Snapshot.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Snapshot.m; sourceTree = ""; };
3BDDA17E14FDA55600829D2A /* WindowSnapshot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowSnapshot.h; sourceTree = "