Repository: eczarny/spectacle Branch: master Commit: e75c341ec2cb Files: 159 Total size: 520.4 KB Directory structure: gitextract_y6p7bt7r/ ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Cartfile ├── Cartfile.resolved ├── LICENSE.md ├── README.md ├── Spectacle/ │ ├── Resources/ │ │ ├── Certificates/ │ │ │ └── dsa_public.pem │ │ ├── Images.xcassets/ │ │ │ ├── 10.9 Crash Fix.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Security & Privacy.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Spectacle Status Item.imageset/ │ │ │ │ └── Contents.json │ │ │ └── Spectacle.appiconset/ │ │ │ └── Contents.json │ │ ├── Localizations/ │ │ │ ├── Base.lproj/ │ │ │ │ ├── Spectacle.xib │ │ │ │ └── SpectaclePreferencesWindow.xib │ │ │ ├── en.lproj/ │ │ │ │ ├── Credits.rtf │ │ │ │ ├── InfoPlist.strings │ │ │ │ └── Localizable.strings │ │ │ ├── es.lproj/ │ │ │ │ ├── Credits.rtf │ │ │ │ ├── InfoPlist.strings │ │ │ │ ├── Localizable.strings │ │ │ │ ├── Spectacle.strings │ │ │ │ └── SpectaclePreferencesWindow.strings │ │ │ ├── fi.lproj/ │ │ │ │ ├── Credits.rtf │ │ │ │ ├── InfoPlist.strings │ │ │ │ ├── Localizable.strings │ │ │ │ ├── Spectacle.strings │ │ │ │ └── SpectaclePreferencesWindow.strings │ │ │ ├── fr.lproj/ │ │ │ │ ├── Credits.rtf │ │ │ │ ├── InfoPlist.strings │ │ │ │ ├── Localizable.strings │ │ │ │ ├── Spectacle.strings │ │ │ │ └── SpectaclePreferencesWindow.strings │ │ │ ├── it.lproj/ │ │ │ │ ├── Credits.rtf │ │ │ │ ├── InfoPlist.strings │ │ │ │ ├── Localizable.strings │ │ │ │ ├── Spectacle.strings │ │ │ │ └── SpectaclePreferencesWindow.strings │ │ │ └── pt.lproj/ │ │ │ ├── Credits.rtf │ │ │ ├── InfoPlist.strings │ │ │ ├── Localizable.strings │ │ │ ├── Spectacle.strings │ │ │ └── SpectaclePreferencesWindow.strings │ │ ├── Property Lists/ │ │ │ └── Defaults.plist │ │ ├── Scripts/ │ │ │ ├── Security & Privacy System Preferences Original.scpt │ │ │ └── Security & Privacy System Preferences.scpt │ │ └── Window Position Calculations/ │ │ ├── SpectacleBottomHalfWindowCalculation.js │ │ ├── SpectacleCenterWindowCalculation.js │ │ ├── SpectacleFullscreenWindowCalculation.js │ │ ├── SpectacleLargerWindowCalculation.js │ │ ├── SpectacleLeftHalfWindowCalculation.js │ │ ├── SpectacleLowerLeftWindowCalculation.js │ │ ├── SpectacleLowerRightWindowCalculation.js │ │ ├── SpectacleNextDisplayWindowCalculation.js │ │ ├── SpectacleNextOrPreviousDisplay.js │ │ ├── SpectacleNextOrPreviousThirds.js │ │ ├── SpectacleNextThirdWindowCalculation.js │ │ ├── SpectaclePreviousDisplayWindowCalculation.js │ │ ├── SpectaclePreviousThirdWindowCalculation.js │ │ ├── SpectacleRightHalfWindowCalculation.js │ │ ├── SpectacleSmallerWindowCalculation.js │ │ ├── SpectacleTopHalfWindowCalculation.js │ │ ├── SpectacleUpperLeftWindowCalculation.js │ │ ├── SpectacleUpperRightWindowCalculation.js │ │ ├── SpectacleWindowCalculationHelpers.js │ │ └── SpectacleWindowSizeAdjuster.js │ ├── Sources/ │ │ ├── SpectacleAccessibilityElement.h │ │ ├── SpectacleAccessibilityElement.m │ │ ├── SpectacleAppDelegate.h │ │ ├── SpectacleAppDelegate.m │ │ ├── SpectacleBestEffortWindowMover.h │ │ ├── SpectacleBestEffortWindowMover.m │ │ ├── SpectacleDefaultShortcutHelpers.h │ │ ├── SpectacleDefaultShortcutHelpers.m │ │ ├── SpectacleHistory.h │ │ ├── SpectacleHistory.m │ │ ├── SpectacleHistoryItem.h │ │ ├── SpectacleHistoryItem.m │ │ ├── SpectacleJavaScriptEnvironment.h │ │ ├── SpectacleJavaScriptEnvironment.m │ │ ├── SpectacleLoginItemHelper.h │ │ ├── SpectacleLoginItemHelper.m │ │ ├── SpectacleMacros.h │ │ ├── SpectacleMigratingShortcutStorage.h │ │ ├── SpectacleMigratingShortcutStorage.m │ │ ├── SpectaclePreferencesController.h │ │ ├── SpectaclePreferencesController.m │ │ ├── SpectacleQuantizedWindowMover.h │ │ ├── SpectacleQuantizedWindowMover.m │ │ ├── SpectacleRegisteredShortcutValidator.h │ │ ├── SpectacleRegisteredShortcutValidator.m │ │ ├── SpectacleScreenDetectionResult.h │ │ ├── SpectacleScreenDetectionResult.m │ │ ├── SpectacleScreenDetector.h │ │ ├── SpectacleScreenDetector.m │ │ ├── SpectacleShortcut.h │ │ ├── SpectacleShortcut.m │ │ ├── SpectacleShortcutHolder.h │ │ ├── SpectacleShortcutHolder.m │ │ ├── SpectacleShortcutJSONStorage.h │ │ ├── SpectacleShortcutJSONStorage.m │ │ ├── SpectacleShortcutKeyBindings.h │ │ ├── SpectacleShortcutKeyBindings.m │ │ ├── SpectacleShortcutManager.h │ │ ├── SpectacleShortcutManager.m │ │ ├── SpectacleShortcutRecorder.h │ │ ├── SpectacleShortcutRecorder.m │ │ ├── SpectacleShortcutRecorderDelegate.h │ │ ├── SpectacleShortcutStorage.h │ │ ├── SpectacleShortcutStorageMigrationDestination.h │ │ ├── SpectacleShortcutTranslations.h │ │ ├── SpectacleShortcutTranslations.m │ │ ├── SpectacleShortcutUserDefaultsStorage.h │ │ ├── SpectacleShortcutUserDefaultsStorage.m │ │ ├── SpectacleShortcutValidation.h │ │ ├── SpectacleShortcutValidation.m │ │ ├── SpectacleShortcutValidator.h │ │ ├── SpectacleStandardWindowMover.h │ │ ├── SpectacleStandardWindowMover.m │ │ ├── SpectacleUtilities.h │ │ ├── SpectacleUtilities.m │ │ ├── SpectacleWindowAction.h │ │ ├── SpectacleWindowAction.m │ │ ├── SpectacleWindowMover.h │ │ ├── SpectacleWindowPositionCalculationRegistry.h │ │ ├── SpectacleWindowPositionCalculationRegistry.m │ │ ├── SpectacleWindowPositionCalculationResult.h │ │ ├── SpectacleWindowPositionCalculationResult.m │ │ ├── SpectacleWindowPositionCalculator.h │ │ ├── SpectacleWindowPositionCalculator.m │ │ ├── SpectacleWindowPositionManager.h │ │ └── SpectacleWindowPositionManager.m │ └── Supporting Files/ │ ├── Info.plist │ └── main.m ├── Spectacle.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata/ │ └── xcschemes/ │ └── Spectacle.xcscheme └── SpectacleSpecs/ ├── Sources/ │ ├── SpectacleBottomHalfWindowCalculationSpec.m │ ├── SpectacleCenterWindowCalculationSpec.m │ ├── SpectacleFullscreenWindowCalculationSpec.m │ ├── SpectacleLargerWindowCalculationSpec.m │ ├── SpectacleLeftHalfWindowCalculationSpec.m │ ├── SpectacleLowerLeftWindowCalculationSpec.m │ ├── SpectacleLowerRightWindowCalculationSpec.m │ ├── SpectacleNextDisplayWindowCalculationSpec.m │ ├── SpectacleNextThirdWindowCalculationSpec.m │ ├── SpectaclePreviousDisplayWindowCalculationSpec.m │ ├── SpectaclePreviousThirdWindowCalculationSpec.m │ ├── SpectacleRightHalfWindowCalculationSpec.m │ ├── SpectacleShortcutKeyBindingsSpec.m │ ├── SpectacleShortcutSpec.m │ ├── SpectacleShortcutTranslationsSpec.m │ ├── SpectacleSmallerWindowCalculationSpec.m │ ├── SpectacleTopHalfWindowCalculationSpec.m │ ├── SpectacleUpperLeftWindowCalculationSpec.m │ ├── SpectacleUpperRightWindowCalculationSpec.m │ └── SpectacleWindowPositionManagerSpec.m └── Supporting Files/ └── Info.plist ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Xcode # build/ *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata *.xccheckout *.moved-aside DerivedData *.hmap *.ipa *.xcuserstate Carthage/Build Carthage/Checkouts ================================================ FILE: .travis.yml ================================================ osx_image: xcode8.2 language: objective-c xcode_project: Spectacle.xcodeproj xcode_scheme: Spectacle before_install: - brew update - brew outdated carthage || brew upgrade carthage - carthage bootstrap --platform Mac script: - set -o pipefail && xcodebuild -project Spectacle.xcodeproj -scheme Spectacle test | xcpretty ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Contributions come in many forms. Creating an issue that documents a previously unreported defect is just as important as sending a pull request. Spectacle can only be great when the folks that use it are passionate about its quality. Before you start working on your contribution to Spectacle please keep reading. For even more information read [Contributing to Open Source on GitHub][1]. ## Issues Before creating a new issue check and make sure somebody hasn't already beat you to it. _Please take this time to also review Spectacle's [common issues][2]_. If your defect or feature request doesn't already exist feel free to create a new issue. When creating a new issue take the time to be as clear and descriptive as possible. Here are a few things to keep in mind when creating an issue for a potential defect: * What version of Spectacle did you use when encountering the defect? * What version of OS X did you use when encountering the defect? * Was there any relevant Spectacle output in the [Console][3]? Including screenshots or videos are extremely helpful so please include them if you can. Diagrams can also be highly valuable when describing a new feature in a feature request. ## Pull Requests 1. Fork the repository and create your branch from `master` 2. Add tests if your changes can and should be tested 3. Run the test suite and confirm that all tests pass 4. Rebase your changes on top of the most recent changes from `master` ## Coding Style To help make it easier maintain Spectacle please match the existing coding style as much as possible. This make it easier to both understand and merge your contributions. Only use two __spaces__ for indentation. ## License By contributing to Spectacle you agree that your contributions will be licensed under its MIT license. [1]: https://guides.github.com/activities/contributing-to-open-source/#contributing [2]: https://github.com/eczarny/spectacle#common-issues [3]: https://en.wikipedia.org/wiki/Console_(OS_X) ================================================ FILE: Cartfile ================================================ github "hamcrest/OCHamcrest" ~> 7.1 github "jonreid/OCMockito" ~> 5.1 github "sparkle-project/Sparkle" ~> 1.22 github "specta/expecta" ~> 1.0 github "specta/specta" ~> 1.0 ================================================ FILE: Cartfile.resolved ================================================ github "hamcrest/OCHamcrest" "v7.1.2" github "jonreid/OCMockito" "v5.1.2" github "sparkle-project/Sparkle" "1.22.0" github "specta/expecta" "v1.0.6" github "specta/specta" "v1.0.7" ================================================ FILE: LICENSE.md ================================================ # License ## Spectacle is distributed under the MIT License: Copyright (c) 2017 Eric Czarny Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Spectacle [![Build Status](https://travis-ci.org/eczarny/spectacle.svg?branch=master)](https://travis-ci.org/eczarny/spectacle) Easily organize windows without using a mouse. ## Important Note **This project is not being actively maintained.** Unfortunately, after almost a decade of on-and-off development I can no longer dedicate the time needed to be a responsible maintainer of this project. Spectacle will remain available for download but please use at your own risk. The source code will continue to be free and open to anyone, so feel free to make Spectacle your own. Thanks to all the folks that have made Spectacle the first app they install a new Mac! _Spectacle users have recommended [Rectangle](https://github.com/rxhanson/Rectangle) as an open source alternative._ ## System requirements Spectacle [0.8.6][1] is the last version to support Mac OS X 10.7; future releases will only support 10.9 or greater. Folks wishing to stay on 10.7 should download [0.8.6][1]. Those looking for a version of Spectacle that supports 10.6 can still download [0.7][2]. ## Keyboard Shortcuts Spectacle makes use of several [keyboard shortcuts][3] that trigger specific _window actions_. A window action is nothing more than a command that tells Spectacle how to change the size and/or position of a particular window. A keyboard shortcut consists of one or more modifier keys paired with a character key. The common modifier keys Spectacle takes advantage of are: | Symbol | Key | |:---------:|:-----------:| | ⌘ | Command Key | | ⌃ | Control Key | | ⌥ | Option Key | | ⇧ | Shift Key | The default keyboard shortcuts can be changed at any time. Use new key combinations to trigger window actions, or clear particular keyboard shortcuts to disable window actions entirely. ### Basic window actions To move a window to the center of the screen use the ⌥⌘C keyboard shortcut. Centered windows will __not__ have their size altered. Maximize a window using the ⌥⌘F shortcut. Windows can be moved to a number of predefined regions of the screen: - _Move to the left half_ — ⌥⌘← - _Move to the right half_ — ⌥⌘→ - _Move to the top half_ — ⌥⌘↑ - _Move to the bottom half_ — ⌥⌘↓

- _Move to the upper left_ — ⌃⌘← - _Move to the lower left_ — ⌃⇧⌘← - _Move to the upper right_ — ⌃⌘→ - _Move to the lower right_ — ⌃⇧⌘→ Windows may also be resized between thirds using any of the shortcuts above. For example, to have a window resized between 1/3 and 2/3 of the left region of the screen simply activate the *left half* ⌥⌘← keyboard shortcut more than once. Each time the shortcut is activated Spectacle will move the window between 1/3, 2/3, and back to 1/2 of the left side of the screen. This feature also applies to the upper left, lower left, upper right, and lower right shortcuts. Spectacle can also move windows between horizontal and vertical thirds of the screen. The ⌃⌥→ keyboard shortcut will move a window to the next third of the screen, starting with the horizontal third region on the left of the screen. ⌃⌥← will move a window to the previous third of the screen. Resizing a window is just as easy. To make a window a bit larger use the ⌃⌥⇧→ keyboard shortcut; ⌃⌥⇧← can be used to make a window smaller. Spectacle will always attempt to maintain contact between the edges of a window and the edges of the screen when resizing. ### Multiple displays The ⌃⌥⌘→ keyboard shortcut will move a window to the next available display. ⌃⌥⌘← can be used to move a window to the previous display. ### Window action history Spectacle remembers where every window was prior to executing a window action. To undo a window action use the ⌥⌘Z keyboard shortcut. Use the ⌥⇧⌘Z shortcut to redo the window action once again. ## Common Issues ### Spectacle is requesting access to use accessibility features Apple's [OS X Accessibility Protocol][4] makes Spectacle possible. This protocol allows assistive applications to _drive the user interface of another application running in OS X_. In order to do its job Spectacle must be granted access to use these accessibility features. Instructions to do so will be displayed if Spectacle determines it does not have sufficient privileges. ### Spectacle does not resize a particular window as expected OS X allows applications to place constraints on the size of its windows. This allows application developers to design their user interfaces without needing to worry about supporting every possible window dimension. In these cases Spectacle will not be able to resize those windows to fit into the exact dimensions intended by the shortcut used. As an example suppose a display has a 2880x1800 resolution and a window is being resized to fit the left half of the display. If the window has specified a minimum allowable width of 1600 pixels Spectacle will not be able to resize the window to fit the expected 1440 pixels. The constraints placed on windows will always be respected despite leading to potentially unexpected results; in this example Spectacle will resize the window's width to 1600 pixels. ### Spectacle behaves strangely with Terminal windows Terminal (and other terminal emulation applications like iTerm 2) place constraints on how windows are resized so entire rows and columns are always visible. This behavior works great since it guarantees no rows or columns are truncated. Unfortunately it does mean that Spectacle needs to do some extra work to make these windows fit. To work around the limitations imposed on Terminal windows Spectacle will first try to resize a window to match the desired dimensions. If a window cannot be resized to fit Spectacle will immediately try to make the window a little smaller. This process is repeated until the window can be made to fit with its constraints still in place. The result is a window that is centered within the desired dimensions at the cost of a slightly jittery experience. ### Spectacle does not work with all applications Most applications built with the Cocoa frameworks can be readily manipulated via the _OS X Accessibility Protocol_; allowing Spectacle to interact with nearly every window it encounters. Unfortunately this is not always the case. Spectacle will be unable to manipulate the windows of applications that build their user interfaces in unexpected ways. ## Building and running Spectacle uses [Carthage][5]. Before building Spectacle in Xcode run: $ carthage bootstrap --platform Mac Once Carthage builds Spectacle's dependencies open the Xcode project: $ open Spectacle.xcodeproj After Xcode finishes loading the workspace press ⌘R to run Spectacle. # License Copyright (c) 2017 Eric Czarny. Spectacle should be accompanied by a LICENSE file containing the license relevant to this distribution. [1]: https://s3.amazonaws.com/spectacle/downloads/Spectacle+0.8.6.zip [2]: https://s3.amazonaws.com/spectacle/downloads/Spectacle+0.7.zip [3]: http://support.apple.com/kb/ht1343 [4]: https://developer.apple.com/library/mac/documentation/Accessibility/Conceptual/AccessibilityMacOSX/OSXAXmodel.html [5]: https://github.com/Carthage/Carthage ================================================ FILE: Spectacle/Resources/Certificates/dsa_public.pem ================================================ -----BEGIN PUBLIC KEY----- MIIDOjCCAi0GByqGSM44BAEwggIgAoIBAQCY2D8N/yE2yx4+fpl4m4M2FzaBOcY8 B/bFN3bExJcapUAn0IpTvUDO0SZCw28WPQuEnu+RAq00UULTtGtO1H6J975csVBC KSx5YbSlcui8MzUASHC99+GfccPCSYXNgqzdq72gdQI0/QRZcBA2MrFDFtNOneCZ 0hCOrROwKB5B2iZ6duITRmcOBkcgx+/3sWPmi3860yCbUymhfIPsvyefJCTwDJbc ulQm4SxPykseLauJluhbSyU5o82CiN2bohv8qzPGq5X29hO77u10QLuyNdpE6pHc I+CmAxY87Z/vgVxf63RbFqZ9HSi3VI3sH9X+imzP8eCrI44I4NS/6o7zAhUArcVK GobiXm0fqWZpD7DrcgnEOF8CggEAJLczjeNtMNTRSrhh5SR06FG2sUiCIe1hHPCz +3WPK+rPDnoC4gft6QxO9zRlnuvISeqNhv1oge5phdVQDWLhUojAQwAi2DU+/hjn 7HjZx0PHs+oE568dH9qyB9F9xKLPpzHld2PJB9t1OMQjrbXuqFSjxecx7Y28f5D6 IaMCS8EwgifS2vMyoPXW8MGpZ6iB5uPHFUZFEvGp6k0+T2V32F5JMgt37DlHA/Ok kmEMWU2JF14e3O1zU3h6ERhXdmNeRA9rarETO9mW8qLg95qRCR73I7CdhMSrY7/S 6s7bPbPR4VKwORP8LJTKbb7bXQpXrso34hZIOPhL/RUuuJqrGQOCAQUAAoIBAFWc 5KE8FFG8IrA+MG6G1gg0aDOPlrpE4Br/YrivsKlhP+9/jaCW+ESeukJIrNI8uw48 9e9s/WrqH8N5EH1AhTUd3yOAu9zyvj/FLyMnnfFCwHe36uyRUFE9k0MR4jS9NOA6 MEGTbPevEyZFwsioJCQOWGmr94LlTfRP1FXkSN7F7xvPqieUG3lMzD5tq+rqKS0R G56LWDh2ccbR+HZXtC513HHz4PUUCy/ukcThdND2ca90qCWtqDIw46GxRyROt1wM qhACEQqijf1GCzEWxSMibVRtTIF2/AWOOOdi8FMOloLnln1R5o3X6XY5nSQ3bj4m ZvUkmWLQi6tvBv98A6Q= -----END PUBLIC KEY----- ================================================ FILE: Spectacle/Resources/Images.xcassets/10.9 Crash Fix.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "Spectacle-1.png", "language-direction" : "left-to-right", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Spectacle-4.png", "language-direction" : "right-to-left", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Spectacle-7.png", "graphics-feature-set" : "metal1v2", "language-direction" : "left-to-right", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Spectacle-10.png", "graphics-feature-set" : "metal1v2", "language-direction" : "right-to-left", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Spectacle-2.png", "language-direction" : "left-to-right", "scale" : "2x" }, { "idiom" : "universal", "filename" : "Spectacle-5.png", "language-direction" : "right-to-left", "scale" : "2x" }, { "idiom" : "universal", "filename" : "Spectacle-8.png", "graphics-feature-set" : "metal1v2", "language-direction" : "left-to-right", "scale" : "2x" }, { "idiom" : "universal", "filename" : "Spectacle-11.png", "graphics-feature-set" : "metal1v2", "language-direction" : "right-to-left", "scale" : "2x" }, { "idiom" : "universal", "filename" : "Spectacle-3.png", "language-direction" : "left-to-right", "scale" : "3x" }, { "idiom" : "universal", "filename" : "Spectacle-6.png", "language-direction" : "right-to-left", "scale" : "3x" }, { "idiom" : "universal", "filename" : "Spectacle-9.png", "graphics-feature-set" : "metal1v2", "language-direction" : "left-to-right", "scale" : "3x" }, { "idiom" : "universal", "filename" : "Spectacle-12.png", "graphics-feature-set" : "metal1v2", "language-direction" : "right-to-left", "scale" : "3x" }, { "idiom" : "universal", "filename" : "Spectacle-13.png", "language-direction" : "left-to-right", "height-class" : "regular", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Spectacle-16.png", "language-direction" : "right-to-left", "height-class" : "regular", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Spectacle-19.png", "graphics-feature-set" : "metal1v2", "language-direction" : "left-to-right", "height-class" : "regular", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Spectacle-22.png", "graphics-feature-set" : "metal1v2", "language-direction" : "right-to-left", "height-class" : "regular", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Spectacle-14.png", "language-direction" : "left-to-right", "height-class" : "regular", "scale" : "2x" }, { "idiom" : "universal", "filename" : "Spectacle-17.png", "language-direction" : "right-to-left", "height-class" : "regular", "scale" : "2x" }, { "idiom" : "universal", "filename" : "Spectacle-20.png", "graphics-feature-set" : "metal1v2", "language-direction" : "left-to-right", "height-class" : "regular", "scale" : "2x" }, { "idiom" : "universal", "filename" : "Spectacle-23.png", "graphics-feature-set" : "metal1v2", "language-direction" : "right-to-left", "height-class" : "regular", "scale" : "2x" }, { "idiom" : "universal", "filename" : "Spectacle-15.png", "language-direction" : "left-to-right", "height-class" : "regular", "scale" : "3x" }, { "idiom" : "universal", "filename" : "Spectacle-18.png", "language-direction" : "right-to-left", "height-class" : "regular", "scale" : "3x" }, { "idiom" : "universal", "filename" : "Spectacle-21.png", "graphics-feature-set" : "metal1v2", "language-direction" : "left-to-right", "height-class" : "regular", "scale" : "3x" }, { "idiom" : "universal", "filename" : "Spectacle-24.png", "graphics-feature-set" : "metal1v2", "language-direction" : "right-to-left", "height-class" : "regular", "scale" : "3x" }, { "idiom" : "universal", "filename" : "Spectacle-25.png", "width-class" : "regular", "language-direction" : "left-to-right", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Spectacle-28.png", "width-class" : "regular", "language-direction" : "right-to-left", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Spectacle-31.png", "width-class" : "regular", "graphics-feature-set" : "metal1v2", "language-direction" : "left-to-right", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Spectacle-34.png", "width-class" : "regular", "graphics-feature-set" : "metal1v2", "language-direction" : "right-to-left", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Spectacle-26.png", "width-class" : "regular", "language-direction" : "left-to-right", "scale" : "2x" }, { "idiom" : "universal", "filename" : "Spectacle-29.png", "width-class" : "regular", "language-direction" : "right-to-left", "scale" : "2x" }, { "idiom" : "universal", "filename" : "Spectacle-32.png", "width-class" : "regular", "graphics-feature-set" : "metal1v2", "language-direction" : "left-to-right", "scale" : "2x" }, { "idiom" : "universal", "filename" : "Spectacle-35.png", "width-class" : "regular", "graphics-feature-set" : "metal1v2", "language-direction" : "right-to-left", "scale" : "2x" }, { "idiom" : "universal", "filename" : "Spectacle-27.png", "width-class" : "regular", "language-direction" : "left-to-right", "scale" : "3x" }, { "idiom" : "universal", "filename" : "Spectacle-30.png", "width-class" : "regular", "language-direction" : "right-to-left", "scale" : "3x" }, { "idiom" : "universal", "filename" : "Spectacle-33.png", "width-class" : "regular", "graphics-feature-set" : "metal1v2", "language-direction" : "left-to-right", "scale" : "3x" }, { "idiom" : "universal", "filename" : "Spectacle-36.png", "width-class" : "regular", "graphics-feature-set" : "metal1v2", "language-direction" : "right-to-left", "scale" : "3x" }, { "idiom" : "universal", "filename" : "Spectacle-37.png", "width-class" : "regular", "language-direction" : "left-to-right", "height-class" : "regular", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Spectacle-40.png", "width-class" : "regular", "language-direction" : "right-to-left", "height-class" : "regular", "scale" : "1x" }, { "language-direction" : "left-to-right", "graphics-feature-set" : "metal1v2", "idiom" : "universal", "width-class" : "regular", "filename" : "Spectacle-43.png", "height-class" : "regular", "scale" : "1x" }, { "language-direction" : "right-to-left", "graphics-feature-set" : "metal1v2", "idiom" : "universal", "width-class" : "regular", "filename" : "Spectacle-46.png", "height-class" : "regular", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Spectacle-38.png", "width-class" : "regular", "language-direction" : "left-to-right", "height-class" : "regular", "scale" : "2x" }, { "idiom" : "universal", "filename" : "Spectacle-41.png", "width-class" : "regular", "language-direction" : "right-to-left", "height-class" : "regular", "scale" : "2x" }, { "language-direction" : "left-to-right", "graphics-feature-set" : "metal1v2", "idiom" : "universal", "width-class" : "regular", "filename" : "Spectacle-44.png", "height-class" : "regular", "scale" : "2x" }, { "language-direction" : "right-to-left", "graphics-feature-set" : "metal1v2", "idiom" : "universal", "width-class" : "regular", "filename" : "Spectacle-47.png", "height-class" : "regular", "scale" : "2x" }, { "idiom" : "universal", "filename" : "Spectacle-39.png", "width-class" : "regular", "language-direction" : "left-to-right", "height-class" : "regular", "scale" : "3x" }, { "idiom" : "universal", "filename" : "Spectacle-42.png", "width-class" : "regular", "language-direction" : "right-to-left", "height-class" : "regular", "scale" : "3x" }, { "language-direction" : "left-to-right", "graphics-feature-set" : "metal1v2", "idiom" : "universal", "width-class" : "regular", "filename" : "Spectacle-45.png", "height-class" : "regular", "scale" : "3x" }, { "language-direction" : "right-to-left", "graphics-feature-set" : "metal1v2", "idiom" : "universal", "width-class" : "regular", "filename" : "Spectacle-48.png", "height-class" : "regular", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Spectacle/Resources/Images.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Spectacle/Resources/Images.xcassets/Security & Privacy.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "mac", "scale" : "1x", "filename" : "Security & Privacy.png" }, { "idiom" : "mac", "scale" : "2x", "filename" : "Security & Privacy@2x.png" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Spectacle/Resources/Images.xcassets/Spectacle Status Item.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "mac", "scale" : "1x", "filename" : "Spectacle Status Item.png" }, { "idiom" : "mac", "scale" : "2x", "filename" : "Spectacle Status Item@2x.png" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Spectacle/Resources/Images.xcassets/Spectacle.appiconset/Contents.json ================================================ { "images" : [ { "size" : "16x16", "idiom" : "mac", "filename" : "Spectacle 16.png", "scale" : "1x" }, { "size" : "16x16", "idiom" : "mac", "filename" : "Spectacle 16@2x.png", "scale" : "2x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "Spectacle 32.png", "scale" : "1x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "Spectacle 32@2x.png", "scale" : "2x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "Spectacle 128.png", "scale" : "1x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "Spectacle 128@2x.png", "scale" : "2x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "Spectacle 256.png", "scale" : "1x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "Spectacle 256@2x.png", "scale" : "2x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "Spectacle 512.png", "scale" : "1x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "Spectacle 512@2x.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Spectacle/Resources/Localizations/Base.lproj/Spectacle.xib ================================================ ================================================ FILE: Spectacle/Resources/Localizations/Base.lproj/SpectaclePreferencesWindow.xib ================================================ ================================================ FILE: Spectacle/Resources/Localizations/en.lproj/Credits.rtf ================================================ {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \margl1440\margr1440\vieww11300\viewh8400\viewkind0 \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\qc \f0\b\fs24 \cf0 Credits \b0 \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \cf0 Icons and other artwork: {\field{\*\fldinst{HYPERLINK "http://iiro.jappinen.fi"}}{\fldrslt \b Iiro J\'e4ppinen}} \b \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \b0 \cf0 \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \cf0 Source code available on {\field{\*\fldinst{HYPERLINK "https://github.com/eczarny/spectacle"}}{\fldrslt \b GitHub}} \b \ {\field{\*\fldinst{HYPERLINK "https://twitter.com/spectacleapp"}}{\fldrslt @spectacleapp}}\ } ================================================ FILE: Spectacle/Resources/Localizations/es.lproj/Credits.rtf ================================================ {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf470 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \paperw12240\paperh15840\margl1440\margr1440\vieww11300\viewh8400\viewkind0 \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\qc\partightenfactor0 \f0\b\fs24 \cf0 Credits \b0 \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 \cf0 Iconos y dise\'f1o gr\'e1fico: {\field{\*\fldinst{HYPERLINK "http://iiro.jappinen.fi"}}{\fldrslt \b Iiro J\'e4ppinen}} \b \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 \b0 \cf0 \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 \cf0 C\'f3digo fuente disponible en {\field{\*\fldinst{HYPERLINK "https://github.com/eczarny/spectacle"}}{\fldrslt \b GitHub}} \b \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 {\field{\*\fldinst{HYPERLINK "https://twitter.com/spectacleapp"}}{\fldrslt \cf0 @spectacleapp}}\ } ================================================ FILE: Spectacle/Resources/Localizations/es.lproj/Spectacle.strings ================================================ /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "187-7k-0Ct"; */ "187-7k-0Ct.title" = "Rehacer"; /* Class = "NSMenuItem"; title = "Check for Updates..."; ObjectID = "2sj-vF-eSL"; */ "2sj-vF-eSL.title" = "Chequear actualizaciones..."; /* Class = "NSMenuItem"; title = "Next Third"; ObjectID = "7HO-L3-O6Q"; */ "7HO-L3-O6Q.title" = "Tercer próximo"; /* Class = "NSMenuItem"; title = "Upper Right"; ObjectID = "C7t-Sf-kOI"; */ "C7t-Sf-kOI.title" = "Superior derecha"; /* Class = "NSMenuItem"; title = "About Spectacle"; ObjectID = "EA9-dx-Qfg"; */ "EA9-dx-Qfg.title" = "Acerca de Spectacle"; /* Class = "NSTextFieldCell"; title = "Spectacle would like to control your windows using accessibility features."; ObjectID = "HRo-tn-M1V"; */ "HRo-tn-M1V.title" = "Spectacle necesita tener control de las funcionalidades de accesibilidad."; /* Class = "NSMenuItem"; title = "Next Display"; ObjectID = "I4U-jL-hXq"; */ "I4U-jL-hXq.title" = "Siguiente monitor"; /* Class = "NSMenuItem"; title = "Quit Spectacle"; ObjectID = "Isn-Og-AEv"; */ "Isn-Og-AEv.title" = "Salir Spectacle"; /* Class = "NSMenuItem"; title = "Left Half"; ObjectID = "Jae-vx-7ZC"; */ "Jae-vx-7ZC.title" = "La mitad izquierda"; /* Class = "NSMenu"; title = "Spectacle Status Item"; ObjectID = "KsA-8w-wcl"; */ "KsA-8w-wcl.title" = "Spectacle estado del elemento"; /* Class = "NSMenuItem"; title = "Preferences..."; ObjectID = "ME4-Br-HLT"; */ "ME4-Br-HLT.title" = "Configuración..."; /* Class = "NSMenuItem"; title = "Make Larger"; ObjectID = "Yun-AA-OVD"; */ "Yun-AA-OVD.title" = "Hacer más grande"; /* Class = "NSMenuItem"; title = "Previous Display"; ObjectID = "aPo-Rp-A1X"; */ "aPo-Rp-A1X.title" = "Monitor anterior"; /* Class = "NSMenuItem"; title = "Previous Third"; ObjectID = "bV9-Ga-jDG"; */ "bV9-Ga-jDG.title" = "Tercer anterior"; /* Class = "NSMenuItem"; title = "Right Half"; ObjectID = "cae-26-Oab"; */ "cae-26-Oab.title" = "Mitad derecha"; /* Class = "NSMenuItem"; title = "Fullscreen"; ObjectID = "dwQ-Bl-nEL"; */ "dwQ-Bl-nEL.title" = "Pantalla completa"; /* Class = "NSMenuItem"; title = "Lower Left"; ObjectID = "eN5-A6-iho"; */ "eN5-A6-iho.title" = "Abajo a la izquierda"; /* Class = "NSMenuItem"; title = "Disable"; ObjectID = "eZT-Mm-qSc"; */ "eZT-Mm-qSc.title" = "Deshabilitado"; /* Class = "NSMenu"; title = "Disable"; ObjectID = "flL-B2-XIP"; */ "flL-B2-XIP.title" = "Deshabilitado"; /* Class = "NSButtonCell"; title = "Quit Spectacle"; ObjectID = "gMV-vD-CUe"; */ "gMV-vD-CUe.title" = "Salir Spectacle"; /* Class = "NSMenuItem"; title = "Make Smaller"; ObjectID = "gTe-ym-DB0"; */ "gTe-ym-DB0.title" = "Hacer mas pequeño"; /* Class = "NSMenuItem"; title = "Bottom Half"; ObjectID = "huV-Gw-lbX"; */ "huV-Gw-lbX.title" = "Mitad inferior"; /* Class = "NSButtonCell"; title = "Open System Preferences"; ObjectID = "iPl-AT-Pei"; */ "iPl-AT-Pei.title" = "Abrir preferencias de sistema"; /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "l9O-0B-fjp"; */ "l9O-0B-fjp.title" = "Deshacer"; /* Class = "NSMenuItem"; title = "Upper Left"; ObjectID = "ldc-QO-Cq6"; */ "ldc-QO-Cq6.title" = "Arriba a la izquierda"; /* Class = "NSMenuItem"; title = "for APPLICATION"; ObjectID = "msZ-id-Pvw"; */ "msZ-id-Pvw.title" = "para la aplicación"; /* Class = "NSMenuItem"; title = "Lower Right"; ObjectID = "oDU-J0-NkJ"; */ "oDU-J0-NkJ.title" = "Inferior derecha"; /* Class = "NSMenuItem"; title = "for an hour"; ObjectID = "qpo-5w-ZIY"; */ "qpo-5w-ZIY.title" = "Por una hora"; /* Class = "NSTextFieldCell"; title = "Grant access to this application in System Preferences."; ObjectID = "ruu-yJ-Q1F"; */ "ruu-yJ-Q1F.title" = "Permitir el acceso a esta aplicación en Preferencias del sistema."; /* Class = "NSMenuItem"; title = "Center"; ObjectID = "xJu-Uo-nZw"; */ "xJu-Uo-nZw.title" = "Centro"; /* Class = "NSMenuItem"; title = "Top Half"; ObjectID = "yh0-ik-weD"; */ "yh0-ik-weD.title" = "Mitad superior"; ================================================ FILE: Spectacle/Resources/Localizations/es.lproj/SpectaclePreferencesWindow.strings ================================================ /* Class = "NSTextFieldCell"; title = "Make Larger:"; ObjectID = "0f9-kj-bro"; */ "0f9-kj-bro.title" = "Hacer más grande:"; /* Class = "NSTextFieldCell"; title = "Control"; ObjectID = "7Fe-Da-fYB"; */ "7Fe-Da-fYB.title" = "Control"; /* Class = "NSTextFieldCell"; title = "⌃"; ObjectID = "806-S1-nYH"; */ "806-S1-nYH.title" = "⌃"; /* Class = "NSTextFieldCell"; title = "Top Half:"; ObjectID = "AnP-Lv-Hbd"; */ "AnP-Lv-Hbd.title" = "Mitad superior:"; /* Class = "NSTextFieldCell"; title = "Bottom Half:"; ObjectID = "Awn-mO-sht"; */ "Awn-mO-sht.title" = "Mitad inferior:"; /* Class = "NSTextFieldCell"; title = "Undo:"; ObjectID = "CJs-az-eG7"; */ "CJs-az-eG7.title" = "Deshacer:"; /* Class = "NSTextFieldCell"; title = "Left Half:"; ObjectID = "IWj-rc-Kvi"; */ "IWj-rc-Kvi.title" = "Mitad izquierda:"; /* Class = "NSTextFieldCell"; title = "⇧"; ObjectID = "Ir0-AJ-ldj"; */ "Ir0-AJ-ldj.title" = "⇧"; /* Class = "NSWindow"; title = "Spectacle X.Y.Z"; ObjectID = "JW4-q9-L7q"; */ "JW4-q9-L7q.title" = "Spectacle X.Y.Z"; /* Class = "NSButtonCell"; title = "Restore Defaults..."; ObjectID = "NT7-Sd-Ofw"; */ "NT7-Sd-Ofw.title" = "Restaurar por defecto..."; /* Class = "NSTextFieldCell"; title = "Make Smaller:"; ObjectID = "SDM-U5-yUR"; */ "SDM-U5-yUR.title" = "Hacer más pequeño:"; /* Class = "NSTextFieldCell"; title = "⌘"; ObjectID = "SQM-4e-yFd"; */ "SQM-4e-yFd.title" = "⌘"; /* Class = "NSTextFieldCell"; title = "Next Third:"; ObjectID = "Tze-mR-IV4"; */ "Tze-mR-IV4.title" = "Siguiente tercero:"; /* Class = "NSTextFieldCell"; title = "Option"; ObjectID = "U0H-hG-KAQ"; */ "U0H-hG-KAQ.title" = "Opciones"; /* Class = "NSTextFieldCell"; title = "Lower Right:"; ObjectID = "Vul-FT-BbB"; */ "Vul-FT-BbB.title" = "Inferior derecho:"; /* Class = "NSMenu"; title = "OtherViews"; ObjectID = "W2t-KP-bYJ"; */ "W2t-KP-bYJ.title" = "Otras vistas"; /* Class = "NSTextFieldCell"; title = "Right Half:"; ObjectID = "WeK-fS-mRh"; */ "WeK-fS-mRh.title" = "Mitad derecha:"; /* Class = "NSTextFieldCell"; title = "⌥"; ObjectID = "YQm-Kc-fYF"; */ "YQm-Kc-fYF.title" = "⌥"; /* Class = "NSMenuItem"; title = "as a background application"; ObjectID = "aWf-FC-J1j"; */ "aWf-FC-J1j.title" = "aplicación en segundo plano"; /* Class = "NSButtonCell"; title = "Launch Spectacle at login"; ObjectID = "cEj-mH-kie"; */ "cEj-mH-kie.title" = "Ejecutar Spectacle al iniciar"; /* Class = "NSTextFieldCell"; title = "Lower Left:"; ObjectID = "dQP-TR-GMx"; */ "dQP-TR-GMx.title" = "Abajo a la izquierda:"; /* Class = "NSTextFieldCell"; title = "Shift"; ObjectID = "dww-8Q-sa1"; */ "dww-8Q-sa1.title" = "Shift"; /* Class = "NSTextFieldCell"; title = "Run..."; ObjectID = "eYU-TH-wcO"; */ "eYU-TH-wcO.title" = "Ejecutar..."; /* Class = "NSTextFieldCell"; title = "Upper Right:"; ObjectID = "gSY-Au-xOH"; */ "gSY-Au-xOH.title" = "Superior derecha:"; /* Class = "NSTextFieldCell"; title = "Previous Display:"; ObjectID = "hmL-EC-pro"; */ "hmL-EC-pro.title" = "Monitor anterior:"; /* Class = "NSMenuItem"; title = "in the status menu"; ObjectID = "in4-v8-tIm"; */ "in4-v8-tIm.title" = "en menu de estado"; /* Class = "NSTextFieldCell"; title = "Command"; ObjectID = "jdf-Yu-zrq"; */ "jdf-Yu-zrq.title" = "Command"; /* Class = "NSTextFieldCell"; title = "Upper Left:"; ObjectID = "ldH-1E-mKW"; */ "ldH-1E-mKW.title" = "Superior izquierdo:"; /* Class = "NSTextFieldCell"; title = "Next Display:"; ObjectID = "lw7-Ap-kJO"; */ "lw7-Ap-kJO.title" = "Monitor siguiente:"; /* Class = "NSTextFieldCell"; title = "Center:"; ObjectID = "oqf-7z-BFa"; */ "oqf-7z-BFa.title" = "Centro:"; /* Class = "NSTextFieldCell"; title = "Previous Third:"; ObjectID = "s3E-Lp-mJ4"; */ "s3E-Lp-mJ4.title" = "Tercer anterior:"; /* Class = "NSTextFieldCell"; title = "Fullscreen:"; ObjectID = "tKr-XE-DEU"; */ "tKr-XE-DEU.title" = "Pantalla completa:"; /* Class = "NSBox"; title = "Box"; ObjectID = "vWw-ws-BgW"; */ "vWw-ws-BgW.title" = "Caja"; /* Class = "NSTextFieldCell"; title = "Redo:"; ObjectID = "y7M-cR-DF2"; */ "y7M-cR-DF2.title" = "Rehacer:"; ================================================ FILE: Spectacle/Resources/Localizations/fi.lproj/Credits.rtf ================================================ {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf420 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \margl1440\margr1440\vieww11300\viewh8400\viewkind0 \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\qc\partightenfactor0 \f0\b\fs24 \cf0 Tekij\'e4t \b0 \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 \cf0 Kuvakkeet ja muut grafiikat: {\field{\*\fldinst{HYPERLINK "http://iiro.jappinen.fi"}}{\fldrslt \b Iiro J\'e4ppinen}} \b \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 \b0 \cf0 \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 \cf0 L\'e4hdekoodi saatavilla {\field{\*\fldinst{HYPERLINK "https://github.com/eczarny/spectacle"}}{\fldrslt \b GitHubissa}} \b \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 {\field{\*\fldinst{HYPERLINK "https://twitter.com/spectacleapp"}}{\fldrslt \cf0 @spectacleapp}}\ } ================================================ FILE: Spectacle/Resources/Localizations/fi.lproj/Spectacle.strings ================================================ /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "187-7k-0Ct"; */ "187-7k-0Ct.title" = "Tee uudelleen"; /* Class = "NSMenuItem"; title = "Check for Updates..."; ObjectID = "2sj-vF-eSL"; */ "2sj-vF-eSL.title" = "Tarkista päivitykset..."; /* Class = "NSMenuItem"; title = "Next Third"; ObjectID = "7HO-L3-O6Q"; */ "7HO-L3-O6Q.title" = "Seuraava kolmasosa"; /* Class = "NSMenuItem"; title = "Upper Right"; ObjectID = "C7t-Sf-kOI"; */ "C7t-Sf-kOI.title" = "Yläoikea"; /* Class = "NSMenuItem"; title = "About Spectacle"; ObjectID = "EA9-dx-Qfg"; */ "EA9-dx-Qfg.title" = "Tietoja Spectaclesta"; /* Class = "NSTextFieldCell"; title = "Spectacle would like to control your windows using accessibility features."; ObjectID = "HRo-tn-M1V"; */ "HRo-tn-M1V.title" = "Spectacle haluaisi hallita ikkunoita käyttöaputoimintojen avulla."; /* Class = "NSMenuItem"; title = "Next Display"; ObjectID = "I4U-jL-hXq"; */ "I4U-jL-hXq.title" = "Seuraava ikkuna"; /* Class = "NSMenuItem"; title = "Quit Spectacle"; ObjectID = "Isn-Og-AEv"; */ "Isn-Og-AEv.title" = "Sulje Spectacle"; /* Class = "NSMenuItem"; title = "Left Half"; ObjectID = "Jae-vx-7ZC"; */ "Jae-vx-7ZC.title" = "Vasen puoli"; /* Class = "NSMenu"; title = "Spectacle Status Item"; ObjectID = "KsA-8w-wcl"; */ "KsA-8w-wcl.title" = "Spectacle Status Item"; /* Class = "NSMenuItem"; title = "Preferences..."; ObjectID = "ME4-Br-HLT"; */ "ME4-Br-HLT.title" = "Asetukset..."; /* Class = "NSMenuItem"; title = "Make Larger"; ObjectID = "Yun-AA-OVD"; */ "Yun-AA-OVD.title" = "Suurenna"; /* Class = "NSMenuItem"; title = "Previous Display"; ObjectID = "aPo-Rp-A1X"; */ "aPo-Rp-A1X.title" = "Edellinen näyttö"; /* Class = "NSMenuItem"; title = "Previous Third"; ObjectID = "bV9-Ga-jDG"; */ "bV9-Ga-jDG.title" = "Edellinen kolmasosa"; /* Class = "NSMenuItem"; title = "Right Half"; ObjectID = "cae-26-Oab"; */ "cae-26-Oab.title" = "Oikea puoli"; /* Class = "NSMenuItem"; title = "Fullscreen"; ObjectID = "dwQ-Bl-nEL"; */ "dwQ-Bl-nEL.title" = "Koko näyttö"; /* Class = "NSMenuItem"; title = "Lower Left"; ObjectID = "eN5-A6-iho"; */ "eN5-A6-iho.title" = "Alavasen"; /* Class = "NSMenuItem"; title = "Disable"; ObjectID = "eZT-Mm-qSc"; */ "eZT-Mm-qSc.title" = "Poista käytöstä"; /* Class = "NSMenu"; title = "Disable"; ObjectID = "flL-B2-XIP"; */ "flL-B2-XIP.title" = "Poista käytöstä"; /* Class = "NSButtonCell"; title = "Quit Spectacle"; ObjectID = "gMV-vD-CUe"; */ "gMV-vD-CUe.title" = "Sulje Spectacle"; /* Class = "NSMenuItem"; title = "Make Smaller"; ObjectID = "gTe-ym-DB0"; */ "gTe-ym-DB0.title" = "Pienennä"; /* Class = "NSMenuItem"; title = "Bottom Half"; ObjectID = "huV-Gw-lbX"; */ "huV-Gw-lbX.title" = "Alapuoli"; /* Class = "NSButtonCell"; title = "Open System Preferences"; ObjectID = "iPl-AT-Pei"; */ "iPl-AT-Pei.title" = "Avaa järjestelmäasetukset"; /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "l9O-0B-fjp"; */ "l9O-0B-fjp.title" = "Kumoa"; /* Class = "NSMenuItem"; title = "Upper Left"; ObjectID = "ldc-QO-Cq6"; */ "ldc-QO-Cq6.title" = "Ylävasen"; /* Class = "NSMenuItem"; title = "for APPLICATION"; ObjectID = "msZ-id-Pvw"; */ "msZ-id-Pvw.title" = "ohjelmassa APPLICATION"; /* Class = "NSMenuItem"; title = "Lower Right"; ObjectID = "oDU-J0-NkJ"; */ "oDU-J0-NkJ.title" = "Alaoikea"; /* Class = "NSMenuItem"; title = "for an hour"; ObjectID = "qpo-5w-ZIY"; */ "qpo-5w-ZIY.title" = "tunniksi"; /* Class = "NSTextFieldCell"; title = "Grant access to this application in System Preferences."; ObjectID = "ruu-yJ-Q1F"; */ "ruu-yJ-Q1F.title" = "Salli pääsy tälle ohjelmalle järjestelmäasetuksissa."; /* Class = "NSMenuItem"; title = "Center"; ObjectID = "xJu-Uo-nZw"; */ "xJu-Uo-nZw.title" = "Keskitä"; /* Class = "NSMenuItem"; title = "Top Half"; ObjectID = "yh0-ik-weD"; */ "yh0-ik-weD.title" = "Yläpuoli"; ================================================ FILE: Spectacle/Resources/Localizations/fi.lproj/SpectaclePreferencesWindow.strings ================================================ /* Class = "NSTextFieldCell"; title = "Make Larger:"; ObjectID = "0f9-kj-bro"; */ "0f9-kj-bro.title" = "Suurenna:"; /* Class = "NSTextFieldCell"; title = "Control"; ObjectID = "7Fe-Da-fYB"; */ "7Fe-Da-fYB.title" = "Ctrl"; /* Class = "NSTextFieldCell"; title = "⌃"; ObjectID = "806-S1-nYH"; */ "806-S1-nYH.title" = "⌃"; /* Class = "NSTextFieldCell"; title = "Top Half:"; ObjectID = "AnP-Lv-Hbd"; */ "AnP-Lv-Hbd.title" = "Yläpuoli:"; /* Class = "NSTextFieldCell"; title = "Bottom Half:"; ObjectID = "Awn-mO-sht"; */ "Awn-mO-sht.title" = "Alapuoli:"; /* Class = "NSTextFieldCell"; title = "Undo:"; ObjectID = "CJs-az-eG7"; */ "CJs-az-eG7.title" = "Kumoa:"; /* Class = "NSTextFieldCell"; title = "Left Half:"; ObjectID = "IWj-rc-Kvi"; */ "IWj-rc-Kvi.title" = "Vasen puoli:"; /* Class = "NSTextFieldCell"; title = "⇧"; ObjectID = "Ir0-AJ-ldj"; */ "Ir0-AJ-ldj.title" = "⇧"; /* Class = "NSWindow"; title = "Spectacle X.Y.Z"; ObjectID = "JW4-q9-L7q"; */ "JW4-q9-L7q.title" = "Spectacle X.Y.Z"; /* Class = "NSButtonCell"; title = "Restore Defaults..."; ObjectID = "NT7-Sd-Ofw"; */ "NT7-Sd-Ofw.title" = "Palauta oletukset..."; /* Class = "NSTextFieldCell"; title = "Make Smaller:"; ObjectID = "SDM-U5-yUR"; */ "SDM-U5-yUR.title" = "Pienennä:"; /* Class = "NSTextFieldCell"; title = "⌘"; ObjectID = "SQM-4e-yFd"; */ "SQM-4e-yFd.title" = "⌘"; /* Class = "NSTextFieldCell"; title = "Next Third:"; ObjectID = "Tze-mR-IV4"; */ "Tze-mR-IV4.title" = "Seuraava kolmasosa:"; /* Class = "NSTextFieldCell"; title = "Option"; ObjectID = "U0H-hG-KAQ"; */ "U0H-hG-KAQ.title" = "Optio"; /* Class = "NSTextFieldCell"; title = "Lower Right:"; ObjectID = "Vul-FT-BbB"; */ "Vul-FT-BbB.title" = "Alaoikea:"; /* Class = "NSMenu"; title = "OtherViews"; ObjectID = "W2t-KP-bYJ"; */ "W2t-KP-bYJ.title" = "MuutNäkymät"; /* Class = "NSTextFieldCell"; title = "Right Half:"; ObjectID = "WeK-fS-mRh"; */ "WeK-fS-mRh.title" = "Oikea puoli:"; /* Class = "NSTextFieldCell"; title = "⌥"; ObjectID = "YQm-Kc-fYF"; */ "YQm-Kc-fYF.title" = "⌥"; /* Class = "NSMenuItem"; title = "as a background application"; ObjectID = "aWf-FC-J1j"; */ "aWf-FC-J1j.title" = "taustaohjelmana"; /* Class = "NSButtonCell"; title = "Launch Spectacle at login"; ObjectID = "cEj-mH-kie"; */ "cEj-mH-kie.title" = "Käynnistä Spectacle kirjauduttaessa"; /* Class = "NSTextFieldCell"; title = "Lower Left:"; ObjectID = "dQP-TR-GMx"; */ "dQP-TR-GMx.title" = "Alavasen:"; /* Class = "NSTextFieldCell"; title = "Shift"; ObjectID = "dww-8Q-sa1"; */ "dww-8Q-sa1.title" = "Vaihto"; /* Class = "NSTextFieldCell"; title = "Run..."; ObjectID = "eYU-TH-wcO"; */ "eYU-TH-wcO.title" = "Suorita..."; /* Class = "NSTextFieldCell"; title = "Upper Right:"; ObjectID = "gSY-Au-xOH"; */ "gSY-Au-xOH.title" = "Yläoikea:"; /* Class = "NSTextFieldCell"; title = "Previous Display:"; ObjectID = "hmL-EC-pro"; */ "hmL-EC-pro.title" = "Edellinen näyttö:"; /* Class = "NSMenuItem"; title = "in the status menu"; ObjectID = "in4-v8-tIm"; */ "in4-v8-tIm.title" = "valikkopalkissa"; /* Class = "NSTextFieldCell"; title = "Command"; ObjectID = "jdf-Yu-zrq"; */ "jdf-Yu-zrq.title" = "Komento"; /* Class = "NSTextFieldCell"; title = "Upper Left:"; ObjectID = "ldH-1E-mKW"; */ "ldH-1E-mKW.title" = "Ylävasen:"; /* Class = "NSTextFieldCell"; title = "Next Display:"; ObjectID = "lw7-Ap-kJO"; */ "lw7-Ap-kJO.title" = "Seuraava näyttö:"; /* Class = "NSTextFieldCell"; title = "Center:"; ObjectID = "oqf-7z-BFa"; */ "oqf-7z-BFa.title" = "Keskitys:"; /* Class = "NSTextFieldCell"; title = "Previous Third:"; ObjectID = "s3E-Lp-mJ4"; */ "s3E-Lp-mJ4.title" = "Edellinen kolmasosa:"; /* Class = "NSTextFieldCell"; title = "Fullscreen:"; ObjectID = "tKr-XE-DEU"; */ "tKr-XE-DEU.title" = "Koko näyttö:"; /* Class = "NSBox"; title = "Box"; ObjectID = "vWw-ws-BgW"; */ "vWw-ws-BgW.title" = "Box"; /* Class = "NSTextFieldCell"; title = "Redo:"; ObjectID = "y7M-cR-DF2"; */ "y7M-cR-DF2.title" = "Tee uudelleen:"; ================================================ FILE: Spectacle/Resources/Localizations/fr.lproj/Credits.rtf ================================================ {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \margl1440\margr1440\vieww11300\viewh8400\viewkind0 \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\qc \f0\b\fs24 \cf0 Cr\'e9dits \b0 \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \cf0 Ic\'f4nes et autres \'9cuvres: {\field{\*\fldinst{HYPERLINK "http://iiro.jappinen.fi"}}{\fldrslt \b Iiro J\'e4ppinen}} \b \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \b0 \cf0 \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \cf0 Code source disponible sur {\field{\*\fldinst{HYPERLINK "https://github.com/eczarny/spectacle"}}{\fldrslt \b GitHub}} \b \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc {\field{\*\fldinst{HYPERLINK "https://twitter.com/spectacleapp"}}{\fldrslt \cf0 @spectacleapp}}\ } ================================================ FILE: Spectacle/Resources/Localizations/fr.lproj/Spectacle.strings ================================================ /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "187-7k-0Ct"; */ "187-7k-0Ct.title" = "Rétablir"; /* Class = "NSMenuItem"; title = "Check for Updates..."; ObjectID = "2sj-vF-eSL"; */ "2sj-vF-eSL.title" = "Vérifier les mises à jour..."; /* Class = "NSMenuItem"; title = "Next Third"; ObjectID = "7HO-L3-O6Q"; */ "7HO-L3-O6Q.title" = "Tiers suivant"; /* Class = "NSMenuItem"; title = "Upper Right"; ObjectID = "C7t-Sf-kOI"; */ "C7t-Sf-kOI.title" = "En haut à droite"; /* Class = "NSMenuItem"; title = "About Spectacle"; ObjectID = "EA9-dx-Qfg"; */ "EA9-dx-Qfg.title" = "À propos de Spectacle"; /* Class = "NSTextFieldCell"; title = "Spectacle would like to control your windows using accessibility features."; ObjectID = "HRo-tn-M1V"; */ "HRo-tn-M1V.title" = "Spectacle souhaite contrôler vos fenêtres à l'aide des fonctionnalités d'accessibilité."; /* Class = "NSMenuItem"; title = "Next Display"; ObjectID = "I4U-jL-hXq"; */ "I4U-jL-hXq.title" = "Écran suivant"; /* Class = "NSMenuItem"; title = "Quit Spectacle"; ObjectID = "Isn-Og-AEv"; */ "Isn-Og-AEv.title" = "Quitter Spectacle"; /* Class = "NSMenuItem"; title = "Left Half"; ObjectID = "Jae-vx-7ZC"; */ "Jae-vx-7ZC.title" = "Moitié gauche"; /* Class = "NSMenu"; title = "Spectacle Status Item"; ObjectID = "KsA-8w-wcl"; */ "KsA-8w-wcl.title" = "Spectacle Status Item"; /* Class = "NSMenuItem"; title = "Preferences..."; ObjectID = "ME4-Br-HLT"; */ "ME4-Br-HLT.title" = "Préférences..."; /* Class = "NSMenuItem"; title = "Make Larger"; ObjectID = "Yun-AA-OVD"; */ "Yun-AA-OVD.title" = "Agrandir"; /* Class = "NSMenuItem"; title = "Previous Display"; ObjectID = "aPo-Rp-A1X"; */ "aPo-Rp-A1X.title" = "Écran précédent"; /* Class = "NSMenuItem"; title = "Previous Third"; ObjectID = "bV9-Ga-jDG"; */ "bV9-Ga-jDG.title" = "Tiers précédent"; /* Class = "NSMenuItem"; title = "Right Half"; ObjectID = "cae-26-Oab"; */ "cae-26-Oab.title" = "Moitié droit"; /* Class = "NSMenuItem"; title = "Fullscreen"; ObjectID = "dwQ-Bl-nEL"; */ "dwQ-Bl-nEL.title" = "Plein écran"; /* Class = "NSMenuItem"; title = "Lower Left"; ObjectID = "eN5-A6-iho"; */ "eN5-A6-iho.title" = "En bas à gauche"; /* Class = "NSMenuItem"; title = "Disable"; ObjectID = "eZT-Mm-qSc"; */ "eZT-Mm-qSc.title" = "Désactiver"; /* Class = "NSMenu"; title = "Disable"; ObjectID = "flL-B2-XIP"; */ "flL-B2-XIP.title" = "Désactiver"; /* Class = "NSButtonCell"; title = "Quit Spectacle"; ObjectID = "gMV-vD-CUe"; */ "gMV-vD-CUe.title" = "Quitter Spectacle"; /* Class = "NSMenuItem"; title = "Make Smaller"; ObjectID = "gTe-ym-DB0"; */ "gTe-ym-DB0.title" = "Réduire"; /* Class = "NSMenuItem"; title = "Bottom Half"; ObjectID = "huV-Gw-lbX"; */ "huV-Gw-lbX.title" = "Moitié bas"; /* Class = "NSButtonCell"; title = "Open System Preferences"; ObjectID = "iPl-AT-Pei"; */ "iPl-AT-Pei.title" = "Ouvrir Préférences Système"; /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "l9O-0B-fjp"; */ "l9O-0B-fjp.title" = "Annuler"; /* Class = "NSMenuItem"; title = "Upper Left"; ObjectID = "ldc-QO-Cq6"; */ "ldc-QO-Cq6.title" = "En haut à gauche"; /* Class = "NSMenuItem"; title = "for APPLICATION"; ObjectID = "msZ-id-Pvw"; */ "msZ-id-Pvw.title" = "for APPLICATION"; /* Class = "NSMenuItem"; title = "Lower Right"; ObjectID = "oDU-J0-NkJ"; */ "oDU-J0-NkJ.title" = "En bas à droite"; /* Class = "NSMenuItem"; title = "for an hour"; ObjectID = "qpo-5w-ZIY"; */ "qpo-5w-ZIY.title" = "pour 1 heure"; /* Class = "NSTextFieldCell"; title = "Grant access to this application in System Preferences."; ObjectID = "ruu-yJ-Q1F"; */ "ruu-yJ-Q1F.title" = "Accorder l'accès à cette application dans les Préférences Système."; /* Class = "NSMenuItem"; title = "Center"; ObjectID = "xJu-Uo-nZw"; */ "xJu-Uo-nZw.title" = "Centrer"; /* Class = "NSMenuItem"; title = "Top Half"; ObjectID = "yh0-ik-weD"; */ "yh0-ik-weD.title" = "Moitié haut"; ================================================ FILE: Spectacle/Resources/Localizations/fr.lproj/SpectaclePreferencesWindow.strings ================================================ /* Class = "NSTextFieldCell"; title = "Make Larger:"; ObjectID = "0f9-kj-bro"; */ "0f9-kj-bro.title" = "Agrandir:"; /* Class = "NSTextFieldCell"; title = "Control"; ObjectID = "7Fe-Da-fYB"; */ "7Fe-Da-fYB.title" = "Contrôle"; /* Class = "NSTextFieldCell"; title = "⌃"; ObjectID = "806-S1-nYH"; */ "806-S1-nYH.title" = "⌃"; /* Class = "NSTextFieldCell"; title = "Top Half:"; ObjectID = "AnP-Lv-Hbd"; */ "AnP-Lv-Hbd.title" = "Moitié haut:"; /* Class = "NSTextFieldCell"; title = "Bottom Half:"; ObjectID = "Awn-mO-sht"; */ "Awn-mO-sht.title" = "Moitié bas:"; /* Class = "NSTextFieldCell"; title = "Undo:"; ObjectID = "CJs-az-eG7"; */ "CJs-az-eG7.title" = "Annuler:"; /* Class = "NSTextFieldCell"; title = "Left Half:"; ObjectID = "IWj-rc-Kvi"; */ "IWj-rc-Kvi.title" = "Moitié gauche:"; /* Class = "NSTextFieldCell"; title = "⇧"; ObjectID = "Ir0-AJ-ldj"; */ "Ir0-AJ-ldj.title" = "⇧"; /* Class = "NSWindow"; title = "Spectacle X.Y.Z"; ObjectID = "JW4-q9-L7q"; */ "JW4-q9-L7q.title" = "Spectacle X.Y.Z"; /* Class = "NSButtonCell"; title = "Restore Defaults..."; ObjectID = "NT7-Sd-Ofw"; */ "NT7-Sd-Ofw.title" = "Rétablir par défaut..."; /* Class = "NSTextFieldCell"; title = "Make Smaller:"; ObjectID = "SDM-U5-yUR"; */ "SDM-U5-yUR.title" = "Réduire:"; /* Class = "NSTextFieldCell"; title = "⌘"; ObjectID = "SQM-4e-yFd"; */ "SQM-4e-yFd.title" = "⌘"; /* Class = "NSTextFieldCell"; title = "Next Third:"; ObjectID = "Tze-mR-IV4"; */ "Tze-mR-IV4.title" = "Tiers suivant:"; /* Class = "NSTextFieldCell"; title = "Option"; ObjectID = "U0H-hG-KAQ"; */ "U0H-hG-KAQ.title" = "Option"; /* Class = "NSTextFieldCell"; title = "Lower Right:"; ObjectID = "Vul-FT-BbB"; */ "Vul-FT-BbB.title" = "En bas à droite:"; /* Class = "NSMenu"; title = "OtherViews"; ObjectID = "W2t-KP-bYJ"; */ "W2t-KP-bYJ.title" = "OtherViews"; /* Class = "NSTextFieldCell"; title = "Right Half:"; ObjectID = "WeK-fS-mRh"; */ "WeK-fS-mRh.title" = "Moitié droit:"; /* Class = "NSTextFieldCell"; title = "⌥"; ObjectID = "YQm-Kc-fYF"; */ "YQm-Kc-fYF.title" = "⌥"; /* Class = "NSMenuItem"; title = "as a background application"; ObjectID = "aWf-FC-J1j"; */ "aWf-FC-J1j.title" = "comme une application d'arrière-plan"; /* Class = "NSButtonCell"; title = "Launch Spectacle at login"; ObjectID = "cEj-mH-kie"; */ "cEj-mH-kie.title" = "Ouvrir Spectacle au démarrage"; /* Class = "NSTextFieldCell"; title = "Lower Left:"; ObjectID = "dQP-TR-GMx"; */ "dQP-TR-GMx.title" = "En bas à gauche:"; /* Class = "NSTextFieldCell"; title = "Shift"; ObjectID = "dww-8Q-sa1"; */ "dww-8Q-sa1.title" = "Maj"; /* Class = "NSTextFieldCell"; title = "Run..."; ObjectID = "eYU-TH-wcO"; */ "eYU-TH-wcO.title" = "Lancer..."; /* Class = "NSTextFieldCell"; title = "Upper Right:"; ObjectID = "gSY-Au-xOH"; */ "gSY-Au-xOH.title" = "En haut à droite:"; /* Class = "NSTextFieldCell"; title = "Previous Display:"; ObjectID = "hmL-EC-pro"; */ "hmL-EC-pro.title" = "Écran précédent:"; /* Class = "NSMenuItem"; title = "in the status menu"; ObjectID = "in4-v8-tIm"; */ "in4-v8-tIm.title" = "dans la barre d'état"; /* Class = "NSTextFieldCell"; title = "Command"; ObjectID = "jdf-Yu-zrq"; */ "jdf-Yu-zrq.title" = "Commande"; /* Class = "NSTextFieldCell"; title = "Upper Left:"; ObjectID = "ldH-1E-mKW"; */ "ldH-1E-mKW.title" = "En haut à gauche:"; /* Class = "NSTextFieldCell"; title = "Next Display:"; ObjectID = "lw7-Ap-kJO"; */ "lw7-Ap-kJO.title" = "Écran suivant:"; /* Class = "NSTextFieldCell"; title = "Center:"; ObjectID = "oqf-7z-BFa"; */ "oqf-7z-BFa.title" = "Centrer:"; /* Class = "NSTextFieldCell"; title = "Previous Third:"; ObjectID = "s3E-Lp-mJ4"; */ "s3E-Lp-mJ4.title" = "Tiers précédent:"; /* Class = "NSTextFieldCell"; title = "Fullscreen:"; ObjectID = "tKr-XE-DEU"; */ "tKr-XE-DEU.title" = "Plein écran:"; /* Class = "NSBox"; title = "Box"; ObjectID = "vWw-ws-BgW"; */ "vWw-ws-BgW.title" = "Box"; /* Class = "NSTextFieldCell"; title = "Redo:"; ObjectID = "y7M-cR-DF2"; */ "y7M-cR-DF2.title" = "Rétablir:"; ================================================ FILE: Spectacle/Resources/Localizations/it.lproj/Credits.rtf ================================================ {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \margl1440\margr1440\vieww11300\viewh8400\viewkind0 \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\qc \f0\b\fs24 \cf0 Riconoscimenti \b0 \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \cf0 Icone ed altri elementi grafici: {\field{\*\fldinst{HYPERLINK "http://iiro.jappinen.fi"}}{\fldrslt \b Iiro J\'e4ppinen}} \b \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \b0 \cf0 \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \cf0 Codice sorgente disponibile su {\field{\*\fldinst{HYPERLINK "https://github.com/eczarny/spectacle"}}{\fldrslt \b GitHub}} \b \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc {\field{\*\fldinst{HYPERLINK "https://twitter.com/spectacleapp"}}{\fldrslt \cf0 @spectacleapp}}\ } ================================================ FILE: Spectacle/Resources/Localizations/it.lproj/Spectacle.strings ================================================ /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "187-7k-0Ct"; */ "187-7k-0Ct.title" = "Ripristina"; /* Class = "NSMenuItem"; title = "Check for Updates..."; ObjectID = "2sj-vF-eSL"; */ "2sj-vF-eSL.title" = "Verifica aggiornamenti…"; /* Class = "NSMenuItem"; title = "Next Third"; ObjectID = "7HO-L3-O6Q"; */ "7HO-L3-O6Q.title" = "Terza successiva"; /* Class = "NSMenuItem"; title = "Upper Right"; ObjectID = "C7t-Sf-kOI"; */ "C7t-Sf-kOI.title" = "In alto a destra"; /* Class = "NSMenuItem"; title = "About Spectacle"; ObjectID = "EA9-dx-Qfg"; */ "EA9-dx-Qfg.title" = "Informazioni su Spectacle"; /* Class = "NSTextFieldCell"; title = "Spectacle would like to control your windows using accessibility features."; ObjectID = "HRo-tn-M1V"; */ "HRo-tn-M1V.title" = "Spectacle vorrebbe controllare le tue finestre usando le funzioni di accessibilità."; /* Class = "NSMenuItem"; title = "Next Display"; ObjectID = "I4U-jL-hXq"; */ "I4U-jL-hXq.title" = "Monitor successivo"; /* Class = "NSMenuItem"; title = "Quit Spectacle"; ObjectID = "Isn-Og-AEv"; */ "Isn-Og-AEv.title" = "Esci da Spectacle"; /* Class = "NSMenuItem"; title = "Left Half"; ObjectID = "Jae-vx-7ZC"; */ "Jae-vx-7ZC.title" = "Metà sinistra"; /* Class = "NSMenu"; title = "Spectacle Status Item"; ObjectID = "KsA-8w-wcl"; */ "KsA-8w-wcl.title" = "Spectacle Status Item"; /* Class = "NSMenuItem"; title = "Preferences..."; ObjectID = "ME4-Br-HLT"; */ "ME4-Br-HLT.title" = "Preferenze…"; /* Class = "NSMenuItem"; title = "Make Larger"; ObjectID = "Yun-AA-OVD"; */ "Yun-AA-OVD.title" = "Ingrandisci"; /* Class = "NSMenuItem"; title = "Previous Display"; ObjectID = "aPo-Rp-A1X"; */ "aPo-Rp-A1X.title" = "Monitor precedente"; /* Class = "NSMenuItem"; title = "Previous Third"; ObjectID = "bV9-Ga-jDG"; */ "bV9-Ga-jDG.title" = "Terza precedente"; /* Class = "NSMenuItem"; title = "Right Half"; ObjectID = "cae-26-Oab"; */ "cae-26-Oab.title" = "Metà destra"; /* Class = "NSMenuItem"; title = "Fullscreen"; ObjectID = "dwQ-Bl-nEL"; */ "dwQ-Bl-nEL.title" = "A tutto schermo"; /* Class = "NSMenuItem"; title = "Lower Left"; ObjectID = "eN5-A6-iho"; */ "eN5-A6-iho.title" = "In basso a sinistra"; /* Class = "NSMenuItem"; title = "Disable"; ObjectID = "eZT-Mm-qSc"; */ "eZT-Mm-qSc.title" = "Disattiva"; /* Class = "NSMenu"; title = "Disable"; ObjectID = "flL-B2-XIP"; */ "flL-B2-XIP.title" = "Disattiva"; /* Class = "NSButtonCell"; title = "Quit Spectacle"; ObjectID = "gMV-vD-CUe"; */ "gMV-vD-CUe.title" = "Esci da Spectacle"; /* Class = "NSMenuItem"; title = "Make Smaller"; ObjectID = "gTe-ym-DB0"; */ "gTe-ym-DB0.title" = "Riduci"; /* Class = "NSMenuItem"; title = "Bottom Half"; ObjectID = "huV-Gw-lbX"; */ "huV-Gw-lbX.title" = "Metà bassa"; /* Class = "NSButtonCell"; title = "Open System Preferences"; ObjectID = "iPl-AT-Pei"; */ "iPl-AT-Pei.title" = "Apri Preferenze di Sistema"; /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "l9O-0B-fjp"; */ "l9O-0B-fjp.title" = "Annulla"; /* Class = "NSMenuItem"; title = "Upper Left"; ObjectID = "ldc-QO-Cq6"; */ "ldc-QO-Cq6.title" = "In alto a sinistra"; /* Class = "NSMenuItem"; title = "for APPLICATION"; ObjectID = "msZ-id-Pvw"; */ "msZ-id-Pvw.title" = "per APPLICATION"; /* Class = "NSMenuItem"; title = "Lower Right"; ObjectID = "oDU-J0-NkJ"; */ "oDU-J0-NkJ.title" = "In basso a destra"; /* Class = "NSMenuItem"; title = "for an hour"; ObjectID = "qpo-5w-ZIY"; */ "qpo-5w-ZIY.title" = "per un'ora"; /* Class = "NSTextFieldCell"; title = "Grant access to this application in System Preferences."; ObjectID = "ruu-yJ-Q1F"; */ "ruu-yJ-Q1F.title" = "Concedere l'accesso a questa applicazione nelle Preferenze di Sistema."; /* Class = "NSMenuItem"; title = "Center"; ObjectID = "xJu-Uo-nZw"; */ "xJu-Uo-nZw.title" = "Centra"; /* Class = "NSMenuItem"; title = "Top Half"; ObjectID = "yh0-ik-weD"; */ "yh0-ik-weD.title" = "Metà alta"; ================================================ FILE: Spectacle/Resources/Localizations/it.lproj/SpectaclePreferencesWindow.strings ================================================ /* Class = "NSTextFieldCell"; title = "Make Larger:"; ObjectID = "0f9-kj-bro"; */ "0f9-kj-bro.title" = "Ingrandisci:"; /* Class = "NSTextFieldCell"; title = "Control"; ObjectID = "7Fe-Da-fYB"; */ "7Fe-Da-fYB.title" = "Control"; /* Class = "NSTextFieldCell"; title = "⌃"; ObjectID = "806-S1-nYH"; */ "806-S1-nYH.title" = "⌃"; /* Class = "NSTextFieldCell"; title = "Top Half:"; ObjectID = "AnP-Lv-Hbd"; */ "AnP-Lv-Hbd.title" = "Metà alta:"; /* Class = "NSTextFieldCell"; title = "Bottom Half:"; ObjectID = "Awn-mO-sht"; */ "Awn-mO-sht.title" = "Metà bassa:"; /* Class = "NSTextFieldCell"; title = "Undo:"; ObjectID = "CJs-az-eG7"; */ "CJs-az-eG7.title" = "Annulla:"; /* Class = "NSTextFieldCell"; title = "Left Half:"; ObjectID = "IWj-rc-Kvi"; */ "IWj-rc-Kvi.title" = "Metà sinistra:"; /* Class = "NSTextFieldCell"; title = "⇧"; ObjectID = "Ir0-AJ-ldj"; */ "Ir0-AJ-ldj.title" = "⇧"; /* Class = "NSWindow"; title = "Spectacle X.Y.Z"; ObjectID = "JW4-q9-L7q"; */ "JW4-q9-L7q.title" = "Spectacle X.Y.Z"; /* Class = "NSButtonCell"; title = "Restore Defaults..."; ObjectID = "NT7-Sd-Ofw"; */ "NT7-Sd-Ofw.title" = "Ripristina predefiniti…"; /* Class = "NSTextFieldCell"; title = "Make Smaller:"; ObjectID = "SDM-U5-yUR"; */ "SDM-U5-yUR.title" = "Riduci:"; /* Class = "NSTextFieldCell"; title = "⌘"; ObjectID = "SQM-4e-yFd"; */ "SQM-4e-yFd.title" = "⌘"; /* Class = "NSTextFieldCell"; title = "Next Third:"; ObjectID = "Tze-mR-IV4"; */ "Tze-mR-IV4.title" = "Terza successiva:"; /* Class = "NSTextFieldCell"; title = "Option"; ObjectID = "U0H-hG-KAQ"; */ "U0H-hG-KAQ.title" = "Opzione"; /* Class = "NSTextFieldCell"; title = "Lower Right:"; ObjectID = "Vul-FT-BbB"; */ "Vul-FT-BbB.title" = "In basso a destra:"; /* Class = "NSMenu"; title = "OtherViews"; ObjectID = "W2t-KP-bYJ"; */ "W2t-KP-bYJ.title" = "OtherViews"; /* Class = "NSTextFieldCell"; title = "Right Half:"; ObjectID = "WeK-fS-mRh"; */ "WeK-fS-mRh.title" = "Metà destra:"; /* Class = "NSTextFieldCell"; title = "⌥"; ObjectID = "YQm-Kc-fYF"; */ "YQm-Kc-fYF.title" = "⌥"; /* Class = "NSMenuItem"; title = "as a background application"; ObjectID = "aWf-FC-J1j"; */ "aWf-FC-J1j.title" = "come applicazione in background"; /* Class = "NSButtonCell"; title = "Launch Spectacle at login"; ObjectID = "cEj-mH-kie"; */ "cEj-mH-kie.title" = "Apri Spectacle al login"; /* Class = "NSTextFieldCell"; title = "Lower Left:"; ObjectID = "dQP-TR-GMx"; */ "dQP-TR-GMx.title" = "In basso a sinistra:"; /* Class = "NSTextFieldCell"; title = "Shift"; ObjectID = "dww-8Q-sa1"; */ "dww-8Q-sa1.title" = "Maiuscole"; /* Class = "NSTextFieldCell"; title = "Run..."; ObjectID = "eYU-TH-wcO"; */ "eYU-TH-wcO.title" = "Esegui…"; /* Class = "NSTextFieldCell"; title = "Upper Right:"; ObjectID = "gSY-Au-xOH"; */ "gSY-Au-xOH.title" = "In alto a destra:"; /* Class = "NSTextFieldCell"; title = "Previous Display:"; ObjectID = "hmL-EC-pro"; */ "hmL-EC-pro.title" = "Monitor precedente:"; /* Class = "NSMenuItem"; title = "in the status menu"; ObjectID = "in4-v8-tIm"; */ "in4-v8-tIm.title" = "nel menu di stato"; /* Class = "NSTextFieldCell"; title = "Command"; ObjectID = "jdf-Yu-zrq"; */ "jdf-Yu-zrq.title" = "Comando"; /* Class = "NSTextFieldCell"; title = "Upper Left:"; ObjectID = "ldH-1E-mKW"; */ "ldH-1E-mKW.title" = "In alto a sinistra:"; /* Class = "NSTextFieldCell"; title = "Next Display:"; ObjectID = "lw7-Ap-kJO"; */ "lw7-Ap-kJO.title" = "Monitor successivo:"; /* Class = "NSTextFieldCell"; title = "Center:"; ObjectID = "oqf-7z-BFa"; */ "oqf-7z-BFa.title" = "Centra:"; /* Class = "NSTextFieldCell"; title = "Previous Third:"; ObjectID = "s3E-Lp-mJ4"; */ "s3E-Lp-mJ4.title" = "Terza precedente:"; /* Class = "NSTextFieldCell"; title = "Fullscreen:"; ObjectID = "tKr-XE-DEU"; */ "tKr-XE-DEU.title" = "A tutto schermo:"; /* Class = "NSBox"; title = "Box"; ObjectID = "vWw-ws-BgW"; */ "vWw-ws-BgW.title" = "Box"; /* Class = "NSTextFieldCell"; title = "Redo:"; ObjectID = "y7M-cR-DF2"; */ "y7M-cR-DF2.title" = "Ripristina:"; ================================================ FILE: Spectacle/Resources/Localizations/pt.lproj/Credits.rtf ================================================ {\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \margl1440\margr1440\vieww11300\viewh8400\viewkind0 \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\qc\partightenfactor0 \f0\b\fs24 \cf0 Cr\'e9ditos \b0 \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 \cf0 \'cdcones e outras ilustra\'e7\'f5es: {\field{\*\fldinst{HYPERLINK "http://iiro.jappinen.fi"}}{\fldrslt \b Iiro J\'e4ppinen}} \b \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 \b0 \cf0 \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 \cf0 C\'f3digo fonte dispon\'edvel no {\field{\*\fldinst{HYPERLINK "https://github.com/eczarny/spectacle"}}{\fldrslt \b GitHub}} \b \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc {\field{\*\fldinst{HYPERLINK "https://twitter.com/spectacleapp"}}{\fldrslt \cf0 @spectacleapp}}\ } ================================================ FILE: Spectacle/Resources/Localizations/pt.lproj/Spectacle.strings ================================================ /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "187-7k-0Ct"; */ "187-7k-0Ct.title" = "Refazer"; /* Class = "NSMenuItem"; title = "Check for Updates..."; ObjectID = "2sj-vF-eSL"; */ "2sj-vF-eSL.title" = "Buscar Atualizações..."; /* Class = "NSMenuItem"; title = "Next Third"; ObjectID = "7HO-L3-O6Q"; */ "7HO-L3-O6Q.title" = "Terço Seguinte"; /* Class = "NSMenuItem"; title = "Upper Right"; ObjectID = "C7t-Sf-kOI"; */ "C7t-Sf-kOI.title" = "Direita Superior"; /* Class = "NSMenuItem"; title = "About Spectacle"; ObjectID = "EA9-dx-Qfg"; */ "EA9-dx-Qfg.title" = "Sobre o Spectacle"; /* Class = "NSTextFieldCell"; title = "Spectacle would like to control your windows using accessibility features."; ObjectID = "HRo-tn-M1V"; */ "HRo-tn-M1V.title" = "O Spectacle deseja controlar as janelas usando recursos de acessibilidade."; /* Class = "NSMenuItem"; title = "Next Display"; ObjectID = "I4U-jL-hXq"; */ "I4U-jL-hXq.title" = "Monitor Seguinte"; /* Class = "NSMenuItem"; title = "Quit Spectacle"; ObjectID = "Isn-Og-AEv"; */ "Isn-Og-AEv.title" = "Encerrar Spectacle"; /* Class = "NSMenuItem"; title = "Left Half"; ObjectID = "Jae-vx-7ZC"; */ "Jae-vx-7ZC.title" = "Metade Esquerda"; /* Class = "NSMenu"; title = "Spectacle Status Item"; ObjectID = "KsA-8w-wcl"; */ "KsA-8w-wcl.title" = "Spectacle Status Item"; /* Class = "NSMenuItem"; title = "Preferences..."; ObjectID = "ME4-Br-HLT"; */ "ME4-Br-HLT.title" = "Preferências..."; /* Class = "NSMenuItem"; title = "Make Larger"; ObjectID = "Yun-AA-OVD"; */ "Yun-AA-OVD.title" = "Aumentar"; /* Class = "NSMenuItem"; title = "Previous Display"; ObjectID = "aPo-Rp-A1X"; */ "aPo-Rp-A1X.title" = "Monitor Anterior"; /* Class = "NSMenuItem"; title = "Previous Third"; ObjectID = "bV9-Ga-jDG"; */ "bV9-Ga-jDG.title" = "Terço Anterior"; /* Class = "NSMenuItem"; title = "Right Half"; ObjectID = "cae-26-Oab"; */ "cae-26-Oab.title" = "Metade Direita"; /* Class = "NSMenuItem"; title = "Fullscreen"; ObjectID = "dwQ-Bl-nEL"; */ "dwQ-Bl-nEL.title" = "Preencher Tela"; /* Class = "NSMenuItem"; title = "Lower Left"; ObjectID = "eN5-A6-iho"; */ "eN5-A6-iho.title" = "Esquerda Inferior"; /* Class = "NSMenuItem"; title = "Disable"; ObjectID = "eZT-Mm-qSc"; */ "eZT-Mm-qSc.title" = "Desativar"; /* Class = "NSMenu"; title = "Disable"; ObjectID = "flL-B2-XIP"; */ "flL-B2-XIP.title" = "Desativar"; /* Class = "NSButtonCell"; title = "Quit Spectacle"; ObjectID = "gMV-vD-CUe"; */ "gMV-vD-CUe.title" = "Encerrar Spectacle"; /* Class = "NSMenuItem"; title = "Make Smaller"; ObjectID = "gTe-ym-DB0"; */ "gTe-ym-DB0.title" = "Diminuir"; /* Class = "NSMenuItem"; title = "Bottom Half"; ObjectID = "huV-Gw-lbX"; */ "huV-Gw-lbX.title" = "Metade Inferior"; /* Class = "NSButtonCell"; title = "Open System Preferences"; ObjectID = "iPl-AT-Pei"; */ "iPl-AT-Pei.title" = "Abrir Preferências do Sistema"; /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "l9O-0B-fjp"; */ "l9O-0B-fjp.title" = "Desfazer"; /* Class = "NSMenuItem"; title = "Upper Left"; ObjectID = "ldc-QO-Cq6"; */ "ldc-QO-Cq6.title" = "Esquerda Superior"; /* Class = "NSMenuItem"; title = "for APPLICATION"; ObjectID = "msZ-id-Pvw"; */ "msZ-id-Pvw.title" = "no APPLICATION"; /* Class = "NSMenuItem"; title = "Lower Right"; ObjectID = "oDU-J0-NkJ"; */ "oDU-J0-NkJ.title" = "Direita Inferior"; /* Class = "NSMenuItem"; title = "for an hour"; ObjectID = "qpo-5w-ZIY"; */ "qpo-5w-ZIY.title" = "por uma hora"; /* Class = "NSTextFieldCell"; title = "Grant access to this application in System Preferences."; ObjectID = "ruu-yJ-Q1F"; */ "ruu-yJ-Q1F.title" = "Conceda acesso a este aplicativo nas Preferências do Sistema."; /* Class = "NSMenuItem"; title = "Center"; ObjectID = "xJu-Uo-nZw"; */ "xJu-Uo-nZw.title" = "Centralizar"; /* Class = "NSMenuItem"; title = "Top Half"; ObjectID = "yh0-ik-weD"; */ "yh0-ik-weD.title" = "Metade Superior"; ================================================ FILE: Spectacle/Resources/Localizations/pt.lproj/SpectaclePreferencesWindow.strings ================================================ /* Class = "NSTextFieldCell"; title = "Make Larger:"; ObjectID = "0f9-kj-bro"; */ "0f9-kj-bro.title" = "Aumentar:"; /* Class = "NSTextFieldCell"; title = "Control"; ObjectID = "7Fe-Da-fYB"; */ "7Fe-Da-fYB.title" = "Controle"; /* Class = "NSTextFieldCell"; title = "⌃"; ObjectID = "806-S1-nYH"; */ "806-S1-nYH.title" = "⌃"; /* Class = "NSTextFieldCell"; title = "Top Half:"; ObjectID = "AnP-Lv-Hbd"; */ "AnP-Lv-Hbd.title" = "Metade Superior:"; /* Class = "NSTextFieldCell"; title = "Bottom Half:"; ObjectID = "Awn-mO-sht"; */ "Awn-mO-sht.title" = "Metade Inferior:"; /* Class = "NSTextFieldCell"; title = "Undo:"; ObjectID = "CJs-az-eG7"; */ "CJs-az-eG7.title" = "Desfazer:"; /* Class = "NSTextFieldCell"; title = "Left Half:"; ObjectID = "IWj-rc-Kvi"; */ "IWj-rc-Kvi.title" = "Metade Esquerda:"; /* Class = "NSTextFieldCell"; title = "⇧"; ObjectID = "Ir0-AJ-ldj"; */ "Ir0-AJ-ldj.title" = "⇧"; /* Class = "NSWindow"; title = "Spectacle X.Y.Z"; ObjectID = "JW4-q9-L7q"; */ "JW4-q9-L7q.title" = "Spectacle X.Y.Z"; /* Class = "NSButtonCell"; title = "Restore Defaults..."; ObjectID = "NT7-Sd-Ofw"; */ "NT7-Sd-Ofw.title" = "Restaurar Padrões..."; /* Class = "NSTextFieldCell"; title = "Make Smaller:"; ObjectID = "SDM-U5-yUR"; */ "SDM-U5-yUR.title" = "Diminuir:"; /* Class = "NSTextFieldCell"; title = "⌘"; ObjectID = "SQM-4e-yFd"; */ "SQM-4e-yFd.title" = "⌘"; /* Class = "NSTextFieldCell"; title = "Next Third:"; ObjectID = "Tze-mR-IV4"; */ "Tze-mR-IV4.title" = "Terço Seguinte:"; /* Class = "NSTextFieldCell"; title = "Option"; ObjectID = "U0H-hG-KAQ"; */ "U0H-hG-KAQ.title" = "Opção"; /* Class = "NSTextFieldCell"; title = "Lower Right:"; ObjectID = "Vul-FT-BbB"; */ "Vul-FT-BbB.title" = "Direita Inferior:"; /* Class = "NSMenu"; title = "OtherViews"; ObjectID = "W2t-KP-bYJ"; */ "W2t-KP-bYJ.title" = "OtherViews"; /* Class = "NSTextFieldCell"; title = "Right Half:"; ObjectID = "WeK-fS-mRh"; */ "WeK-fS-mRh.title" = "Metade Direita:"; /* Class = "NSTextFieldCell"; title = "⌥"; ObjectID = "YQm-Kc-fYF"; */ "YQm-Kc-fYF.title" = "⌥"; /* Class = "NSMenuItem"; title = "as a background application"; ObjectID = "aWf-FC-J1j"; */ "aWf-FC-J1j.title" = "em 2º plano"; /* Class = "NSButtonCell"; title = "Launch Spectacle at login"; ObjectID = "cEj-mH-kie"; */ "cEj-mH-kie.title" = "Abrir ao iniciar a sessão"; /* Class = "NSTextFieldCell"; title = "Lower Left:"; ObjectID = "dQP-TR-GMx"; */ "dQP-TR-GMx.title" = "Esquerda Inferior:"; /* Class = "NSTextFieldCell"; title = "Shift"; ObjectID = "dww-8Q-sa1"; */ "dww-8Q-sa1.title" = "Maiúsculas"; /* Class = "NSTextFieldCell"; title = "Run..."; ObjectID = "eYU-TH-wcO"; */ "eYU-TH-wcO.title" = "Manter:"; /* Class = "NSTextFieldCell"; title = "Upper Right:"; ObjectID = "gSY-Au-xOH"; */ "gSY-Au-xOH.title" = "Direita Superior:"; /* Class = "NSTextFieldCell"; title = "Previous Display:"; ObjectID = "hmL-EC-pro"; */ "hmL-EC-pro.title" = "Monitor Anterior:"; /* Class = "NSMenuItem"; title = "in the status menu"; ObjectID = "in4-v8-tIm"; */ "in4-v8-tIm.title" = "na barra de menus"; /* Class = "NSTextFieldCell"; title = "Command"; ObjectID = "jdf-Yu-zrq"; */ "jdf-Yu-zrq.title" = "Comando"; /* Class = "NSTextFieldCell"; title = "Upper Left:"; ObjectID = "ldH-1E-mKW"; */ "ldH-1E-mKW.title" = "Esquerda Superior:"; /* Class = "NSTextFieldCell"; title = "Next Display:"; ObjectID = "lw7-Ap-kJO"; */ "lw7-Ap-kJO.title" = "Monitor Seguinte:"; /* Class = "NSTextFieldCell"; title = "Center:"; ObjectID = "oqf-7z-BFa"; */ "oqf-7z-BFa.title" = "Centralizar:"; /* Class = "NSTextFieldCell"; title = "Previous Third:"; ObjectID = "s3E-Lp-mJ4"; */ "s3E-Lp-mJ4.title" = "Terço Anterior:"; /* Class = "NSTextFieldCell"; title = "Fullscreen:"; ObjectID = "tKr-XE-DEU"; */ "tKr-XE-DEU.title" = "Preencher Tela:"; /* Class = "NSBox"; title = "Box"; ObjectID = "vWw-ws-BgW"; */ "vWw-ws-BgW.title" = "Box"; /* Class = "NSTextFieldCell"; title = "Redo:"; ObjectID = "y7M-cR-DF2"; */ "y7M-cR-DF2.title" = "Refazer:"; ================================================ FILE: Spectacle/Resources/Property Lists/Defaults.plist ================================================ AutomaticUpdateCheckEnabled BackgroundAlertSuppressed BlacklistedApplications com.adobe.Photoshop com.divisiblebyzero.Spectacle com.valvesoftware.steam DisabledApplications StatusItemEnabled ================================================ FILE: Spectacle/Resources/Window Position Calculations/SpectacleBottomHalfWindowCalculation.js ================================================ windowPositionCalculationRegistry.registerWindowPositionCalculationWithAction(function (windowRect, visibleFrameOfSourceScreen, visibleFrameOfDestinationScreen) { var oneHalfRect = SpectacleCalculationHelpers.copyRect(visibleFrameOfDestinationScreen); oneHalfRect.height = Math.floor(oneHalfRect.height / 2.0); if (Math.abs(CGRectGetMidX(windowRect) - CGRectGetMidX(oneHalfRect)) <= 1.0) { var twoThirdsRect = SpectacleCalculationHelpers.copyRect(oneHalfRect); twoThirdsRect.height = Math.floor(visibleFrameOfDestinationScreen.height * 2 / 3.0); if (SpectacleCalculationHelpers.rectCenteredWithinRect(oneHalfRect, windowRect)) { return twoThirdsRect; } if (SpectacleCalculationHelpers.rectCenteredWithinRect(twoThirdsRect, windowRect)) { var oneThirdRect = SpectacleCalculationHelpers.copyRect(oneHalfRect); oneThirdRect.height = Math.floor(visibleFrameOfDestinationScreen.height / 3.0); return oneThirdRect; } } return oneHalfRect; }, "SpectacleWindowActionBottomHalf"); ================================================ FILE: Spectacle/Resources/Window Position Calculations/SpectacleCenterWindowCalculation.js ================================================ var SpectacleCenterWindowCalculation = (function () { var centerWindowRectInVisibleFrameOfScreen = function(windowRect, visibleFrameOfScreen) { var calculatedWindowRect = SpectacleCalculationHelpers.copyRect(windowRect); calculatedWindowRect.x = Math.round((visibleFrameOfScreen.width - windowRect.width) / 2.0) + visibleFrameOfScreen.x; calculatedWindowRect.y = Math.round((visibleFrameOfScreen.height - windowRect.height) / 2.0) + visibleFrameOfScreen.y; return calculatedWindowRect; }; return { centerWindowRectInVisibleFrameOfScreen: centerWindowRectInVisibleFrameOfScreen, }; })(); windowPositionCalculationRegistry.registerWindowPositionCalculationWithAction(function (windowRect, visibleFrameOfSourceScreen, visibleFrameOfDestinationScreen) { return SpectacleCenterWindowCalculation.centerWindowRectInVisibleFrameOfScreen(windowRect, visibleFrameOfDestinationScreen); }, "SpectacleWindowActionCenter"); ================================================ FILE: Spectacle/Resources/Window Position Calculations/SpectacleFullscreenWindowCalculation.js ================================================ windowPositionCalculationRegistry.registerWindowPositionCalculationWithAction(function (windowRect, visibleFrameOfSourceScreen, visibleFrameOfDestinationScreen) { return visibleFrameOfDestinationScreen; }, "SpectacleWindowActionFullscreen"); ================================================ FILE: Spectacle/Resources/Window Position Calculations/SpectacleLargerWindowCalculation.js ================================================ windowPositionCalculationRegistry.registerWindowPositionCalculationWithAction(function (windowRect, visibleFrameOfSourceScreen, visibleFrameOfDestinationScreen) { return SpectacleWindowSizeAdjuster.resizeWindowRect(windowRect, visibleFrameOfDestinationScreen, 30.0); }, "SpectacleWindowActionLarger"); ================================================ FILE: Spectacle/Resources/Window Position Calculations/SpectacleLeftHalfWindowCalculation.js ================================================ windowPositionCalculationRegistry.registerWindowPositionCalculationWithAction(function (windowRect, visibleFrameOfSourceScreen, visibleFrameOfDestinationScreen) { var oneHalfRect = SpectacleCalculationHelpers.copyRect(visibleFrameOfDestinationScreen); oneHalfRect.width = Math.floor(oneHalfRect.width / 2.0); if (Math.abs(CGRectGetMidY(windowRect) - CGRectGetMidY(oneHalfRect)) <= 1.0) { var twoThirdRect = SpectacleCalculationHelpers.copyRect(oneHalfRect); twoThirdRect.width = Math.floor(visibleFrameOfDestinationScreen.width * 2 / 3.0); if (SpectacleCalculationHelpers.rectCenteredWithinRect(oneHalfRect, windowRect)) { return twoThirdRect; } if (SpectacleCalculationHelpers.rectCenteredWithinRect(twoThirdRect, windowRect)) { var oneThirdsRect = SpectacleCalculationHelpers.copyRect(oneHalfRect); oneThirdsRect.width = Math.floor(visibleFrameOfDestinationScreen.width / 3.0); return oneThirdsRect; } } return oneHalfRect; }, "SpectacleWindowActionLeftHalf"); ================================================ FILE: Spectacle/Resources/Window Position Calculations/SpectacleLowerLeftWindowCalculation.js ================================================ windowPositionCalculationRegistry.registerWindowPositionCalculationWithAction(function (windowRect, visibleFrameOfSourceScreen, visibleFrameOfDestinationScreen) { var oneQuarterRect = SpectacleCalculationHelpers.copyRect(visibleFrameOfDestinationScreen); oneQuarterRect.width = Math.floor(visibleFrameOfDestinationScreen.width / 2.0); oneQuarterRect.height = Math.floor(visibleFrameOfDestinationScreen.height / 2.0); if (Math.abs(CGRectGetMidY(windowRect) - CGRectGetMidY(oneQuarterRect)) <= 1.0) { var twoThirdRect = SpectacleCalculationHelpers.copyRect(oneQuarterRect); twoThirdRect.width = Math.floor(visibleFrameOfDestinationScreen.width * 2 / 3.0); if (SpectacleCalculationHelpers.rectCenteredWithinRect(oneQuarterRect, windowRect)) { return twoThirdRect; } if (SpectacleCalculationHelpers.rectCenteredWithinRect(twoThirdRect, windowRect)) { var oneThirdsRect = SpectacleCalculationHelpers.copyRect(oneQuarterRect); oneThirdsRect.width = Math.floor(visibleFrameOfDestinationScreen.width / 3.0); return oneThirdsRect; } } return oneQuarterRect; }, "SpectacleWindowActionLowerLeft"); ================================================ FILE: Spectacle/Resources/Window Position Calculations/SpectacleLowerRightWindowCalculation.js ================================================ windowPositionCalculationRegistry.registerWindowPositionCalculationWithAction(function (windowRect, visibleFrameOfSourceScreen, visibleFrameOfDestinationScreen) { var oneQuarterRect = SpectacleCalculationHelpers.copyRect(visibleFrameOfDestinationScreen); oneQuarterRect.width = Math.floor(visibleFrameOfDestinationScreen.width / 2.0); oneQuarterRect.height = Math.floor(visibleFrameOfDestinationScreen.height / 2.0); oneQuarterRect.x += oneQuarterRect.width; if (Math.abs(CGRectGetMidY(windowRect) - CGRectGetMidY(oneQuarterRect)) <= 1.0) { var twoThirdRect = SpectacleCalculationHelpers.copyRect(oneQuarterRect); twoThirdRect.width = Math.floor(visibleFrameOfDestinationScreen.width * 2 / 3.0); twoThirdRect.x = visibleFrameOfDestinationScreen.x + visibleFrameOfDestinationScreen.width - twoThirdRect.width; if (SpectacleCalculationHelpers.rectCenteredWithinRect(oneQuarterRect, windowRect)) { return twoThirdRect; } if (SpectacleCalculationHelpers.rectCenteredWithinRect(twoThirdRect, windowRect)) { var oneThirdsRect = SpectacleCalculationHelpers.copyRect(oneQuarterRect); oneThirdsRect.width = Math.floor(visibleFrameOfDestinationScreen.width / 3.0); oneThirdsRect.x = visibleFrameOfDestinationScreen.x + visibleFrameOfDestinationScreen.width - oneThirdsRect.width; return oneThirdsRect; } } return oneQuarterRect; }, "SpectacleWindowActionLowerRight"); ================================================ FILE: Spectacle/Resources/Window Position Calculations/SpectacleNextDisplayWindowCalculation.js ================================================ windowPositionCalculationRegistry.registerWindowPositionCalculationWithAction(function (windowRect, visibleFrameOfSourceScreen, visibleFrameOfDestinationScreen) { return SpectacleNextOrPreviousDisplay.moveWindowRectToDisplay(windowRect, visibleFrameOfSourceScreen, visibleFrameOfDestinationScreen); }, "SpectacleWindowActionNextDisplay"); ================================================ FILE: Spectacle/Resources/Window Position Calculations/SpectacleNextOrPreviousDisplay.js ================================================ var SpectacleNextOrPreviousDisplay = (function () { var moveWindowRectToDisplay = function(windowRect, visibleFrameOfSourceScreen, visibleFrameOfDestinationScreen) { if (SpectacleCalculationHelpers.rectFitsWithinRect(windowRect, visibleFrameOfDestinationScreen)) { return SpectacleCenterWindowCalculation.centerWindowRectInVisibleFrameOfScreen(windowRect, visibleFrameOfDestinationScreen); } else { return visibleFrameOfDestinationScreen; } }; return { moveWindowRectToDisplay: moveWindowRectToDisplay, }; })(); ================================================ FILE: Spectacle/Resources/Window Position Calculations/SpectacleNextOrPreviousThirds.js ================================================ var SpectacleNextOrPreviousThirds = (function () { var findNextThird = function(windowRect, visibleFrameOfDestinationScreen) { var thirds = thirdsFromVisibleFrameOfScreen(visibleFrameOfDestinationScreen); var result = thirds[0]; for (var i = 0; i < thirds.length; i++) { var third = thirds[i]; if (SpectacleCalculationHelpers.rectCenteredWithinRect(third, windowRect)) { var j = i; if (++j >= thirds.length) { j = 0; } result = thirds[j]; break; } } return result; }; var findPreviousThird = function(windowRect, visibleFrameOfDestinationScreen) { var thirds = thirdsFromVisibleFrameOfScreen(visibleFrameOfDestinationScreen); var result = thirds[0]; for (var i = 0; i < thirds.length; i++) { var third = thirds[i]; if (SpectacleCalculationHelpers.rectCenteredWithinRect(third, windowRect)) { var j = i; if (--j < 0) { j = thirds.length - 1; } result = thirds[j]; break; } } return result; }; var thirdsFromVisibleFrameOfScreen = function(visibleFrameOfDestinationScreen) { var thirds = []; for (var i = 0; i < 3; i++) { var third = SpectacleCalculationHelpers.copyRect(visibleFrameOfDestinationScreen); third.x = visibleFrameOfDestinationScreen.x + (Math.floor(visibleFrameOfDestinationScreen.width / 3.0) * i); third.width = Math.floor(visibleFrameOfDestinationScreen.width / 3.0); thirds.push(third); } for (var i = 0; i < 3; i++) { var third = SpectacleCalculationHelpers.copyRect(visibleFrameOfDestinationScreen); third.y = visibleFrameOfDestinationScreen.y + visibleFrameOfDestinationScreen.height - (Math.floor(visibleFrameOfDestinationScreen.height / 3.0) * (i + 1)); third.height = Math.floor(visibleFrameOfDestinationScreen.height / 3.0); thirds.push(third); } return thirds; }; return { findNextThird: findNextThird, findPreviousThird: findPreviousThird, }; })(); ================================================ FILE: Spectacle/Resources/Window Position Calculations/SpectacleNextThirdWindowCalculation.js ================================================ windowPositionCalculationRegistry.registerWindowPositionCalculationWithAction(function (windowRect, visibleFrameOfSourceScreen, visibleFrameOfDestinationScreen) { return SpectacleNextOrPreviousThirds.findNextThird(windowRect, visibleFrameOfDestinationScreen); }, "SpectacleWindowActionNextThird"); ================================================ FILE: Spectacle/Resources/Window Position Calculations/SpectaclePreviousDisplayWindowCalculation.js ================================================ windowPositionCalculationRegistry.registerWindowPositionCalculationWithAction(function (windowRect, visibleFrameOfSourceScreen, visibleFrameOfDestinationScreen) { return SpectacleNextOrPreviousDisplay.moveWindowRectToDisplay(windowRect, visibleFrameOfSourceScreen, visibleFrameOfDestinationScreen); }, "SpectacleWindowActionPreviousDisplay"); ================================================ FILE: Spectacle/Resources/Window Position Calculations/SpectaclePreviousThirdWindowCalculation.js ================================================ windowPositionCalculationRegistry.registerWindowPositionCalculationWithAction(function (windowRect, visibleFrameOfSourceScreen, visibleFrameOfDestinationScreen) { return SpectacleNextOrPreviousThirds.findPreviousThird(windowRect, visibleFrameOfDestinationScreen); }, "SpectacleWindowActionPreviousThird"); ================================================ FILE: Spectacle/Resources/Window Position Calculations/SpectacleRightHalfWindowCalculation.js ================================================ windowPositionCalculationRegistry.registerWindowPositionCalculationWithAction(function (windowRect, visibleFrameOfSourceScreen, visibleFrameOfDestinationScreen) { var oneHalfRect = SpectacleCalculationHelpers.copyRect(visibleFrameOfDestinationScreen); oneHalfRect.width = Math.floor(oneHalfRect.width / 2.0); oneHalfRect.x += oneHalfRect.width; if (Math.abs(CGRectGetMidY(windowRect) - CGRectGetMidY(oneHalfRect)) <= 1.0) { var twoThirdRect = SpectacleCalculationHelpers.copyRect(oneHalfRect); twoThirdRect.width = Math.floor(visibleFrameOfDestinationScreen.width * 2 / 3.0); twoThirdRect.x = visibleFrameOfDestinationScreen.x + visibleFrameOfDestinationScreen.width - twoThirdRect.width; if (SpectacleCalculationHelpers.rectCenteredWithinRect(oneHalfRect, windowRect)) { return twoThirdRect; } if (SpectacleCalculationHelpers.rectCenteredWithinRect(twoThirdRect, windowRect)) { var oneThirdsRect = SpectacleCalculationHelpers.copyRect(oneHalfRect); oneThirdsRect.width = Math.floor(visibleFrameOfDestinationScreen.width / 3.0); oneThirdsRect.x = visibleFrameOfDestinationScreen.x + visibleFrameOfDestinationScreen.width - oneThirdsRect.width; return oneThirdsRect; } } return oneHalfRect; }, "SpectacleWindowActionRightHalf"); ================================================ FILE: Spectacle/Resources/Window Position Calculations/SpectacleSmallerWindowCalculation.js ================================================ windowPositionCalculationRegistry.registerWindowPositionCalculationWithAction(function (windowRect, visibleFrameOfSourceScreen, visibleFrameOfDestinationScreen) { return SpectacleWindowSizeAdjuster.resizeWindowRect(windowRect, visibleFrameOfDestinationScreen, -30.0); }, "SpectacleWindowActionSmaller"); ================================================ FILE: Spectacle/Resources/Window Position Calculations/SpectacleTopHalfWindowCalculation.js ================================================ windowPositionCalculationRegistry.registerWindowPositionCalculationWithAction(function (windowRect, visibleFrameOfSourceScreen, visibleFrameOfDestinationScreen) { var oneHalfRect = SpectacleCalculationHelpers.copyRect(visibleFrameOfDestinationScreen); oneHalfRect.height = Math.floor(oneHalfRect.height / 2.0); oneHalfRect.y += oneHalfRect.height + (visibleFrameOfDestinationScreen.height % 2.0); if (Math.abs(CGRectGetMidX(windowRect) - CGRectGetMidX(oneHalfRect)) <= 1.0) { var twoThirdsRect = SpectacleCalculationHelpers.copyRect(oneHalfRect); twoThirdsRect.height = Math.floor(visibleFrameOfDestinationScreen.height * 2 / 3.0); twoThirdsRect.y = visibleFrameOfDestinationScreen.y + visibleFrameOfDestinationScreen.height - twoThirdsRect.height; if (SpectacleCalculationHelpers.rectCenteredWithinRect(oneHalfRect, windowRect)) { return twoThirdsRect; } if (SpectacleCalculationHelpers.rectCenteredWithinRect(twoThirdsRect, windowRect)) { var oneThirdRect = SpectacleCalculationHelpers.copyRect(oneHalfRect); oneThirdRect.height = Math.floor(visibleFrameOfDestinationScreen.height / 3.0); oneThirdRect.y = visibleFrameOfDestinationScreen.y + visibleFrameOfDestinationScreen.height - oneThirdRect.height; return oneThirdRect; } } return oneHalfRect; }, "SpectacleWindowActionTopHalf"); ================================================ FILE: Spectacle/Resources/Window Position Calculations/SpectacleUpperLeftWindowCalculation.js ================================================ windowPositionCalculationRegistry.registerWindowPositionCalculationWithAction(function (windowRect, visibleFrameOfSourceScreen, visibleFrameOfDestinationScreen) { var oneQuarterRect = SpectacleCalculationHelpers.copyRect(visibleFrameOfDestinationScreen); oneQuarterRect.width = Math.floor(visibleFrameOfDestinationScreen.width / 2.0); oneQuarterRect.height = Math.floor(visibleFrameOfDestinationScreen.height / 2.0); oneQuarterRect.y = visibleFrameOfDestinationScreen.y + Math.floor(visibleFrameOfDestinationScreen.height / 2.0) + (visibleFrameOfDestinationScreen.height % 2.0); if (Math.abs(CGRectGetMidY(windowRect) - CGRectGetMidY(oneQuarterRect)) <= 1.0) { var twoThirdRect = SpectacleCalculationHelpers.copyRect(oneQuarterRect); twoThirdRect.width = Math.floor(visibleFrameOfDestinationScreen.width * 2 / 3.0); if (SpectacleCalculationHelpers.rectCenteredWithinRect(oneQuarterRect, windowRect)) { return twoThirdRect; } if (SpectacleCalculationHelpers.rectCenteredWithinRect(twoThirdRect, windowRect)) { var oneThirdsRect = SpectacleCalculationHelpers.copyRect(oneQuarterRect); oneThirdsRect.width = Math.floor(visibleFrameOfDestinationScreen.width / 3.0); return oneThirdsRect; } } return oneQuarterRect; }, "SpectacleWindowActionUpperLeft"); ================================================ FILE: Spectacle/Resources/Window Position Calculations/SpectacleUpperRightWindowCalculation.js ================================================ windowPositionCalculationRegistry.registerWindowPositionCalculationWithAction(function (windowRect, visibleFrameOfSourceScreen, visibleFrameOfDestinationScreen) { var oneQuarterRect = SpectacleCalculationHelpers.copyRect(visibleFrameOfDestinationScreen); oneQuarterRect.width = Math.floor(visibleFrameOfDestinationScreen.width / 2.0); oneQuarterRect.height = Math.floor(visibleFrameOfDestinationScreen.height / 2.0); oneQuarterRect.x += oneQuarterRect.width; oneQuarterRect.y = visibleFrameOfDestinationScreen.y + Math.floor(visibleFrameOfDestinationScreen.height / 2.0) + (visibleFrameOfDestinationScreen.height % 2.0); if (Math.abs(CGRectGetMidY(windowRect) - CGRectGetMidY(oneQuarterRect)) <= 1.0) { var twoThirdRect = SpectacleCalculationHelpers.copyRect(oneQuarterRect); twoThirdRect.width = Math.floor(visibleFrameOfDestinationScreen.width * 2 / 3.0); twoThirdRect.x = visibleFrameOfDestinationScreen.x + visibleFrameOfDestinationScreen.width - twoThirdRect.width; if (SpectacleCalculationHelpers.rectCenteredWithinRect(oneQuarterRect, windowRect)) { return twoThirdRect; } if (SpectacleCalculationHelpers.rectCenteredWithinRect(twoThirdRect, windowRect)) { var oneThirdsRect = SpectacleCalculationHelpers.copyRect(oneQuarterRect); oneThirdsRect.width = Math.floor(visibleFrameOfDestinationScreen.width / 3.0); oneThirdsRect.x = visibleFrameOfDestinationScreen.x + visibleFrameOfDestinationScreen.width - oneThirdsRect.width; return oneThirdsRect; } } return oneQuarterRect; }, "SpectacleWindowActionUpperRight"); ================================================ FILE: Spectacle/Resources/Window Position Calculations/SpectacleWindowCalculationHelpers.js ================================================ var SpectacleCalculationHelpers = (function () { var copyRect = function(rect) { return { x: rect.x, y: rect.y, width: rect.width, height: rect.height, }; }; var rectCenteredWithinRect = function(rect1, rect2) { var centeredMidX = Math.abs(CGRectGetMidX(rect2) - CGRectGetMidX(rect1)) <= 1.0; var centeredMidY = Math.abs(CGRectGetMidY(rect2) - CGRectGetMidY(rect1)) <= 1.0; return CGRectContainsRect(rect1, rect2) && centeredMidX && centeredMidY; }; var rectFitsWithinRect = function(rect1, rect2) { return (rect1.width <= rect2.width) && (rect1.height <= rect2.height); }; return { copyRect: copyRect, rectCenteredWithinRect: rectCenteredWithinRect, rectFitsWithinRect: rectFitsWithinRect, }; })(); ================================================ FILE: Spectacle/Resources/Window Position Calculations/SpectacleWindowSizeAdjuster.js ================================================ var SpectacleWindowSizeAdjuster = (function () { var resizeWindowRect = function(windowRect, visibleFrameOfDestinationScreen, sizeOffset) { var resizedWindowRect = SpectacleCalculationHelpers.copyRect(windowRect); resizedWindowRect.width = resizedWindowRect.width + sizeOffset; resizedWindowRect.x = resizedWindowRect.x - Math.floor(sizeOffset / 2.0); resizedWindowRect = adjustedWindowRectAgainstLeftAndRightEdgesOfScreen(windowRect, resizedWindowRect, visibleFrameOfDestinationScreen); if (resizedWindowRect.width >= visibleFrameOfDestinationScreen.width) { resizedWindowRect.width = visibleFrameOfDestinationScreen.width; } resizedWindowRect.height = resizedWindowRect.height + sizeOffset; resizedWindowRect.y = resizedWindowRect.y - Math.floor(sizeOffset / 2.0); resizedWindowRect = adjustedWindowRectAgainstTopAndBottomEdgesOfScreen(windowRect, resizedWindowRect, visibleFrameOfDestinationScreen); if (resizedWindowRect.height >= visibleFrameOfDestinationScreen.height) { resizedWindowRect.height = visibleFrameOfDestinationScreen.height; resizedWindowRect.y = windowRect.y; } if (againstAllEdgesOfScreen(windowRect, visibleFrameOfDestinationScreen) && (sizeOffset < 0)) { resizedWindowRect.width = windowRect.width + sizeOffset; resizedWindowRect.x = windowRect.x - Math.floor(sizeOffset / 2.0); resizedWindowRect.height = windowRect.height + sizeOffset; resizedWindowRect.y = windowRect.y - Math.floor(sizeOffset / 2.0); } if (resizedWindowRectIsTooSmall(resizedWindowRect, visibleFrameOfDestinationScreen)) { resizedWindowRect = windowRect; } return resizedWindowRect; }; var againstEdgeOfScreen = function(gap) { return Math.abs(gap) <= 5.0; }; var againstTheLeftEdgeOfScreen = function(windowRect, visibleFrameOfDestinationScreen) { return againstEdgeOfScreen(windowRect.x - visibleFrameOfDestinationScreen.x); }; var againstTheRightEdgeOfScreen = function(windowRect, visibleFrameOfDestinationScreen) { return againstEdgeOfScreen(CGRectGetMaxX(windowRect) - CGRectGetMaxX(visibleFrameOfDestinationScreen)); }; var againstTheTopEdgeOfScreen = function(windowRect, visibleFrameOfDestinationScreen) { return againstEdgeOfScreen(CGRectGetMaxY(windowRect) - CGRectGetMaxY(visibleFrameOfDestinationScreen)); }; var againstTheBottomEdgeOfScreen = function(windowRect, visibleFrameOfDestinationScreen) { return againstEdgeOfScreen(windowRect.y - visibleFrameOfDestinationScreen.y); }; var againstAllEdgesOfScreen = function(windowRect, visibleFrameOfDestinationScreen) { return (againstTheLeftEdgeOfScreen(windowRect, visibleFrameOfDestinationScreen) && againstTheRightEdgeOfScreen(windowRect, visibleFrameOfDestinationScreen) && againstTheTopEdgeOfScreen(windowRect, visibleFrameOfDestinationScreen) && againstTheBottomEdgeOfScreen(windowRect, visibleFrameOfDestinationScreen)); }; var adjustedWindowRectAgainstLeftAndRightEdgesOfScreen = function(originalWindowRect, resizedWindowRect, visibleFrameOfDestinationScreen) { var adjustedWindowRect = SpectacleCalculationHelpers.copyRect(resizedWindowRect); if (againstTheRightEdgeOfScreen(originalWindowRect, visibleFrameOfDestinationScreen)) { adjustedWindowRect.x = CGRectGetMaxX(visibleFrameOfDestinationScreen) - adjustedWindowRect.width; if (againstTheLeftEdgeOfScreen(originalWindowRect, visibleFrameOfDestinationScreen)) { adjustedWindowRect.width = visibleFrameOfDestinationScreen.width; } } if (againstTheLeftEdgeOfScreen(originalWindowRect, visibleFrameOfDestinationScreen)) { adjustedWindowRect.x = visibleFrameOfDestinationScreen.x; } return adjustedWindowRect; }; var adjustedWindowRectAgainstTopAndBottomEdgesOfScreen = function(originalWindowRect, resizedWindowRect, visibleFrameOfDestinationScreen) { var adjustedWindowRect = SpectacleCalculationHelpers.copyRect(resizedWindowRect); if (againstTheTopEdgeOfScreen(originalWindowRect, visibleFrameOfDestinationScreen)) { adjustedWindowRect.y = CGRectGetMaxY(visibleFrameOfDestinationScreen) - adjustedWindowRect.height; if (againstTheBottomEdgeOfScreen(originalWindowRect, visibleFrameOfDestinationScreen)) { adjustedWindowRect.height = visibleFrameOfDestinationScreen.height; } } if (againstTheBottomEdgeOfScreen(originalWindowRect, visibleFrameOfDestinationScreen)) { adjustedWindowRect.y = visibleFrameOfDestinationScreen.y; } return adjustedWindowRect; }; var resizedWindowRectIsTooSmall = function(windowRect, visibleFrameOfDestinationScreen) { var minimumWindowRectWidth = Math.floor(visibleFrameOfDestinationScreen.width / 4.0); var minimumWindowRectHeight = Math.floor(visibleFrameOfDestinationScreen.height / 4.0); return (windowRect.width <= minimumWindowRectWidth) || (windowRect.height <= minimumWindowRectHeight); }; return { resizeWindowRect: resizeWindowRect, }; })(); ================================================ FILE: Spectacle/Sources/SpectacleAccessibilityElement.h ================================================ #import @interface SpectacleAccessibilityElement : NSObject + (SpectacleAccessibilityElement *)frontmostApplicationElement; + (SpectacleAccessibilityElement *)frontmostWindowElement; - (SpectacleAccessibilityElement *)elementWithAttribute:(CFStringRef)attribute; - (NSString *)stringValueOfAttribute:(CFStringRef)attribute; - (AXValueRef)valueOfAttribute:(CFStringRef)attribute type:(AXValueType)type; - (void)setValue:(AXValueRef)value forAttribute:(CFStringRef)attribute; - (CGRect)rectOfElement; - (void)setRectOfElement:(CGRect)rect; + (CGRect)normalizeCoordinatesOfRect:(CGRect)rect frameOfScreen:(CGRect)frameOfScreen; - (BOOL)isSheet; - (BOOL)isSystemDialog; @end ================================================ FILE: Spectacle/Sources/SpectacleAccessibilityElement.m ================================================ #import "SpectacleAccessibilityElement.h" #import @implementation SpectacleAccessibilityElement { AXUIElementRef _underlyingElement; } - (instancetype)init { if (self = [super init]) { _underlyingElement = NULL; } return self; } + (SpectacleAccessibilityElement *)frontmostApplicationElement { NSRunningApplication *frontmostApplication = [NSWorkspace sharedWorkspace].frontmostApplication; SpectacleAccessibilityElement *frontmostApplicationElement = [SpectacleAccessibilityElement new]; AXUIElementRef underlyingElement = AXUIElementCreateApplication(frontmostApplication.processIdentifier); frontmostApplicationElement.underlyingElement = underlyingElement; CFRelease(underlyingElement); return frontmostApplicationElement; } + (SpectacleAccessibilityElement *)frontmostWindowElement { SpectacleAccessibilityElement *frontmostApplicationElement = [SpectacleAccessibilityElement frontmostApplicationElement]; SpectacleAccessibilityElement *frontmostWindowElement = nil; if (frontmostApplicationElement) { frontmostWindowElement = [frontmostApplicationElement elementWithAttribute:kAXFocusedWindowAttribute]; if (!frontmostWindowElement) { NSLog(@"Invalid accessibility element provided, unable to determine the size and position of the window."); } } else { NSLog(@"Failed to find the application that currently has focus."); } return frontmostWindowElement; } - (SpectacleAccessibilityElement *)elementWithAttribute:(CFStringRef)attribute { SpectacleAccessibilityElement *newElement = nil; AXUIElementRef underlyingElement; AXError result = AXUIElementCopyAttributeValue(_underlyingElement, attribute, (CFTypeRef *)&underlyingElement); if (result == kAXErrorSuccess) { newElement = [SpectacleAccessibilityElement new]; newElement.underlyingElement = underlyingElement; CFRelease(underlyingElement); } else { NSLog(@"Unable to obtain the accessibility element with the specified attribute: %@ (error code %d)", attribute, result); } return newElement; } - (NSString *)stringValueOfAttribute:(CFStringRef)attribute { if (CFGetTypeID(_underlyingElement) == AXUIElementGetTypeID()) { CFTypeRef value; AXError result; result = AXUIElementCopyAttributeValue(_underlyingElement, attribute, &value); if (result == kAXErrorSuccess) { return CFBridgingRelease(value); } else { NSLog(@"There was a problem getting the string value of the specified attribute: %@ (error code %d)", attribute, result); } } return nil; } - (AXValueRef)valueOfAttribute:(CFStringRef)attribute type:(AXValueType)type { if (CFGetTypeID(_underlyingElement) == AXUIElementGetTypeID()) { CFTypeRef value; AXError result; result = AXUIElementCopyAttributeValue(_underlyingElement, attribute, (CFTypeRef *)&value); if ((result == kAXErrorSuccess) && (AXValueGetType(value) == type)) { return value; } else { NSLog(@"There was a problem getting the value of the specified attribute: %@ (error code %d)", attribute, result); } } return NULL; } - (void)setValue:(AXValueRef)value forAttribute:(CFStringRef)attribute { AXError result = AXUIElementSetAttributeValue(_underlyingElement, attribute, (CFTypeRef *)value); if (result != kAXErrorSuccess) { NSLog(@"There was a problem setting the value of the specified attribute: %@ (error code %d)", attribute, result); } } - (CGRect)rectOfElement { CGRect result = CGRectNull; CFTypeRef positionValue = [self valueOfAttribute:kAXPositionAttribute type:kAXValueCGPointType]; CFTypeRef sizeValue = [self valueOfAttribute:kAXSizeAttribute type:kAXValueCGSizeType]; CGPoint position; CGSize size; AXValueGetValue(positionValue, kAXValueCGPointType, (void *)&position); AXValueGetValue(sizeValue, kAXValueCGSizeType, (void *)&size); if ((positionValue != NULL) && (sizeValue != NULL)) { CFRelease(positionValue); CFRelease(sizeValue); result = CGRectMake(position.x, position.y, size.width, size.height); } return result; } - (void)setRectOfElement:(CGRect)rect { AXValueRef positionRef; AXValueRef sizeRef; positionRef = AXValueCreate(kAXValueCGPointType, (const void *)&rect.origin); sizeRef = AXValueCreate(kAXValueCGSizeType, (const void *)&rect.size); [self setValue:sizeRef forAttribute:kAXSizeAttribute]; [self setValue:positionRef forAttribute:kAXPositionAttribute]; [self setValue:sizeRef forAttribute:kAXSizeAttribute]; CFRelease(positionRef); CFRelease(sizeRef); } + (CGRect)normalizeCoordinatesOfRect:(CGRect)rect frameOfScreen:(CGRect)frameOfScreen { CGRect frameOfScreenWithMenuBar = [[[NSScreen screens] objectAtIndex:0] frame]; rect.origin.y = frameOfScreen.size.height - NSMaxY(rect) + (frameOfScreenWithMenuBar.size.height - frameOfScreen.size.height); return rect; } - (BOOL)isSheet { return [[self stringValueOfAttribute:kAXRoleAttribute] isEqualToString:(__bridge NSString *)kAXSheetRole]; } - (BOOL)isSystemDialog { return [[self stringValueOfAttribute:kAXSubroleAttribute] isEqualToString:(__bridge NSString *)kAXSystemDialogSubrole]; } - (void)dealloc { if (_underlyingElement != NULL) { CFRelease(_underlyingElement); } } - (void)setUnderlyingElement:(AXUIElementRef)underlyingElement { if (_underlyingElement != NULL) { CFRelease(_underlyingElement); } _underlyingElement = CFRetain(underlyingElement); } @end ================================================ FILE: Spectacle/Sources/SpectacleAppDelegate.h ================================================ #import @class SpectaclePreferencesController; @interface SpectacleAppDelegate : NSObject @property (nonatomic, strong) IBOutlet NSMenu *statusItemMenu; @property (nonatomic, strong) IBOutlet NSWindow *accessiblityAccessDialogWindow; @property (nonatomic, strong) IBOutlet NSMenuItem *moveToCenterShortcutMenuItem; @property (nonatomic, strong) IBOutlet NSMenuItem *moveToFullscreenShortcutMenuItem; @property (nonatomic, strong) IBOutlet NSMenuItem *moveToLeftShortcutMenuItem; @property (nonatomic, strong) IBOutlet NSMenuItem *moveToRightShortcutMenuItem; @property (nonatomic, strong) IBOutlet NSMenuItem *moveToTopShortcutMenuItem; @property (nonatomic, strong) IBOutlet NSMenuItem *moveToBottomShortcutMenuItem; @property (nonatomic, strong) IBOutlet NSMenuItem *moveToUpperLeftShortcutMenuItem; @property (nonatomic, strong) IBOutlet NSMenuItem *moveToLowerLeftShortcutMenuItem; @property (nonatomic, strong) IBOutlet NSMenuItem *moveToUpperRightShortcutMenuItem; @property (nonatomic, strong) IBOutlet NSMenuItem *moveToLowerRightShortcutMenuItem; @property (nonatomic, strong) IBOutlet NSMenuItem *moveToNextDisplayShortcutMenuItem; @property (nonatomic, strong) IBOutlet NSMenuItem *moveToPreviousDisplayShortcutMenuItem; @property (nonatomic, strong) IBOutlet NSMenuItem *moveToNextThirdShortcutMenuItem; @property (nonatomic, strong) IBOutlet NSMenuItem *moveToPreviousThirdShortcutMenuItem; @property (nonatomic, strong) IBOutlet NSMenuItem *makeLargerShortcutMenuItem; @property (nonatomic, strong) IBOutlet NSMenuItem *makeSmallerShortcutMenuItem; @property (nonatomic, strong) IBOutlet NSMenuItem *undoLastMoveShortcutMenuItem; @property (nonatomic, strong) IBOutlet NSMenuItem *redoLastMoveShortcutMenuItem; @property (nonatomic, strong) IBOutlet NSMenuItem *disableShortcutsForAnHourMenuItem; @property (nonatomic, strong) IBOutlet NSMenuItem *disableShortcutsForApplicationMenuItem; - (IBAction)showPreferencesWindow:(id)sender; - (IBAction)moveFrontmostWindowToFullscreen:(id)sender; - (IBAction)moveFrontmostWindowToCenter:(id)sender; - (IBAction)moveFrontmostWindowToLeftHalf:(id)sender; - (IBAction)moveFrontmostWindowToRightHalf:(id)sender; - (IBAction)moveFrontmostWindowToTopHalf:(id)sender; - (IBAction)moveFrontmostWindowToBottomHalf:(id)sender; - (IBAction)moveFrontmostWindowToUpperLeft:(id)sender; - (IBAction)moveFrontmostWindowToLowerLeft:(id)sender; - (IBAction)moveFrontmostWindowToUpperRight:(id)sender; - (IBAction)moveFrontmostWindowToLowerRight:(id)sender; - (IBAction)moveFrontmostWindowToNextDisplay:(id)sender; - (IBAction)moveFrontmostWindowToPreviousDisplay:(id)sender; - (IBAction)moveFrontmostWindowToNextThird:(id)sender; - (IBAction)moveFrontmostWindowToPreviousThird:(id)sender; - (IBAction)makeFrontmostWindowLarger:(id)sender; - (IBAction)makeFrontmostWindowSmaller:(id)sender; - (IBAction)undoLastWindowAction:(id)sender; - (IBAction)redoLastWindowAction:(id)sender; - (IBAction)disableOrEnableShortcutsForAnHour:(id)sender; - (IBAction)disableOrEnableShortcutsForApplication:(id)sender; - (IBAction)openSystemPreferences:(id)sender; @end ================================================ FILE: Spectacle/Sources/SpectacleAppDelegate.m ================================================ #import "SpectacleAppDelegate.h" #import #import "SpectacleAccessibilityElement.h" #import "SpectacleDefaultShortcutHelpers.h" #import "SpectacleMigratingShortcutStorage.h" #import "SpectaclePreferencesController.h" #import "SpectacleScreenDetector.h" #import "SpectacleShortcutJSONStorage.h" #import "SpectacleShortcutManager.h" #import "SpectacleShortcutTranslations.h" #import "SpectacleShortcutUserDefaultsStorage.h" #import "SpectacleUtilities.h" #import "SpectacleWindowPositionCalculator.h" #import "SpectacleWindowPositionManager.h" @implementation SpectacleAppDelegate { NSDictionary *_shortcutMenuItems; NSStatusItem *_statusItem; id _shortcutStorage; SpectacleShortcutManager *_shortcutManager; SpectacleWindowPositionManager *_windowPositionManager; SpectaclePreferencesController *_preferencesController; NSTimer *_disableShortcutsForAnHourTimer; NSSet *_blacklistedApplications; NSMutableSet *_disabledApplications; BOOL _shortcutsAreDisabledForAnHour; } - (void)applicationDidFinishLaunching:(NSNotification *)notification { NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; NSNotificationCenter *workspaceNotificationCenter = [NSWorkspace sharedWorkspace].notificationCenter; [notificationCenter addObserver:self selector:@selector(enableStatusItem) name:@"SpectacleStatusItemEnabledNotification" object:nil]; [notificationCenter addObserver:self selector:@selector(disableStatusItem) name:@"SpectacleStatusItemDisabledNotification" object:nil]; [notificationCenter addObserver:self selector:@selector(updateShortcutMenuItems) name:@"SpectacleShortcutChangedNotification" object:nil]; [notificationCenter addObserver:self selector:@selector(updateShortcutMenuItems) name:@"SpectacleRestoreDefaultShortcutsNotification" object:nil]; [notificationCenter addObserver:self selector:@selector(menuDidSendAction:) name:NSMenuDidSendActionNotification object:nil]; [notificationCenter addObserver:self selector:@selector(inputSourceSelectionDidChange) name:NSTextInputContextKeyboardSelectionDidChangeNotification object:nil]; [workspaceNotificationCenter addObserver:self selector:@selector(applicationDidActivate:) name:NSWorkspaceDidActivateApplicationNotification object:nil]; [workspaceNotificationCenter addObserver:self selector:@selector(applicationDidDeactivate:) name:NSWorkspaceDidDeactivateApplicationNotification object:nil]; [SpectacleUtilities registerDefaultsForBundle:[NSBundle mainBundle]]; _shortcutMenuItems = @{ @"MoveToCenter": _moveToCenterShortcutMenuItem, @"MoveToFullscreen": _moveToFullscreenShortcutMenuItem, @"MoveToLeftHalf": _moveToLeftShortcutMenuItem, @"MoveToRightHalf": _moveToRightShortcutMenuItem, @"MoveToTopHalf": _moveToTopShortcutMenuItem, @"MoveToBottomHalf": _moveToBottomShortcutMenuItem, @"MoveToUpperLeft": _moveToUpperLeftShortcutMenuItem, @"MoveToLowerLeft": _moveToLowerLeftShortcutMenuItem, @"MoveToUpperRight": _moveToUpperRightShortcutMenuItem, @"MoveToLowerRight": _moveToLowerRightShortcutMenuItem, @"MoveToNextDisplay": _moveToNextDisplayShortcutMenuItem, @"MoveToPreviousDisplay": _moveToPreviousDisplayShortcutMenuItem, @"MoveToNextThird": _moveToNextThirdShortcutMenuItem, @"MoveToPreviousThird": _moveToPreviousThirdShortcutMenuItem, @"MakeLarger": _makeLargerShortcutMenuItem, @"MakeSmaller": _makeSmallerShortcutMenuItem, @"UndoLastMove": _undoLastMoveShortcutMenuItem, @"RedoLastMove": _redoLastMoveShortcutMenuItem, }; NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; _blacklistedApplications = [NSSet setWithArray:[userDefaults objectForKey:@"BlacklistedApplications"]]; _disabledApplications = [NSMutableSet setWithArray:[userDefaults objectForKey:@"DisabledApplications"]]; _shortcutStorage = [[SpectacleMigratingShortcutStorage alloc] initWithShortcutStorage:[SpectacleShortcutUserDefaultsStorage new] migrationDestination:[SpectacleShortcutJSONStorage new]]; _shortcutManager = [[SpectacleShortcutManager alloc] initWithShortcutStorage:_shortcutStorage]; SpectacleWindowPositionCalculator *windowPositionCalculator = [[SpectacleWindowPositionCalculator alloc] initWithErrorHandler:^(NSString *errorMessage) { NSAlert *alert = [NSAlert new]; alert.alertStyle = NSWarningAlertStyle; alert.messageText = @"Encountered an unexpected error"; alert.informativeText = errorMessage; [alert runModal]; }]; _windowPositionManager = [[SpectacleWindowPositionManager alloc] initWithScreenDetector:[SpectacleScreenDetector new] windowPositionCalculator:windowPositionCalculator sharedWorkspace:[NSWorkspace sharedWorkspace]]; _preferencesController = [[SpectaclePreferencesController alloc] initWithShortcutManager:_shortcutManager windowPositionManager:_windowPositionManager shortcutStorage:_shortcutStorage]; _shortcutsAreDisabledForAnHour = NO; [self manageShortcuts]; [self disableShortcutsIfFrontmostApplicationIsBlacklistedOrDisabled]; BOOL automaticallyChecksForUpdates = [userDefaults boolForKey:@"AutomaticUpdateCheckEnabled"]; BOOL statusItemEnabled = [userDefaults boolForKey:@"StatusItemEnabled"]; if (statusItemEnabled) { [self enableStatusItem]; } [[SUUpdater sharedUpdater] setAutomaticallyChecksForUpdates:automaticallyChecksForUpdates]; [self updateShortcutMenuItems]; if (!AXIsProcessTrustedWithOptions(NULL)) { [[NSApplication sharedApplication] runModalForWindow:self.accessiblityAccessDialogWindow]; } } - (BOOL)applicationShouldHandleReopen:(NSApplication *)application hasVisibleWindows:(BOOL)visibleWindows { [self showPreferencesWindow:self]; return YES; } - (IBAction)showPreferencesWindow:(id)sender { [_preferencesController showWindow:sender]; } - (IBAction)moveFrontmostWindowToFullscreen:(id)sender { [_windowPositionManager moveFrontmostWindowElement:[SpectacleAccessibilityElement frontmostWindowElement] action:kSpectacleWindowActionFullscreen]; } - (IBAction)moveFrontmostWindowToCenter:(id)sender { [_windowPositionManager moveFrontmostWindowElement:[SpectacleAccessibilityElement frontmostWindowElement] action:kSpectacleWindowActionCenter]; } - (IBAction)moveFrontmostWindowToLeftHalf:(id)sender { [_windowPositionManager moveFrontmostWindowElement:[SpectacleAccessibilityElement frontmostWindowElement] action:kSpectacleWindowActionLeftHalf]; } - (IBAction)moveFrontmostWindowToRightHalf:(id)sender { [_windowPositionManager moveFrontmostWindowElement:[SpectacleAccessibilityElement frontmostWindowElement] action:kSpectacleWindowActionRightHalf]; } - (IBAction)moveFrontmostWindowToTopHalf:(id)sender { [_windowPositionManager moveFrontmostWindowElement:[SpectacleAccessibilityElement frontmostWindowElement] action:kSpectacleWindowActionTopHalf]; } - (IBAction)moveFrontmostWindowToBottomHalf:(id)sender { [_windowPositionManager moveFrontmostWindowElement:[SpectacleAccessibilityElement frontmostWindowElement] action:kSpectacleWindowActionBottomHalf]; } - (IBAction)moveFrontmostWindowToUpperLeft:(id)sender { [_windowPositionManager moveFrontmostWindowElement:[SpectacleAccessibilityElement frontmostWindowElement] action:kSpectacleWindowActionUpperLeft]; } - (IBAction)moveFrontmostWindowToLowerLeft:(id)sender { [_windowPositionManager moveFrontmostWindowElement:[SpectacleAccessibilityElement frontmostWindowElement] action:kSpectacleWindowActionLowerLeft]; } - (IBAction)moveFrontmostWindowToUpperRight:(id)sender { [_windowPositionManager moveFrontmostWindowElement:[SpectacleAccessibilityElement frontmostWindowElement] action:kSpectacleWindowActionUpperRight]; } - (IBAction)moveFrontmostWindowToLowerRight:(id)sender { [_windowPositionManager moveFrontmostWindowElement:[SpectacleAccessibilityElement frontmostWindowElement] action:kSpectacleWindowActionLowerRight]; } - (IBAction)moveFrontmostWindowToNextDisplay:(id)sender { [_windowPositionManager moveFrontmostWindowElement:[SpectacleAccessibilityElement frontmostWindowElement] action:kSpectacleWindowActionNextDisplay]; } - (IBAction)moveFrontmostWindowToPreviousDisplay:(id)sender { [_windowPositionManager moveFrontmostWindowElement:[SpectacleAccessibilityElement frontmostWindowElement] action:kSpectacleWindowActionPreviousDisplay]; } - (IBAction)moveFrontmostWindowToNextThird:(id)sender { [_windowPositionManager moveFrontmostWindowElement:[SpectacleAccessibilityElement frontmostWindowElement] action:kSpectacleWindowActionNextThird]; } - (IBAction)moveFrontmostWindowToPreviousThird:(id)sender { [_windowPositionManager moveFrontmostWindowElement:[SpectacleAccessibilityElement frontmostWindowElement] action:kSpectacleWindowActionPreviousThird]; } - (IBAction)makeFrontmostWindowLarger:(id)sender { [_windowPositionManager moveFrontmostWindowElement:[SpectacleAccessibilityElement frontmostWindowElement] action:kSpectacleWindowActionLarger]; } - (IBAction)makeFrontmostWindowSmaller:(id)sender { [_windowPositionManager moveFrontmostWindowElement:[SpectacleAccessibilityElement frontmostWindowElement] action:kSpectacleWindowActionSmaller]; } - (IBAction)undoLastWindowAction:(id)sender { [_windowPositionManager undoLastWindowAction]; } - (IBAction)redoLastWindowAction:(id)sender { [_windowPositionManager redoLastWindowAction]; } - (IBAction)disableOrEnableShortcutsForAnHour:(id)sender { NSInteger newMenuItemState = NSMixedState; if (_shortcutsAreDisabledForAnHour) { _shortcutsAreDisabledForAnHour = NO; [_disableShortcutsForAnHourTimer invalidate]; [self enableShortcutsIfPermitted]; newMenuItemState = NSOffState; } else { _shortcutsAreDisabledForAnHour = YES; _disableShortcutsForAnHourTimer = [NSTimer scheduledTimerWithTimeInterval:3600 target:self selector:@selector(disableOrEnableShortcutsForAnHour:) userInfo:nil repeats:NO]; [_shortcutManager unregisterShortcuts]; newMenuItemState = NSOnState; } self.disableShortcutsForAnHourMenuItem.state = newMenuItemState; } - (IBAction)disableOrEnableShortcutsForApplication:(id)sender { NSRunningApplication *frontmostApplication = [NSWorkspace sharedWorkspace].frontmostApplication; if ([_disabledApplications containsObject:frontmostApplication.bundleIdentifier]) { [_disabledApplications removeObject:frontmostApplication.bundleIdentifier]; [self enableShortcutsIfPermitted]; self.disableShortcutsForApplicationMenuItem.state = NSOffState; } else { [_disabledApplications addObject:frontmostApplication.bundleIdentifier]; [_shortcutManager unregisterShortcuts]; self.disableShortcutsForApplicationMenuItem.state = NSOnState; } [NSUserDefaults.standardUserDefaults setObject:_disabledApplications.allObjects forKey:@"DisabledApplications"]; } - (IBAction)openSystemPreferences:(id)sender { NSURL *preferencePaneURL = [NSURL fileURLWithPath:[SpectacleUtilities pathForPreferencePaneNamed:@"Security"]]; NSBundle *applicationBundle = NSBundle.mainBundle; NSURL *scriptURL = [applicationBundle URLForResource:@"Security & Privacy System Preferences" withExtension:@"scpt"]; [[NSApplication sharedApplication] stopModal]; [self.accessiblityAccessDialogWindow orderOut:self]; if (![[[NSAppleScript alloc] initWithContentsOfURL:scriptURL error:nil] executeAndReturnError:nil]) { [[NSWorkspace sharedWorkspace] openURL:preferencePaneURL]; } } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)manageShortcuts { SpectacleShortcutAction action = ^(SpectacleShortcut *shortcut) { [self->_windowPositionManager moveFrontmostWindowElement:[SpectacleAccessibilityElement frontmostWindowElement] action:shortcut.windowAction]; }; NSArray *shortcuts = [_shortcutStorage loadShortcutsWithAction:action]; if (shortcuts.count != 0) { [_shortcutManager manageShortcuts:shortcuts]; } else { [_shortcutManager manageShortcuts:SpectacleDefaultShortcutsWithAction(action)]; } } - (void)enableStatusItem { _statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; NSImage *statusImage = [NSBundle.mainBundle imageForResource:@"Spectacle Status Item"]; [statusImage setTemplate:YES]; _statusItem.highlightMode = YES; _statusItem.image = statusImage; _statusItem.menu = _statusItemMenu; _statusItem.toolTip = [@"Spectacle " stringByAppendingString:SpectacleUtilities.applicationVersion]; } - (void)disableStatusItem { [NSStatusBar.systemStatusBar removeStatusItem:_statusItem]; } - (void)updateShortcutMenuItems { for (NSString *shortcutName in _shortcutMenuItems.allKeys) { NSMenuItem *shortcutMenuItem = _shortcutMenuItems[shortcutName]; SpectacleShortcut *shortcut = [_shortcutManager shortcutForShortcutName:shortcutName]; if (shortcut) { shortcutMenuItem.keyEquivalent = [SpectacleTranslateKeyCode(shortcut.shortcutKeyCode) lowercaseString]; shortcutMenuItem.keyEquivalentModifierMask = SpectacleConvertModifiersToCocoaIfNecessary(shortcut.shortcutModifiers); } else { shortcutMenuItem.keyEquivalent = @""; shortcutMenuItem.keyEquivalentModifierMask = 0; } } } - (void)enableShortcutsIfPermitted { NSRunningApplication *frontmostApplication = [NSWorkspace sharedWorkspace].frontmostApplication; // Do not enable shortcuts if they should remain disabled for an hour. if (_shortcutsAreDisabledForAnHour) { return; } // Do not enable shortcuts if the application is blacklisted or disabled. if ([_blacklistedApplications containsObject:frontmostApplication.bundleIdentifier] || [_disabledApplications containsObject:frontmostApplication.bundleIdentifier]) { return; } [_shortcutManager registerShortcuts]; [[NSNotificationCenter defaultCenter] postNotificationName:@"SpectacleShortcutChangedNotification" object:self]; } - (void)disableShortcutsIfFrontmostApplicationIsBlacklistedOrDisabled { NSRunningApplication *frontmostApplication = [NSWorkspace sharedWorkspace].frontmostApplication; // Do not disable shortcuts if the application is not blacklisted or disabled. if (![_blacklistedApplications containsObject:frontmostApplication.bundleIdentifier] && ![_disabledApplications containsObject:frontmostApplication.bundleIdentifier]) { return; } [_shortcutManager unregisterShortcuts]; } - (void)applicationDidActivate:(NSNotification *)notification { NSRunningApplication *application = notification.userInfo[NSWorkspaceApplicationKey]; if ([_blacklistedApplications containsObject:application.bundleIdentifier] || [_disabledApplications containsObject:application.bundleIdentifier]) { [_shortcutManager unregisterShortcuts]; } } - (void)applicationDidDeactivate:(NSNotification *)notification { NSRunningApplication *application = notification.userInfo[NSWorkspaceApplicationKey]; if ([_blacklistedApplications containsObject:application.bundleIdentifier] || [_disabledApplications containsObject:application.bundleIdentifier]) { [self enableShortcutsIfPermitted]; } } - (void)menuWillOpen:(NSMenu *)menu { NSRunningApplication *frontmostApplication = [NSWorkspace sharedWorkspace].frontmostApplication; self.disableShortcutsForApplicationMenuItem.hidden = NO; if ([_blacklistedApplications containsObject:frontmostApplication.bundleIdentifier]) { self.disableShortcutsForApplicationMenuItem.hidden = YES; } else { self.disableShortcutsForApplicationMenuItem.title = [NSString stringWithFormat:NSLocalizedString(@"MenuItemTitleDisableShortcutsForApplication", @"The menu item title that displays the application to disable shortcuts for"), frontmostApplication.localizedName]; } if ([_disabledApplications containsObject:frontmostApplication.bundleIdentifier]) { self.disableShortcutsForApplicationMenuItem.state = NSOnState; } else { self.disableShortcutsForApplicationMenuItem.state = NSOffState; } } - (void)menuDidSendAction:(NSNotification *)notification { NSMenuItem *menuItem = notification.userInfo[@"MenuItem"]; if (menuItem.tag == -1) { [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; } } - (void)inputSourceSelectionDidChange { [_preferencesController loadRegisteredShortcuts]; [self updateShortcutMenuItems]; } @end ================================================ FILE: Spectacle/Sources/SpectacleBestEffortWindowMover.h ================================================ #import #import "SpectacleWindowMover.h" @interface SpectacleBestEffortWindowMover : NSObject - (instancetype)initWithInnerWindowMover:(id)innerWindowMover NS_DESIGNATED_INITIALIZER; @end ================================================ FILE: Spectacle/Sources/SpectacleBestEffortWindowMover.m ================================================ #import "SpectacleBestEffortWindowMover.h" #import "SpectacleAccessibilityElement.h" #import "SpectacleWindowPositionManager.h" @implementation SpectacleBestEffortWindowMover { id _innerWindowMover; } - (instancetype)initWithInnerWindowMover:(id)innerWindowMover { if (self = [super init]) { _innerWindowMover = innerWindowMover; } return self; } - (instancetype)init { return [self initWithInnerWindowMover:nil]; } + (instancetype)newWithInnerWindowMover:(id)innerWindowMover { return [[self alloc] initWithInnerWindowMover:innerWindowMover]; } - (void)moveWindowRect:(CGRect)windowRect frameOfScreen:(CGRect)frameOfScreen visibleFrameOfScreen:(CGRect)visibleFrameOfScreen frontmostWindowElement:(SpectacleAccessibilityElement *)frontmostWindowElement action:(SpectacleWindowAction *)action { [_innerWindowMover moveWindowRect:windowRect frameOfScreen:frameOfScreen visibleFrameOfScreen:visibleFrameOfScreen frontmostWindowElement:frontmostWindowElement action:action]; CGRect movedWindowRect = [frontmostWindowElement rectOfElement]; CGRect previouslyMovedWindowRect = movedWindowRect; if (movedWindowRect.origin.x < visibleFrameOfScreen.origin.x) { movedWindowRect.origin.x = visibleFrameOfScreen.origin.x; } else if ((movedWindowRect.origin.x + movedWindowRect.size.width) > (visibleFrameOfScreen.origin.x + visibleFrameOfScreen.size.width)) { movedWindowRect.origin.x = visibleFrameOfScreen.origin.x + visibleFrameOfScreen.size.width - movedWindowRect.size.width; } movedWindowRect = [SpectacleAccessibilityElement normalizeCoordinatesOfRect:movedWindowRect frameOfScreen:frameOfScreen]; if (movedWindowRect.origin.y < visibleFrameOfScreen.origin.y) { movedWindowRect.origin.y = visibleFrameOfScreen.origin.y; } else if ((movedWindowRect.origin.y + movedWindowRect.size.height) > (visibleFrameOfScreen.origin.y + visibleFrameOfScreen.size.height)) { movedWindowRect.origin.y = visibleFrameOfScreen.origin.y + visibleFrameOfScreen.size.height - movedWindowRect.size.height; } movedWindowRect = [SpectacleAccessibilityElement normalizeCoordinatesOfRect:movedWindowRect frameOfScreen:frameOfScreen]; if (!CGRectEqualToRect(movedWindowRect, previouslyMovedWindowRect)) { [frontmostWindowElement setRectOfElement:movedWindowRect]; } } @end ================================================ FILE: Spectacle/Sources/SpectacleDefaultShortcutHelpers.h ================================================ #import #import "SpectacleShortcut.h" NSArray *SpectacleDefaultShortcutsWithAction(SpectacleShortcutAction action); ================================================ FILE: Spectacle/Sources/SpectacleDefaultShortcutHelpers.m ================================================ #import "SpectacleDefaultShortcutHelpers.h" #import #import static NSArray *builtinDefaultShortcuts(void); NSArray *SpectacleDefaultShortcutsWithAction(SpectacleShortcutAction action) { NSMutableArray *defaultShortcuts = [NSMutableArray new]; for (SpectacleShortcut *defaultShortcut in builtinDefaultShortcuts()) { [defaultShortcuts addObject:[defaultShortcut copyWithShortcutAction:action]]; } return defaultShortcuts; } static NSArray *builtinDefaultShortcuts(void) { return @[ [[SpectacleShortcut alloc] initWithShortcutName:@"MoveToCenter" shortcutKeyBinding:@"alt+cmd+c"], [[SpectacleShortcut alloc] initWithShortcutName:@"MoveToFullscreen" shortcutKeyBinding:@"alt+cmd+f"], [[SpectacleShortcut alloc] initWithShortcutName:@"MoveToLeftHalf" shortcutKeyBinding:@"alt+cmd+left"], [[SpectacleShortcut alloc] initWithShortcutName:@"MoveToRightHalf" shortcutKeyBinding:@"alt+cmd+right"], [[SpectacleShortcut alloc] initWithShortcutName:@"MoveToTopHalf" shortcutKeyBinding:@"alt+cmd+up"], [[SpectacleShortcut alloc] initWithShortcutName:@"MoveToBottomHalf" shortcutKeyBinding:@"alt+cmd+down"], [[SpectacleShortcut alloc] initWithShortcutName:@"MoveToUpperLeft" shortcutKeyBinding:@"ctrl+cmd+left"], [[SpectacleShortcut alloc] initWithShortcutName:@"MoveToLowerLeft" shortcutKeyBinding:@"ctrl+shift+cmd+left"], [[SpectacleShortcut alloc] initWithShortcutName:@"MoveToUpperRight" shortcutKeyBinding:@"ctrl+cmd+right"], [[SpectacleShortcut alloc] initWithShortcutName:@"MoveToLowerRight" shortcutKeyBinding:@"ctrl+shift+cmd+right"], [[SpectacleShortcut alloc] initWithShortcutName:@"MoveToNextDisplay" shortcutKeyBinding:@"ctrl+alt+cmd+right"], [[SpectacleShortcut alloc] initWithShortcutName:@"MoveToPreviousDisplay" shortcutKeyBinding:@"ctrl+alt+cmd+left"], [[SpectacleShortcut alloc] initWithShortcutName:@"MoveToNextThird" shortcutKeyBinding:@"ctrl+alt+right"], [[SpectacleShortcut alloc] initWithShortcutName:@"MoveToPreviousThird" shortcutKeyBinding:@"ctrl+alt+left"], [[SpectacleShortcut alloc] initWithShortcutName:@"MakeLarger" shortcutKeyBinding:@"ctrl+alt+shift+right"], [[SpectacleShortcut alloc] initWithShortcutName:@"MakeSmaller" shortcutKeyBinding:@"ctrl+alt+shift+left"], [[SpectacleShortcut alloc] initWithShortcutName:@"UndoLastMove" shortcutKeyBinding:@"alt+cmd+z"], [[SpectacleShortcut alloc] initWithShortcutName:@"RedoLastMove" shortcutKeyBinding:@"alt+shift+cmd+z"], ]; } ================================================ FILE: Spectacle/Sources/SpectacleHistory.h ================================================ #import @class SpectacleHistoryItem; @interface SpectacleHistory : NSObject - (void)addHistoryItem:(SpectacleHistoryItem *)historyItem; - (SpectacleHistoryItem *)nextHistoryItem; - (SpectacleHistoryItem *)previousHistoryItem; - (BOOL)isEmpty; @end ================================================ FILE: Spectacle/Sources/SpectacleHistory.m ================================================ #import "SpectacleHistory.h" #import "SpectacleHistoryItem.h" @implementation SpectacleHistory { SpectacleHistoryItem *_lastHistoryItem; SpectacleHistoryItem *_currentHistoryItem; NSInteger _size; } - (void)addHistoryItem:(SpectacleHistoryItem *)historyItem { if ([self isEmpty]) { _currentHistoryItem = historyItem; _lastHistoryItem = _currentHistoryItem; } else { historyItem.previousHistoryItem = _currentHistoryItem; _currentHistoryItem.nextHistoryItem.previousHistoryItem = historyItem; _currentHistoryItem.nextHistoryItem = historyItem; if (![historyItem previousHistoryItem]) { _lastHistoryItem = historyItem; } _currentHistoryItem = historyItem; } if (++_size > 50) { _lastHistoryItem.nextHistoryItem.previousHistoryItem = nil; _lastHistoryItem = _lastHistoryItem.nextHistoryItem; _size--; } } - (SpectacleHistoryItem *)nextHistoryItem { SpectacleHistoryItem *historyItem = _currentHistoryItem.nextHistoryItem; if (historyItem) { _currentHistoryItem = historyItem; _size++; } return historyItem; } - (SpectacleHistoryItem *)previousHistoryItem { SpectacleHistoryItem *historyItem = _currentHistoryItem.previousHistoryItem; if (historyItem) { _currentHistoryItem = historyItem; _size--; } return historyItem; } - (BOOL)isEmpty { return _size == 0; } @end ================================================ FILE: Spectacle/Sources/SpectacleHistoryItem.h ================================================ #import #import "SpectacleMacros.h" @class SpectacleAccessibilityElement; @interface SpectacleHistoryItem : NSObject @property (nonatomic, strong) SpectacleAccessibilityElement *accessibilityElement; @property (nonatomic, assign) CGRect windowRect; @property (nonatomic, strong) SpectacleHistoryItem *nextHistoryItem; @property (nonatomic, weak) SpectacleHistoryItem *previousHistoryItem; - (instancetype)initWithAccessibilityElement:(SpectacleAccessibilityElement *)accessibilityElement windowRect:(CGRect)windowRect NS_DESIGNATED_INITIALIZER; + (SpectacleHistoryItem *)historyItemFromAccessibilityElement:(SpectacleAccessibilityElement *)accessibilityElement windowRect:(CGRect)windowRect; SPECTACLE_INIT_AND_NEW_UNAVAILABLE @end ================================================ FILE: Spectacle/Sources/SpectacleHistoryItem.m ================================================ #import "SpectacleHistoryItem.h" @implementation SpectacleHistoryItem - (instancetype)initWithAccessibilityElement:(SpectacleAccessibilityElement *)accessibilityElement windowRect:(CGRect)windowRect { if (self = [super init]) { _accessibilityElement = accessibilityElement; _windowRect = windowRect; } return self; } + (SpectacleHistoryItem *)historyItemFromAccessibilityElement:(SpectacleAccessibilityElement *)accessibilityElement windowRect:(CGRect)windowRect { return [[SpectacleHistoryItem alloc] initWithAccessibilityElement:accessibilityElement windowRect:windowRect]; } @end ================================================ FILE: Spectacle/Sources/SpectacleJavaScriptEnvironment.h ================================================ #import #import "SpectacleMacros.h" @class JSContext; @class JSValue; @class SpectacleWindowPositionCalculationResult; @interface SpectacleJavaScriptEnvironment : NSObject - (instancetype)initWithContextBuilder:(void(^)(JSContext *))contextBuilder NS_DESIGNATED_INITIALIZER; - (JSValue *)valueWithRect:(CGRect)rect; SPECTACLE_INIT_AND_NEW_UNAVAILABLE @end ================================================ FILE: Spectacle/Sources/SpectacleJavaScriptEnvironment.m ================================================ #import "SpectacleJavaScriptEnvironment.h" #import @implementation SpectacleJavaScriptEnvironment { JSContext *_context; } - (instancetype)initWithContextBuilder:(void(^)(JSContext *))contextBuilder { if (self = [super init]) { _context = [JSContext new]; contextBuilder(_context); [self evaluateBundledScripts]; } return self; } - (JSValue *)valueWithRect:(CGRect)rect { return [JSValue valueWithRect:rect inContext:_context]; } - (void)evaluateBundledScripts { NSArray *scriptPaths = [[NSBundle mainBundle] pathsForResourcesOfType:@"js" inDirectory:@"Window Position Calculations"]; [scriptPaths enumerateObjectsUsingBlock:^(NSString *scriptPath, NSUInteger index, BOOL *stop) { NSString *script = loadScriptAtPath(scriptPath); if (script) { [self->_context evaluateScript:script]; } }]; } static NSString *loadScriptAtPath(NSString *scriptPath) { NSError *error; NSString *script = [NSString stringWithContentsOfFile:scriptPath encoding:NSUTF8StringEncoding error:&error]; if (!script) { NSLog(@"Unable to load script at path %@; reason %@", scriptPath, error.localizedDescription); } return script; } @end ================================================ FILE: Spectacle/Sources/SpectacleLoginItemHelper.h ================================================ #import @interface SpectacleLoginItemHelper : NSObject + (BOOL)isLoginItemEnabledForBundle:(NSBundle *)bundle; + (void)enableLoginItemForBundle:(NSBundle *)bundle; + (void)disableLoginItemForBundle:(NSBundle *)bundle; @end ================================================ FILE: Spectacle/Sources/SpectacleLoginItemHelper.m ================================================ #import "SpectacleLoginItemHelper.h" @implementation SpectacleLoginItemHelper + (BOOL)isLoginItemEnabledForBundle:(NSBundle *)bundle { LSSharedFileListRef sharedFileList = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL); NSString *applicationPath = bundle.bundlePath; BOOL result = NO; if (sharedFileList) { UInt32 seedValue; NSArray *sharedFileListArray = CFBridgingRelease(LSSharedFileListCopySnapshot(sharedFileList, &seedValue)); for (id sharedFile in sharedFileListArray) { LSSharedFileListItemRef sharedFileListItem = (__bridge LSSharedFileListItemRef)sharedFile; CFURLRef applicationPathURL = NULL; LSSharedFileListItemResolve(sharedFileListItem, 0, (CFURLRef *)&applicationPathURL, NULL); if (applicationPathURL != NULL) { NSString *resolvedApplicationPath = [(__bridge NSURL *)applicationPathURL path]; CFRelease(applicationPathURL); if ([resolvedApplicationPath compare:applicationPath] == NSOrderedSame) { result = YES; break; } } } CFRelease(sharedFileList); } else { NSLog(@"Unable to create the shared file list."); } return result; } + (void)enableLoginItemForBundle:(NSBundle *)bundle { LSSharedFileListRef sharedFileList = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL); NSString *applicationPath = bundle.bundlePath; NSURL *applicationPathURL = [NSURL fileURLWithPath:applicationPath]; if (sharedFileList) { LSSharedFileListItemRef sharedFileListItem = LSSharedFileListInsertItemURL(sharedFileList, kLSSharedFileListItemLast, NULL, NULL, (__bridge CFURLRef)applicationPathURL, NULL, NULL); if (sharedFileListItem) { CFRelease(sharedFileListItem); } CFRelease(sharedFileList); } else { NSLog(@"Unable to create the shared file list."); } } + (void)disableLoginItemForBundle:(NSBundle *)bundle { LSSharedFileListRef sharedFileList = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL); NSString *applicationPath = bundle.bundlePath; if (sharedFileList) { UInt32 seedValue; NSArray *sharedFileListArray = CFBridgingRelease(LSSharedFileListCopySnapshot(sharedFileList, &seedValue)); for (id sharedFile in sharedFileListArray) { LSSharedFileListItemRef sharedFileListItem = (__bridge LSSharedFileListItemRef)sharedFile; CFURLRef applicationPathURL; if (LSSharedFileListItemResolve(sharedFileListItem, 0, &applicationPathURL, NULL) == noErr) { NSString *resolvedApplicationPath = [(__bridge NSURL *)applicationPathURL path]; if ([resolvedApplicationPath compare:applicationPath] == NSOrderedSame) { LSSharedFileListItemRemove(sharedFileList, sharedFileListItem); } CFRelease(applicationPathURL); } } CFRelease(sharedFileList); } else { NSLog(@"Unable to create the shared file list."); } } @end ================================================ FILE: Spectacle/Sources/SpectacleMacros.h ================================================ #pragma once #ifndef SPECTACLE_INIT_AND_NEW_UNAVAILABLE #define SPECTACLE_INIT_AND_NEW_UNAVAILABLE \ - (instancetype)init NS_UNAVAILABLE; \ + (instancetype)new NS_UNAVAILABLE; #endif // SPECTACLE_INIT_AND_NEW_UNAVAILABLE ================================================ FILE: Spectacle/Sources/SpectacleMigratingShortcutStorage.h ================================================ #import #import "SpectacleMacros.h" #import "SpectacleShortcutStorage.h" @protocol SpectacleShortcutStorageMigrationDestination; @interface SpectacleMigratingShortcutStorage : NSObject - (instancetype)initWithShortcutStorage:(id)shortcutStorage migrationDestination:(id)migrationDestination NS_DESIGNATED_INITIALIZER; SPECTACLE_INIT_AND_NEW_UNAVAILABLE @end ================================================ FILE: Spectacle/Sources/SpectacleMigratingShortcutStorage.m ================================================ #import "SpectacleMigratingShortcutStorage.h" #import "SpectacleShortcutStorageMigrationDestination.h" @implementation SpectacleMigratingShortcutStorage { id _shortcutStorage; id _migrationDestination; } - (instancetype)initWithShortcutStorage:(id)shortcutStorage migrationDestination:(id)migrationDestination { if (self = [super init]) { _shortcutStorage = shortcutStorage; _migrationDestination = migrationDestination; } return self; } - (NSArray *)loadShortcutsWithAction:(SpectacleShortcutAction)action { return _migrationDestination.isMigrationNeeded ? [_shortcutStorage loadShortcutsWithAction:action] : [_migrationDestination loadShortcutsWithAction:action]; } - (void)storeShortcuts:(NSArray *)shortcuts { [_migrationDestination storeShortcuts:shortcuts]; } @end ================================================ FILE: Spectacle/Sources/SpectaclePreferencesController.h ================================================ #import #import "SpectacleShortcutRecorderDelegate.h" @class SpectacleAppDelegate; @class SpectacleShortcutManager; @class SpectacleShortcutRecorder; @class SpectacleWindowPositionManager; @protocol SpectacleShortcutStorage; @interface SpectaclePreferencesController : NSWindowController @property (nonatomic, strong) IBOutlet SpectacleShortcutRecorder *moveToCenterShortcutRecorder; @property (nonatomic, strong) IBOutlet SpectacleShortcutRecorder *moveToFullscreenShortcutRecorder; @property (nonatomic, strong) IBOutlet SpectacleShortcutRecorder *moveToLeftShortcutRecorder; @property (nonatomic, strong) IBOutlet SpectacleShortcutRecorder *moveToRightShortcutRecorder; @property (nonatomic, strong) IBOutlet SpectacleShortcutRecorder *moveToTopShortcutRecorder; @property (nonatomic, strong) IBOutlet SpectacleShortcutRecorder *moveToBottomShortcutRecorder; @property (nonatomic, strong) IBOutlet SpectacleShortcutRecorder *moveToUpperLeftShortcutRecorder; @property (nonatomic, strong) IBOutlet SpectacleShortcutRecorder *moveToLowerLeftShortcutRecorder; @property (nonatomic, strong) IBOutlet SpectacleShortcutRecorder *moveToUpperRightShortcutRecorder; @property (nonatomic, strong) IBOutlet SpectacleShortcutRecorder *moveToLowerRightShortcutRecorder; @property (nonatomic, strong) IBOutlet SpectacleShortcutRecorder *moveToNextDisplayShortcutRecorder; @property (nonatomic, strong) IBOutlet SpectacleShortcutRecorder *moveToPreviousDisplayShortcutRecorder; @property (nonatomic, strong) IBOutlet SpectacleShortcutRecorder *moveToNextThirdShortcutRecorder; @property (nonatomic, strong) IBOutlet SpectacleShortcutRecorder *moveToPreviousThirdShortcutRecorder; @property (nonatomic, strong) IBOutlet SpectacleShortcutRecorder *makeLargerShortcutRecorder; @property (nonatomic, strong) IBOutlet SpectacleShortcutRecorder *makeSmallerShortcutRecorder; @property (nonatomic, strong) IBOutlet SpectacleShortcutRecorder *undoLastMoveShortcutRecorder; @property (nonatomic, strong) IBOutlet SpectacleShortcutRecorder *redoLastMoveShortcutRecorder; @property (nonatomic, strong) IBOutlet NSView *footerView; @property (nonatomic, strong) IBOutlet NSView *shortcutModifierLegendFooterView; @property (nonatomic, strong) IBOutlet NSView *optionsFooterView; @property (nonatomic, strong) IBOutlet NSButton *loginItemEnabled; @property (nonatomic, strong) IBOutlet NSPopUpButton *statusItemEnabled; - (instancetype)initWithShortcutManager:(SpectacleShortcutManager *)shortcutManager windowPositionManager:(SpectacleWindowPositionManager *)windowPositionManager shortcutStorage:(id)shortcutStorage; - (void)loadRegisteredShortcuts; - (IBAction)swapFooterViews:(id)sender; - (IBAction)restoreDefaults:(id)sender; - (IBAction)toggleLoginItem:(id)sender; - (IBAction)toggleStatusItem:(id)sender; @end ================================================ FILE: Spectacle/Sources/SpectaclePreferencesController.m ================================================ #import "SpectaclePreferencesController.h" #import #import "SpectacleAccessibilityElement.h" #import "SpectacleDefaultShortcutHelpers.h" #import "SpectacleLoginItemHelper.h" #import "SpectacleRegisteredShortcutValidator.h" #import "SpectacleShortcut.h" #import "SpectacleShortcutManager.h" #import "SpectacleShortcutRecorder.h" #import "SpectacleShortcutStorage.h" #import "SpectacleShortcutValidation.h" #import "SpectacleUtilities.h" #import "SpectacleWindowPositionManager.h" @implementation SpectaclePreferencesController { SpectacleShortcutManager *_shortcutManager; SpectacleWindowPositionManager *_windowPositionManager; id _shortcutStorage; NSDictionary *_shortcutRecorders; } - (instancetype)initWithShortcutManager:(SpectacleShortcutManager *)shortcutManager windowPositionManager:(SpectacleWindowPositionManager *)windowPositionManager shortcutStorage:(id)shortcutStorage { if (self = [super initWithWindowNibName:@"SpectaclePreferencesWindow"]) { _shortcutManager = shortcutManager; _windowPositionManager = windowPositionManager; _shortcutStorage = shortcutStorage; } return self; } - (void)windowDidLoad { NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; NSInteger loginItemEnabledState = NSOffState; BOOL isStatusItemEnabled = [NSUserDefaults.standardUserDefaults boolForKey:@"StatusItemEnabled"]; _shortcutRecorders = @{ @"MoveToCenter": _moveToCenterShortcutRecorder, @"MoveToFullscreen": _moveToFullscreenShortcutRecorder, @"MoveToLeftHalf": _moveToLeftShortcutRecorder, @"MoveToRightHalf": _moveToRightShortcutRecorder, @"MoveToTopHalf": _moveToTopShortcutRecorder, @"MoveToBottomHalf": _moveToBottomShortcutRecorder, @"MoveToUpperLeft": _moveToUpperLeftShortcutRecorder, @"MoveToLowerLeft": _moveToLowerLeftShortcutRecorder, @"MoveToUpperRight": _moveToUpperRightShortcutRecorder, @"MoveToLowerRight": _moveToLowerRightShortcutRecorder, @"MoveToNextDisplay": _moveToNextDisplayShortcutRecorder, @"MoveToPreviousDisplay": _moveToPreviousDisplayShortcutRecorder, @"MoveToNextThird": _moveToNextThirdShortcutRecorder, @"MoveToPreviousThird": _moveToPreviousThirdShortcutRecorder, @"MakeLarger": _makeLargerShortcutRecorder, @"MakeSmaller": _makeSmallerShortcutRecorder, @"UndoLastMove": _undoLastMoveShortcutRecorder, @"RedoLastMove": _redoLastMoveShortcutRecorder, }; [self loadRegisteredShortcuts]; [notificationCenter addObserver:self selector:@selector(loadRegisteredShortcuts) name:@"SpectacleShortcutChangedNotification" object:nil]; [notificationCenter addObserver:self selector:@selector(loadRegisteredShortcuts) name:@"SpectacleRestoreDefaultShortcutsNotification" object:nil]; if ([SpectacleLoginItemHelper isLoginItemEnabledForBundle:NSBundle.mainBundle]) { loginItemEnabledState = NSOnState; } self.loginItemEnabled.state = loginItemEnabledState; [self.statusItemEnabled selectItemWithTag:isStatusItemEnabled ? 0 : 1]; self.window.title = [@"Spectacle " stringByAppendingString:SpectacleUtilities.applicationVersion]; self.footerView.wantsLayer = YES; } - (void)shortcutRecorder:(SpectacleShortcutRecorder *)shortcutRecorder didReceiveNewShortcut:(SpectacleShortcut *)shortcut { [_shortcutManager updateShortcut:[shortcut copyWithShortcutAction:^(SpectacleShortcut *shortcut) { [self->_windowPositionManager moveFrontmostWindowElement:[SpectacleAccessibilityElement frontmostWindowElement] action:shortcut.windowAction]; }]]; [[NSNotificationCenter defaultCenter] postNotificationName:@"SpectacleShortcutChangedNotification" object:self]; } - (void)shortcutRecorder:(SpectacleShortcutRecorder *)shortcutRecorder didClearExistingShortcut:(SpectacleShortcut *)shortcut { [_shortcutManager clearShortcut:shortcut]; [[NSNotificationCenter defaultCenter] postNotificationName:@"SpectacleShortcutChangedNotification" object:self]; } - (void)loadRegisteredShortcuts { for (NSString *shortcutName in _shortcutRecorders.allKeys) { SpectacleShortcutRecorder *shortcutRecorder = _shortcutRecorders[shortcutName]; SpectacleShortcut *shortcut = [_shortcutManager shortcutForShortcutName:shortcutName]; shortcutRecorder.shortcutName = shortcutName; shortcutRecorder.shortcut = shortcut; shortcutRecorder.delegate = self; shortcutRecorder.shortcutValidation = [[SpectacleShortcutValidation alloc] initWithShortcutManager:_shortcutManager validators:@[ [SpectacleRegisteredShortcutValidator new], ]]; } } - (IBAction)swapFooterViews:(id)sender { CATransition *transition = [CATransition animation]; NSView *currentFooterView = self.footerView.subviews[0]; NSView *nextFooterView = nil; transition.type = kCATransitionPush; if (currentFooterView == self.shortcutModifierLegendFooterView) { nextFooterView = self.optionsFooterView; transition.subtype = kCATransitionFromRight; } else { nextFooterView = self.shortcutModifierLegendFooterView; transition.subtype = kCATransitionFromLeft; } self.footerView.animations = @{@"subviews": transition}; [self.footerView.animator replaceSubview:currentFooterView with:nextFooterView]; } - (IBAction)restoreDefaults:(id)sender { [SpectacleUtilities displayRestoreDefaultsAlertWithConfirmationCallback:^() { NSArray *shortcuts = SpectacleDefaultShortcutsWithAction(^(SpectacleShortcut *shortcut) { [self->_windowPositionManager moveFrontmostWindowElement:[SpectacleAccessibilityElement frontmostWindowElement] action:shortcut.windowAction]; }); [self->_shortcutManager updateShortcuts:shortcuts]; [[NSNotificationCenter defaultCenter] postNotificationName:@"SpectacleRestoreDefaultShortcutsNotification" object:self]; }]; } - (IBAction)toggleLoginItem:(id)sender { NSBundle *applicationBundle = NSBundle.mainBundle; if (self.loginItemEnabled.state == NSOnState) { [SpectacleLoginItemHelper enableLoginItemForBundle:applicationBundle]; } else { [SpectacleLoginItemHelper disableLoginItemForBundle:applicationBundle]; } } - (IBAction)toggleStatusItem:(id)sender { NSString *notificationName = @"SpectacleStatusItemEnabledNotification"; BOOL isStatusItemEnabled = YES; __block BOOL statusItemStateChanged = YES; NSUserDefaults *userDefaults = NSUserDefaults.standardUserDefaults; if ([userDefaults boolForKey:@"StatusItemEnabled"] == ([[sender selectedItem] tag] == 0)) { return; } if ([sender selectedItem].tag != 0) { notificationName = @"SpectacleStatusItemDisabledNotification"; isStatusItemEnabled = NO; if (![userDefaults boolForKey:@"BackgroundAlertSuppressed"]) { [SpectacleUtilities displayRunningInBackgroundAlertWithCallback:^(BOOL isConfirmed, BOOL isSuppressed) { if (!isConfirmed) { statusItemStateChanged = NO; [sender selectItemWithTag:0]; } [userDefaults setBool:isSuppressed forKey:@"BackgroundAlertSuppressed"]; }]; } } if (statusItemStateChanged) { [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:self]; [userDefaults setBool:isStatusItemEnabled forKey:@"StatusItemEnabled"]; } } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } @end ================================================ FILE: Spectacle/Sources/SpectacleQuantizedWindowMover.h ================================================ #import #import "SpectacleWindowMover.h" @interface SpectacleQuantizedWindowMover : NSObject - (instancetype)initWithInnerWindowMover:(id)innerWindowMover NS_DESIGNATED_INITIALIZER; @end ================================================ FILE: Spectacle/Sources/SpectacleQuantizedWindowMover.m ================================================ #import "SpectacleQuantizedWindowMover.h" #import "SpectacleAccessibilityElement.h" @implementation SpectacleQuantizedWindowMover { id _innerWindowMover; } - (instancetype)initWithInnerWindowMover:(id)innerWindowMover { if (self = [super init]) { _innerWindowMover = innerWindowMover; } return self; } - (instancetype)init { return [self initWithInnerWindowMover:nil]; } + (instancetype)newWithInnerWindowMover:(id)innerWindowMover { return [[self alloc] initWithInnerWindowMover:innerWindowMover]; } - (void)moveWindowRect:(CGRect)windowRect frameOfScreen:(CGRect)frameOfScreen visibleFrameOfScreen:(CGRect)visibleFrameOfScreen frontmostWindowElement:(SpectacleAccessibilityElement *)frontmostWindowElement action:(SpectacleWindowAction *)action { CGRect movedWindowRect = [frontmostWindowElement rectOfElement]; if (!CGRectEqualToRect(movedWindowRect, windowRect)) { CGRect adjustedWindowRect = windowRect; while (movedWindowRect.size.width > windowRect.size.width || movedWindowRect.size.height > windowRect.size.height) { if (movedWindowRect.size.width > windowRect.size.width) { adjustedWindowRect.size.width -= 2; } if (movedWindowRect.size.height > windowRect.size.height) { adjustedWindowRect.size.height -= 2; } if (adjustedWindowRect.size.width < windowRect.size.width * 0.85f || adjustedWindowRect.size.height < windowRect.size.height * 0.85f) { break; } [frontmostWindowElement setRectOfElement:adjustedWindowRect]; movedWindowRect = [frontmostWindowElement rectOfElement]; } adjustedWindowRect.origin.x += floor((windowRect.size.width - movedWindowRect.size.width) / 2.0f); adjustedWindowRect.origin.y += floor((windowRect.size.height - movedWindowRect.size.height) / 2.0f); [frontmostWindowElement setRectOfElement:adjustedWindowRect]; } [_innerWindowMover moveWindowRect:windowRect frameOfScreen:frameOfScreen visibleFrameOfScreen:visibleFrameOfScreen frontmostWindowElement:frontmostWindowElement action:action]; } @end ================================================ FILE: Spectacle/Sources/SpectacleRegisteredShortcutValidator.h ================================================ #import #import "SpectacleShortcutValidator.h" @interface SpectacleRegisteredShortcutValidator : NSObject @end ================================================ FILE: Spectacle/Sources/SpectacleRegisteredShortcutValidator.m ================================================ #import "SpectacleRegisteredShortcutValidator.h" #import "SpectacleShortcutManager.h" @implementation SpectacleRegisteredShortcutValidator - (BOOL)isShortcutValid:(SpectacleShortcut *)shortcut shortcutManager:(SpectacleShortcutManager *)shortcutManager { return ![shortcutManager doesShortcutExist:shortcut]; } @end ================================================ FILE: Spectacle/Sources/SpectacleScreenDetectionResult.h ================================================ #import #import "SpectacleMacros.h" @class NSScreen; @interface SpectacleScreenDetectionResult : NSObject @property (nonatomic, readonly, strong) NSScreen *sourceScreen; @property (nonatomic, readonly, strong) NSScreen *destinationScreen; - (instancetype)initWithSourceScreen:(NSScreen *)sourceScreen destinationScreen:(NSScreen *)destinationScreen NS_DESIGNATED_INITIALIZER; + (instancetype)resultWithSourceScreen:(NSScreen *)sourceScreen destinationScreen:(NSScreen *)destinationScreen; SPECTACLE_INIT_AND_NEW_UNAVAILABLE @end ================================================ FILE: Spectacle/Sources/SpectacleScreenDetectionResult.m ================================================ #import "SpectacleScreenDetectionResult.h" @implementation SpectacleScreenDetectionResult @synthesize sourceScreen = _sourceScreen; @synthesize destinationScreen = _destinationScreen; - (instancetype)initWithSourceScreen:(NSScreen *)sourceScreen destinationScreen:(NSScreen *)destinationScreen { if (self = [super init]) { _sourceScreen = sourceScreen; _destinationScreen = destinationScreen; } return self; } + (instancetype)resultWithSourceScreen:(NSScreen *)sourceScreen destinationScreen:(NSScreen *)destinationScreen { return [[SpectacleScreenDetectionResult alloc] initWithSourceScreen:sourceScreen destinationScreen:destinationScreen]; } @end ================================================ FILE: Spectacle/Sources/SpectacleScreenDetector.h ================================================ #import #import "SpectacleWindowAction.h" @class SpectacleAccessibilityElement; @class SpectacleScreenDetectionResult; @interface SpectacleScreenDetector : NSObject - (SpectacleScreenDetectionResult *)screenWithAction:(SpectacleWindowAction *)action frontmostWindowElement:(SpectacleAccessibilityElement *)frontmostWindowElement screens:(NSArray *)screens mainScreen:(NSScreen *)mainScreen; @end ================================================ FILE: Spectacle/Sources/SpectacleScreenDetector.m ================================================ #import "SpectacleScreenDetector.h" #import "SpectacleAccessibilityElement.h" #import "SpectacleScreenDetectionResult.h" @implementation SpectacleScreenDetector - (SpectacleScreenDetectionResult *)screenWithAction:(SpectacleWindowAction *)action frontmostWindowElement:(SpectacleAccessibilityElement *)frontmostWindowElement screens:(NSArray *)screens mainScreen:(NSScreen *)mainScreen { NSArray *screensInConsistentOrder = [self screensInConsistentOrder:screens]; NSScreen *sourceScreen = [self screenContainingRect:[frontmostWindowElement rectOfElement] screens:screensInConsistentOrder mainScreen:mainScreen]; NSScreen *destinationScreen = sourceScreen; if (SpectacleIsMovingToDisplayWindowAction(action)) { destinationScreen = [self nextOrPreviousScreenToFrameOfScreen:NSRectToCGRect([sourceScreen frame]) inDirectionOfAction:action screens:screensInConsistentOrder]; } return [SpectacleScreenDetectionResult resultWithSourceScreen:sourceScreen destinationScreen:destinationScreen]; } - (NSScreen *)screenContainingRect:(CGRect)rect screens:(NSArray *)screens mainScreen:(NSScreen *)mainScreen { CGFloat largestPercentageOfRectWithinFrameOfScreen = 0.0f; NSScreen *result = mainScreen; for (NSScreen *currentScreen in screens) { CGRect currentFrameOfScreen = NSRectToCGRect(currentScreen.frame); CGRect normalizedRect = [SpectacleAccessibilityElement normalizeCoordinatesOfRect:rect frameOfScreen:currentFrameOfScreen]; if (CGRectContainsRect(currentFrameOfScreen, normalizedRect)) { result = currentScreen; break; } CGFloat percentageOfRectWithinCurrentFrameOfScreen = [self percentageOfRect:normalizedRect withinFrameOfScreen:currentFrameOfScreen]; if (percentageOfRectWithinCurrentFrameOfScreen > largestPercentageOfRectWithinFrameOfScreen) { largestPercentageOfRectWithinFrameOfScreen = percentageOfRectWithinCurrentFrameOfScreen; result = currentScreen; } } return result; } - (CGFloat)percentageOfRect:(CGRect)rect withinFrameOfScreen:(CGRect)frameOfScreen { CGRect intersectionOfRectAndFrameOfScreen = CGRectIntersection(rect, frameOfScreen); CGFloat result = 0.0f; if (!CGRectIsNull(intersectionOfRectAndFrameOfScreen)) { result = computeAreaOfRect(intersectionOfRectAndFrameOfScreen) / computeAreaOfRect(rect); } return result; } - (NSScreen *)nextOrPreviousScreenToFrameOfScreen:(CGRect)frameOfScreen inDirectionOfAction:(SpectacleWindowAction *)action screens:(NSArray *)screens { NSScreen *result = nil; if (screens.count <= 1) { return result; } for (NSInteger i = 0; i < screens.count; i++) { NSScreen *currentScreen = screens[i]; CGRect currentFrameOfScreen = NSRectToCGRect(currentScreen.frame); NSInteger nextOrPreviousIndex = i; if (!CGRectEqualToRect(currentFrameOfScreen, frameOfScreen)) { continue; } if (SpectacleIsNextDisplayWindowAction(action)) { nextOrPreviousIndex++; } else if (SpectacleIsPreviousDisplayWindowAction(action)) { nextOrPreviousIndex--; } if (nextOrPreviousIndex < 0) { nextOrPreviousIndex = screens.count - 1; } else if (nextOrPreviousIndex >= screens.count) { nextOrPreviousIndex = 0; } result = screens[nextOrPreviousIndex]; break; } return result; } - (NSArray *)screensInConsistentOrder:(NSArray *)screens { return [[screens sortedArrayWithOptions:NSSortStable usingComparator:^(NSScreen *screenOne, NSScreen *screenTwo) { if (CGPointEqualToPoint(screenOne.frame.origin, CGPointMake(0, 0))) { return NSOrderedAscending; } else if (CGPointEqualToPoint(screenTwo.frame.origin, CGPointMake(0, 0))) { return NSOrderedDescending; } return (NSComparisonResult)(screenTwo.frame.origin.y - screenOne.frame.origin.y); }] sortedArrayWithOptions:NSSortStable usingComparator:^(NSScreen *screenOne, NSScreen *screenTwo) { if (CGPointEqualToPoint(screenOne.frame.origin, CGPointMake(0, 0))) { return NSOrderedAscending; } else if (CGPointEqualToPoint(screenTwo.frame.origin, CGPointMake(0, 0))) { return NSOrderedDescending; } return (NSComparisonResult)(screenTwo.frame.origin.x - screenOne.frame.origin.x); }]; } static CGFloat computeAreaOfRect(CGRect rect) { return rect.size.width * rect.size.height; } @end ================================================ FILE: Spectacle/Sources/SpectacleShortcut.h ================================================ #import #import "SpectacleMacros.h" #import "SpectacleWindowAction.h" @class SpectacleShortcut; typedef void(^SpectacleShortcutAction)(SpectacleShortcut *); @interface SpectacleShortcut : NSObject @property (nonatomic, readonly, copy) NSString *shortcutName; @property (nonatomic, readonly, assign) NSInteger shortcutKeyCode; @property (nonatomic, readonly, assign) NSUInteger shortcutModifiers; @property (nonatomic, readonly, strong) SpectacleShortcutAction shortcutAction; @property (nonatomic, readonly, copy) NSString *shortcutKeyBinding; @property (nonatomic, readonly, strong) SpectacleWindowAction *windowAction; - (instancetype)initWithShortcutName:(NSString *)shortcutName shortcutKeyBinding:(NSString *)shortcutKeyBinding; - (instancetype)initWithShortcutName:(NSString *)shortcutName shortcutKeyBinding:(NSString *)shortcutKeyBinding shortcutAction:(SpectacleShortcutAction)shortcutAction; - (instancetype)initWithShortcutName:(NSString *)shortcutName shortcutKeyCode:(NSInteger)shortcutKeyCode shortcutModifiers:(NSUInteger)shortcutModifiers; - (instancetype)initWithShortcutName:(NSString *)shortcutName shortcutKeyCode:(NSInteger)shortcutKeyCode shortcutModifiers:(NSUInteger)shortcutModifiers shortcutAction:(SpectacleShortcutAction)shortcutAction NS_DESIGNATED_INITIALIZER; SPECTACLE_INIT_AND_NEW_UNAVAILABLE - (instancetype)copyWithShortcutAction:(SpectacleShortcutAction)shortcutAction; - (void)triggerShortcutAction; - (BOOL)isClearedShortcut; - (NSString *)displayString; - (BOOL)isEqual:(id)object; - (BOOL)isEqualToShortcut:(SpectacleShortcut *)shortcut; - (BOOL)containsModifiers:(NSUInteger)modifiers; + (BOOL)validCocoaModifiers:(NSUInteger)modifiers; @end ================================================ FILE: Spectacle/Sources/SpectacleShortcut.m ================================================ #import "SpectacleShortcut.h" #import #import "SpectacleShortcutKeyBindings.h" #import "SpectacleShortcutTranslations.h" @implementation SpectacleShortcut - (instancetype)initWithShortcutName:(NSString *)shortcutName shortcutKeyBinding:(NSString *)shortcutKeyBinding { return [self initWithShortcutName:shortcutName shortcutKeyBinding:shortcutKeyBinding shortcutAction:nil]; } - (instancetype)initWithShortcutName:(NSString *)shortcutName shortcutKeyBinding:(NSString *)shortcutKeyBinding shortcutAction:(SpectacleShortcutAction)shortcutAction { NSNumber *shortcutKeyCode = SpectacleConvertShortcutKeyBindingToKeyCode(shortcutKeyBinding); return [self initWithShortcutName:shortcutName shortcutKeyCode:shortcutKeyCode ? [shortcutKeyCode integerValue] : -1 shortcutModifiers:[SpectacleConvertShortcutKeyBindingToModifiers(shortcutKeyBinding) unsignedIntegerValue] shortcutAction:shortcutAction]; } - (instancetype)initWithShortcutName:(NSString *)shortcutName shortcutKeyCode:(NSInteger)shortcutKeyCode shortcutModifiers:(NSUInteger)shortcutModifiers { return [self initWithShortcutName:shortcutName shortcutKeyCode:shortcutKeyCode shortcutModifiers:shortcutModifiers shortcutAction:nil]; } - (instancetype)initWithShortcutName:(NSString *)shortcutName shortcutKeyCode:(NSInteger)shortcutKeyCode shortcutModifiers:(NSUInteger)shortcutModifiers shortcutAction:(SpectacleShortcutAction)shortcutAction { if (self = [super init]) { _shortcutName = shortcutName; _shortcutKeyCode = shortcutKeyCode; _shortcutModifiers = SpectacleConvertModifiersToCarbonIfNecessary(shortcutModifiers); _shortcutAction = shortcutAction; } return self; } - (instancetype)copyWithShortcutAction:(SpectacleShortcutAction)shortcutAction { return [[SpectacleShortcut alloc] initWithShortcutName:_shortcutName shortcutKeyCode:_shortcutKeyCode shortcutModifiers:_shortcutModifiers shortcutAction:shortcutAction]; } - (instancetype)initWithCoder:(NSCoder *)coder { return [self initWithShortcutName:[coder decodeObjectForKey:@"name"] shortcutKeyCode:[coder decodeIntegerForKey:@"keyCode"] shortcutModifiers:[coder decodeIntegerForKey:@"modifiers"]]; } - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:_shortcutName forKey:@"name"]; [coder encodeInteger:_shortcutKeyCode forKey:@"keyCode"]; [coder encodeInteger:_shortcutModifiers forKey:@"modifiers"]; } - (NSString *)shortcutKeyBinding { return SpectacleConvertShortcutToKeyBinding(self); } - (SpectacleWindowAction *)windowAction { NSString *name = _shortcutName; SpectacleWindowAction *windowAction = kSpectacleWindowActionNone; if ([name isEqualToString:@"MoveToCenter"]) { windowAction = kSpectacleWindowActionCenter; } else if ([name isEqualToString:@"MoveToFullscreen"]) { windowAction = kSpectacleWindowActionFullscreen; } else if ([name isEqualToString:@"MoveToLeftHalf"]) { windowAction = kSpectacleWindowActionLeftHalf; } else if ([name isEqualToString:@"MoveToRightHalf"]) { windowAction = kSpectacleWindowActionRightHalf; } else if ([name isEqualToString:@"MoveToTopHalf"]) { windowAction = kSpectacleWindowActionTopHalf; } else if ([name isEqualToString:@"MoveToBottomHalf"]) { windowAction = kSpectacleWindowActionBottomHalf; } else if ([name isEqualToString:@"MoveToUpperLeft"]) { windowAction = kSpectacleWindowActionUpperLeft; } else if ([name isEqualToString:@"MoveToLowerLeft"]) { windowAction = kSpectacleWindowActionLowerLeft; } else if ([name isEqualToString:@"MoveToUpperRight"]) { windowAction = kSpectacleWindowActionUpperRight; } else if ([name isEqualToString:@"MoveToLowerRight"]) { windowAction = kSpectacleWindowActionLowerRight; } else if ([name isEqualToString:@"MoveToNextDisplay"]) { windowAction = kSpectacleWindowActionNextDisplay; } else if ([name isEqualToString:@"MoveToPreviousDisplay"]) { windowAction = kSpectacleWindowActionPreviousDisplay; } else if ([name isEqualToString:@"MoveToNextThird"]) { windowAction = kSpectacleWindowActionNextThird; } else if ([name isEqualToString:@"MoveToPreviousThird"]) { windowAction = kSpectacleWindowActionPreviousThird; } else if ([name isEqualToString:@"MakeLarger"]) { windowAction = kSpectacleWindowActionLarger; } else if ([name isEqualToString:@"MakeSmaller"]) { windowAction = kSpectacleWindowActionSmaller; } else if ([name isEqualToString:@"UndoLastMove"]) { windowAction = kSpectacleWindowActionUndo; } else if ([name isEqualToString:@"RedoLastMove"]) { windowAction = kSpectacleWindowActionRedo; } return windowAction; } - (void)triggerShortcutAction { if (_shortcutAction) { _shortcutAction(self); } } - (BOOL)isClearedShortcut { return _shortcutKeyCode == -1 || (_shortcutKeyCode == 0 && _shortcutModifiers == 0); } - (NSString *)displayString { return SpectacleTranslateShortcut(self); } - (BOOL)isEqual:(id)object { if (object == self) { return YES; } if (!object || ![object isKindOfClass:[self class]]) { return NO; } return [self isEqualToShortcut:object]; } - (BOOL)isEqualToShortcut:(SpectacleShortcut *)shortcut { if (shortcut == self) { return YES; } if (shortcut.shortcutKeyCode != _shortcutKeyCode) { return NO; } if (shortcut.shortcutModifiers != _shortcutModifiers) { return NO; } return YES; } - (BOOL)containsModifiers:(NSUInteger)modifiers { return _shortcutModifiers == SpectacleConvertModifiersToCarbonIfNecessary(modifiers); } + (BOOL)validCocoaModifiers:(NSUInteger)modifiers { return ((modifiers & NSAlternateKeyMask) || (modifiers & NSCommandKeyMask) || (modifiers & NSControlKeyMask) || (modifiers & NSShiftKeyMask)); } @end ================================================ FILE: Spectacle/Sources/SpectacleShortcutHolder.h ================================================ #import #import "SpectacleMacros.h" @class SpectacleShortcut; @interface SpectacleShortcutHolder : NSObject @property (nonatomic, readonly, assign) EventHotKeyID shortcutID; @property (nonatomic, readonly, strong) SpectacleShortcut *shortcut; @property (nonatomic, readonly, assign) EventHotKeyRef shortcutRef; - (instancetype)initWithShortcutID:(EventHotKeyID)shortcutID; - (instancetype)initWithShortcutID:(EventHotKeyID)shortcutID shortcut:(SpectacleShortcut *)shortcut; - (instancetype)initWithShortcutID:(EventHotKeyID)shortcutID shortcut:(SpectacleShortcut *)shortcut shortcutRef:(EventHotKeyRef)shortcutRef NS_DESIGNATED_INITIALIZER; SPECTACLE_INIT_AND_NEW_UNAVAILABLE - (instancetype)copyWithShortcut:(SpectacleShortcut *)shortcut; - (instancetype)copyWithShortcutRef:(EventHotKeyRef)shortcutRef; - (instancetype)copyWithClearedShortcut; @end ================================================ FILE: Spectacle/Sources/SpectacleShortcutHolder.m ================================================ #import "SpectacleShortcutHolder.h" #import "SpectacleShortcut.h" @implementation SpectacleShortcutHolder - (instancetype)initWithShortcutID:(EventHotKeyID)shortcutID { return [self initWithShortcutID:shortcutID shortcut:nil shortcutRef:nil]; } - (instancetype)initWithShortcutID:(EventHotKeyID)shortcutID shortcut:(SpectacleShortcut *)shortcut { return [self initWithShortcutID:shortcutID shortcut:shortcut shortcutRef:nil]; } - (instancetype)initWithShortcutID:(EventHotKeyID)shortcutID shortcut:(SpectacleShortcut *)shortcut shortcutRef:(EventHotKeyRef)shortcutRef { if (self = [super init]) { _shortcutID = shortcutID; _shortcut = shortcut; _shortcutRef = shortcutRef; } return self; } - (instancetype)copyWithShortcut:(SpectacleShortcut *)shortcut { return [[SpectacleShortcutHolder alloc] initWithShortcutID:_shortcutID shortcut:shortcut shortcutRef:_shortcutRef]; } - (instancetype)copyWithShortcutRef:(EventHotKeyRef)shortcutRef { return [[SpectacleShortcutHolder alloc] initWithShortcutID:_shortcutID shortcut:_shortcut shortcutRef:shortcutRef]; } - (instancetype)copyWithClearedShortcut { SpectacleShortcut *clearedShortcut = [[SpectacleShortcut alloc] initWithShortcutName:_shortcut.shortcutName shortcutKeyCode:-1 shortcutModifiers:0]; return [[SpectacleShortcutHolder alloc] initWithShortcutID:_shortcutID shortcut:clearedShortcut shortcutRef:nil]; } @end ================================================ FILE: Spectacle/Sources/SpectacleShortcutJSONStorage.h ================================================ #import #import "SpectacleShortcutStorageMigrationDestination.h" @interface SpectacleShortcutJSONStorage : NSObject @end ================================================ FILE: Spectacle/Sources/SpectacleShortcutJSONStorage.m ================================================ #import "SpectacleShortcutJSONStorage.h" @implementation SpectacleShortcutJSONStorage - (NSArray *)loadShortcutsWithAction:(SpectacleShortcutAction)action { NSError *error; NSURL *shortcutsFileURL = findShortcutsFileURL(&error); if (!shortcutsFileURL) { NSLog(@"Unable to locate the shortcuts file: %@", error.localizedDescription); return nil; } NSData *content = [NSData dataWithContentsOfURL:shortcutsFileURL]; NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:content options:0 error:&error]; if (!jsonArray) { NSLog(@"Deserializing shortcuts failed: %@", error.localizedDescription); return nil; } return shortcutsFromJsonObject(jsonArray, action); } - (void)storeShortcuts:(NSArray *)shortcuts { NSError *error; NSURL *shortcutsFileURL = findShortcutsFileURL(&error); if (!shortcutsFileURL) { NSLog(@"Unable to locate the shortcuts file: %@", error.localizedDescription); return; } NSData *contents = [NSJSONSerialization dataWithJSONObject:jsonObjectFromShortcuts(shortcuts) options:NSJSONWritingPrettyPrinted error:&error]; if (![contents writeToURL:shortcutsFileURL atomically:YES]) { NSLog(@"Unable to store shortcuts at location: %@", [shortcutsFileURL path]); } } - (BOOL)isMigrationNeeded { NSError *error; NSURL *shortcutsFileURL = findShortcutsFileURL(&error); if (!shortcutsFileURL) { NSLog(@"Unable to locate the shortcuts file: %@", error.localizedDescription); return YES; } return ![[NSFileManager defaultManager] fileExistsAtPath:[shortcutsFileURL path]]; } static NSURL *findShortcutsFileURL(NSError **error) { NSURL *shortcutsFileURL = [findOrCreateSpectacleDirectory(error) URLByAppendingPathComponent:@"Shortcuts.json"]; return shortcutsFileURL.URLByResolvingSymlinksInPath; } static NSURL *findOrCreateSpectacleDirectory(NSError **error) { NSURL *applicationSupportDirectory = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:error]; if (!applicationSupportDirectory) { return nil; } NSURL *spectacleDirectory = [applicationSupportDirectory URLByAppendingPathComponent:@"Spectacle"]; BOOL success = [[NSFileManager defaultManager] createDirectoryAtURL:spectacleDirectory withIntermediateDirectories:YES attributes:nil error:error]; if (!success) { return nil; } return spectacleDirectory; } static NSArray *jsonObjectFromShortcuts(NSArray *shortcuts) { NSMutableArray *jsonArray = [NSMutableArray new]; for (SpectacleShortcut *shortcut in shortcuts) { [jsonArray addObject:@{ @"shortcut_name" : shortcut.shortcutName, @"shortcut_key_binding" : shortcut.shortcutKeyBinding ?: [NSNull null], }]; } return jsonArray; } static NSArray *shortcutsFromJsonObject(NSArray *jsonArray, SpectacleShortcutAction action) { NSMutableArray *shortcuts = [NSMutableArray new]; for (NSDictionary *jsonObject in jsonArray) { NSString *shortcutName = jsonObject[@"shortcut_name"]; NSString *shortcutKeyBinding = jsonObject[@"shortcut_key_binding"]; [shortcuts addObject:[[SpectacleShortcut alloc] initWithShortcutName:shortcutName shortcutKeyBinding:[shortcutKeyBinding isKindOfClass:[NSNull class]] ? nil : shortcutKeyBinding shortcutAction:action]]; } return shortcuts; } @end ================================================ FILE: Spectacle/Sources/SpectacleShortcutKeyBindings.h ================================================ #import @class SpectacleShortcut; NSNumber *SpectacleConvertShortcutKeyBindingToKeyCode(NSString *keyBinding); NSNumber *SpectacleConvertShortcutKeyBindingToModifiers(NSString *keyBinding); NSString *SpectacleConvertShortcutToKeyBinding(SpectacleShortcut *shortcut); ================================================ FILE: Spectacle/Sources/SpectacleShortcutKeyBindings.m ================================================ #import "SpectacleShortcutKeyBindings.h" #import #import "SpectacleShortcut.h" #import "SpectacleShortcutTranslations.h" static NSDictionary *keyCodeToKeyCodeComponentConversionTable(void); static NSDictionary *keyCodeToNamedKeyCodeComponentConversionTable(void); static NSDictionary *namedKeyCodeComponentToKeyCodeConversionTable(void); static NSDictionary *modiferComponentToCarbonModifierConversionTable(void); static NSDictionary *keyCodeComponentToKeyCodeConversionTable(void); static NSString *keyCodeComponentForKeyCode(NSInteger keyCode); static NSArray *modifierComponentsForModifiers(NSUInteger modifiers); NSNumber *SpectacleConvertShortcutKeyBindingToKeyCode(NSString *keyBinding) { NSString *keyCodeComponent = [[keyBinding componentsSeparatedByString:@"+"] lastObject]; NSString *sanitizedKeyCodeComponent = [keyCodeComponent stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; if (sanitizedKeyCodeComponent.length == 0) { return nil; } NSString *normalizedKeyCodeComponent = [sanitizedKeyCodeComponent uppercaseString]; NSNumber *namedBindingKeyCode = namedKeyCodeComponentToKeyCodeConversionTable()[normalizedKeyCodeComponent]; if (namedBindingKeyCode) { return namedBindingKeyCode; } return keyCodeComponentToKeyCodeConversionTable()[normalizedKeyCodeComponent]; } NSNumber *SpectacleConvertShortcutKeyBindingToModifiers(NSString *keyBinding) { NSArray *keyBindingComponents = [keyBinding componentsSeparatedByString:@"+"]; if (keyBindingComponents.count < 2) { return nil; } NSRange rangeOfModifierComponents = NSMakeRange(0, keyBindingComponents.count - 1); NSArray *modifierComponents = [keyBindingComponents subarrayWithRange:rangeOfModifierComponents]; NSNumber *modifiers = @0; for (NSString *modifierComponent in modifierComponents) { NSString *sanitizedModifierComponent = [modifierComponent stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; NSString *normalizedModifierComponent = [sanitizedModifierComponent uppercaseString]; NSNumber *modifier = modiferComponentToCarbonModifierConversionTable()[normalizedModifierComponent]; if (modifier) { modifiers = @([modifiers unsignedIntegerValue] | [modifier unsignedIntegerValue]); } } return modifiers; } NSString *SpectacleConvertShortcutToKeyBinding(SpectacleShortcut *shortcut) { NSString *keyCodeComponent = [keyCodeComponentForKeyCode(shortcut.shortcutKeyCode) lowercaseString]; NSArray *modifierComponents = modifierComponentsForModifiers(shortcut.shortcutModifiers); if ([shortcut isClearedShortcut] || !keyCodeComponent) { return nil; } return [[modifierComponents arrayByAddingObject:keyCodeComponent] componentsJoinedByString:@"+"]; } static NSDictionary *keyCodeToKeyCodeComponentConversionTable(void) { static dispatch_once_t onceToken; static NSDictionary *conversionTable; dispatch_once(&onceToken, ^{ conversionTable = @{ @(kVK_ANSI_0): @"0", @(kVK_ANSI_1): @"1", @(kVK_ANSI_2): @"2", @(kVK_ANSI_3): @"3", @(kVK_ANSI_4): @"4", @(kVK_ANSI_5): @"5", @(kVK_ANSI_6): @"6", @(kVK_ANSI_7): @"7", @(kVK_ANSI_8): @"8", @(kVK_ANSI_9): @"9", @(kVK_ANSI_A): @"A", @(kVK_ANSI_B): @"B", @(kVK_ANSI_C): @"C", @(kVK_ANSI_D): @"D", @(kVK_ANSI_E): @"E", @(kVK_ANSI_F): @"F", @(kVK_ANSI_G): @"G", @(kVK_ANSI_H): @"H", @(kVK_ANSI_I): @"I", @(kVK_ANSI_J): @"J", @(kVK_ANSI_K): @"K", @(kVK_ANSI_L): @"L", @(kVK_ANSI_M): @"M", @(kVK_ANSI_N): @"N", @(kVK_ANSI_O): @"O", @(kVK_ANSI_P): @"P", @(kVK_ANSI_Q): @"Q", @(kVK_ANSI_R): @"R", @(kVK_ANSI_S): @"S", @(kVK_ANSI_T): @"T", @(kVK_ANSI_U): @"U", @(kVK_ANSI_V): @"V", @(kVK_ANSI_W): @"W", @(kVK_ANSI_X): @"X", @(kVK_ANSI_Y): @"Y", @(kVK_ANSI_Z): @"Z", @(kVK_ANSI_Equal): @"=", @(kVK_ANSI_Minus): @"-", @(kVK_ANSI_RightBracket): @"]", @(kVK_ANSI_LeftBracket): @"[", @(kVK_ANSI_Quote): @"'", @(kVK_ANSI_Semicolon): @";", @(kVK_ANSI_Backslash): @"\\", @(kVK_ANSI_Comma): @",", @(kVK_ANSI_Slash): @"/", @(kVK_ANSI_Period): @".", @(kVK_ANSI_Grave): @"`", }; }); return conversionTable; } static NSDictionary *keyCodeToNamedKeyCodeComponentConversionTable(void) { static dispatch_once_t onceToken; static NSDictionary *conversionTable; dispatch_once(&onceToken, ^{ NSMutableDictionary *generatedConversionTable = [NSMutableDictionary new]; for (NSString *namedKeyCodeComponent in [namedKeyCodeComponentToKeyCodeConversionTable() allKeys]) { generatedConversionTable[namedKeyCodeComponentToKeyCodeConversionTable()[namedKeyCodeComponent]] = namedKeyCodeComponent; } conversionTable = generatedConversionTable; }); return conversionTable; } static NSDictionary *namedKeyCodeComponentToKeyCodeConversionTable(void) { static dispatch_once_t onceToken; static NSDictionary *conversionTable; dispatch_once(&onceToken, ^{ conversionTable = @{ @"F1": @(kVK_F1), @"F2": @(kVK_F2), @"F3": @(kVK_F3), @"F4": @(kVK_F4), @"F5": @(kVK_F5), @"F6": @(kVK_F6), @"F7": @(kVK_F7), @"F8": @(kVK_F8), @"F9": @(kVK_F9), @"F10": @(kVK_F10), @"F11": @(kVK_F11), @"F12": @(kVK_F12), @"F13": @(kVK_F13), @"F14": @(kVK_F14), @"F15": @(kVK_F15), @"F16": @(kVK_F16), @"F17": @(kVK_F17), @"F18": @(kVK_F18), @"F19": @(kVK_F19), @"F20": @(kVK_F20), @"KEYPADDECIMAL": @(kVK_ANSI_KeypadDecimal), @"KEYPADMULTIPLY": @(kVK_ANSI_KeypadMultiply), @"KEYPADPLUS": @(kVK_ANSI_KeypadPlus), @"KEYPADCLEAR": @(kVK_ANSI_KeypadClear), @"KEYPADDIVIDE": @(kVK_ANSI_KeypadDivide), @"KEYPADENTER": @(kVK_ANSI_KeypadEnter), @"KEYPADMINUS": @(kVK_ANSI_KeypadMinus), @"KEYPADEQUALS": @(kVK_ANSI_KeypadEquals), @"KEYPAD0": @(kVK_ANSI_Keypad0), @"KEYPAD1": @(kVK_ANSI_Keypad1), @"KEYPAD2": @(kVK_ANSI_Keypad2), @"KEYPAD3": @(kVK_ANSI_Keypad3), @"KEYPAD4": @(kVK_ANSI_Keypad4), @"KEYPAD5": @(kVK_ANSI_Keypad5), @"KEYPAD6": @(kVK_ANSI_Keypad6), @"KEYPAD7": @(kVK_ANSI_Keypad7), @"KEYPAD8": @(kVK_ANSI_Keypad8), @"KEYPAD9": @(kVK_ANSI_Keypad9), @"RETURN": @(kVK_Return), @"TAB": @(kVK_Tab), @"SPACE": @(kVK_Space), @"DELETE": @(kVK_Delete), @"ESCAPE": @(kVK_Escape), @"COMMAND": @(kVK_Command), @"SHIFT": @(kVK_Shift), @"CAPSLOCK": @(kVK_CapsLock), @"OPTION": @(kVK_Option), @"CONTROL": @(kVK_Control), @"RIGHTSHIFT": @(kVK_RightShift), @"RIGHTOPTION": @(kVK_RightOption), @"RIGHTCONTROL": @(kVK_RightControl), @"FUNCTION": @(kVK_Function), @"VOLUMEUP": @(kVK_VolumeUp), @"VOLUMEDOWN": @(kVK_VolumeDown), @"MUTE": @(kVK_Mute), @"HELP": @(kVK_Help), @"HOME": @(kVK_Home), @"PAGEUP": @(kVK_PageUp), @"FORWARDDELETE": @(kVK_ForwardDelete), @"END": @(kVK_End), @"PAGEDOWN": @(kVK_PageDown), @"LEFT": @(kVK_LeftArrow), @"RIGHT": @(kVK_RightArrow), @"DOWN": @(kVK_DownArrow), @"UP": @(kVK_UpArrow), }; }); return conversionTable; } static NSDictionary *modiferComponentToCarbonModifierConversionTable(void) { static dispatch_once_t onceToken; static NSDictionary *conversionTable; dispatch_once(&onceToken, ^{ conversionTable = @{ @"COMMAND": @(cmdKey), @"CMD": @(cmdKey), // Alias for "COMMAND" @"SHIFT": @(shiftKey), @"OPTION": @(optionKey), @"ALT": @(optionKey), // Alias for "OPTION" @"CONTROL": @(controlKey), @"CTRL": @(controlKey), // Alias for "CONTROL" }; }); return conversionTable; } static NSDictionary *keyCodeComponentToKeyCodeConversionTable(void) { static dispatch_once_t onceToken; static NSDictionary *conversionTable; dispatch_once(&onceToken, ^{ NSMutableDictionary *generatedConversionTable = [NSMutableDictionary new]; for (NSNumber *keyCode in [keyCodeToKeyCodeComponentConversionTable() allKeys]) { generatedConversionTable[keyCodeComponentForKeyCode([keyCode integerValue])] = keyCode; } conversionTable = generatedConversionTable; }); return conversionTable; } static NSString *keyCodeComponentForKeyCode(NSInteger keyCode) { NSString *namedKeyCodeComponent = keyCodeToNamedKeyCodeComponentConversionTable()[@(keyCode)]; return (namedKeyCodeComponent.length > 0) ? namedKeyCodeComponent : keyCodeToKeyCodeComponentConversionTable()[@(keyCode)]; } static NSArray *modifierComponentsForModifiers(NSUInteger modifiers) { NSMutableArray *modifierComponents = [NSMutableArray new]; if (modifiers & controlKey) { [modifierComponents addObject:@"ctrl"]; } if (modifiers & optionKey) { [modifierComponents addObject:@"alt"]; } if (modifiers & shiftKey) { [modifierComponents addObject:@"shift"]; } if (modifiers & cmdKey) { [modifierComponents addObject:@"cmd"]; } return modifierComponents; } ================================================ FILE: Spectacle/Sources/SpectacleShortcutManager.h ================================================ #import #import "SpectacleMacros.h" @class SpectacleShortcut; @protocol SpectacleShortcutStorage; @interface SpectacleShortcutManager : NSObject - (instancetype)initWithShortcutStorage:(id)shortcutStorage NS_DESIGNATED_INITIALIZER; SPECTACLE_INIT_AND_NEW_UNAVAILABLE - (void)manageShortcuts:(NSArray *)shortcuts; - (void)updateShortcut:(SpectacleShortcut *)shortcut; - (void)updateShortcuts:(NSArray *)shortcuts; - (void)clearShortcut:(SpectacleShortcut *)shortcut; - (NSArray *)shortcuts; - (SpectacleShortcut *)shortcutForShortcutName:(NSString *)shortcutName; - (BOOL)doesShortcutExist:(SpectacleShortcut *)shortcut; - (void)registerShortcuts; - (void)unregisterShortcuts; @end ================================================ FILE: Spectacle/Sources/SpectacleShortcutManager.m ================================================ #import "SpectacleShortcutManager.h" #import #import "SpectacleShortcutHolder.h" #import "SpectacleShortcut.h" #import "SpectacleShortcutStorage.h" static EventHotKeyID currentShortcutID = { .signature = 'ZERO', .id = 0 }; @implementation SpectacleShortcutManager { id _shortcutStorage; NSMutableDictionary *_registeredShortcutsByName; BOOL _areShorcutsEnabled; } - (instancetype)initWithShortcutStorage:(id)shortcutStorage { if (self = [super init]) { _shortcutStorage = shortcutStorage; _registeredShortcutsByName = [NSMutableDictionary new]; _areShorcutsEnabled = YES; [self installApplicationEventHandler]; } return self; } - (void)manageShortcuts:(NSArray *)shortcuts { for (SpectacleShortcut *shortcut in shortcuts) { NSString *shortcutName = shortcut.shortcutName; SpectacleShortcutHolder *shortcutHolder = _registeredShortcutsByName[shortcutName]; if (shortcutHolder) { NSLog(@"Unable to add shortcut %@ as it already exists.", shortcutName); continue; } currentShortcutID.id = ++currentShortcutID.id; shortcutHolder = [[SpectacleShortcutHolder alloc] initWithShortcutID:currentShortcutID shortcut:shortcut]; _registeredShortcutsByName[shortcutName] = _areShorcutsEnabled ? [self registerEventHotKey:shortcutHolder] : shortcutHolder; } [self storeShortcuts]; } - (void)updateShortcut:(SpectacleShortcut *)shortcut { NSString *shortcutName = shortcut.shortcutName; SpectacleShortcutHolder *shortcutHolder = _registeredShortcutsByName[shortcutName]; if (!shortcutHolder) { NSLog(@"Unable to update shortcut %@ as it does not exists.", shortcutName); return; } if (_areShorcutsEnabled && [self unregisterEventHotKey:shortcutHolder]) { NSLog(@"Unable to unregister event hot key for shortcut: %@", shortcutName); return; } SpectacleShortcutHolder *registeredShortcutHolder = _areShorcutsEnabled ? [self registerEventHotKey:shortcutHolder] : shortcutHolder; _registeredShortcutsByName[shortcutName] = [registeredShortcutHolder copyWithShortcut: [shortcut copyWithShortcutAction: shortcut.shortcutAction ?: shortcutHolder.shortcut.shortcutAction]]; [self storeShortcuts]; } - (void)updateShortcuts:(NSArray *)shortcuts { for (SpectacleShortcut *shortcut in shortcuts) { [self updateShortcut:shortcut]; } } - (void)clearShortcut:(SpectacleShortcut *)shortcut { NSString *shortcutName = shortcut.shortcutName; SpectacleShortcutHolder *shortcutHolder = _registeredShortcutsByName[shortcutName]; if (!shortcutHolder) { NSLog(@"Unable to clear shortcut %@ as it does not exists.", shortcutName); return; } BOOL eventHotKeyUnregistered = NO; if (_areShorcutsEnabled) { eventHotKeyUnregistered = [self unregisterEventHotKey:shortcutHolder]; } _registeredShortcutsByName[shortcutName] = [shortcutHolder copyWithClearedShortcut]; if (_areShorcutsEnabled && !eventHotKeyUnregistered) { _registeredShortcutsByName[shortcutName] = shortcutHolder; } [self storeShortcuts]; } - (NSArray *)shortcuts { NSMutableArray *shortcuts = [NSMutableArray new]; for (SpectacleShortcutHolder *shortcutHolder in _registeredShortcutsByName.allValues) { [shortcuts addObject:shortcutHolder.shortcut]; } return shortcuts; } - (SpectacleShortcut *)shortcutForShortcutName:(NSString *)shortcutName { SpectacleShortcutHolder *shortcutHolder = _registeredShortcutsByName[shortcutName]; SpectacleShortcut *shortcut = shortcutHolder.shortcut; if (shortcut.isClearedShortcut) { return nil; } return shortcut; } - (BOOL)doesShortcutExist:(SpectacleShortcut *)shortcut { for (SpectacleShortcutHolder *shortcutHolder in _registeredShortcutsByName.allValues) { if ([shortcutHolder.shortcut isEqualToShortcut:shortcut]) { return YES; } } return NO; } - (void)registerShortcuts { if (_areShorcutsEnabled) { return; } for (SpectacleShortcutHolder *shortcutHolder in _registeredShortcutsByName.allValues) { _registeredShortcutsByName[shortcutHolder.shortcut.shortcutName] = [self registerEventHotKey:shortcutHolder]; } _areShorcutsEnabled = YES; } - (void)unregisterShortcuts { if (!_areShorcutsEnabled) { return; } for (SpectacleShortcutHolder *shortcutHolder in _registeredShortcutsByName.allValues) { [self unregisterEventHotKey:shortcutHolder]; } _areShorcutsEnabled = NO; } - (void)installApplicationEventHandler { EventTypeSpec typeSpec; typeSpec.eventClass = kEventClassKeyboard; typeSpec.eventKind = kEventHotKeyPressed; InstallApplicationEventHandler(&hotKeyEventHandler, 1, &typeSpec, (__bridge void*)self, NULL); } - (void)storeShortcuts { [_shortcutStorage storeShortcuts:self.shortcuts]; } - (SpectacleShortcut *)shortcutForShortcutID:(EventHotKeyID)shortcutID { for (SpectacleShortcutHolder *shortcut in _registeredShortcutsByName.allValues) { if (shortcut.shortcutID.id == shortcutID.id) { return shortcut.shortcut; } } return nil; } - (SpectacleShortcutHolder *)registerEventHotKey:(SpectacleShortcutHolder *)shortcutHolder { SpectacleShortcut *shortcut = shortcutHolder.shortcut; if (shortcut.isClearedShortcut) { return shortcutHolder; } EventHotKeyRef shortcutRef; OSStatus err; err = RegisterEventHotKey((unsigned int)shortcut.shortcutKeyCode, (unsigned int)shortcut.shortcutModifiers, shortcutHolder.shortcutID, GetEventDispatcherTarget(), kEventHotKeyNoOptions, &shortcutRef); if (err) { NSLog(@"There was a problem registering shortcut %@.", shortcut.shortcutName); return [shortcutHolder copyWithClearedShortcut]; } return [shortcutHolder copyWithShortcutRef:shortcutRef]; } - (BOOL)unregisterEventHotKey:(SpectacleShortcutHolder *)shortcutHolder { NSString *shortcutName = shortcutHolder.shortcut.shortcutName; EventHotKeyRef shortcutRef = shortcutHolder.shortcutRef; OSStatus err; if (shortcutRef) { err = UnregisterEventHotKey(shortcutRef); if (err) { NSLog(@"Received the following error code when unregistering shortcut %@: %d", shortcutName, err); return NO; } } return YES; } - (OSStatus)handleHotKeyEvent:(EventRef)event { SpectacleShortcut *shortcut; EventHotKeyID shortcutID; OSStatus err = GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(EventHotKeyID), NULL, &shortcutID); if (err) { return err; } shortcut = [self shortcutForShortcutID:shortcutID]; if (!shortcut) { NSLog(@"Unable to handle event for shortcut with handle %d, the registered shortcut does not exist.", shortcutID.id); } switch (GetEventKind(event)) { case kEventHotKeyPressed: [shortcut triggerShortcutAction]; break; default: break; } return noErr; } OSStatus hotKeyEventHandler(EventHandlerCallRef handlerCall, EventRef event, void *shortcutManager) { return [(__bridge SpectacleShortcutManager *)shortcutManager handleHotKeyEvent:event]; } @end ================================================ FILE: Spectacle/Sources/SpectacleShortcutRecorder.h ================================================ #import @class SpectacleShortcut; @class SpectacleShortcutValidation; @protocol SpectacleShortcutRecorderDelegate; @interface SpectacleShortcutRecorder : NSView @property (nonatomic, copy) NSString *shortcutName; @property (nonatomic, strong) SpectacleShortcut *shortcut; @property (nonatomic, strong) SpectacleShortcutValidation *shortcutValidation; @property (nonatomic, weak) id delegate; @end ================================================ FILE: Spectacle/Sources/SpectacleShortcutRecorder.m ================================================ #import "SpectacleShortcutRecorder.h" #import #import "SpectacleShortcut.h" #import "SpectacleShortcutRecorder.h" #import "SpectacleShortcutRecorderDelegate.h" #import "SpectacleShortcutTranslations.h" #import "SpectacleShortcutValidation.h" static const NSTrackingAreaOptions kTrackingAreaOptions = (NSTrackingMouseEnteredAndExited | NSTrackingActiveWhenFirstResponder | NSTrackingEnabledDuringMouseDrag); static const NSEventModifierFlags kCocoaModifierFlagsMask = (NSControlKeyMask | NSAlternateKeyMask | NSShiftKeyMask | NSCommandKeyMask); @implementation SpectacleShortcutRecorder { BOOL _isRecording; BOOL _isMouseDown; BOOL _isMouseAboveBadge; NSTrackingArea *_badgeButtonTrackingArea; void *_shortcutMode; } - (instancetype)initWithFrame:(NSRect)frame { if (self = [super initWithFrame:frame]) { [self _updateTrackingArea]; } return self; } - (void)setShortcut:(SpectacleShortcut *)shortcut { _shortcut = shortcut; [self _updateTrackingArea]; [self setNeedsDisplay:YES]; } - (BOOL)acceptsFirstResponder { return YES; } - (BOOL)resignFirstResponder { [self _stopRecording]; return YES; } - (BOOL)acceptsFirstMouse:(NSEvent *)event { return YES; } - (void)mouseDown:(NSEvent *)event { _isMouseDown = YES; [self setNeedsDisplay:YES]; } - (void)mouseUp:(NSEvent *)event { NSPoint locationInView = [self convertPoint:event.locationInWindow fromView:nil]; if (_badgeButtonTrackingArea && [self mouse:locationInView inRect:badgeRectInBounds(self.bounds)]) { if (_isRecording) { [self _stopRecording]; } else { [self _clearShortcut]; } } else if ([self mouse:locationInView inRect:self.bounds]) { [self _startRecording]; } else { [self setNeedsDisplay:YES]; } _isMouseDown = NO; } - (void)mouseEntered:(NSEvent *)event { _isMouseAboveBadge = event.trackingArea == _badgeButtonTrackingArea; [self setNeedsDisplay:YES]; } - (void)mouseExited:(NSEvent *)event { _isMouseAboveBadge = event.trackingArea != _badgeButtonTrackingArea; [self setNeedsDisplay:YES]; } - (void)keyDown:(NSEvent *)event { if (![self performKeyEquivalent:event]) { [super keyDown:event]; } } - (BOOL)performKeyEquivalent:(NSEvent *)event { if (self.window.firstResponder != self) { return NO; } NSEventModifierFlags modifierFlags = event.modifierFlags & kCocoaModifierFlagsMask; if (event.keyCode == kVK_Escape && modifierFlags == 0) { [self _stopRecording]; return YES; } NSInteger keyCode = event.keyCode; BOOL functionKey = ((keyCode == kVK_F1) || (keyCode == kVK_F2) || (keyCode == kVK_F3) || (keyCode == kVK_F4) || (keyCode == kVK_F5) || (keyCode == kVK_F6) || (keyCode == kVK_F7) || (keyCode == kVK_F8) || (keyCode == kVK_F9) || (keyCode == kVK_F10) || (keyCode == kVK_F11) || (keyCode == kVK_F12) || (keyCode == kVK_F13) || (keyCode == kVK_F14) || (keyCode == kVK_F15) || (keyCode == kVK_F16) || (keyCode == kVK_F17) || (keyCode == kVK_F18) || (keyCode == kVK_F19) || (keyCode == kVK_F20)); if (_isRecording && (functionKey || [SpectacleShortcut validCocoaModifiers:modifierFlags])) { SpectacleShortcut *shortcut = [[SpectacleShortcut alloc] initWithShortcutName:_shortcutName shortcutKeyCode:keyCode shortcutModifiers:modifierFlags]; NSError *error = nil; if ([_shortcutValidation isShortcutValid:shortcut error:&error]) { _shortcut = shortcut; [_delegate shortcutRecorder:self didReceiveNewShortcut:shortcut]; } else { [[NSAlert alertWithError:error] runModal]; } [self _stopRecording]; return YES; } return NO; } - (void)flagsChanged:(NSEvent *)event { if (!_isRecording) { return; } [self setNeedsDisplay:YES]; } - (void)drawRect:(NSRect)rect { CGFloat radius = NSHeight(rect) / 2.0f; [self _drawBorderInRect:rect withRadius:radius]; [self _drawBackgroundInRect:rect withRadius:radius]; [self _drawBadgeInRect:rect]; [self _drawLabelInRect:rect]; } - (void)_startRecording { _isRecording = YES; _shortcutMode = PushSymbolicHotKeyMode(kHIHotKeyModeAllDisabled); [self _updateTrackingArea]; [self setNeedsDisplay:YES]; } - (void)_stopRecording { if (!_isRecording) { return; } PopSymbolicHotKeyMode(_shortcutMode); _isRecording = NO; _isMouseAboveBadge = NO; [self _updateTrackingArea]; [self setNeedsDisplay:YES]; } - (void)_clearShortcut { [_delegate shortcutRecorder:self didClearExistingShortcut:_shortcut]; _shortcut = nil; _isMouseAboveBadge = NO; [self _updateTrackingArea]; [self setNeedsDisplay:YES]; } - (void)_updateTrackingArea { if (_badgeButtonTrackingArea) { [self removeTrackingArea: _badgeButtonTrackingArea]; _badgeButtonTrackingArea = nil; } if (_isRecording || _shortcut) { _badgeButtonTrackingArea = [[NSTrackingArea alloc] initWithRect:badgeRectInBounds(self.bounds) options:kTrackingAreaOptions owner:self userInfo:nil]; [self addTrackingArea:_badgeButtonTrackingArea]; } } - (void)_drawBorderInRect:(NSRect)rect withRadius:(CGFloat)radius { NSBezierPath *roundedPath = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:radius yRadius:radius]; [NSGraphicsContext.currentContext saveGraphicsState]; [roundedPath addClip]; [[NSColor windowFrameColor] set]; [NSBezierPath fillRect:rect]; [NSGraphicsContext.currentContext restoreGraphicsState]; } - (void)_drawBackgroundInRect:(NSRect)rect withRadius:(CGFloat)radius { NSBezierPath *roundedPath = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(rect, 1.0f, 1.0f) xRadius:radius yRadius:radius]; NSColor *gradientStartingColor = nil; NSColor *gradientEndingColor = nil; NSGradient *gradient = nil; [NSGraphicsContext.currentContext saveGraphicsState]; [roundedPath addClip]; if (_isRecording) { gradientStartingColor = [NSColor colorWithDeviceRed:0.784f green:0.953f blue:1.0f alpha:1.0f]; gradientEndingColor = [NSColor colorWithDeviceRed:0.694f green:0.859f blue:1.0f alpha:1.0f]; } else { gradientStartingColor = [[[NSColor whiteColor] shadowWithLevel:0.2f] colorWithAlphaComponent:0.9f]; gradientEndingColor = [[[NSColor whiteColor] highlightWithLevel:0.2f] colorWithAlphaComponent:0.9f]; } if (!_isRecording && _isMouseDown && !_isMouseAboveBadge) { gradient = [[NSGradient alloc] initWithStartingColor:gradientEndingColor endingColor:gradientStartingColor]; } else { gradient = [[NSGradient alloc] initWithStartingColor:gradientStartingColor endingColor:gradientEndingColor]; } [gradient drawInRect:rect angle:90.0f]; [NSGraphicsContext.currentContext restoreGraphicsState]; } - (void)_drawBadgeInRect:(NSRect)rect { NSRect badgeRect = badgeRectInBounds(rect); if ((_isRecording && !_shortcut) || (!_isRecording && _shortcut)) { [self _drawCancelOrClearShortcutBadgeInRect:badgeRect opacity:(_isMouseAboveBadge && _isMouseDown) ? 0.50 : 0.25f]; } else if (_isRecording) { [self _drawRevertShortcutBadgeInRect:badgeRect]; } } - (void)_drawCancelOrClearShortcutBadgeInRect:(NSRect)rect opacity:(CGFloat)opacity { CGFloat horizontalScale = (rect.size.width / 13.0f); CGFloat verticalScale = (rect.size.height / 13.0f); [NSGraphicsContext.currentContext saveGraphicsState]; [[NSColor colorWithCalibratedWhite:0.0f alpha:opacity] setFill]; [[NSBezierPath bezierPathWithOvalInRect:rect] fill]; [[NSColor whiteColor] setStroke]; NSBezierPath *cross = [NSBezierPath new]; [cross setLineWidth:horizontalScale * 1.4f]; [cross moveToPoint:relativePointInRect(4.0f, 4.0f, rect, horizontalScale, verticalScale)]; [cross lineToPoint:relativePointInRect(9.0f, 9.0f, rect, horizontalScale, verticalScale)]; [cross moveToPoint:relativePointInRect(9.0f, 4.0f, rect, horizontalScale, verticalScale)]; [cross lineToPoint:relativePointInRect(4.0f, 9.0f, rect, horizontalScale, verticalScale)]; [cross stroke]; [NSGraphicsContext.currentContext restoreGraphicsState]; } - (void)_drawRevertShortcutBadgeInRect:(NSRect)rect { CGFloat horizontalScale = (rect.size.width / 1.0f); CGFloat verticalScale = (rect.size.height / 1.0f); [NSGraphicsContext.currentContext saveGraphicsState]; NSBezierPath *swoosh = [NSBezierPath new]; [swoosh setLineWidth:horizontalScale]; [swoosh moveToPoint:relativePointInRect(0.0489685f, 0.6181513f, rect, horizontalScale, verticalScale)]; [swoosh lineToPoint:relativePointInRect(0.4085750f, 0.9469318f, rect, horizontalScale, verticalScale)]; [swoosh lineToPoint:relativePointInRect(0.4085750f, 0.7226146f, rect, horizontalScale, verticalScale)]; [swoosh curveToPoint:relativePointInRect(0.8508247f, 0.4836237f, rect, horizontalScale, verticalScale) controlPoint1:relativePointInRect(0.4085750f, 0.7226146f, rect, horizontalScale, verticalScale) controlPoint2:relativePointInRect(0.8371143f, 0.7491841f, rect, horizontalScale, verticalScale)]; [swoosh curveToPoint:relativePointInRect(0.5507195f, 0.0530682f, rect, horizontalScale, verticalScale) controlPoint1:relativePointInRect(0.8677834f, 0.1545071f, rect, horizontalScale, verticalScale) controlPoint2:relativePointInRect(0.5507195f, 0.0530682f, rect, horizontalScale, verticalScale)]; [swoosh curveToPoint:relativePointInRect(0.7421721f, 0.3391942f, rect, horizontalScale, verticalScale) controlPoint1:relativePointInRect(0.5507195f, 0.0530682f, rect, horizontalScale, verticalScale) controlPoint2:relativePointInRect(0.7458685f, 0.1913146f, rect, horizontalScale, verticalScale)]; [swoosh curveToPoint:relativePointInRect(0.4085750f, 0.5154130f, rect, horizontalScale, verticalScale) controlPoint1:relativePointInRect(0.7383412f, 0.4930328f, rect, horizontalScale, verticalScale) controlPoint2:relativePointInRect(0.4085750f, 0.5154130f, rect, horizontalScale, verticalScale)]; [swoosh lineToPoint:relativePointInRect(0.4085750f, 0.2654000f, rect, horizontalScale, verticalScale)]; [swoosh fill]; [NSGraphicsContext.currentContext restoreGraphicsState]; } - (void)_drawLabelInRect:(NSRect)rect { NSString *label = nil; if (_isRecording && !_isMouseAboveBadge) { label = NSLocalizedString(@"ShortcutRecorderLabelEnterShortcut", @"The shortcut recorder label displayed when the shorcut recorder is recording a shortcut"); } else if (_isRecording && _isMouseAboveBadge && !_shortcut) { label = NSLocalizedString(@"ShortcutRecorderLabelStopRecording", @"The shortcut recorder label displayed when the shorcut recorder is recording a shortcut and the shortcut recorder does not have a previously recorded shortcut"); } else if (_isRecording && _isMouseAboveBadge) { label = NSLocalizedString(@"ShortcutRecorderLabelUseExisting", "The shortcut recorder label displayed when the shorcut recorder is recording a shortcut and the shortcut recorder does have a previously recorded shortcut"); } else if (_shortcut) { label = _shortcut.displayString; } else { label = NSLocalizedString(@"ShortcutRecorderLabelClickToRecord", @"The shortcut recorder label displayed when the shorcut recorder is cleared and ready to record a new shortcut"); } NSEventModifierFlags modifierFlags = [NSEvent modifierFlags] & kCocoaModifierFlagsMask; if (_isRecording && modifierFlags) { label = SpectacleTranslateModifiers(modifierFlags); } [self _drawString:label inRect:rect]; } - (void)_drawString:(NSString *)string inRect:(NSRect)rect { NSMutableDictionary *attributes = stringAttributesWithShadow(); NSRect labelRect = rect; attributes[NSFontAttributeName] = [NSFont systemFontOfSize:NSFont.smallSystemFontSize]; attributes[NSForegroundColorAttributeName] = [NSColor blackColor]; labelRect.origin.y = -(NSMidY(rect) - [string sizeWithAttributes:attributes].height / 2.0f); [string drawInRect:labelRect withAttributes:attributes]; } static NSRect badgeRectInBounds(NSRect bounds) { NSRect badgeRect; NSSize badgeSize; badgeSize.width = 13.0f; badgeSize.height = 13.0f; badgeRect.origin = NSMakePoint(NSMaxX(bounds) - badgeSize.width - 4.0f, floor((NSMaxY(bounds) - badgeSize.height) / 2.0f)); badgeRect.size = badgeSize; return badgeRect; } static NSMutableDictionary *stringAttributesWithShadow(void) { NSMutableParagraphStyle *paragraphStyle = NSParagraphStyle.defaultParagraphStyle.mutableCopy; NSShadow *textShadow = [NSShadow new]; NSMutableDictionary *stringAttributes = [NSMutableDictionary new]; paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail; paragraphStyle.alignment = NSCenterTextAlignment; textShadow.shadowColor = [NSColor whiteColor]; textShadow.shadowOffset = NSMakeSize(0.0f, -1.0); textShadow.shadowBlurRadius = 0.0f; stringAttributes[NSParagraphStyleAttributeName] = paragraphStyle; stringAttributes[NSShadowAttributeName] = textShadow; return stringAttributes; } static NSPoint relativePointInRect(CGFloat x, CGFloat y, CGRect rect, CGFloat horizontalScale, CGFloat verticalScale) { return NSMakePoint((x * horizontalScale) + rect.origin.x, (y * verticalScale) + rect.origin.y); } @end ================================================ FILE: Spectacle/Sources/SpectacleShortcutRecorderDelegate.h ================================================ #import @class SpectacleShortcut; @class SpectacleShortcutRecorder; @protocol SpectacleShortcutRecorderDelegate - (void)shortcutRecorder:(SpectacleShortcutRecorder *)shortcutRecorder didReceiveNewShortcut:(SpectacleShortcut *)shortcut; - (void)shortcutRecorder:(SpectacleShortcutRecorder *)shortcutRecorder didClearExistingShortcut:(SpectacleShortcut *)shortcut; @end ================================================ FILE: Spectacle/Sources/SpectacleShortcutStorage.h ================================================ #import #import "SpectacleShortcut.h" @protocol SpectacleShortcutStorage - (NSArray *)loadShortcutsWithAction:(SpectacleShortcutAction)action; - (void)storeShortcuts:(NSArray *)shortcuts; @end ================================================ FILE: Spectacle/Sources/SpectacleShortcutStorageMigrationDestination.h ================================================ #import #import "SpectacleShortcutStorage.h" @protocol SpectacleShortcutStorageMigrationDestination @property (nonatomic, readonly, assign, getter=isMigrationNeeded) BOOL migrationNeeded; @end ================================================ FILE: Spectacle/Sources/SpectacleShortcutTranslations.h ================================================ #import @class SpectacleShortcut; NSString *SpectacleTranslateKeyCode(NSInteger keyCode); NSString *SpectacleTranslateModifiers(NSUInteger modifiers); NSString *SpectacleTranslateShortcut(SpectacleShortcut *shortcut); NSUInteger SpectacleConvertCocoaModifiersToCarbon(NSUInteger modifiers); NSUInteger SpectacleConvertCarbonModifiersToCocoa(NSUInteger modifiers); NSUInteger SpectacleConvertModifiersToCarbonIfNecessary(NSUInteger modifiers); NSUInteger SpectacleConvertModifiersToCocoaIfNecessary(NSUInteger modifiers); ================================================ FILE: Spectacle/Sources/SpectacleShortcutTranslations.m ================================================ #import "SpectacleShortcutTranslations.h" #import #import #import "SpectacleShortcut.h" typedef NS_ENUM(unichar, SpectacleUnicodeGlyph) { SpectacleUnicodeGlyphCapsLock = 0x21EA, // ⇪ SpectacleUnicodeGlyphClear = 0x2327, // ⌧ SpectacleUnicodeGlyphCommand = 0x2318, // ⌘ SpectacleUnicodeGlyphControl = 0x2303, // ⌃ SpectacleUnicodeGlyphDelete = 0x232B, // ⌫ SpectacleUnicodeGlyphDownArrow = 0x2193, // ↓ SpectacleUnicodeGlyphEnd = 0x2198, // ↘ SpectacleUnicodeGlyphEnter = 0x2324, // ⌤ SpectacleUnicodeGlyphEscape = 0x238B, // ⎋ SpectacleUnicodeGlyphForwardDelete = 0x2326, // ⌦ SpectacleUnicodeGlyphHome = 0x2196, // ↖ SpectacleUnicodeGlyphLeftArrow = 0x2190, // ← SpectacleUnicodeGlyphOption = 0x2325, // ⌥ SpectacleUnicodeGlyphPageDown = 0x21DF, // ⇟ SpectacleUnicodeGlyphPageUp = 0x21DE, // ⇞ SpectacleUnicodeGlyphReturn = 0x21A9, // ↩ SpectacleUnicodeGlyphRightArrow = 0x2192, // → SpectacleUnicodeGlyphShift = 0x21E7, // ⇧ SpectacleUnicodeGlyphSpace = 0x2423, // ␣ SpectacleUnicodeGlyphTab = 0x21E5, // ⇥ SpectacleUnicodeGlyphUpArrow = 0x2191, // ↑ }; static NSDictionary *layoutIndependentKeyCodeTranslations(void); static NSString *glyphForUnicodeChar(unichar unicodeChar); NSString *SpectacleTranslateKeyCode(NSInteger keyCode) { NSString *translatedLayoutIndependentKeyCode = layoutIndependentKeyCodeTranslations()[@(keyCode)]; if (translatedLayoutIndependentKeyCode) { return translatedLayoutIndependentKeyCode; } TISInputSourceRef inputSource = TISCopyCurrentKeyboardLayoutInputSource(); if (!inputSource) { return @"?"; } CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(inputSource, kTISPropertyUnicodeKeyLayoutData); CFRelease(inputSource); if (!layoutData) { inputSource = TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); if (!inputSource) { return @"?"; } layoutData = (CFDataRef)TISGetInputSourceProperty(inputSource, kTISPropertyUnicodeKeyLayoutData); CFRelease(inputSource); if (!layoutData) { return @"?"; } } const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData); UInt32 deadKeyState = 0; UniCharCount maximumStringLength = 4; UniCharCount actualStringLength = 0; UniChar unicodeString[maximumStringLength]; OSStatus err = UCKeyTranslate(keyboardLayout, keyCode, kUCKeyActionDisplay, 0, LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &deadKeyState, maximumStringLength, &actualStringLength, unicodeString); if (err != noErr) { return @"?"; } return [[NSString stringWithCharacters:unicodeString length:actualStringLength] uppercaseString]; } NSString *SpectacleTranslateModifiers(NSUInteger modifiers) { NSString *modifierGlyphs = @""; modifiers = SpectacleConvertModifiersToCocoaIfNecessary(modifiers); if (modifiers & NSControlKeyMask) { modifierGlyphs = [modifierGlyphs stringByAppendingFormat:@"%C", (UInt16)kControlUnicode]; } if (modifiers & NSAlternateKeyMask) { modifierGlyphs = [modifierGlyphs stringByAppendingFormat:@"%C", (UInt16)kOptionUnicode]; } if (modifiers & NSShiftKeyMask) { modifierGlyphs = [modifierGlyphs stringByAppendingFormat:@"%C", (UInt16)kShiftUnicode]; } if (modifiers & NSCommandKeyMask) { modifierGlyphs = [modifierGlyphs stringByAppendingFormat:@"%C", (UInt16)kCommandUnicode]; } return modifierGlyphs; } NSString *SpectacleTranslateShortcut(SpectacleShortcut *shortcut) { return [NSString stringWithFormat:@"%@%@", SpectacleTranslateModifiers(shortcut.shortcutModifiers), SpectacleTranslateKeyCode(shortcut.shortcutKeyCode)]; } NSUInteger SpectacleConvertCocoaModifiersToCarbon(NSUInteger modifiers) { NSUInteger convertedModifiers = 0; if (modifiers & NSControlKeyMask) { convertedModifiers |= controlKey; } if (modifiers & NSAlternateKeyMask) { convertedModifiers |= optionKey; } if (modifiers & NSShiftKeyMask) { convertedModifiers |= shiftKey; } if (modifiers & NSCommandKeyMask) { convertedModifiers |= cmdKey; } return convertedModifiers; } NSUInteger SpectacleConvertCarbonModifiersToCocoa(NSUInteger modifiers) { NSUInteger convertedModifiers = 0; if (modifiers & controlKey) { convertedModifiers |= NSControlKeyMask; } if (modifiers & optionKey) { convertedModifiers |= NSAlternateKeyMask; } if (modifiers & shiftKey) { convertedModifiers |= NSShiftKeyMask; } if (modifiers & cmdKey) { convertedModifiers |= NSCommandKeyMask; } return convertedModifiers; } NSUInteger SpectacleConvertModifiersToCarbonIfNecessary(NSUInteger modifiers) { if ([SpectacleShortcut validCocoaModifiers:modifiers]) { modifiers = SpectacleConvertCocoaModifiersToCarbon(modifiers); } return modifiers; } NSUInteger SpectacleConvertModifiersToCocoaIfNecessary(NSUInteger modifiers) { if (![SpectacleShortcut validCocoaModifiers:modifiers]) { modifiers = SpectacleConvertCarbonModifiersToCocoa(modifiers); } return modifiers; } static NSDictionary *layoutIndependentKeyCodeTranslations(void) { static dispatch_once_t onceToken; static NSDictionary *translations; dispatch_once(&onceToken, ^{ translations = @{ @(kVK_F1): @"F1", @(kVK_F2): @"F2", @(kVK_F3): @"F3", @(kVK_F4): @"F4", @(kVK_F5): @"F5", @(kVK_F6): @"F6", @(kVK_F7): @"F7", @(kVK_F8): @"F8", @(kVK_F9): @"F9", @(kVK_F10): @"F10", @(kVK_F11): @"F11", @(kVK_F12): @"F12", @(kVK_F13): @"F13", @(kVK_F14): @"F14", @(kVK_F15): @"F15", @(kVK_F16): @"F16", @(kVK_F17): @"F17", @(kVK_F18): @"F18", @(kVK_F19): @"F19", @(kVK_F20): @"F20", @(kVK_ANSI_KeypadDecimal): @".", @(kVK_ANSI_KeypadMultiply): @"*", @(kVK_ANSI_KeypadPlus): @"+", @(kVK_ANSI_KeypadClear): glyphForUnicodeChar(SpectacleUnicodeGlyphClear), @(kVK_ANSI_KeypadDivide): @"/", @(kVK_ANSI_KeypadEnter): glyphForUnicodeChar(SpectacleUnicodeGlyphEnter), @(kVK_ANSI_KeypadMinus): @"-", @(kVK_ANSI_KeypadEquals): @"=", @(kVK_ANSI_Keypad0): @"0", @(kVK_ANSI_Keypad1): @"1", @(kVK_ANSI_Keypad2): @"2", @(kVK_ANSI_Keypad3): @"3", @(kVK_ANSI_Keypad4): @"4", @(kVK_ANSI_Keypad5): @"5", @(kVK_ANSI_Keypad6): @"6", @(kVK_ANSI_Keypad7): @"7", @(kVK_ANSI_Keypad8): @"8", @(kVK_ANSI_Keypad9): @"9", @(kVK_Return): glyphForUnicodeChar(SpectacleUnicodeGlyphReturn), @(kVK_Tab): glyphForUnicodeChar(SpectacleUnicodeGlyphTab), @(kVK_Space): glyphForUnicodeChar(SpectacleUnicodeGlyphSpace), @(kVK_Delete): glyphForUnicodeChar(SpectacleUnicodeGlyphDelete), @(kVK_Escape): glyphForUnicodeChar(SpectacleUnicodeGlyphEscape), @(kVK_Command): glyphForUnicodeChar(SpectacleUnicodeGlyphCommand), @(kVK_Shift): glyphForUnicodeChar(SpectacleUnicodeGlyphShift), @(kVK_CapsLock): glyphForUnicodeChar(SpectacleUnicodeGlyphCapsLock), @(kVK_Option): glyphForUnicodeChar(SpectacleUnicodeGlyphOption), @(kVK_Control): glyphForUnicodeChar(SpectacleUnicodeGlyphControl), @(kVK_RightShift): @"", @(kVK_RightOption): @"", @(kVK_RightControl): @"", @(kVK_Function): @"", @(kVK_VolumeUp): @"", @(kVK_VolumeDown): @"", @(kVK_Mute): @"", @(kVK_Help): @"", @(kVK_Home): glyphForUnicodeChar(SpectacleUnicodeGlyphHome), @(kVK_PageUp): glyphForUnicodeChar(SpectacleUnicodeGlyphPageUp), @(kVK_ForwardDelete): glyphForUnicodeChar(SpectacleUnicodeGlyphForwardDelete), @(kVK_End): glyphForUnicodeChar(SpectacleUnicodeGlyphEnd), @(kVK_PageDown): glyphForUnicodeChar(SpectacleUnicodeGlyphPageDown), @(kVK_LeftArrow): glyphForUnicodeChar(SpectacleUnicodeGlyphLeftArrow), @(kVK_RightArrow): glyphForUnicodeChar(SpectacleUnicodeGlyphRightArrow), @(kVK_DownArrow): glyphForUnicodeChar(SpectacleUnicodeGlyphDownArrow), @(kVK_UpArrow): glyphForUnicodeChar(SpectacleUnicodeGlyphUpArrow), }; }); return translations; } static NSString *glyphForUnicodeChar(unichar unicodeChar) { return [NSString stringWithFormat: @"%C", unicodeChar]; } ================================================ FILE: Spectacle/Sources/SpectacleShortcutUserDefaultsStorage.h ================================================ #import #import "SpectacleShortcutStorage.h" @interface SpectacleShortcutUserDefaultsStorage : NSObject @end ================================================ FILE: Spectacle/Sources/SpectacleShortcutUserDefaultsStorage.m ================================================ #import "SpectacleShortcutUserDefaultsStorage.h" @implementation SpectacleShortcutUserDefaultsStorage + (void)initialize { [NSKeyedUnarchiver setClass:[SpectacleShortcut class] forClassName:@"SpectacleHotKey"]; [NSKeyedUnarchiver setClass:[SpectacleShortcut class] forClassName:@"ZeroKitHotKey"]; [NSKeyedUnarchiver setClass:[SpectacleShortcut class] forClassName:@"ZKHotKey"]; } - (NSArray *)loadShortcutsWithAction:(SpectacleShortcutAction)action { NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; NSMutableDictionary *shortcutsFromUserDefaults = [NSMutableDictionary new]; for (NSString *shortcutName in [self shortcutNames]) { shortcutsFromUserDefaults[shortcutName] = [userDefaults dataForKey:shortcutName]; } return [self shortcutsFromDictionary:shortcutsFromUserDefaults action:action]; } - (void)storeShortcuts:(NSArray *)shortcuts { NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; for (SpectacleShortcut *shortcut in shortcuts) { NSData *shortcutData = [NSKeyedArchiver archivedDataWithRootObject:shortcut]; NSString *shortcutName = shortcut.shortcutName; if (![shortcutData isEqualToData:[userDefaults dataForKey:shortcutName]]) { [userDefaults setObject:shortcutData forKey:shortcutName]; } } } - (BOOL)isMigrationNeeded { return NO; } - (NSArray *)shortcutNames { return @[ @"MoveToCenter", @"MoveToFullscreen", @"MoveToLeftHalf", @"MoveToRightHalf", @"MoveToTopHalf", @"MoveToBottomHalf", @"MoveToUpperLeft", @"MoveToLowerLeft", @"MoveToUpperRight", @"MoveToLowerRight", @"MoveToNextDisplay", @"MoveToPreviousDisplay", @"MoveToNextThird", @"MoveToPreviousThird", @"MakeLarger", @"MakeSmaller", @"UndoLastMove", @"RedoLastMove", ]; } - (NSArray *)shortcutsFromDictionary:(NSDictionary *)dictionary action:(SpectacleShortcutAction)action { NSMutableArray *shortcuts = [NSMutableArray new]; for (NSData *shortcutData in dictionary.allValues) { SpectacleShortcut *shortcut = [NSKeyedUnarchiver unarchiveObjectWithData:shortcutData]; [shortcuts addObject:[shortcut copyWithShortcutAction:action]]; } return shortcuts; } @end ================================================ FILE: Spectacle/Sources/SpectacleShortcutValidation.h ================================================ #import #import "SpectacleMacros.h" @class SpectacleShortcut; @class SpectacleShortcutManager; @protocol SpectacleShortcutValidator; @interface SpectacleShortcutValidation : NSObject - (instancetype)initWithShortcutManager:(SpectacleShortcutManager *)shortcutManager validators:(NSArray> *)validators NS_DESIGNATED_INITIALIZER; SPECTACLE_INIT_AND_NEW_UNAVAILABLE - (BOOL)isShortcutValid:(SpectacleShortcut *)shortcut error:(NSError **)error; @end ================================================ FILE: Spectacle/Sources/SpectacleShortcutValidation.m ================================================ #import "SpectacleShortcutValidation.h" #import #import "SpectacleShortcut.h" #import "SpectacleShortcutTranslations.h" #import "SpectacleShortcutValidator.h" @implementation SpectacleShortcutValidation { SpectacleShortcutManager *_shortcutManager; NSArray> *_validators; } - (instancetype)initWithShortcutManager:(SpectacleShortcutManager *)shortcutManager validators:(NSArray> *)validators { if (self = [super init]) { _shortcutManager = shortcutManager; _validators = validators; } return self; } - (BOOL)isShortcutValid:(SpectacleShortcut *)shortcut error:(NSError **)error { CFArrayRef shortcuts = NULL; if (CopySymbolicHotKeys(&shortcuts)) { return YES; } for (CFIndex i = 0; i < CFArrayGetCount(shortcuts); i++) { CFDictionaryRef shortcutDictionary = (CFDictionaryRef)CFArrayGetValueAtIndex(shortcuts, i); if (!shortcutDictionary || (CFGetTypeID(shortcutDictionary) != CFDictionaryGetTypeID())) { continue; } if (kCFBooleanTrue != (CFBooleanRef)CFDictionaryGetValue(shortcutDictionary, kHISymbolicHotKeyEnabled)) { continue; } NSInteger keyCode = keyCodeFromDictionary(shortcutDictionary); NSUInteger modifiers = modifiersFromDictionary(shortcutDictionary); if (([shortcut shortcutKeyCode] == keyCode) && [shortcut containsModifiers:modifiers]) { if (error) { NSString *description = NSLocalizedString(@"AlertMessageTextShortcutValidationError", @"The message text of the alert displayed when a shortcut is invalid"); NSString *recoverySuggestion = NSLocalizedString(@"AlertInformativeTextSystemWideShortcutAlreadyUsed", @"The informative text of the alert displayed when a system-wide shortcut is already in use"); *error = [SpectacleShortcutValidation errorWithShortcut:shortcut description:description recoverySuggestion:recoverySuggestion]; } return NO; } } for (id validator in _validators) { if ([validator conformsToProtocol:@protocol(SpectacleShortcutValidator)] && ![validator isShortcutValid:shortcut shortcutManager:_shortcutManager]) { if (error) { NSString *description = NSLocalizedString(@"AlertMessageTextShortcutValidationError", @"The message text of the alert displayed when a shortcut is invalid"); NSString *recoverySuggestion = NSLocalizedString(@"AlertInformativeTextShortcutAlreadyInUse", @"The informative text of the alert displayed when a shortcut is already in use"); *error = [SpectacleShortcutValidation errorWithShortcut:shortcut description:description recoverySuggestion:recoverySuggestion]; } return NO; } } return YES; } + (NSError *)errorWithShortcut:(SpectacleShortcut *)shortcut description:(NSString *)description recoverySuggestion:(NSString *)recoverySuggestion { NSString *shortcutString = SpectacleTranslateShortcut(shortcut); return [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:description, shortcutString], NSLocalizedRecoverySuggestionErrorKey: [NSString stringWithFormat:recoverySuggestion, shortcutString], NSLocalizedRecoveryOptionsErrorKey: @[NSLocalizedString(@"ButtonLabelAffirmative", @"The button label used in the affirmative")] }]; } static NSInteger keyCodeFromDictionary(CFDictionaryRef dictionary) { CFNumberRef keyCodeFromDictionary = (CFNumberRef)CFDictionaryGetValue(dictionary, kHISymbolicHotKeyCode); NSInteger keyCode = 0; CFNumberGetValue(keyCodeFromDictionary, kCFNumberLongType, &keyCode); return keyCode; } static NSUInteger modifiersFromDictionary(CFDictionaryRef dictionary) { CFNumberRef modifiersFromDictionary = (CFNumberRef)CFDictionaryGetValue(dictionary, kHISymbolicHotKeyModifiers); NSUInteger modifiers = 0; CFNumberGetValue(modifiersFromDictionary, kCFNumberLongType, &modifiers); return modifiers; } @end ================================================ FILE: Spectacle/Sources/SpectacleShortcutValidator.h ================================================ #import @class SpectacleShortcut; @class SpectacleShortcutManager; @protocol SpectacleShortcutValidator - (BOOL)isShortcutValid:(SpectacleShortcut *)shortcut shortcutManager:(SpectacleShortcutManager *)shortcutManager; @end ================================================ FILE: Spectacle/Sources/SpectacleStandardWindowMover.h ================================================ #import #import "SpectacleWindowMover.h" @interface SpectacleStandardWindowMover : NSObject - (instancetype)initWithInnerWindowMover:(id)innerWindowMover NS_DESIGNATED_INITIALIZER; @end ================================================ FILE: Spectacle/Sources/SpectacleStandardWindowMover.m ================================================ #import "SpectacleStandardWindowMover.h" #import "SpectacleAccessibilityElement.h" @implementation SpectacleStandardWindowMover { id _innerWindowMover; } - (instancetype)initWithInnerWindowMover:(id)innerWindowMover { if (self = [super init]) { _innerWindowMover = innerWindowMover; } return self; } - (instancetype)init { return [self initWithInnerWindowMover:nil]; } + (instancetype)newWithInnerWindowMover:(id)innerWindowMover { return [[self alloc] initWithInnerWindowMover:innerWindowMover]; } - (void)moveWindowRect:(CGRect)windowRect frameOfScreen:(CGRect)frameOfScreen visibleFrameOfScreen:(CGRect)visibleFrameOfScreen frontmostWindowElement:(SpectacleAccessibilityElement *)frontmostWindowElement action:(SpectacleWindowAction *)action { CGRect previousWindowRect = [frontmostWindowElement rectOfElement]; if (CGRectIsNull(previousWindowRect)) { return; } [frontmostWindowElement setRectOfElement:windowRect]; [_innerWindowMover moveWindowRect:windowRect frameOfScreen:frameOfScreen visibleFrameOfScreen:visibleFrameOfScreen frontmostWindowElement:frontmostWindowElement action:action]; } @end ================================================ FILE: Spectacle/Sources/SpectacleUtilities.h ================================================ #import @interface SpectacleUtilities : NSObject + (NSString *)applicationVersion; + (void)registerDefaultsForBundle:(NSBundle *)bundle; + (void)displayRunningInBackgroundAlertWithCallback:(void (^)(BOOL, BOOL))callback; + (void)displayRestoreDefaultsAlertWithConfirmationCallback:(void (^)(void))callback; + (NSString *)pathForPreferencePaneNamed:(NSString *)preferencePaneName; @end ================================================ FILE: Spectacle/Sources/SpectacleUtilities.m ================================================ #import "SpectacleUtilities.h" @implementation SpectacleUtilities + (NSString *)applicationVersion { NSBundle *mainBundle = NSBundle.mainBundle; NSString *bundleVersion = mainBundle.infoDictionary[@"CFBundleShortVersionString"]; if (!bundleVersion) { bundleVersion = mainBundle.infoDictionary[@"CFBundleVersion"]; } return bundleVersion; } + (void)registerDefaultsForBundle:(NSBundle *)bundle { NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults; NSString *path = [bundle pathForResource:@"Defaults" ofType:@"plist"]; [defaults registerDefaults:[[NSDictionary alloc] initWithContentsOfFile:path]]; } + (void)displayRunningInBackgroundAlertWithCallback:(void (^)(BOOL, BOOL))callback { NSAlert *alert = [NSAlert new]; alert.alertStyle = NSInformationalAlertStyle; alert.showsSuppressionButton = YES; alert.messageText = NSLocalizedString(@"AlertMessageTextRunningInBackground", @"The message text of the alert displayed when prompting to run Spectacle in the background"); alert.informativeText = NSLocalizedString(@"AlertInformativeTextRunningInBackground", @"The informative text of the alert displayed when prompting to run Spectacle in the background"); [alert addButtonWithTitle:NSLocalizedString(@"ButtonLabelAffirmative", @"The button label used in the affirmative")]; [alert addButtonWithTitle:NSLocalizedString(@"ButtonLabelNegative", @"The button label used in the negative")]; NSInteger response = [alert runModal]; BOOL isAlertSuppressed = [alert.suppressionButton state] == NSOnState; switch (response) { case NSAlertFirstButtonReturn: callback(YES, isAlertSuppressed); break; case NSAlertSecondButtonReturn: callback(NO, isAlertSuppressed); break; default: break; } } + (void)displayRestoreDefaultsAlertWithConfirmationCallback:(void (^)(void))callback { NSAlert *alert = [NSAlert new]; alert.messageText = NSLocalizedString(@"AlertMessageTextRestoreDefaults", @"The message text of the alert displayed when prompting to restore Spectacle's default shortcuts"); alert.informativeText = NSLocalizedString(@"AlertInformativeTextRestoreDefaults", @"The informative text of the alert displayed when prompting to restore Spectacle's default shortcuts"); [alert addButtonWithTitle:NSLocalizedString(@"ButtonLabelAffirmative", @"The button label used in the affirmative")]; [alert addButtonWithTitle:NSLocalizedString(@"ButtonLabelNegative", @"The button label used in the negative")]; NSInteger response = [alert runModal]; switch (response) { case NSAlertFirstButtonReturn: callback(); break; case NSAlertSecondButtonReturn: break; default: break; } } + (NSString *)pathForPreferencePaneNamed:(NSString *)preferencePaneName { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSPreferencePanesDirectory, NSAllDomainsMask, YES); NSFileManager *fileManager = NSFileManager.defaultManager; NSString *preferencePanePath = nil; if (preferencePaneName) { preferencePaneName = [preferencePaneName stringByAppendingString:@".prefPane"]; for (__strong NSString *path in paths) { path = [path stringByAppendingPathComponent:preferencePaneName]; if (path && [fileManager fileExistsAtPath:path isDirectory:nil]) { preferencePanePath = path; break; } } if (!preferencePanePath) { NSLog(@"There was a problem obtaining the path for the specified preference pane: %@", preferencePaneName); } } return preferencePanePath; } @end ================================================ FILE: Spectacle/Sources/SpectacleWindowAction.h ================================================ #import typedef NSString SpectacleWindowAction; extern SpectacleWindowAction *const kSpectacleWindowActionUndo; extern SpectacleWindowAction *const kSpectacleWindowActionRedo; extern SpectacleWindowAction *const kSpectacleWindowActionLarger; extern SpectacleWindowAction *const kSpectacleWindowActionSmaller; extern SpectacleWindowAction *const kSpectacleWindowActionNone; extern SpectacleWindowAction *const kSpectacleWindowActionCenter; extern SpectacleWindowAction *const kSpectacleWindowActionFullscreen; extern SpectacleWindowAction *const kSpectacleWindowActionLeftHalf; extern SpectacleWindowAction *const kSpectacleWindowActionUpperLeft; extern SpectacleWindowAction *const kSpectacleWindowActionLowerLeft; extern SpectacleWindowAction *const kSpectacleWindowActionRightHalf; extern SpectacleWindowAction *const kSpectacleWindowActionUpperRight; extern SpectacleWindowAction *const kSpectacleWindowActionLowerRight; extern SpectacleWindowAction *const kSpectacleWindowActionTopHalf; extern SpectacleWindowAction *const kSpectacleWindowActionBottomHalf; extern SpectacleWindowAction *const kSpectacleWindowActionNextDisplay; extern SpectacleWindowAction *const kSpectacleWindowActionPreviousDisplay; extern SpectacleWindowAction *const kSpectacleWindowActionNextThird; extern SpectacleWindowAction *const kSpectacleWindowActionPreviousThird; BOOL SpectacleIsUndoWindowAction(SpectacleWindowAction *action); BOOL SpectacleIsRedoWindowAction(SpectacleWindowAction *action); BOOL SpectacleIsNextDisplayWindowAction(SpectacleWindowAction *action); BOOL SpectacleIsPreviousDisplayWindowAction(SpectacleWindowAction *action); BOOL SpectacleIsMovingToDisplayWindowAction(SpectacleWindowAction *action); ================================================ FILE: Spectacle/Sources/SpectacleWindowAction.m ================================================ #import "SpectacleWindowAction.h" SpectacleWindowAction *const kSpectacleWindowActionUndo = @"SpectacleWindowActionUndo"; SpectacleWindowAction *const kSpectacleWindowActionRedo = @"SpectacleWindowActionRedo"; SpectacleWindowAction *const kSpectacleWindowActionLarger = @"SpectacleWindowActionLarger"; SpectacleWindowAction *const kSpectacleWindowActionSmaller = @"SpectacleWindowActionSmaller"; SpectacleWindowAction *const kSpectacleWindowActionNone = @"SpectacleWindowActionNone"; SpectacleWindowAction *const kSpectacleWindowActionCenter = @"SpectacleWindowActionCenter"; SpectacleWindowAction *const kSpectacleWindowActionFullscreen = @"SpectacleWindowActionFullscreen"; SpectacleWindowAction *const kSpectacleWindowActionLeftHalf = @"SpectacleWindowActionLeftHalf"; SpectacleWindowAction *const kSpectacleWindowActionUpperLeft = @"SpectacleWindowActionUpperLeft"; SpectacleWindowAction *const kSpectacleWindowActionLowerLeft = @"SpectacleWindowActionLowerLeft"; SpectacleWindowAction *const kSpectacleWindowActionRightHalf = @"SpectacleWindowActionRightHalf"; SpectacleWindowAction *const kSpectacleWindowActionUpperRight = @"SpectacleWindowActionUpperRight"; SpectacleWindowAction *const kSpectacleWindowActionLowerRight = @"SpectacleWindowActionLowerRight"; SpectacleWindowAction *const kSpectacleWindowActionTopHalf = @"SpectacleWindowActionTopHalf"; SpectacleWindowAction *const kSpectacleWindowActionBottomHalf = @"SpectacleWindowActionBottomHalf"; SpectacleWindowAction *const kSpectacleWindowActionNextDisplay = @"SpectacleWindowActionNextDisplay"; SpectacleWindowAction *const kSpectacleWindowActionPreviousDisplay = @"SpectacleWindowActionPreviousDisplay"; SpectacleWindowAction *const kSpectacleWindowActionNextThird = @"SpectacleWindowActionNextThird"; SpectacleWindowAction *const kSpectacleWindowActionPreviousThird = @"SpectacleWindowActionPreviousThird"; BOOL SpectacleIsUndoWindowAction(SpectacleWindowAction *action) { return [action isEqualToString:kSpectacleWindowActionUndo]; } BOOL SpectacleIsRedoWindowAction(SpectacleWindowAction *action) { return [action isEqualToString:kSpectacleWindowActionRedo]; } BOOL SpectacleIsNextDisplayWindowAction(SpectacleWindowAction *action) { return [action isEqualToString:kSpectacleWindowActionNextDisplay]; } BOOL SpectacleIsPreviousDisplayWindowAction(SpectacleWindowAction *action) { return [action isEqualToString:kSpectacleWindowActionPreviousDisplay]; } BOOL SpectacleIsMovingToDisplayWindowAction(SpectacleWindowAction *action) { return SpectacleIsNextDisplayWindowAction(action) || SpectacleIsPreviousDisplayWindowAction(action); } ================================================ FILE: Spectacle/Sources/SpectacleWindowMover.h ================================================ #import #import "SpectacleWindowAction.h" @class SpectacleAccessibilityElement; @protocol SpectacleWindowMover - (instancetype)initWithInnerWindowMover:(id)innerWindowMover; + (instancetype)newWithInnerWindowMover:(id)innerWindowMover; - (void)moveWindowRect:(CGRect)windowRect frameOfScreen:(CGRect)frameOfScreen visibleFrameOfScreen:(CGRect)visibleFrameOfScreen frontmostWindowElement:(SpectacleAccessibilityElement *)frontmostWindowElement action:(SpectacleWindowAction *)action; @end ================================================ FILE: Spectacle/Sources/SpectacleWindowPositionCalculationRegistry.h ================================================ #import #import #import "SpectacleWindowAction.h" @class SpectacleWindowPositionCalculationResult; typedef CGRect *(^SpectacleWindowPositionCalculation)(CGRect windowRect, CGRect visibleFrameOfSourceScreen, CGRect visibleFrameOfDestinationScreen); @protocol SpectacleWindowPositionCalculationRegistryExports - (void)registerWindowPositionCalculation:(JSValue *)windowPositionCalculation withAction:(NSString *)action; @end @interface SpectacleWindowPositionCalculationRegistry : NSObject - (JSValue *)windowPositionCalculationWithAction:(SpectacleWindowAction *)action; @end ================================================ FILE: Spectacle/Sources/SpectacleWindowPositionCalculationRegistry.m ================================================ #import "SpectacleWindowPositionCalculationRegistry.h" #import "SpectacleJavaScriptEnvironment.h" #import "SpectacleWindowPositionCalculationResult.h" @interface SpectacleWindowPositionCalculationRegistry () @property (nonatomic, readonly, strong) SpectacleJavaScriptEnvironment *javaScriptEnvironment; @end @implementation SpectacleWindowPositionCalculationRegistry { NSMutableDictionary *_windowPositionCalculations; } - (instancetype)init { if (self = [super init]) { _windowPositionCalculations = [NSMutableDictionary new]; } return self; } - (void)registerWindowPositionCalculation:(JSValue *)windowPositionCalculation withAction:(SpectacleWindowAction *)action { _windowPositionCalculations[action] = windowPositionCalculation; } - (JSValue *)windowPositionCalculationWithAction:(SpectacleWindowAction *)action { return _windowPositionCalculations[action]; } @end ================================================ FILE: Spectacle/Sources/SpectacleWindowPositionCalculationResult.h ================================================ #import #import "SpectacleMacros.h" #import "SpectacleWindowAction.h" @interface SpectacleWindowPositionCalculationResult : NSObject @property (nonatomic, readonly, strong) SpectacleWindowAction *action; @property (nonatomic, readonly, assign) CGRect windowRect; - (instancetype)initWithAction:(SpectacleWindowAction *)action windowRect:(CGRect)windowRect NS_DESIGNATED_INITIALIZER; + (instancetype)resultWithAction:(SpectacleWindowAction *)action windowRect:(CGRect)windowRect; SPECTACLE_INIT_AND_NEW_UNAVAILABLE @end ================================================ FILE: Spectacle/Sources/SpectacleWindowPositionCalculationResult.m ================================================ #import "SpectacleWindowPositionCalculationResult.h" @implementation SpectacleWindowPositionCalculationResult @synthesize action = _action; @synthesize windowRect = _windowRect; - (instancetype)initWithAction:(SpectacleWindowAction *)action windowRect:(CGRect)windowRect { if (self = [super init]) { _action = action; _windowRect = windowRect; } return self; } + (instancetype)resultWithAction:(SpectacleWindowAction *)action windowRect:(CGRect)windowRect { return [[SpectacleWindowPositionCalculationResult alloc] initWithAction:action windowRect:windowRect]; } @end ================================================ FILE: Spectacle/Sources/SpectacleWindowPositionCalculator.h ================================================ #import #import "SpectacleMacros.h" #import "SpectacleWindowAction.h" @class SpectacleWindowPositionCalculationResult; @interface SpectacleWindowPositionCalculator : NSObject - (instancetype)initWithErrorHandler:(void(^)(NSString *message))errorHandler NS_DESIGNATED_INITIALIZER; - (SpectacleWindowPositionCalculationResult *)calculateWindowRect:(CGRect)windowRect visibleFrameOfSourceScreen:(CGRect)visibleFrameOfSourceScreen visibleFrameOfDestinationScreen:(CGRect)visibleFrameOfDestinationScreen action:(SpectacleWindowAction *)action; SPECTACLE_INIT_AND_NEW_UNAVAILABLE @end ================================================ FILE: Spectacle/Sources/SpectacleWindowPositionCalculator.m ================================================ #import "SpectacleWindowPositionCalculator.h" #import #import "SpectacleJavaScriptEnvironment.h" #import "SpectacleWindowPositionCalculationRegistry.h" #import "SpectacleWindowPositionCalculationResult.h" @implementation SpectacleWindowPositionCalculator { SpectacleWindowPositionCalculationRegistry *_windowPositionCalculationRegistry; SpectacleJavaScriptEnvironment *_javaScriptEnvironment; } - (instancetype)initWithErrorHandler:(void(^)(NSString *message))errorHandler { if (self = [super init]) { _windowPositionCalculationRegistry = [SpectacleWindowPositionCalculationRegistry new]; _javaScriptEnvironment = [[SpectacleJavaScriptEnvironment alloc] initWithContextBuilder:^(JSContext *context) { context.exceptionHandler = ^(JSContext *context, JSValue *exception) { NSString *errorName = [exception[@"name"] toString]; NSString *errorMessage = [exception[@"message"] toString]; errorHandler([NSString stringWithFormat:@"%@\n%@", errorName, errorMessage]); }; context[@"windowPositionCalculationRegistry"] = self->_windowPositionCalculationRegistry; context[@"CGRectContainsRect"] = ^BOOL(CGRect rect1, CGRect rect2) { return CGRectContainsRect(rect1, rect2); }; context[@"CGRectEqualToRect"] = ^BOOL(CGRect rect1, CGRect rect2) { return CGRectEqualToRect(rect1, rect2); }; context[@"CGRectGetMinX"] = ^CGFloat(CGRect rect) { return CGRectGetMinX(rect); }; context[@"CGRectGetMinY"] = ^CGFloat(CGRect rect) { return CGRectGetMinY(rect); }; context[@"CGRectGetMidX"] = ^CGFloat(CGRect rect) { return CGRectGetMidX(rect); }; context[@"CGRectGetMidY"] = ^CGFloat(CGRect rect) { return CGRectGetMidY(rect); }; context[@"CGRectGetMaxX"] = ^CGFloat(CGRect rect) { return CGRectGetMaxX(rect); }; context[@"CGRectGetMaxY"] = ^CGFloat(CGRect rect) { return CGRectGetMaxY(rect); }; }]; } return self; } - (SpectacleWindowPositionCalculationResult *)calculateWindowRect:(CGRect)windowRect visibleFrameOfSourceScreen:(CGRect)visibleFrameOfSourceScreen visibleFrameOfDestinationScreen:(CGRect)visibleFrameOfDestinationScreen action:(SpectacleWindowAction *)action { JSValue *windowPositionCalculation = [_windowPositionCalculationRegistry windowPositionCalculationWithAction:action]; if (!windowPositionCalculation) { return nil; } JSValue *result = [windowPositionCalculation callWithArguments:@[ [_javaScriptEnvironment valueWithRect:windowRect], [_javaScriptEnvironment valueWithRect:visibleFrameOfSourceScreen], [_javaScriptEnvironment valueWithRect:visibleFrameOfDestinationScreen], ]]; return [SpectacleWindowPositionCalculationResult resultWithAction:action windowRect:[result toRect]]; } @end ================================================ FILE: Spectacle/Sources/SpectacleWindowPositionManager.h ================================================ #import #import "SpectacleMacros.h" #import "SpectacleWindowAction.h" typedef void (^SpectacleFailureFeedback)(void); @class SpectacleAccessibilityElement; @class SpectacleScreenDetector; @class SpectacleShortcut; @class SpectacleWindowPositionCalculator; @protocol SpectacleWindowMover; @interface SpectacleWindowPositionManager : NSObject - (instancetype)initWithScreenDetector:(SpectacleScreenDetector *)screenDetector windowPositionCalculator:(SpectacleWindowPositionCalculator *)windowPositionCalculator sharedWorkspace:(NSWorkspace *)sharedWorkspace failureFeedback:(SpectacleFailureFeedback)failureFeedback windowMover:(id)windowMover NS_DESIGNATED_INITIALIZER; - (instancetype)initWithScreenDetector:(SpectacleScreenDetector *)screenDetector windowPositionCalculator:(SpectacleWindowPositionCalculator *)windowPositionCalculator sharedWorkspace:(NSWorkspace *)sharedWorkspace; SPECTACLE_INIT_AND_NEW_UNAVAILABLE - (void)moveFrontmostWindowElement:(SpectacleAccessibilityElement *)frontmostWindowElement action:(SpectacleWindowAction *)action screens:(NSArray *)screens mainScreen:(NSScreen *)mainScreen; - (void)moveFrontmostWindowElement:(SpectacleAccessibilityElement *)frontmostWindowElement action:(SpectacleWindowAction *)action; - (void)undoLastWindowAction; - (void)redoLastWindowAction; @end ================================================ FILE: Spectacle/Sources/SpectacleWindowPositionManager.m ================================================ #import "SpectacleWindowPositionManager.h" #import "SpectacleAccessibilityElement.h" #import "SpectacleBestEffortWindowMover.h" #import "SpectacleHistory.h" #import "SpectacleHistoryItem.h" #import "SpectacleQuantizedWindowMover.h" #import "SpectacleScreenDetectionResult.h" #import "SpectacleScreenDetector.h" #import "SpectacleShortcut.h" #import "SpectacleStandardWindowMover.h" #import "SpectacleWindowPositionCalculationResult.h" #import "SpectacleWindowPositionCalculator.h" @implementation SpectacleWindowPositionManager { NSMutableDictionary *_applicationHistories; SpectacleScreenDetector *_screenDetector; SpectacleWindowPositionCalculator *_windowPositionCalculator; NSWorkspace *_sharedWorkspace; SpectacleFailureFeedback _failureFeedback; id _windowMover; } - (instancetype)initWithScreenDetector:(SpectacleScreenDetector *)screenDetector windowPositionCalculator:(SpectacleWindowPositionCalculator *)windowPositionCalculator sharedWorkspace:(NSWorkspace *)sharedWorkspace failureFeedback:(SpectacleFailureFeedback)failureFeedback windowMover:(id)windowMover { if (self = [super init]) { _applicationHistories = [NSMutableDictionary new]; _screenDetector = screenDetector; _windowPositionCalculator = windowPositionCalculator; _sharedWorkspace = sharedWorkspace; _failureFeedback = failureFeedback; _windowMover = windowMover; } return self; } - (instancetype)initWithScreenDetector:(SpectacleScreenDetector *)screenDetector windowPositionCalculator:(SpectacleWindowPositionCalculator *)windowPositionCalculator sharedWorkspace:(NSWorkspace *)sharedWorkspace { return [self initWithScreenDetector:screenDetector windowPositionCalculator:windowPositionCalculator sharedWorkspace:sharedWorkspace failureFeedback:^() { NSBeep(); } windowMover:[SpectacleStandardWindowMover newWithInnerWindowMover: [SpectacleQuantizedWindowMover newWithInnerWindowMover: [SpectacleBestEffortWindowMover new]]]]; } - (void)moveFrontmostWindowElement:(SpectacleAccessibilityElement *)frontmostWindowElement action:(SpectacleWindowAction *)action screens:(NSArray *)screens mainScreen:(NSScreen *)mainScreen { SpectacleScreenDetectionResult *screenDetectionResult = [_screenDetector screenWithAction:action frontmostWindowElement:frontmostWindowElement screens:screens mainScreen:mainScreen]; CGRect frameOfDestinationScreen = CGRectNull; CGRect visibleFrameOfDestinationScreen = CGRectNull; CGRect visibleFrameOfSourceScreen = CGRectNull; SpectacleHistory *history = [self historyForCurrentApplication]; SpectacleHistoryItem *historyItem = nil; SpectacleWindowPositionCalculationResult *windowPositionCalculationResult = nil; if (screenDetectionResult.destinationScreen && screenDetectionResult.sourceScreen) { frameOfDestinationScreen = NSRectToCGRect([screenDetectionResult.destinationScreen frame]); visibleFrameOfDestinationScreen = NSRectToCGRect([screenDetectionResult.destinationScreen visibleFrame]); visibleFrameOfSourceScreen = NSRectToCGRect([screenDetectionResult.sourceScreen visibleFrame]); } CGRect frontmostWindowRect = [frontmostWindowElement rectOfElement]; CGRect previousFrontmostWindowRect = CGRectNull; if ([frontmostWindowElement isSheet] || [frontmostWindowElement isSystemDialog] || CGRectIsNull(frontmostWindowRect) || CGRectIsNull(frameOfDestinationScreen) || CGRectIsNull(visibleFrameOfDestinationScreen) || CGRectIsNull(visibleFrameOfSourceScreen)) { _failureFeedback(); return; } if ([history isEmpty]) { historyItem = [SpectacleHistoryItem historyItemFromAccessibilityElement:frontmostWindowElement windowRect:frontmostWindowRect]; [history addHistoryItem:historyItem]; } frontmostWindowRect = [SpectacleAccessibilityElement normalizeCoordinatesOfRect:frontmostWindowRect frameOfScreen:frameOfDestinationScreen]; previousFrontmostWindowRect = frontmostWindowRect; windowPositionCalculationResult = [_windowPositionCalculator calculateWindowRect:frontmostWindowRect visibleFrameOfSourceScreen:visibleFrameOfSourceScreen visibleFrameOfDestinationScreen:visibleFrameOfDestinationScreen action:action]; if (!windowPositionCalculationResult) { _failureFeedback(); return; } action = windowPositionCalculationResult.action; frontmostWindowRect = windowPositionCalculationResult.windowRect; if (CGRectEqualToRect(previousFrontmostWindowRect, frontmostWindowRect)) { _failureFeedback(); return; } frontmostWindowRect = [SpectacleAccessibilityElement normalizeCoordinatesOfRect:frontmostWindowRect frameOfScreen:frameOfDestinationScreen]; historyItem = [SpectacleHistoryItem historyItemFromAccessibilityElement:frontmostWindowElement windowRect:frontmostWindowRect]; [history addHistoryItem:historyItem]; [_windowMover moveWindowRect:frontmostWindowRect frameOfScreen:frameOfDestinationScreen visibleFrameOfScreen:visibleFrameOfDestinationScreen frontmostWindowElement:frontmostWindowElement action:action]; } - (void)moveFrontmostWindowElement:(SpectacleAccessibilityElement *)frontmostWindowElement action:(SpectacleWindowAction *)action { if (SpectacleIsUndoWindowAction(action)) { [self undoLastWindowAction]; } else if (SpectacleIsRedoWindowAction(action)) { [self redoLastWindowAction]; } else { [self moveFrontmostWindowElement:frontmostWindowElement action:action screens:[NSScreen screens] mainScreen:[NSScreen mainScreen]]; } } - (void)undoLastWindowAction { [self moveWithHistoryItem:[self historyForCurrentApplication].previousHistoryItem action:kSpectacleWindowActionUndo screens:[NSScreen screens] mainScreen:[NSScreen mainScreen]]; } - (void)redoLastWindowAction { [self moveWithHistoryItem:[self historyForCurrentApplication].nextHistoryItem action:kSpectacleWindowActionRedo screens:[NSScreen screens] mainScreen:[NSScreen mainScreen]]; } - (SpectacleHistory *)historyForCurrentApplication { NSString *frontmostApplicationBundleIdentifier = _sharedWorkspace.frontmostApplication.bundleIdentifier; if (!frontmostApplicationBundleIdentifier) { return nil; } if (!_applicationHistories[frontmostApplicationBundleIdentifier]) { _applicationHistories[frontmostApplicationBundleIdentifier] = [SpectacleHistory new]; } return _applicationHistories[frontmostApplicationBundleIdentifier]; } - (void)moveWithHistoryItem:(SpectacleHistoryItem *)historyItem action:(SpectacleWindowAction *)action screens:(NSArray *)screens mainScreen:(NSScreen *)mainScreen { SpectacleScreenDetectionResult *screenDetectionResult = [_screenDetector screenWithAction:action frontmostWindowElement:historyItem.accessibilityElement screens:screens mainScreen:mainScreen]; CGRect visibleFrameOfScreen = CGRectNull; if (screenDetectionResult.destinationScreen) { visibleFrameOfScreen = NSRectToCGRect(screenDetectionResult.destinationScreen.visibleFrame); } if (![self moveWithHistoryItem:historyItem visibleFrameOfScreen:visibleFrameOfScreen action:action]) { _failureFeedback(); } } - (BOOL)moveWithHistoryItem:(SpectacleHistoryItem *)historyItem visibleFrameOfScreen:(CGRect)visibleFrameOfScreen action:(SpectacleWindowAction *)action { SpectacleAccessibilityElement *frontmostWindowElement = historyItem.accessibilityElement; CGRect windowRect = historyItem.windowRect; if (!historyItem || !frontmostWindowElement || CGRectIsNull(windowRect) || CGRectIsNull(visibleFrameOfScreen)) { return NO; } [_windowMover moveWindowRect:windowRect frameOfScreen:CGRectNull visibleFrameOfScreen:visibleFrameOfScreen frontmostWindowElement:frontmostWindowElement action:action]; return YES; } @end ================================================ FILE: Spectacle/Supporting Files/Info.plist ================================================ CFBundleDevelopmentRegion en_US CFBundleExecutable Spectacle CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName Spectacle CFBundlePackageType APPL CFBundleShortVersionString 1.2 CFBundleSignature ZERO CFBundleVersion 0 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion 10.9 LSUIElement NSMainNibFile Spectacle NSPrincipalClass NSApplication SUAllowsAutomaticUpdates SUEnableAutomaticChecks SUFeedURL https://spectacleapp.com/updates/appcast.xml SUPublicDSAKeyFile dsa_public.pem ================================================ FILE: Spectacle/Supporting Files/main.m ================================================ #import int main(int argc, char *argv[]) { return NSApplicationMain(argc, (const char **)argv); } ================================================ FILE: Spectacle.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 47; objects = { /* Begin PBXBuildFile section */ 07838603148BD517001E7BD7 /* SpectaclePreferencesController.m in Sources */ = {isa = PBXBuildFile; fileRef = 07838602148BD517001E7BD7 /* SpectaclePreferencesController.m */; }; 2D0759A71C71746500A17E80 /* SpectacleWindowPositionCalculationRegistry.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D0759A61C71746500A17E80 /* SpectacleWindowPositionCalculationRegistry.m */; }; 2D1AE52515D715F400EAC5BD /* SpectacleRegisteredShortcutValidator.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D1AE52415D715F400EAC5BD /* SpectacleRegisteredShortcutValidator.m */; }; 2D20F0181CD18ED7003DDA5B /* SpectacleBottomHalfWindowCalculationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D20F0081CD18ED7003DDA5B /* SpectacleBottomHalfWindowCalculationSpec.m */; }; 2D20F0191CD18ED7003DDA5B /* SpectacleCenterWindowCalculationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D20F0091CD18ED7003DDA5B /* SpectacleCenterWindowCalculationSpec.m */; }; 2D20F01A1CD18ED7003DDA5B /* SpectacleFullscreenWindowCalculationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D20F00A1CD18ED7003DDA5B /* SpectacleFullscreenWindowCalculationSpec.m */; }; 2D20F01B1CD18ED7003DDA5B /* SpectacleLargerWindowCalculationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D20F00B1CD18ED7003DDA5B /* SpectacleLargerWindowCalculationSpec.m */; }; 2D20F01C1CD18ED7003DDA5B /* SpectacleLeftHalfWindowCalculationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D20F00C1CD18ED7003DDA5B /* SpectacleLeftHalfWindowCalculationSpec.m */; }; 2D20F01D1CD18ED7003DDA5B /* SpectacleLowerLeftWindowCalculationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D20F00D1CD18ED7003DDA5B /* SpectacleLowerLeftWindowCalculationSpec.m */; }; 2D20F01E1CD18ED7003DDA5B /* SpectacleLowerRightWindowCalculationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D20F00E1CD18ED7003DDA5B /* SpectacleLowerRightWindowCalculationSpec.m */; }; 2D20F01F1CD18ED7003DDA5B /* SpectacleNextDisplayWindowCalculationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D20F00F1CD18ED7003DDA5B /* SpectacleNextDisplayWindowCalculationSpec.m */; }; 2D20F0201CD18ED7003DDA5B /* SpectacleNextThirdWindowCalculationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D20F0101CD18ED7003DDA5B /* SpectacleNextThirdWindowCalculationSpec.m */; }; 2D20F0211CD18ED7003DDA5B /* SpectaclePreviousDisplayWindowCalculationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D20F0111CD18ED7003DDA5B /* SpectaclePreviousDisplayWindowCalculationSpec.m */; }; 2D20F0221CD18ED7003DDA5B /* SpectaclePreviousThirdWindowCalculationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D20F0121CD18ED7003DDA5B /* SpectaclePreviousThirdWindowCalculationSpec.m */; }; 2D20F0231CD18ED7003DDA5B /* SpectacleRightHalfWindowCalculationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D20F0131CD18ED7003DDA5B /* SpectacleRightHalfWindowCalculationSpec.m */; }; 2D20F0241CD18ED7003DDA5B /* SpectacleSmallerWindowCalculationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D20F0141CD18ED7003DDA5B /* SpectacleSmallerWindowCalculationSpec.m */; }; 2D20F0251CD18ED7003DDA5B /* SpectacleTopHalfWindowCalculationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D20F0151CD18ED7003DDA5B /* SpectacleTopHalfWindowCalculationSpec.m */; }; 2D20F0261CD18ED7003DDA5B /* SpectacleUpperLeftWindowCalculationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D20F0161CD18ED7003DDA5B /* SpectacleUpperLeftWindowCalculationSpec.m */; }; 2D20F0271CD18ED7003DDA5B /* SpectacleUpperRightWindowCalculationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D20F0171CD18ED7003DDA5B /* SpectacleUpperRightWindowCalculationSpec.m */; }; 2D20F0291CD1969D003DDA5B /* SpectacleWindowPositionManagerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D20F0281CD1969D003DDA5B /* SpectacleWindowPositionManagerSpec.m */; }; 2D24ACB41CC723F600AB3984 /* SpectacleWindowAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D24ACB31CC723F600AB3984 /* SpectacleWindowAction.m */; }; 2D24ACB71CC8728E00AB3984 /* SpectacleShortcutHolder.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D24ACB61CC8728E00AB3984 /* SpectacleShortcutHolder.m */; }; 2D2629461CCECB9E009D1ED0 /* SpectacleScreenDetectionResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2629451CCECB9E009D1ED0 /* SpectacleScreenDetectionResult.m */; }; 2D279ED416BB4A7800F6894A /* SpectacleHistory.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D279ED316BB4A7800F6894A /* SpectacleHistory.m */; }; 2D3A050D194956E100CD96E7 /* SpectacleAccessibilityElement.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D3A04FD194956E100CD96E7 /* SpectacleAccessibilityElement.m */; }; 2D3A050E194956E100CD96E7 /* SpectacleShortcut.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D3A0500194956E100CD96E7 /* SpectacleShortcut.m */; }; 2D3A050F194956E100CD96E7 /* SpectacleShortcutRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D3A0502194956E100CD96E7 /* SpectacleShortcutRecorder.m */; }; 2D3A0511194956E100CD96E7 /* SpectacleShortcutTranslations.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D3A0507194956E100CD96E7 /* SpectacleShortcutTranslations.m */; }; 2D3A0512194956E100CD96E7 /* SpectacleShortcutValidation.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D3A0509194956E100CD96E7 /* SpectacleShortcutValidation.m */; }; 2D4F3ED014A27B4500C29D11 /* SpectacleScreenDetector.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D4F3ECF14A27B4500C29D11 /* SpectacleScreenDetector.m */; }; 2D5565B1181EDB7F00EFB43A /* Security & Privacy System Preferences.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 2D5565B0181EDB7F00EFB43A /* Security & Privacy System Preferences.scpt */; }; 2D5783F11BB63BDD00695771 /* SpectacleStandardWindowMover.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D5783F01BB63BDD00695771 /* SpectacleStandardWindowMover.m */; }; 2D58AE341D7D19AF00BFEEA2 /* SpectacleShortcutTranslationsSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D58AE331D7D19AF00BFEEA2 /* SpectacleShortcutTranslationsSpec.m */; }; 2D58AE361D7D2D5300BFEEA2 /* SpectacleShortcutSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D58AE351D7D2D5300BFEEA2 /* SpectacleShortcutSpec.m */; }; 2D58CC391C7030B800E3DC74 /* Window Position Calculations in Resources */ = {isa = PBXBuildFile; fileRef = 2D58CC381C7030B800E3DC74 /* Window Position Calculations */; }; 2D5A83051CDAFDEB00EBE80A /* SpectacleShortcutJSONStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D5A83041CDAFDEB00EBE80A /* SpectacleShortcutJSONStorage.m */; }; 2D5A83091CDB020A00EBE80A /* SpectacleMigratingShortcutStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D5A83081CDB020A00EBE80A /* SpectacleMigratingShortcutStorage.m */; }; 2D5A830F1CDC475B00EBE80A /* SpectacleDefaultShortcutHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D5A830E1CDC475B00EBE80A /* SpectacleDefaultShortcutHelpers.m */; }; 2D5CCCE915E96F740009CC43 /* dsa_public.pem in Resources */ = {isa = PBXBuildFile; fileRef = 2D5CCCE815E96F740009CC43 /* dsa_public.pem */; }; 2D7146541CCC12A6003B8292 /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 2D7146521CCC12A6003B8292 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 2D71466B1CCC1715003B8292 /* Expecta.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 2D7146671CCC1715003B8292 /* Expecta.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 2D71466C1CCC1715003B8292 /* OCHamcrest.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 2D7146681CCC1715003B8292 /* OCHamcrest.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 2D71466D1CCC1715003B8292 /* OCMockito.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 2D7146691CCC1715003B8292 /* OCMockito.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 2D71466E1CCC1715003B8292 /* Specta.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 2D71466A1CCC1715003B8292 /* Specta.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 2D71466F1CCC171F003B8292 /* Expecta.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D7146671CCC1715003B8292 /* Expecta.framework */; }; 2D7146701CCC171F003B8292 /* OCHamcrest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D7146681CCC1715003B8292 /* OCHamcrest.framework */; }; 2D7146711CCC171F003B8292 /* OCMockito.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D7146691CCC1715003B8292 /* OCMockito.framework */; }; 2D7146721CCC171F003B8292 /* Specta.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D71466A1CCC1715003B8292 /* Specta.framework */; }; 2D768E8D147ED51A00901E76 /* SpectacleAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D768E8C147ED51A00901E76 /* SpectacleAppDelegate.m */; }; 2D7752101B83E94C00C022EE /* Spectacle.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2DC4E3D01AD84E5C0022ABF0 /* Spectacle.xib */; }; 2D7752111B83E95000C022EE /* SpectaclePreferencesWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2DC4E3D21AD84E5C0022ABF0 /* SpectaclePreferencesWindow.xib */; }; 2D7C114A1B86C7CD00B81E11 /* SpectacleShortcutUserDefaultsStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D7C11491B86C7CD00B81E11 /* SpectacleShortcutUserDefaultsStorage.m */; }; 2D7CF9EB1C6FED50004DC708 /* SpectacleWindowPositionCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D7CF9EA1C6FED50004DC708 /* SpectacleWindowPositionCalculator.m */; }; 2D8DA04D147E0FA0003BE1D3 /* Defaults.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0706C0431191092B0068F04B /* Defaults.plist */; }; 2D8DA056147E113D003BE1D3 /* SpectacleShortcutManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 07678096118679C600614FB5 /* SpectacleShortcutManager.m */; }; 2D8DA057147E113D003BE1D3 /* SpectacleUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 07BB542C1186662F0045663F /* SpectacleUtilities.m */; }; 2D8DA058147E113D003BE1D3 /* SpectacleWindowPositionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 072EACFF118A4D5A0015FC8A /* SpectacleWindowPositionManager.m */; }; 2D8DA05D147E113D003BE1D3 /* SpectacleHistoryItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 079CDA0E1221BAFF0099612C /* SpectacleHistoryItem.m */; }; 2D8DA05E147E113D003BE1D3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 077938E51184FADB0013BC14 /* main.m */; }; 2D8E2C641C7AAD5A0074129C /* SpectacleJavaScriptEnvironment.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D8E2C631C7AAD5A0074129C /* SpectacleJavaScriptEnvironment.m */; }; 2D98D7311AE064B7007AD995 /* SpectacleWindowPositionCalculationResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D98D7301AE064B7007AD995 /* SpectacleWindowPositionCalculationResult.m */; }; 2DA23EFC1B7D736100C9E8EF /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2DA23EFB1B7D736100C9E8EF /* Images.xcassets */; }; 2DAC59F01BB238D900C660ED /* SpectacleQuantizedWindowMover.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DAC59EF1BB238D900C660ED /* SpectacleQuantizedWindowMover.m */; }; 2DAC59F31BB23BE200C660ED /* SpectacleBestEffortWindowMover.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DAC59F21BB23BE200C660ED /* SpectacleBestEffortWindowMover.m */; }; 2DC4E3D41AD84E5C0022ABF0 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 2DC4E3CA1AD84E5C0022ABF0 /* Credits.rtf */; }; 2DC4E3D51AD84E5C0022ABF0 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2DC4E3CC1AD84E5C0022ABF0 /* InfoPlist.strings */; }; 2DC4E3D61AD84E5C0022ABF0 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2DC4E3CE1AD84E5C0022ABF0 /* Localizable.strings */; }; 2DC8A0171B85097900ABEBD5 /* SpectacleLoginItemHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DC8A0161B85097900ABEBD5 /* SpectacleLoginItemHelper.m */; }; 2DE03E481D7B8F5A004E80D6 /* SpectacleShortcutKeyBindingsSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DE03E471D7B8F5A004E80D6 /* SpectacleShortcutKeyBindingsSpec.m */; }; 2DE03E4B1D7B98D6004E80D6 /* SpectacleShortcutKeyBindings.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DE03E4A1D7B98D6004E80D6 /* SpectacleShortcutKeyBindings.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 2DA5B7CC19F8BA79007EDE8E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 089C1669FE841209C02AAC07 /* Project object */; proxyType = 1; remoteGlobalIDString = 2D1E1AAF147E06AD0021D73D; remoteInfo = Spectacle; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 2D7146551CCC12A6003B8292 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 2D7146541CCC12A6003B8292 /* Sparkle.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; 2D7146661CCC16FD003B8292 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 2D71466B1CCC1715003B8292 /* Expecta.framework in CopyFiles */, 2D71466C1CCC1715003B8292 /* OCHamcrest.framework in CopyFiles */, 2D71466D1CCC1715003B8292 /* OCMockito.framework in CopyFiles */, 2D71466E1CCC1715003B8292 /* Specta.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 0706C0431191092B0068F04B /* Defaults.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Defaults.plist; path = "Spectacle/Resources/Property Lists/Defaults.plist"; sourceTree = ""; }; 072EACFE118A4D5A0015FC8A /* SpectacleWindowPositionManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; name = SpectacleWindowPositionManager.h; path = Spectacle/Sources/SpectacleWindowPositionManager.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 072EACFF118A4D5A0015FC8A /* SpectacleWindowPositionManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = SpectacleWindowPositionManager.m; path = Spectacle/Sources/SpectacleWindowPositionManager.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 07678095118679C600614FB5 /* SpectacleShortcutManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; name = SpectacleShortcutManager.h; path = Spectacle/Sources/SpectacleShortcutManager.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 07678096118679C600614FB5 /* SpectacleShortcutManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = SpectacleShortcutManager.m; path = Spectacle/Sources/SpectacleShortcutManager.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 077938E51184FADB0013BC14 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = main.m; path = "Spectacle/Supporting Files/main.m"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 07838601148BD517001E7BD7 /* SpectaclePreferencesController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectaclePreferencesController.h; path = Spectacle/Sources/SpectaclePreferencesController.h; sourceTree = ""; }; 07838602148BD517001E7BD7 /* SpectaclePreferencesController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectaclePreferencesController.m; path = Spectacle/Sources/SpectaclePreferencesController.m; sourceTree = ""; }; 079CDA0D1221BAFF0099612C /* SpectacleHistoryItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleHistoryItem.h; path = Spectacle/Sources/SpectacleHistoryItem.h; sourceTree = ""; }; 079CDA0E1221BAFF0099612C /* SpectacleHistoryItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleHistoryItem.m; path = Spectacle/Sources/SpectacleHistoryItem.m; sourceTree = ""; }; 07BB542B1186662F0045663F /* SpectacleUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; name = SpectacleUtilities.h; path = Spectacle/Sources/SpectacleUtilities.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 07BB542C1186662F0045663F /* SpectacleUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = SpectacleUtilities.m; path = Spectacle/Sources/SpectacleUtilities.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 2D0759A51C71746500A17E80 /* SpectacleWindowPositionCalculationRegistry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleWindowPositionCalculationRegistry.h; path = Spectacle/Sources/SpectacleWindowPositionCalculationRegistry.h; sourceTree = ""; }; 2D0759A61C71746500A17E80 /* SpectacleWindowPositionCalculationRegistry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleWindowPositionCalculationRegistry.m; path = Spectacle/Sources/SpectacleWindowPositionCalculationRegistry.m; sourceTree = ""; }; 2D1AE52315D715F400EAC5BD /* SpectacleRegisteredShortcutValidator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; name = SpectacleRegisteredShortcutValidator.h; path = Spectacle/Sources/SpectacleRegisteredShortcutValidator.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 2D1AE52415D715F400EAC5BD /* SpectacleRegisteredShortcutValidator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = SpectacleRegisteredShortcutValidator.m; path = Spectacle/Sources/SpectacleRegisteredShortcutValidator.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 2D1E1AB0147E06AD0021D73D /* Spectacle.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Spectacle.app; sourceTree = BUILT_PRODUCTS_DIR; }; 2D1E1AD2147E07E50021D73D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = "Spectacle/Supporting Files/Info.plist"; sourceTree = ""; }; 2D20F0081CD18ED7003DDA5B /* SpectacleBottomHalfWindowCalculationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleBottomHalfWindowCalculationSpec.m; path = Sources/SpectacleBottomHalfWindowCalculationSpec.m; sourceTree = ""; }; 2D20F0091CD18ED7003DDA5B /* SpectacleCenterWindowCalculationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleCenterWindowCalculationSpec.m; path = Sources/SpectacleCenterWindowCalculationSpec.m; sourceTree = ""; }; 2D20F00A1CD18ED7003DDA5B /* SpectacleFullscreenWindowCalculationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleFullscreenWindowCalculationSpec.m; path = Sources/SpectacleFullscreenWindowCalculationSpec.m; sourceTree = ""; }; 2D20F00B1CD18ED7003DDA5B /* SpectacleLargerWindowCalculationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleLargerWindowCalculationSpec.m; path = Sources/SpectacleLargerWindowCalculationSpec.m; sourceTree = ""; }; 2D20F00C1CD18ED7003DDA5B /* SpectacleLeftHalfWindowCalculationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleLeftHalfWindowCalculationSpec.m; path = Sources/SpectacleLeftHalfWindowCalculationSpec.m; sourceTree = ""; }; 2D20F00D1CD18ED7003DDA5B /* SpectacleLowerLeftWindowCalculationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleLowerLeftWindowCalculationSpec.m; path = Sources/SpectacleLowerLeftWindowCalculationSpec.m; sourceTree = ""; }; 2D20F00E1CD18ED7003DDA5B /* SpectacleLowerRightWindowCalculationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleLowerRightWindowCalculationSpec.m; path = Sources/SpectacleLowerRightWindowCalculationSpec.m; sourceTree = ""; }; 2D20F00F1CD18ED7003DDA5B /* SpectacleNextDisplayWindowCalculationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleNextDisplayWindowCalculationSpec.m; path = Sources/SpectacleNextDisplayWindowCalculationSpec.m; sourceTree = ""; }; 2D20F0101CD18ED7003DDA5B /* SpectacleNextThirdWindowCalculationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleNextThirdWindowCalculationSpec.m; path = Sources/SpectacleNextThirdWindowCalculationSpec.m; sourceTree = ""; }; 2D20F0111CD18ED7003DDA5B /* SpectaclePreviousDisplayWindowCalculationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectaclePreviousDisplayWindowCalculationSpec.m; path = Sources/SpectaclePreviousDisplayWindowCalculationSpec.m; sourceTree = ""; }; 2D20F0121CD18ED7003DDA5B /* SpectaclePreviousThirdWindowCalculationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectaclePreviousThirdWindowCalculationSpec.m; path = Sources/SpectaclePreviousThirdWindowCalculationSpec.m; sourceTree = ""; }; 2D20F0131CD18ED7003DDA5B /* SpectacleRightHalfWindowCalculationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleRightHalfWindowCalculationSpec.m; path = Sources/SpectacleRightHalfWindowCalculationSpec.m; sourceTree = ""; }; 2D20F0141CD18ED7003DDA5B /* SpectacleSmallerWindowCalculationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleSmallerWindowCalculationSpec.m; path = Sources/SpectacleSmallerWindowCalculationSpec.m; sourceTree = ""; }; 2D20F0151CD18ED7003DDA5B /* SpectacleTopHalfWindowCalculationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleTopHalfWindowCalculationSpec.m; path = Sources/SpectacleTopHalfWindowCalculationSpec.m; sourceTree = ""; }; 2D20F0161CD18ED7003DDA5B /* SpectacleUpperLeftWindowCalculationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleUpperLeftWindowCalculationSpec.m; path = Sources/SpectacleUpperLeftWindowCalculationSpec.m; sourceTree = ""; }; 2D20F0171CD18ED7003DDA5B /* SpectacleUpperRightWindowCalculationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleUpperRightWindowCalculationSpec.m; path = Sources/SpectacleUpperRightWindowCalculationSpec.m; sourceTree = ""; }; 2D20F0281CD1969D003DDA5B /* SpectacleWindowPositionManagerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleWindowPositionManagerSpec.m; path = Sources/SpectacleWindowPositionManagerSpec.m; sourceTree = ""; }; 2D2490DF1C7A5BD900A4282B /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = Spectacle/Resources/Localizations/fi.lproj/Spectacle.strings; sourceTree = ""; }; 2D2490E01C7A5BD900A4282B /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = Spectacle/Resources/Localizations/fi.lproj/SpectaclePreferencesWindow.strings; sourceTree = ""; }; 2D2490E11C7A5BD900A4282B /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = fi; path = Spectacle/Resources/Localizations/fi.lproj/Credits.rtf; sourceTree = ""; }; 2D2490E21C7A5BD900A4282B /* fi */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = fi; path = Spectacle/Resources/Localizations/fi.lproj/InfoPlist.strings; sourceTree = ""; }; 2D2490E31C7A5BD900A4282B /* fi */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = fi; path = Spectacle/Resources/Localizations/fi.lproj/Localizable.strings; sourceTree = ""; }; 2D24ACB31CC723F600AB3984 /* SpectacleWindowAction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleWindowAction.m; path = Spectacle/Sources/SpectacleWindowAction.m; sourceTree = ""; }; 2D24ACB51CC8728E00AB3984 /* SpectacleShortcutHolder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleShortcutHolder.h; path = Spectacle/Sources/SpectacleShortcutHolder.h; sourceTree = ""; }; 2D24ACB61CC8728E00AB3984 /* SpectacleShortcutHolder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = SpectacleShortcutHolder.m; path = Spectacle/Sources/SpectacleShortcutHolder.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 2D2629441CCECB9E009D1ED0 /* SpectacleScreenDetectionResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleScreenDetectionResult.h; path = Spectacle/Sources/SpectacleScreenDetectionResult.h; sourceTree = ""; }; 2D2629451CCECB9E009D1ED0 /* SpectacleScreenDetectionResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleScreenDetectionResult.m; path = Spectacle/Sources/SpectacleScreenDetectionResult.m; sourceTree = ""; }; 2D279ED216BB4A7800F6894A /* SpectacleHistory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleHistory.h; path = Spectacle/Sources/SpectacleHistory.h; sourceTree = ""; }; 2D279ED316BB4A7800F6894A /* SpectacleHistory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = SpectacleHistory.m; path = Spectacle/Sources/SpectacleHistory.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 2D2A61A31DC2CD9C00D86193 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = Spectacle/Resources/Localizations/es.lproj/Spectacle.strings; sourceTree = ""; }; 2D2A61A41DC2CD9C00D86193 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = Spectacle/Resources/Localizations/es.lproj/SpectaclePreferencesWindow.strings; sourceTree = ""; }; 2D2A61A51DC2CD9C00D86193 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = es; path = Spectacle/Resources/Localizations/es.lproj/Credits.rtf; sourceTree = ""; }; 2D2A61A61DC2CD9C00D86193 /* es */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = es; path = Spectacle/Resources/Localizations/es.lproj/InfoPlist.strings; sourceTree = ""; }; 2D2A61A71DC2CD9C00D86193 /* es */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = es; path = Spectacle/Resources/Localizations/es.lproj/Localizable.strings; sourceTree = ""; }; 2D3A04FC194956E100CD96E7 /* SpectacleAccessibilityElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; name = SpectacleAccessibilityElement.h; path = Spectacle/Sources/SpectacleAccessibilityElement.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 2D3A04FD194956E100CD96E7 /* SpectacleAccessibilityElement.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = SpectacleAccessibilityElement.m; path = Spectacle/Sources/SpectacleAccessibilityElement.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 2D3A04FF194956E100CD96E7 /* SpectacleShortcut.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; name = SpectacleShortcut.h; path = Spectacle/Sources/SpectacleShortcut.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 2D3A0500194956E100CD96E7 /* SpectacleShortcut.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = SpectacleShortcut.m; path = Spectacle/Sources/SpectacleShortcut.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 2D3A0501194956E100CD96E7 /* SpectacleShortcutRecorder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleShortcutRecorder.h; path = Spectacle/Sources/SpectacleShortcutRecorder.h; sourceTree = ""; }; 2D3A0502194956E100CD96E7 /* SpectacleShortcutRecorder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleShortcutRecorder.m; path = Spectacle/Sources/SpectacleShortcutRecorder.m; sourceTree = ""; }; 2D3A0505194956E100CD96E7 /* SpectacleShortcutRecorderDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleShortcutRecorderDelegate.h; path = Spectacle/Sources/SpectacleShortcutRecorderDelegate.h; sourceTree = ""; }; 2D3A0506194956E100CD96E7 /* SpectacleShortcutTranslations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleShortcutTranslations.h; path = Spectacle/Sources/SpectacleShortcutTranslations.h; sourceTree = ""; }; 2D3A0507194956E100CD96E7 /* SpectacleShortcutTranslations.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = SpectacleShortcutTranslations.m; path = Spectacle/Sources/SpectacleShortcutTranslations.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 2D3A0508194956E100CD96E7 /* SpectacleShortcutValidation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleShortcutValidation.h; path = Spectacle/Sources/SpectacleShortcutValidation.h; sourceTree = ""; }; 2D3A0509194956E100CD96E7 /* SpectacleShortcutValidation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = SpectacleShortcutValidation.m; path = Spectacle/Sources/SpectacleShortcutValidation.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 2D3A050A194956E100CD96E7 /* SpectacleShortcutValidator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleShortcutValidator.h; path = Spectacle/Sources/SpectacleShortcutValidator.h; sourceTree = ""; }; 2D4F3ECE14A27B4500C29D11 /* SpectacleScreenDetector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleScreenDetector.h; path = Spectacle/Sources/SpectacleScreenDetector.h; sourceTree = ""; }; 2D4F3ECF14A27B4500C29D11 /* SpectacleScreenDetector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleScreenDetector.m; path = Spectacle/Sources/SpectacleScreenDetector.m; sourceTree = ""; }; 2D5565B0181EDB7F00EFB43A /* Security & Privacy System Preferences.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; name = "Security & Privacy System Preferences.scpt"; path = "Spectacle/Resources/Scripts/Security & Privacy System Preferences.scpt"; sourceTree = ""; }; 2D5783EF1BB63BDD00695771 /* SpectacleStandardWindowMover.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleStandardWindowMover.h; path = Spectacle/Sources/SpectacleStandardWindowMover.h; sourceTree = ""; }; 2D5783F01BB63BDD00695771 /* SpectacleStandardWindowMover.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleStandardWindowMover.m; path = Spectacle/Sources/SpectacleStandardWindowMover.m; sourceTree = ""; }; 2D58AE331D7D19AF00BFEEA2 /* SpectacleShortcutTranslationsSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleShortcutTranslationsSpec.m; path = Sources/SpectacleShortcutTranslationsSpec.m; sourceTree = ""; }; 2D58AE351D7D2D5300BFEEA2 /* SpectacleShortcutSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = SpectacleShortcutSpec.m; path = Sources/SpectacleShortcutSpec.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 2D58CC381C7030B800E3DC74 /* Window Position Calculations */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "Window Position Calculations"; path = "Spectacle/Resources/Window Position Calculations"; sourceTree = ""; }; 2D5A83031CDAFDEB00EBE80A /* SpectacleShortcutJSONStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleShortcutJSONStorage.h; path = Spectacle/Sources/SpectacleShortcutJSONStorage.h; sourceTree = ""; }; 2D5A83041CDAFDEB00EBE80A /* SpectacleShortcutJSONStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = SpectacleShortcutJSONStorage.m; path = Spectacle/Sources/SpectacleShortcutJSONStorage.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 2D5A83071CDB020A00EBE80A /* SpectacleMigratingShortcutStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleMigratingShortcutStorage.h; path = Spectacle/Sources/SpectacleMigratingShortcutStorage.h; sourceTree = ""; }; 2D5A83081CDB020A00EBE80A /* SpectacleMigratingShortcutStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleMigratingShortcutStorage.m; path = Spectacle/Sources/SpectacleMigratingShortcutStorage.m; sourceTree = ""; }; 2D5A830C1CDC363B00EBE80A /* SpectacleShortcutStorageMigrationDestination.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SpectacleShortcutStorageMigrationDestination.h; path = Spectacle/Sources/SpectacleShortcutStorageMigrationDestination.h; sourceTree = ""; }; 2D5A830D1CDC475B00EBE80A /* SpectacleDefaultShortcutHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleDefaultShortcutHelpers.h; path = Spectacle/Sources/SpectacleDefaultShortcutHelpers.h; sourceTree = ""; }; 2D5A830E1CDC475B00EBE80A /* SpectacleDefaultShortcutHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = SpectacleDefaultShortcutHelpers.m; path = Spectacle/Sources/SpectacleDefaultShortcutHelpers.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 2D5CCCE815E96F740009CC43 /* dsa_public.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = dsa_public.pem; path = Spectacle/Resources/Certificates/dsa_public.pem; sourceTree = ""; }; 2D7146521CCC12A6003B8292 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Carthage/Build/Mac/Sparkle.framework; sourceTree = ""; }; 2D7146671CCC1715003B8292 /* Expecta.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Expecta.framework; path = ../Carthage/Build/Mac/Expecta.framework; sourceTree = ""; }; 2D7146681CCC1715003B8292 /* OCHamcrest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OCHamcrest.framework; path = ../Carthage/Build/Mac/OCHamcrest.framework; sourceTree = ""; }; 2D7146691CCC1715003B8292 /* OCMockito.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OCMockito.framework; path = ../Carthage/Build/Mac/OCMockito.framework; sourceTree = ""; }; 2D71466A1CCC1715003B8292 /* Specta.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Specta.framework; path = ../Carthage/Build/Mac/Specta.framework; sourceTree = ""; }; 2D768E8B147ED51A00901E76 /* SpectacleAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; name = SpectacleAppDelegate.h; path = Spectacle/Sources/SpectacleAppDelegate.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 2D768E8C147ED51A00901E76 /* SpectacleAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = SpectacleAppDelegate.m; path = Spectacle/Sources/SpectacleAppDelegate.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 2D7C11471B86C63A00B81E11 /* SpectacleShortcutStorage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SpectacleShortcutStorage.h; path = Spectacle/Sources/SpectacleShortcutStorage.h; sourceTree = ""; }; 2D7C11481B86C7CD00B81E11 /* SpectacleShortcutUserDefaultsStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleShortcutUserDefaultsStorage.h; path = Spectacle/Sources/SpectacleShortcutUserDefaultsStorage.h; sourceTree = ""; }; 2D7C11491B86C7CD00B81E11 /* SpectacleShortcutUserDefaultsStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleShortcutUserDefaultsStorage.m; path = Spectacle/Sources/SpectacleShortcutUserDefaultsStorage.m; sourceTree = ""; }; 2D7CF9E91C6FED50004DC708 /* SpectacleWindowPositionCalculator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleWindowPositionCalculator.h; path = Spectacle/Sources/SpectacleWindowPositionCalculator.h; sourceTree = ""; }; 2D7CF9EA1C6FED50004DC708 /* SpectacleWindowPositionCalculator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleWindowPositionCalculator.m; path = Spectacle/Sources/SpectacleWindowPositionCalculator.m; sourceTree = ""; }; 2D8E2C621C7AAD5A0074129C /* SpectacleJavaScriptEnvironment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleJavaScriptEnvironment.h; path = Spectacle/Sources/SpectacleJavaScriptEnvironment.h; sourceTree = ""; }; 2D8E2C631C7AAD5A0074129C /* SpectacleJavaScriptEnvironment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleJavaScriptEnvironment.m; path = Spectacle/Sources/SpectacleJavaScriptEnvironment.m; sourceTree = ""; }; 2D98D72C1ADDBBF6007AD995 /* fr */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = fr; path = Spectacle/Resources/Localizations/fr.lproj/InfoPlist.strings; sourceTree = ""; }; 2D98D72D1ADDBBF6007AD995 /* fr */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = fr; path = Spectacle/Resources/Localizations/fr.lproj/Localizable.strings; sourceTree = ""; }; 2D98D72E1ADDBBF6007AD995 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = fr; path = Spectacle/Resources/Localizations/fr.lproj/Credits.rtf; sourceTree = ""; }; 2D98D72F1AE064B7007AD995 /* SpectacleWindowPositionCalculationResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleWindowPositionCalculationResult.h; path = Spectacle/Sources/SpectacleWindowPositionCalculationResult.h; sourceTree = ""; }; 2D98D7301AE064B7007AD995 /* SpectacleWindowPositionCalculationResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = SpectacleWindowPositionCalculationResult.m; path = Spectacle/Sources/SpectacleWindowPositionCalculationResult.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 2DA23EFB1B7D736100C9E8EF /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Spectacle/Resources/Images.xcassets; sourceTree = ""; }; 2DA23EFD1B80FB6400C9E8EF /* it */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = it; path = Spectacle/Resources/Localizations/it.lproj/InfoPlist.strings; sourceTree = ""; }; 2DA23EFE1B80FF6F00C9E8EF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Spectacle/Resources/Localizations/Base.lproj/Spectacle.xib; sourceTree = ""; }; 2DA23EFF1B80FF6F00C9E8EF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Spectacle/Resources/Localizations/Base.lproj/SpectaclePreferencesWindow.xib; sourceTree = ""; }; 2DA23F031B81016B00C9E8EF /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = Spectacle/Resources/Localizations/fr.lproj/Spectacle.strings; sourceTree = ""; }; 2DA23F051B81017000C9E8EF /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = Spectacle/Resources/Localizations/fr.lproj/SpectaclePreferencesWindow.strings; sourceTree = ""; }; 2DA23F071B8109D400C9E8EF /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = Spectacle/Resources/Localizations/it.lproj/Spectacle.strings; sourceTree = ""; }; 2DA23F091B8109D900C9E8EF /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = Spectacle/Resources/Localizations/it.lproj/SpectaclePreferencesWindow.strings; sourceTree = ""; }; 2DA5B7C619F8BA79007EDE8E /* SpectacleSpecs.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SpectacleSpecs.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 2DA5B7C919F8BA79007EDE8E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = "Supporting Files/Info.plist"; sourceTree = ""; }; 2DAC59E81BB234F500C660ED /* SpectacleWindowMover.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SpectacleWindowMover.h; path = Spectacle/Sources/SpectacleWindowMover.h; sourceTree = ""; }; 2DAC59EE1BB238D900C660ED /* SpectacleQuantizedWindowMover.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleQuantizedWindowMover.h; path = Spectacle/Sources/SpectacleQuantizedWindowMover.h; sourceTree = ""; }; 2DAC59EF1BB238D900C660ED /* SpectacleQuantizedWindowMover.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleQuantizedWindowMover.m; path = Spectacle/Sources/SpectacleQuantizedWindowMover.m; sourceTree = ""; }; 2DAC59F11BB23BE200C660ED /* SpectacleBestEffortWindowMover.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleBestEffortWindowMover.h; path = Spectacle/Sources/SpectacleBestEffortWindowMover.h; sourceTree = ""; }; 2DAC59F21BB23BE200C660ED /* SpectacleBestEffortWindowMover.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleBestEffortWindowMover.m; path = Spectacle/Sources/SpectacleBestEffortWindowMover.m; sourceTree = ""; }; 2DAC59F51BB241DE00C660ED /* SpectacleWindowAction.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SpectacleWindowAction.h; path = Spectacle/Sources/SpectacleWindowAction.h; sourceTree = ""; }; 2DB075441CD041A5001CE204 /* SpectacleMacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleMacros.h; path = Spectacle/Sources/SpectacleMacros.h; sourceTree = ""; }; 2DC4E3CB1AD84E5C0022ABF0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = Spectacle/Resources/Localizations/en.lproj/Credits.rtf; sourceTree = ""; }; 2DC4E3CD1AD84E5C0022ABF0 /* en */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = en; path = Spectacle/Resources/Localizations/en.lproj/InfoPlist.strings; sourceTree = ""; }; 2DC4E3CF1AD84E5C0022ABF0 /* en */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = en; path = Spectacle/Resources/Localizations/en.lproj/Localizable.strings; sourceTree = ""; }; 2DC4E3D91AD84E7F0022ABF0 /* it */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = it; path = Spectacle/Resources/Localizations/it.lproj/Localizable.strings; sourceTree = ""; }; 2DC4E3DA1AD84E7F0022ABF0 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = it; path = Spectacle/Resources/Localizations/it.lproj/Credits.rtf; sourceTree = ""; }; 2DC8A0151B85097900ABEBD5 /* SpectacleLoginItemHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleLoginItemHelper.h; path = Spectacle/Sources/SpectacleLoginItemHelper.h; sourceTree = ""; }; 2DC8A0161B85097900ABEBD5 /* SpectacleLoginItemHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleLoginItemHelper.m; path = Spectacle/Sources/SpectacleLoginItemHelper.m; sourceTree = ""; }; 2DE03E471D7B8F5A004E80D6 /* SpectacleShortcutKeyBindingsSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SpectacleShortcutKeyBindingsSpec.m; path = Sources/SpectacleShortcutKeyBindingsSpec.m; sourceTree = ""; }; 2DE03E491D7B98D6004E80D6 /* SpectacleShortcutKeyBindings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SpectacleShortcutKeyBindings.h; path = Spectacle/Sources/SpectacleShortcutKeyBindings.h; sourceTree = ""; }; 2DE03E4A1D7B98D6004E80D6 /* SpectacleShortcutKeyBindings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = SpectacleShortcutKeyBindings.m; path = Spectacle/Sources/SpectacleShortcutKeyBindings.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 574F6A1A1C57BE40001A5679 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = Spectacle/Resources/Localizations/pt.lproj/Spectacle.strings; sourceTree = ""; }; 574F6A1B1C57BE40001A5679 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = Spectacle/Resources/Localizations/pt.lproj/SpectaclePreferencesWindow.strings; sourceTree = ""; }; 574F6A1C1C57BE40001A5679 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = pt; path = Spectacle/Resources/Localizations/pt.lproj/Credits.rtf; sourceTree = ""; }; 574F6A1D1C57BE40001A5679 /* pt */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = pt; path = Spectacle/Resources/Localizations/pt.lproj/InfoPlist.strings; sourceTree = ""; }; 574F6A1E1C57BE40001A5679 /* pt */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = pt; path = Spectacle/Resources/Localizations/pt.lproj/Localizable.strings; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 2D1E1AAD147E06AD0021D73D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 2DA5B7C319F8BA79007EDE8E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2D71466F1CCC171F003B8292 /* Expecta.framework in Frameworks */, 2D7146701CCC171F003B8292 /* OCHamcrest.framework in Frameworks */, 2D7146711CCC171F003B8292 /* OCMockito.framework in Frameworks */, 2D7146721CCC171F003B8292 /* Specta.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 0747DAE81183AABB00F75DD2 /* Preferences */ = { isa = PBXGroup; children = ( 2DC8A0151B85097900ABEBD5 /* SpectacleLoginItemHelper.h */, 2DC8A0161B85097900ABEBD5 /* SpectacleLoginItemHelper.m */, 07838601148BD517001E7BD7 /* SpectaclePreferencesController.h */, 07838602148BD517001E7BD7 /* SpectaclePreferencesController.m */, ); name = Preferences; sourceTree = ""; }; 0747DAE91183AAC100F75DD2 /* Accessibility API */ = { isa = PBXGroup; children = ( 2D3A04FC194956E100CD96E7 /* SpectacleAccessibilityElement.h */, 2D3A04FD194956E100CD96E7 /* SpectacleAccessibilityElement.m */, ); name = "Accessibility API"; sourceTree = ""; }; 0747DAEB1183AAE700F75DD2 /* Property Lists */ = { isa = PBXGroup; children = ( 0706C0431191092B0068F04B /* Defaults.plist */, ); name = "Property Lists"; sourceTree = ""; }; 089C166AFE841209C02AAC07 /* Spectacle */ = { isa = PBXGroup; children = ( 2D89B5731B8133E900F9FC1C /* Spectacle */, 2DA5B7C719F8BA79007EDE8E /* SpectacleSpecs */, 19C28FB8FE9D52D311CA2CBB /* Products */, ); name = Spectacle; sourceTree = ""; }; 089C1671FE841209C02AAC07 /* Frameworks */ = { isa = PBXGroup; children = ( 2D7146521CCC12A6003B8292 /* Sparkle.framework */, ); name = Frameworks; sourceTree = ""; }; 089C167CFE841241C02AAC07 /* Resources */ = { isa = PBXGroup; children = ( 2D5CCCE715E96F4F0009CC43 /* Certificates */, 0747DAEB1183AAE700F75DD2 /* Property Lists */, 2DB2DC22181ECFCF0008A192 /* Scripts */, 2DC4E3CA1AD84E5C0022ABF0 /* Credits.rtf */, 2DA23EFB1B7D736100C9E8EF /* Images.xcassets */, 2DC4E3CC1AD84E5C0022ABF0 /* InfoPlist.strings */, 2DC4E3CE1AD84E5C0022ABF0 /* Localizable.strings */, 2DC4E3D01AD84E5C0022ABF0 /* Spectacle.xib */, 2DC4E3D21AD84E5C0022ABF0 /* SpectaclePreferencesWindow.xib */, ); name = Resources; sourceTree = ""; }; 08FB77AFFE84173DC02AAC07 /* Sources */ = { isa = PBXGroup; children = ( 0747DAE81183AABB00F75DD2 /* Preferences */, 2DC8A0131B84E1AF00ABEBD5 /* Shortcuts */, 2DC8A0141B84E24500ABEBD5 /* Window Management */, 2D768E8B147ED51A00901E76 /* SpectacleAppDelegate.h */, 2D768E8C147ED51A00901E76 /* SpectacleAppDelegate.m */, 2DB075441CD041A5001CE204 /* SpectacleMacros.h */, 07BB542B1186662F0045663F /* SpectacleUtilities.h */, 07BB542C1186662F0045663F /* SpectacleUtilities.m */, ); name = Sources; sourceTree = ""; }; 19C28FB8FE9D52D311CA2CBB /* Products */ = { isa = PBXGroup; children = ( 2D1E1AB0147E06AD0021D73D /* Spectacle.app */, 2DA5B7C619F8BA79007EDE8E /* SpectacleSpecs.xctest */, ); name = Products; sourceTree = ""; }; 2D5CCCE715E96F4F0009CC43 /* Certificates */ = { isa = PBXGroup; children = ( 2D5CCCE815E96F740009CC43 /* dsa_public.pem */, ); name = Certificates; sourceTree = ""; }; 2D7146611CCC1381003B8292 /* Frameworks */ = { isa = PBXGroup; children = ( 2D7146671CCC1715003B8292 /* Expecta.framework */, 2D7146681CCC1715003B8292 /* OCHamcrest.framework */, 2D7146691CCC1715003B8292 /* OCMockito.framework */, 2D71466A1CCC1715003B8292 /* Specta.framework */, ); name = Frameworks; sourceTree = ""; }; 2D7C11441B86829300B81E11 /* Sources */ = { isa = PBXGroup; children = ( 2D20F0081CD18ED7003DDA5B /* SpectacleBottomHalfWindowCalculationSpec.m */, 2D20F0091CD18ED7003DDA5B /* SpectacleCenterWindowCalculationSpec.m */, 2D20F00A1CD18ED7003DDA5B /* SpectacleFullscreenWindowCalculationSpec.m */, 2D20F00B1CD18ED7003DDA5B /* SpectacleLargerWindowCalculationSpec.m */, 2D20F00C1CD18ED7003DDA5B /* SpectacleLeftHalfWindowCalculationSpec.m */, 2D20F00D1CD18ED7003DDA5B /* SpectacleLowerLeftWindowCalculationSpec.m */, 2D20F00E1CD18ED7003DDA5B /* SpectacleLowerRightWindowCalculationSpec.m */, 2D20F00F1CD18ED7003DDA5B /* SpectacleNextDisplayWindowCalculationSpec.m */, 2D20F0101CD18ED7003DDA5B /* SpectacleNextThirdWindowCalculationSpec.m */, 2D20F0111CD18ED7003DDA5B /* SpectaclePreviousDisplayWindowCalculationSpec.m */, 2D20F0121CD18ED7003DDA5B /* SpectaclePreviousThirdWindowCalculationSpec.m */, 2D20F0131CD18ED7003DDA5B /* SpectacleRightHalfWindowCalculationSpec.m */, 2DE03E471D7B8F5A004E80D6 /* SpectacleShortcutKeyBindingsSpec.m */, 2D58AE351D7D2D5300BFEEA2 /* SpectacleShortcutSpec.m */, 2D58AE331D7D19AF00BFEEA2 /* SpectacleShortcutTranslationsSpec.m */, 2D20F0141CD18ED7003DDA5B /* SpectacleSmallerWindowCalculationSpec.m */, 2D20F0151CD18ED7003DDA5B /* SpectacleTopHalfWindowCalculationSpec.m */, 2D20F0161CD18ED7003DDA5B /* SpectacleUpperLeftWindowCalculationSpec.m */, 2D20F0171CD18ED7003DDA5B /* SpectacleUpperRightWindowCalculationSpec.m */, 2D20F0281CD1969D003DDA5B /* SpectacleWindowPositionManagerSpec.m */, ); name = Sources; sourceTree = ""; }; 2D7C11461B86C61800B81E11 /* Shortcut Storage */ = { isa = PBXGroup; children = ( 2D5A83071CDB020A00EBE80A /* SpectacleMigratingShortcutStorage.h */, 2D5A83081CDB020A00EBE80A /* SpectacleMigratingShortcutStorage.m */, 2D7C11471B86C63A00B81E11 /* SpectacleShortcutStorage.h */, 2D5A830C1CDC363B00EBE80A /* SpectacleShortcutStorageMigrationDestination.h */, 2D5A83031CDAFDEB00EBE80A /* SpectacleShortcutJSONStorage.h */, 2D5A83041CDAFDEB00EBE80A /* SpectacleShortcutJSONStorage.m */, 2D7C11481B86C7CD00B81E11 /* SpectacleShortcutUserDefaultsStorage.h */, 2D7C11491B86C7CD00B81E11 /* SpectacleShortcutUserDefaultsStorage.m */, ); name = "Shortcut Storage"; sourceTree = ""; }; 2D7CF9E81C6FEC2E004DC708 /* Window Position Calculator */ = { isa = PBXGroup; children = ( 2D58CC381C7030B800E3DC74 /* Window Position Calculations */, 2D8E2C621C7AAD5A0074129C /* SpectacleJavaScriptEnvironment.h */, 2D8E2C631C7AAD5A0074129C /* SpectacleJavaScriptEnvironment.m */, 2D98D72F1AE064B7007AD995 /* SpectacleWindowPositionCalculationResult.h */, 2D98D7301AE064B7007AD995 /* SpectacleWindowPositionCalculationResult.m */, 2D7CF9E91C6FED50004DC708 /* SpectacleWindowPositionCalculator.h */, 2D7CF9EA1C6FED50004DC708 /* SpectacleWindowPositionCalculator.m */, 2D0759A51C71746500A17E80 /* SpectacleWindowPositionCalculationRegistry.h */, 2D0759A61C71746500A17E80 /* SpectacleWindowPositionCalculationRegistry.m */, ); name = "Window Position Calculator"; sourceTree = ""; }; 2D89B5731B8133E900F9FC1C /* Spectacle */ = { isa = PBXGroup; children = ( 089C167CFE841241C02AAC07 /* Resources */, 08FB77AFFE84173DC02AAC07 /* Sources */, 32DBCFA10370C40200C91783 /* Supporting Files */, 089C1671FE841209C02AAC07 /* Frameworks */, ); name = Spectacle; sourceTree = ""; }; 2D89B5741B81340B00F9FC1C /* Supporting Files */ = { isa = PBXGroup; children = ( 2DA5B7C919F8BA79007EDE8E /* Info.plist */, ); name = "Supporting Files"; sourceTree = ""; }; 2DA5B7C719F8BA79007EDE8E /* SpectacleSpecs */ = { isa = PBXGroup; children = ( 2D7C11441B86829300B81E11 /* Sources */, 2D89B5741B81340B00F9FC1C /* Supporting Files */, 2D7146611CCC1381003B8292 /* Frameworks */, ); path = SpectacleSpecs; sourceTree = ""; }; 2DAC59EA1BB2364700C660ED /* Window Movement History */ = { isa = PBXGroup; children = ( 2D279ED216BB4A7800F6894A /* SpectacleHistory.h */, 2D279ED316BB4A7800F6894A /* SpectacleHistory.m */, 079CDA0D1221BAFF0099612C /* SpectacleHistoryItem.h */, 079CDA0E1221BAFF0099612C /* SpectacleHistoryItem.m */, ); name = "Window Movement History"; sourceTree = ""; }; 2DAC59F41BB23DAD00C660ED /* Window Movers */ = { isa = PBXGroup; children = ( 2DAC59F11BB23BE200C660ED /* SpectacleBestEffortWindowMover.h */, 2DAC59F21BB23BE200C660ED /* SpectacleBestEffortWindowMover.m */, 2DAC59EE1BB238D900C660ED /* SpectacleQuantizedWindowMover.h */, 2DAC59EF1BB238D900C660ED /* SpectacleQuantizedWindowMover.m */, 2D5783EF1BB63BDD00695771 /* SpectacleStandardWindowMover.h */, 2D5783F01BB63BDD00695771 /* SpectacleStandardWindowMover.m */, 2DAC59E81BB234F500C660ED /* SpectacleWindowMover.h */, ); name = "Window Movers"; sourceTree = ""; }; 2DB2DC22181ECFCF0008A192 /* Scripts */ = { isa = PBXGroup; children = ( 2D5565B0181EDB7F00EFB43A /* Security & Privacy System Preferences.scpt */, ); name = Scripts; sourceTree = ""; }; 2DC8A0111B84E17400ABEBD5 /* Shortcut Management */ = { isa = PBXGroup; children = ( 07678095118679C600614FB5 /* SpectacleShortcutManager.h */, 07678096118679C600614FB5 /* SpectacleShortcutManager.m */, 2D1AE52315D715F400EAC5BD /* SpectacleRegisteredShortcutValidator.h */, 2D1AE52415D715F400EAC5BD /* SpectacleRegisteredShortcutValidator.m */, ); name = "Shortcut Management"; sourceTree = ""; }; 2DC8A0121B84E18400ABEBD5 /* Shortcut Recorder */ = { isa = PBXGroup; children = ( 2D3A0501194956E100CD96E7 /* SpectacleShortcutRecorder.h */, 2D3A0502194956E100CD96E7 /* SpectacleShortcutRecorder.m */, 2D3A0505194956E100CD96E7 /* SpectacleShortcutRecorderDelegate.h */, ); name = "Shortcut Recorder"; sourceTree = ""; }; 2DC8A0131B84E1AF00ABEBD5 /* Shortcuts */ = { isa = PBXGroup; children = ( 2DC8A0111B84E17400ABEBD5 /* Shortcut Management */, 2DC8A0121B84E18400ABEBD5 /* Shortcut Recorder */, 2D7C11461B86C61800B81E11 /* Shortcut Storage */, 2D5A830D1CDC475B00EBE80A /* SpectacleDefaultShortcutHelpers.h */, 2D5A830E1CDC475B00EBE80A /* SpectacleDefaultShortcutHelpers.m */, 2D3A04FF194956E100CD96E7 /* SpectacleShortcut.h */, 2D3A0500194956E100CD96E7 /* SpectacleShortcut.m */, 2D24ACB51CC8728E00AB3984 /* SpectacleShortcutHolder.h */, 2D24ACB61CC8728E00AB3984 /* SpectacleShortcutHolder.m */, 2DE03E491D7B98D6004E80D6 /* SpectacleShortcutKeyBindings.h */, 2DE03E4A1D7B98D6004E80D6 /* SpectacleShortcutKeyBindings.m */, 2D3A0506194956E100CD96E7 /* SpectacleShortcutTranslations.h */, 2D3A0507194956E100CD96E7 /* SpectacleShortcutTranslations.m */, 2D3A050A194956E100CD96E7 /* SpectacleShortcutValidator.h */, 2D3A0508194956E100CD96E7 /* SpectacleShortcutValidation.h */, 2D3A0509194956E100CD96E7 /* SpectacleShortcutValidation.m */, ); name = Shortcuts; sourceTree = ""; }; 2DC8A0141B84E24500ABEBD5 /* Window Management */ = { isa = PBXGroup; children = ( 0747DAE91183AAC100F75DD2 /* Accessibility API */, 2DAC59EA1BB2364700C660ED /* Window Movement History */, 2DAC59F41BB23DAD00C660ED /* Window Movers */, 2D7CF9E81C6FEC2E004DC708 /* Window Position Calculator */, 2D2629441CCECB9E009D1ED0 /* SpectacleScreenDetectionResult.h */, 2D2629451CCECB9E009D1ED0 /* SpectacleScreenDetectionResult.m */, 2D4F3ECE14A27B4500C29D11 /* SpectacleScreenDetector.h */, 2D4F3ECF14A27B4500C29D11 /* SpectacleScreenDetector.m */, 2DAC59F51BB241DE00C660ED /* SpectacleWindowAction.h */, 2D24ACB31CC723F600AB3984 /* SpectacleWindowAction.m */, 072EACFE118A4D5A0015FC8A /* SpectacleWindowPositionManager.h */, 072EACFF118A4D5A0015FC8A /* SpectacleWindowPositionManager.m */, ); name = "Window Management"; sourceTree = ""; }; 32DBCFA10370C40200C91783 /* Supporting Files */ = { isa = PBXGroup; children = ( 2D1E1AD2147E07E50021D73D /* Info.plist */, 077938E51184FADB0013BC14 /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 2D1E1AAF147E06AD0021D73D /* Spectacle */ = { isa = PBXNativeTarget; buildConfigurationList = 2D1E1ACB147E06AD0021D73D /* Build configuration list for PBXNativeTarget "Spectacle" */; buildPhases = ( 2D1E1AAC147E06AD0021D73D /* Sources */, 2D1E1AAD147E06AD0021D73D /* Frameworks */, 2D1E1AAE147E06AD0021D73D /* Resources */, 2DC25F9D19FF3F93009591A9 /* Update CFBundleVersion */, 2D7146551CCC12A6003B8292 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Spectacle; productName = Spectacle; productReference = 2D1E1AB0147E06AD0021D73D /* Spectacle.app */; productType = "com.apple.product-type.application"; }; 2DA5B7C519F8BA79007EDE8E /* SpectacleSpecs */ = { isa = PBXNativeTarget; buildConfigurationList = 2DA5B7D019F8BA79007EDE8E /* Build configuration list for PBXNativeTarget "SpectacleSpecs" */; buildPhases = ( 2DA5B7C219F8BA79007EDE8E /* Sources */, 2DA5B7C319F8BA79007EDE8E /* Frameworks */, 2DA5B7C419F8BA79007EDE8E /* Resources */, 2D7146661CCC16FD003B8292 /* CopyFiles */, ); buildRules = ( ); dependencies = ( 2DA5B7CD19F8BA79007EDE8E /* PBXTargetDependency */, ); name = SpectacleSpecs; productName = SpectacleSpecs; productReference = 2DA5B7C619F8BA79007EDE8E /* SpectacleSpecs.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 089C1669FE841209C02AAC07 /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1120; TargetAttributes = { 2DA5B7C519F8BA79007EDE8E = { CreatedOnToolsVersion = 6.1; TestTargetID = 2D1E1AAF147E06AD0021D73D; }; }; }; buildConfigurationList = 1DBD214C08BA80EA00186707 /* Build configuration list for PBXProject "Spectacle" */; compatibilityVersion = "Xcode 6.3"; developmentRegion = en; hasScannedForEncodings = 1; knownRegions = ( en, it, fr, Base, pt, fi, es, ); mainGroup = 089C166AFE841209C02AAC07 /* Spectacle */; projectDirPath = ""; projectRoot = ""; targets = ( 2D1E1AAF147E06AD0021D73D /* Spectacle */, 2DA5B7C519F8BA79007EDE8E /* SpectacleSpecs */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 2D1E1AAE147E06AD0021D73D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 2DC4E3D41AD84E5C0022ABF0 /* Credits.rtf in Resources */, 2D8DA04D147E0FA0003BE1D3 /* Defaults.plist in Resources */, 2D5CCCE915E96F740009CC43 /* dsa_public.pem in Resources */, 2DA23EFC1B7D736100C9E8EF /* Images.xcassets in Resources */, 2DC4E3D51AD84E5C0022ABF0 /* InfoPlist.strings in Resources */, 2DC4E3D61AD84E5C0022ABF0 /* Localizable.strings in Resources */, 2D5565B1181EDB7F00EFB43A /* Security & Privacy System Preferences.scpt in Resources */, 2D7752101B83E94C00C022EE /* Spectacle.xib in Resources */, 2D7752111B83E95000C022EE /* SpectaclePreferencesWindow.xib in Resources */, 2D58CC391C7030B800E3DC74 /* Window Position Calculations in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 2DA5B7C419F8BA79007EDE8E /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 2DC25F9D19FF3F93009591A9 /* Update CFBundleVersion */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Update CFBundleVersion"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "# See: http://tgoode.com/2014/06/05/sensible-way-increment-bundle-version-cfbundleversion-xcode/\nbranch=${1:-'master'}\nbuild_number=$(expr $(git rev-list $branch --count) - $(git rev-list HEAD...$branch --count))\necho \"Updating build number to $build_number using branch: $branch\"\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $build_number\" \"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 2D1E1AAC147E06AD0021D73D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 2D8DA05E147E113D003BE1D3 /* main.m in Sources */, 2D3A050D194956E100CD96E7 /* SpectacleAccessibilityElement.m in Sources */, 2D768E8D147ED51A00901E76 /* SpectacleAppDelegate.m in Sources */, 2DAC59F31BB23BE200C660ED /* SpectacleBestEffortWindowMover.m in Sources */, 2D5A830F1CDC475B00EBE80A /* SpectacleDefaultShortcutHelpers.m in Sources */, 2D279ED416BB4A7800F6894A /* SpectacleHistory.m in Sources */, 2D8DA05D147E113D003BE1D3 /* SpectacleHistoryItem.m in Sources */, 2D8E2C641C7AAD5A0074129C /* SpectacleJavaScriptEnvironment.m in Sources */, 2DC8A0171B85097900ABEBD5 /* SpectacleLoginItemHelper.m in Sources */, 2D5A83091CDB020A00EBE80A /* SpectacleMigratingShortcutStorage.m in Sources */, 07838603148BD517001E7BD7 /* SpectaclePreferencesController.m in Sources */, 2DAC59F01BB238D900C660ED /* SpectacleQuantizedWindowMover.m in Sources */, 2D1AE52515D715F400EAC5BD /* SpectacleRegisteredShortcutValidator.m in Sources */, 2D2629461CCECB9E009D1ED0 /* SpectacleScreenDetectionResult.m in Sources */, 2D4F3ED014A27B4500C29D11 /* SpectacleScreenDetector.m in Sources */, 2D3A050E194956E100CD96E7 /* SpectacleShortcut.m in Sources */, 2D24ACB71CC8728E00AB3984 /* SpectacleShortcutHolder.m in Sources */, 2D5A83051CDAFDEB00EBE80A /* SpectacleShortcutJSONStorage.m in Sources */, 2DE03E4B1D7B98D6004E80D6 /* SpectacleShortcutKeyBindings.m in Sources */, 2D8DA056147E113D003BE1D3 /* SpectacleShortcutManager.m in Sources */, 2D3A050F194956E100CD96E7 /* SpectacleShortcutRecorder.m in Sources */, 2D3A0511194956E100CD96E7 /* SpectacleShortcutTranslations.m in Sources */, 2D7C114A1B86C7CD00B81E11 /* SpectacleShortcutUserDefaultsStorage.m in Sources */, 2D3A0512194956E100CD96E7 /* SpectacleShortcutValidation.m in Sources */, 2D5783F11BB63BDD00695771 /* SpectacleStandardWindowMover.m in Sources */, 2D8DA057147E113D003BE1D3 /* SpectacleUtilities.m in Sources */, 2D24ACB41CC723F600AB3984 /* SpectacleWindowAction.m in Sources */, 2D0759A71C71746500A17E80 /* SpectacleWindowPositionCalculationRegistry.m in Sources */, 2D98D7311AE064B7007AD995 /* SpectacleWindowPositionCalculationResult.m in Sources */, 2D7CF9EB1C6FED50004DC708 /* SpectacleWindowPositionCalculator.m in Sources */, 2D8DA058147E113D003BE1D3 /* SpectacleWindowPositionManager.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 2DA5B7C219F8BA79007EDE8E /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 2D20F0181CD18ED7003DDA5B /* SpectacleBottomHalfWindowCalculationSpec.m in Sources */, 2D20F0191CD18ED7003DDA5B /* SpectacleCenterWindowCalculationSpec.m in Sources */, 2D20F01A1CD18ED7003DDA5B /* SpectacleFullscreenWindowCalculationSpec.m in Sources */, 2D20F01B1CD18ED7003DDA5B /* SpectacleLargerWindowCalculationSpec.m in Sources */, 2D20F01C1CD18ED7003DDA5B /* SpectacleLeftHalfWindowCalculationSpec.m in Sources */, 2D20F01D1CD18ED7003DDA5B /* SpectacleLowerLeftWindowCalculationSpec.m in Sources */, 2D20F01E1CD18ED7003DDA5B /* SpectacleLowerRightWindowCalculationSpec.m in Sources */, 2D20F01F1CD18ED7003DDA5B /* SpectacleNextDisplayWindowCalculationSpec.m in Sources */, 2D20F0201CD18ED7003DDA5B /* SpectacleNextThirdWindowCalculationSpec.m in Sources */, 2D20F0211CD18ED7003DDA5B /* SpectaclePreviousDisplayWindowCalculationSpec.m in Sources */, 2D20F0221CD18ED7003DDA5B /* SpectaclePreviousThirdWindowCalculationSpec.m in Sources */, 2D20F0231CD18ED7003DDA5B /* SpectacleRightHalfWindowCalculationSpec.m in Sources */, 2DE03E481D7B8F5A004E80D6 /* SpectacleShortcutKeyBindingsSpec.m in Sources */, 2D58AE361D7D2D5300BFEEA2 /* SpectacleShortcutSpec.m in Sources */, 2D58AE341D7D19AF00BFEEA2 /* SpectacleShortcutTranslationsSpec.m in Sources */, 2D20F0241CD18ED7003DDA5B /* SpectacleSmallerWindowCalculationSpec.m in Sources */, 2D20F0251CD18ED7003DDA5B /* SpectacleTopHalfWindowCalculationSpec.m in Sources */, 2D20F0261CD18ED7003DDA5B /* SpectacleUpperLeftWindowCalculationSpec.m in Sources */, 2D20F0271CD18ED7003DDA5B /* SpectacleUpperRightWindowCalculationSpec.m in Sources */, 2D20F0291CD1969D003DDA5B /* SpectacleWindowPositionManagerSpec.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 2DA5B7CD19F8BA79007EDE8E /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 2D1E1AAF147E06AD0021D73D /* Spectacle */; targetProxy = 2DA5B7CC19F8BA79007EDE8E /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 2DC4E3CA1AD84E5C0022ABF0 /* Credits.rtf */ = { isa = PBXVariantGroup; children = ( 2DC4E3CB1AD84E5C0022ABF0 /* en */, 2D98D72E1ADDBBF6007AD995 /* fr */, 2DC4E3DA1AD84E7F0022ABF0 /* it */, 574F6A1C1C57BE40001A5679 /* pt */, 2D2490E11C7A5BD900A4282B /* fi */, 2D2A61A51DC2CD9C00D86193 /* es */, ); name = Credits.rtf; sourceTree = ""; }; 2DC4E3CC1AD84E5C0022ABF0 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 2DC4E3CD1AD84E5C0022ABF0 /* en */, 2D98D72C1ADDBBF6007AD995 /* fr */, 2DA23EFD1B80FB6400C9E8EF /* it */, 574F6A1D1C57BE40001A5679 /* pt */, 2D2490E21C7A5BD900A4282B /* fi */, 2D2A61A61DC2CD9C00D86193 /* es */, ); name = InfoPlist.strings; sourceTree = ""; }; 2DC4E3CE1AD84E5C0022ABF0 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( 2DC4E3CF1AD84E5C0022ABF0 /* en */, 2D98D72D1ADDBBF6007AD995 /* fr */, 2DC4E3D91AD84E7F0022ABF0 /* it */, 574F6A1E1C57BE40001A5679 /* pt */, 2D2490E31C7A5BD900A4282B /* fi */, 2D2A61A71DC2CD9C00D86193 /* es */, ); name = Localizable.strings; sourceTree = ""; }; 2DC4E3D01AD84E5C0022ABF0 /* Spectacle.xib */ = { isa = PBXVariantGroup; children = ( 2DA23EFE1B80FF6F00C9E8EF /* Base */, 2DA23F031B81016B00C9E8EF /* fr */, 2DA23F071B8109D400C9E8EF /* it */, 574F6A1A1C57BE40001A5679 /* pt */, 2D2490DF1C7A5BD900A4282B /* fi */, 2D2A61A31DC2CD9C00D86193 /* es */, ); name = Spectacle.xib; sourceTree = ""; }; 2DC4E3D21AD84E5C0022ABF0 /* SpectaclePreferencesWindow.xib */ = { isa = PBXVariantGroup; children = ( 2DA23EFF1B80FF6F00C9E8EF /* Base */, 2DA23F051B81017000C9E8EF /* fr */, 2DA23F091B8109D900C9E8EF /* it */, 574F6A1B1C57BE40001A5679 /* pt */, 2D2490E01C7A5BD900A4282B /* fi */, 2D2A61A41DC2CD9C00D86193 /* es */, ); name = SpectaclePreferencesWindow.xib; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 1DBD214D08BA80EA00186707 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.9; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; }; name = Debug; }; 1DBD214E08BA80EA00186707 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.9; SDKROOT = macosx; }; name = Release; }; 2D1E1ACC147E06AD0021D73D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = Spectacle; CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/Mac", ); INFOPLIST_FILE = "Spectacle/Supporting Files/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.divisiblebyzero.Spectacle; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 2D1E1ACD147E06AD0021D73D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = Spectacle; CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/Mac", ); INFOPLIST_FILE = "Spectacle/Supporting Files/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.divisiblebyzero.Spectacle; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; 2DA5B7CE19F8BA79007EDE8E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Spectacle.app/Contents/MacOS/Spectacle"; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(DEVELOPER_FRAMEWORKS_DIR)", "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/Mac", ); INFOPLIST_FILE = "SpectacleSpecs/Supporting Files/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.divisiblebyzero.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUNDLE_LOADER)"; }; name = Debug; }; 2DA5B7CF19F8BA79007EDE8E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Spectacle.app/Contents/MacOS/Spectacle"; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(DEVELOPER_FRAMEWORKS_DIR)", "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/Mac", ); INFOPLIST_FILE = "SpectacleSpecs/Supporting Files/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.divisiblebyzero.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUNDLE_LOADER)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 1DBD214C08BA80EA00186707 /* Build configuration list for PBXProject "Spectacle" */ = { isa = XCConfigurationList; buildConfigurations = ( 1DBD214D08BA80EA00186707 /* Debug */, 1DBD214E08BA80EA00186707 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2D1E1ACB147E06AD0021D73D /* Build configuration list for PBXNativeTarget "Spectacle" */ = { isa = XCConfigurationList; buildConfigurations = ( 2D1E1ACC147E06AD0021D73D /* Debug */, 2D1E1ACD147E06AD0021D73D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2DA5B7D019F8BA79007EDE8E /* Build configuration list for PBXNativeTarget "SpectacleSpecs" */ = { isa = XCConfigurationList; buildConfigurations = ( 2DA5B7CE19F8BA79007EDE8E /* Debug */, 2DA5B7CF19F8BA79007EDE8E /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 089C1669FE841209C02AAC07 /* Project object */; } ================================================ FILE: Spectacle.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Spectacle.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: Spectacle.xcodeproj/xcshareddata/xcschemes/Spectacle.xcscheme ================================================ ================================================ FILE: SpectacleSpecs/Sources/SpectacleBottomHalfWindowCalculationSpec.m ================================================ #import #import #import "SpectacleWindowPositionCalculationResult.h" #import "SpectacleWindowPositionCalculator.h" SpecBegin(SpectacleBottomHalfWindowCalculation) describe(@"SpectacleBottomHalfWindowCalculation", ^{ CGRect visibleFrameSourceScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); CGRect visibleFrameDestinationScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); SpectacleWindowPositionCalculator *windowPositionCalculator = [[SpectacleWindowPositionCalculator alloc] initWithErrorHandler:^(NSString *message) { EXP_failure(message); }]; it(@"should calculate a window's CGRect in the bottom half of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(165.0f, 245.0f, 564.0f, 384.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionBottomHalf]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 4.0f, 1440.0f, 436.0f)); }); it(@"should calculate a window's CGRect in the bottom 2/3 of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 4.0f, 1440.0f, 436.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionBottomHalf]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 4.0f, 1440.0f, 582.0f)); }); it(@"should calculate a window's CGRect in the bottom 1/3 of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 4.0f, 1440.0f, 582.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionBottomHalf]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 4.0f, 1440.0f, 291.0f)); }); }); SpecEnd ================================================ FILE: SpectacleSpecs/Sources/SpectacleCenterWindowCalculationSpec.m ================================================ #import #import #import "SpectacleWindowPositionCalculationResult.h" #import "SpectacleWindowPositionCalculator.h" SpecBegin(SpectacleCenterWindowCalculation) describe(@"SpectacleCenterWindowCalculation", ^{ CGRect visibleFrameSourceScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); CGRect visibleFrameDestinationScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); SpectacleWindowPositionCalculator *windowPositionCalculator = [[SpectacleWindowPositionCalculator alloc] initWithErrorHandler:^(NSString *message) { EXP_failure(message); }]; it(@"should calculate a centered window's CGRect", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(165.0f, 245.0f, 564.0f, 384.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionCenter]; expect(result.windowRect).to.equal(CGRectMake(438.0f, 249.0f, 564.0f, 384.0f)); }); }); SpecEnd ================================================ FILE: SpectacleSpecs/Sources/SpectacleFullscreenWindowCalculationSpec.m ================================================ #import #import #import "SpectacleWindowPositionCalculationResult.h" #import "SpectacleWindowPositionCalculator.h" SpecBegin(SpectacleFullscreenWindowCalculation) describe(@"SpectacleFullscreenWindowCalculation", ^{ CGRect visibleFrameSourceScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); CGRect visibleFrameDestinationScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); SpectacleWindowPositionCalculator *windowPositionCalculator = [[SpectacleWindowPositionCalculator alloc] initWithErrorHandler:^(NSString *message) { EXP_failure(message); }]; it(@"should calculate a fullscreen window's CGRect", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(165.0f, 245.0f, 564.0f, 384.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionFullscreen]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f)); }); }); SpecEnd ================================================ FILE: SpectacleSpecs/Sources/SpectacleLargerWindowCalculationSpec.m ================================================ #import #import #import "SpectacleWindowPositionCalculationResult.h" #import "SpectacleWindowPositionCalculator.h" SpecBegin(SpectacleLargerWindowCalculation) describe(@"SpectacleLargerWindowCalculation", ^{ CGRect visibleFrameSourceScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); CGRect visibleFrameDestinationScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); SpectacleWindowPositionCalculator *windowPositionCalculator = [[SpectacleWindowPositionCalculator alloc] initWithErrorHandler:^(NSString *message) { EXP_failure(message); }]; it(@"should calculate a window's larger CGRect when centered in the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(360.0f, 222.0f, 720.0f, 436.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(345.0f, 207.0f, 750.0f, 466.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(345.0f, 207.0f, 750.0f, 466.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(330.0f, 192.0f, 780.0f, 496.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(330.0f, 192.0f, 780.0f, 496.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(315.0f, 177.0f, 810.0f, 526.0f)); }); it(@"should calculate a window's larger CGRect when against the top edge of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(402.0f, 441.0f, 720.0f, 436.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(387.0f, 411.0f, 750.0f, 466.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(387.0f, 411.0f, 750.0f, 466.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(372.0f, 381.0f, 780.0f, 496.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(372.0f, 381.0f, 780.0f, 496.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(357.0f, 351.0f, 810.0f, 526.0f)); }); it(@"should calculate a window's larger CGRect when against the bottom edge of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(238.0f, 4.0f, 720.0f, 436.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(223.0f, 4.0f, 750.0f, 466.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(223.0f, 4.0f, 750.0f, 466.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(208.0f, 4.0f, 780.0f, 496.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(208.0f, 4.0f, 780.0f, 496.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(193.0f, 4.0f, 810.0f, 526.0f)); }); it(@"should calculate a window's larger CGRect when against the left edge of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 250.0f, 720.0f, 436.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 235.0f, 750.0f, 466.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 235.0f, 750.0f, 466.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 220.0f, 780.0f, 496.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 220.0f, 780.0f, 496.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 205.0f, 810.0f, 526.0f)); }); it(@"should calculate a window's larger CGRect when against the right edge of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(720.0f, 303.0f, 720.0f, 436.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(690.0f, 288.0f, 750.0f, 466.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(690.0f, 288.0f, 750.0f, 466.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(660.0f, 273.0f, 780.0f, 496.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(660.0f, 273.0f, 780.0f, 496.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(630.0f, 258.0f, 810.0f, 526.0f)); }); it(@"should calculate a window's larger CGRect when against the top and left edges of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 441.0f, 720.0f, 436.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 411.0f, 750.0f, 466.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 411.0f, 750.0f, 466.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 381.0f, 780.0f, 496.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 381.0f, 780.0f, 496.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 351.0f, 810.0f, 526.0f)); }); it(@"should calculate a window's larger CGRect when against the top and right edges of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(720.0f, 441.0f, 720.0f, 436.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(690.0f, 411.0f, 750.0f, 466.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(690.0f, 411.0f, 750.0f, 466.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(660.0f, 381.0f, 780.0f, 496.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(660.0f, 381.0f, 780.0f, 496.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(630.0f, 351.0f, 810.0f, 526.0f)); }); it(@"should calculate a window's larger CGRect when against the bottom and left edges of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 3.0f, 720.0f, 436.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 4.0f, 750.0f, 466.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 4.0f, 750.0f, 466.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 4.0f, 780.0f, 496.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 4.0f, 780.0f, 496.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 4.0f, 810.0f, 526.0f)); }); it(@"should calculate a window's larger CGRect when against the bottom and right edges of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(720.0f, 4.0f, 720.0f, 436.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(690.0f, 4.0f, 750.0f, 466.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(690.0f, 4.0f, 750.0f, 466.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(660.0f, 4.0f, 780.0f, 496.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(660.0f, 4.0f, 780.0f, 496.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(630.0f, 4.0f, 810.0f, 526.0f)); }); it(@"should calculate a window's larger CGRect when against the top and bottom edges of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(344.0f, 4.0f, 720.0f, 873.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(329.0f, 4.0f, 750.0f, 873.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(329.0f, 4.0f, 750.0f, 873.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(314.0f, 4.0f, 780.0f, 873.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(314.0f, 4.0f, 780.0f, 873.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(299.0f, 4.0f, 810.0f, 873.0f)); }); it(@"should calculate a window's larger CGRect when against the left and right edges of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 285.0f, 1436.0f, 446.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 270.0f, 1440.0f, 476.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 270.0f, 1440.0f, 476.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 255.0f, 1440.0f, 506.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 255.0f, 1440.0f, 506.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLarger]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 240.0f, 1440.0f, 536.0f)); }); }); SpecEnd ================================================ FILE: SpectacleSpecs/Sources/SpectacleLeftHalfWindowCalculationSpec.m ================================================ #import #import #import "SpectacleWindowPositionCalculationResult.h" #import "SpectacleWindowPositionCalculator.h" SpecBegin(SpectacleLeftHalfWindowCalculation) describe(@"SpectacleLeftHalfWindowCalculation", ^{ CGRect visibleFrameSourceScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); CGRect visibleFrameDestinationScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); SpectacleWindowPositionCalculator *windowPositionCalculator = [[SpectacleWindowPositionCalculator alloc] initWithErrorHandler:^(NSString *message) { EXP_failure(message); }]; it(@"should calculate a window's CGRect in the left half of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(165.0f, 245.0f, 564.0f, 384.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLeftHalf]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 4.0f, 720.0f, 873.0f)); }); it(@"should calculate a window's CGRect in the left 2/3 of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 4.0f, 720.0f, 873.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLeftHalf]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 4.0f, 960.0f, 873.0f)); }); it(@"should calculate a window's CGRect in the left 1/3 of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 4.0f, 960.0f, 873.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLeftHalf]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 4.0f, 480.0f, 873.0f)); }); }); SpecEnd ================================================ FILE: SpectacleSpecs/Sources/SpectacleLowerLeftWindowCalculationSpec.m ================================================ #import #import #import "SpectacleWindowPositionCalculationResult.h" #import "SpectacleWindowPositionCalculator.h" SpecBegin(SpectacleLowerLeftWindowCalculation) describe(@"SpectacleLowerLeftWindowCalculation", ^{ CGRect visibleFrameSourceScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); CGRect visibleFrameDestinationScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); SpectacleWindowPositionCalculator *windowPositionCalculator = [[SpectacleWindowPositionCalculator alloc] initWithErrorHandler:^(NSString *message) { EXP_failure(message); }]; it(@"should calculate a window's CGRect in the lower left corner of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(165.0f, 245.0f, 564.0f, 384.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLowerLeft]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 4.0f, 720.0f, 436.0f)); }); }); SpecEnd ================================================ FILE: SpectacleSpecs/Sources/SpectacleLowerRightWindowCalculationSpec.m ================================================ #import #import #import "SpectacleWindowPositionCalculationResult.h" #import "SpectacleWindowPositionCalculator.h" SpecBegin(SpectacleLowerRightWindowCalculation) describe(@"SpectacleLowerRightWindowCalculation", ^{ CGRect visibleFrameSourceScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); CGRect visibleFrameDestinationScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); SpectacleWindowPositionCalculator *windowPositionCalculator = [[SpectacleWindowPositionCalculator alloc] initWithErrorHandler:^(NSString *message) { EXP_failure(message); }]; it(@"should calculate a window's CGRect in the lower right corner of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(165.0f, 245.0f, 564.0f, 384.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionLowerRight]; expect(result.windowRect).to.equal(CGRectMake(720.0f, 4.0f, 720.0f, 436.0f)); }); }); SpecEnd ================================================ FILE: SpectacleSpecs/Sources/SpectacleNextDisplayWindowCalculationSpec.m ================================================ #import #import #import "SpectacleWindowPositionCalculationResult.h" #import "SpectacleWindowPositionCalculator.h" SpecBegin(SpectacleNextDisplayWindowCalculation) describe(@"SpectacleNextDisplayWindowCalculation", ^{ CGRect visibleFrameSourceScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); CGRect visibleFrameDestinationScreen = CGRectMake(1440.0f, 4.0f, 1440.0f, 873.0f); SpectacleWindowPositionCalculator *windowPositionCalculator = [[SpectacleWindowPositionCalculator alloc] initWithErrorHandler:^(NSString *message) { EXP_failure(message); }]; it(@"should calculate a centered window's CGRect in the next display", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(165.0f, 245.0f, 564.0f, 384.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionNextDisplay]; expect(result.windowRect).to.equal(CGRectMake(1878.0f, 249.0f, 564.0f, 384.0f)); }); }); SpecEnd ================================================ FILE: SpectacleSpecs/Sources/SpectacleNextThirdWindowCalculationSpec.m ================================================ #import #import #import "SpectacleWindowPositionCalculationResult.h" #import "SpectacleWindowPositionCalculator.h" SpecBegin(SpectacleNextThirdWindowCalculation) describe(@"SpectacleNextThirdWindowCalculation", ^{ CGRect visibleFrameSourceScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); CGRect visibleFrameDestinationScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); SpectacleWindowPositionCalculator *windowPositionCalculator = [[SpectacleWindowPositionCalculator alloc] initWithErrorHandler:^(NSString *message) { EXP_failure(message); }]; it(@"should calculate a window's CGRect in the first horizontal third of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(165.0f, 245.0f, 564.0f, 384.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionNextThird]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 4.0f, 480.0f, 873.0f)); }); it(@"should calculate a window's CGRect in the second horizontal third of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 4.0f, 480.0f, 873.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionNextThird]; expect(result.windowRect).to.equal(CGRectMake(480.0f, 4.0f, 480.0f, 873.0f)); }); it(@"should calculate a window's CGRect in the last horizontal third of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(480.0f, 4.0f, 480.0f, 873.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionNextThird]; expect(result.windowRect).to.equal(CGRectMake(960.0f, 4.0f, 480.0f, 873.0f)); }); it(@"should calculate a window's CGRect in the first vertical third of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(960.0f, 4.0f, 480.0f, 873.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionNextThird]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 586.0f, 1440.0f, 291.0f)); }); it(@"should calculate a window's CGRect in the second vertical third of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 586.0f, 1440.0f, 291.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionNextThird]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 295.0f, 1440.0f, 291.0f)); }); it(@"should calculate a window's CGRect in the last vertical third of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 295.0f, 1440.0f, 291.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionNextThird]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 4.0f, 1440.0f, 291.0f)); }); }); SpecEnd ================================================ FILE: SpectacleSpecs/Sources/SpectaclePreviousDisplayWindowCalculationSpec.m ================================================ #import #import #import "SpectacleWindowPositionCalculationResult.h" #import "SpectacleWindowPositionCalculator.h" SpecBegin(SpectaclePreviousDisplayWindowCalculation) describe(@"SpectaclePreviousDisplayWindowCalculation", ^{ CGRect visibleFrameSourceScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); CGRect visibleFrameDestinationScreen = CGRectMake(1440.0f, 4.0f, 1440.0f, 873.0f); SpectacleWindowPositionCalculator *windowPositionCalculator = [[SpectacleWindowPositionCalculator alloc] initWithErrorHandler:^(NSString *message) { EXP_failure(message); }]; it(@"should calculate a centered window's CGRect in the previous display", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(165.0f, 245.0f, 564.0f, 384.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionPreviousDisplay]; expect(result.windowRect).to.equal(CGRectMake(1878.0f, 249.0f, 564.0f, 384.0f)); }); }); SpecEnd ================================================ FILE: SpectacleSpecs/Sources/SpectaclePreviousThirdWindowCalculationSpec.m ================================================ #import #import #import "SpectacleWindowPositionCalculationResult.h" #import "SpectacleWindowPositionCalculator.h" SpecBegin(SpectaclePreviousThirdWindowCalculation) describe(@"SpectaclePreviousThirdWindowCalculation", ^{ CGRect visibleFrameSourceScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); CGRect visibleFrameDestinationScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); SpectacleWindowPositionCalculator *windowPositionCalculator = [[SpectacleWindowPositionCalculator alloc] initWithErrorHandler:^(NSString *message) { EXP_failure(message); }]; it(@"should calculate a window's CGRect in the first horizontal third of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(165.0f, 245.0f, 564.0f, 384.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionPreviousThird]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 4.0f, 480.0f, 873.0f)); }); it(@"should calculate a window's CGRect in the last vertical third of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 4.0f, 480.0f, 873.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionPreviousThird]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 4.0f, 1440.0f, 291.0f)); }); it(@"should calculate a window's CGRect in the second vertical third of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 4.0f, 1440.0f, 291.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionPreviousThird]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 295.0f, 1440.0f, 291.0f)); }); it(@"should calculate a window's CGRect in the first vertical third of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 295.0f, 1440.0f, 291.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionPreviousThird]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 586.0f, 1440.0f, 291.0f)); }); it(@"should calculate a window's CGRect in the last horizontal third of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 586.0f, 1440.0f, 291.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionPreviousThird]; expect(result.windowRect).to.equal(CGRectMake(960.0f, 4.0f, 480.0f, 873.0f)); }); it(@"should calculate a window's CGRect in the second horizontal third of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(960.0f, 4.0f, 480.0f, 873.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionPreviousThird]; expect(result.windowRect).to.equal(CGRectMake(480.0f, 4.0f, 480.0f, 873.0f)); }); }); SpecEnd ================================================ FILE: SpectacleSpecs/Sources/SpectacleRightHalfWindowCalculationSpec.m ================================================ #import #import #import "SpectacleWindowPositionCalculationResult.h" #import "SpectacleWindowPositionCalculator.h" SpecBegin(SpectacleRightHalfWindowCalculation) describe(@"SpectacleRightHalfWindowCalculation", ^{ CGRect visibleFrameSourceScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); CGRect visibleFrameDestinationScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); SpectacleWindowPositionCalculator *windowPositionCalculator = [[SpectacleWindowPositionCalculator alloc] initWithErrorHandler:^(NSString *message) { EXP_failure(message); }]; it(@"should calculate a window's CGRect in the right half of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(165.0f, 245.0f, 564.0f, 384.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionRightHalf]; expect(result.windowRect).to.equal(CGRectMake(720.0f, 4.0f, 720.0f, 873.0f)); }); it(@"should calculate a window's CGRect in the right 2/3 of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(720.0f, 4.0f, 720.0f, 873.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionRightHalf]; expect(result.windowRect).to.equal(CGRectMake(480.0f, 4.0f, 960.0f, 873.0f)); }); it(@"should calculate a window's CGRect in the right 1/3 of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(480.0f, 4.0f, 960.0f, 873.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionRightHalf]; expect(result.windowRect).to.equal(CGRectMake(960.0f, 4.0f, 480.0f, 873.0f)); }); }); SpecEnd ================================================ FILE: SpectacleSpecs/Sources/SpectacleShortcutKeyBindingsSpec.m ================================================ #import #import #import #import "SpectacleShortcut.h" #import "SpectacleShortcutKeyBindings.h" static SpectacleShortcut *shortcutForKeyBinding(NSString *keyBinding); SpecBegin(SpectacleShortcutKeyBindings) describe(@"SpectacleShortcutKeyBindings", ^{ it(@"should convert an empty or nil key binding to nil modifiers", ^{ expect(SpectacleConvertShortcutKeyBindingToModifiers(nil)).to.equal(nil); expect(SpectacleConvertShortcutKeyBindingToModifiers(@"")).to.equal(nil); expect(SpectacleConvertShortcutKeyBindingToModifiers(@" ")).to.equal(nil); }); it(@"should convert key bindings to modifiers", ^{ expect(SpectacleConvertShortcutKeyBindingToModifiers(@"cmd+c")).to.equal(cmdKey); expect(SpectacleConvertShortcutKeyBindingToModifiers(@"shift+cmd+c")).to.equal(shiftKey |cmdKey); expect(SpectacleConvertShortcutKeyBindingToModifiers(@"alt+shift+cmd+c")).to.equal(optionKey | shiftKey |cmdKey); expect(SpectacleConvertShortcutKeyBindingToModifiers(@"ctrl+alt+shift+cmd+c")).to.equal(controlKey | optionKey | shiftKey | cmdKey); expect(SpectacleConvertShortcutKeyBindingToModifiers(@"command+c")).to.equal(cmdKey); expect(SpectacleConvertShortcutKeyBindingToModifiers(@"shift+command+c")).to.equal(shiftKey |cmdKey); expect(SpectacleConvertShortcutKeyBindingToModifiers(@"option+shift+command+c")).to.equal(optionKey | shiftKey |cmdKey); expect(SpectacleConvertShortcutKeyBindingToModifiers(@"control+option+shift+command+c")).to.equal(controlKey | optionKey | shiftKey | cmdKey); }); it(@"should convert mixed case key bindings to modifiers", ^{ expect(SpectacleConvertShortcutKeyBindingToModifiers(@"Cmd+C")).to.equal(cmdKey); expect(SpectacleConvertShortcutKeyBindingToModifiers(@"Shift+Cmd+C")).to.equal(shiftKey |cmdKey); expect(SpectacleConvertShortcutKeyBindingToModifiers(@"Alt+Shift+Cmd+C")).to.equal(optionKey | shiftKey |cmdKey); expect(SpectacleConvertShortcutKeyBindingToModifiers(@"Ctrl+Alt+Shift+Cmd+C")).to.equal(controlKey | optionKey | shiftKey | cmdKey); expect(SpectacleConvertShortcutKeyBindingToModifiers(@"COMMAND+C")).to.equal(cmdKey); expect(SpectacleConvertShortcutKeyBindingToModifiers(@"SHIFT+COMMAND+C")).to.equal(shiftKey |cmdKey); expect(SpectacleConvertShortcutKeyBindingToModifiers(@"OPTION+SHIFT+COMMAND+C")).to.equal(optionKey | shiftKey |cmdKey); expect(SpectacleConvertShortcutKeyBindingToModifiers(@"CONTROL+OPTION+SHIFT+COMMAND+C")).to.equal(controlKey | optionKey | shiftKey | cmdKey); }); it(@"should convert key bindings with whitespace to modifiers", ^{ expect(SpectacleConvertShortcutKeyBindingToModifiers(@" alt + shift + cmd +c")).to.equal(optionKey | shiftKey |cmdKey); }); it(@"should convert an empty or nil key binding to nil key codes", ^{ expect(SpectacleConvertShortcutKeyBindingToKeyCode(nil)).to.equal(nil); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"")).to.equal(nil); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@" ")).to.equal(nil); }); it(@"should convert lowercase alphabetical key bindings to key codes", ^{ expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"a")).to.equal(kVK_ANSI_A); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"b")).to.equal(kVK_ANSI_B); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"c")).to.equal(kVK_ANSI_C); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"d")).to.equal(kVK_ANSI_D); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"e")).to.equal(kVK_ANSI_E); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"f")).to.equal(kVK_ANSI_F); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"g")).to.equal(kVK_ANSI_G); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"h")).to.equal(kVK_ANSI_H); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"i")).to.equal(kVK_ANSI_I); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"j")).to.equal(kVK_ANSI_J); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"k")).to.equal(kVK_ANSI_K); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"l")).to.equal(kVK_ANSI_L); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"m")).to.equal(kVK_ANSI_M); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"n")).to.equal(kVK_ANSI_N); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"o")).to.equal(kVK_ANSI_O); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"p")).to.equal(kVK_ANSI_P); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"q")).to.equal(kVK_ANSI_Q); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"r")).to.equal(kVK_ANSI_R); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"s")).to.equal(kVK_ANSI_S); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"t")).to.equal(kVK_ANSI_T); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"u")).to.equal(kVK_ANSI_U); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"v")).to.equal(kVK_ANSI_V); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"w")).to.equal(kVK_ANSI_W); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"x")).to.equal(kVK_ANSI_X); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"y")).to.equal(kVK_ANSI_Y); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"z")).to.equal(kVK_ANSI_Z); }); it(@"should convert uppercase alphabetical key bindings to key codes", ^{ expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"A")).to.equal(kVK_ANSI_A); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"B")).to.equal(kVK_ANSI_B); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"C")).to.equal(kVK_ANSI_C); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"D")).to.equal(kVK_ANSI_D); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"E")).to.equal(kVK_ANSI_E); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"F")).to.equal(kVK_ANSI_F); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"G")).to.equal(kVK_ANSI_G); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"H")).to.equal(kVK_ANSI_H); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"I")).to.equal(kVK_ANSI_I); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"J")).to.equal(kVK_ANSI_J); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"K")).to.equal(kVK_ANSI_K); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"L")).to.equal(kVK_ANSI_L); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"M")).to.equal(kVK_ANSI_M); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"N")).to.equal(kVK_ANSI_N); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"O")).to.equal(kVK_ANSI_O); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"P")).to.equal(kVK_ANSI_P); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Q")).to.equal(kVK_ANSI_Q); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"R")).to.equal(kVK_ANSI_R); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"S")).to.equal(kVK_ANSI_S); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"T")).to.equal(kVK_ANSI_T); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"U")).to.equal(kVK_ANSI_U); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"V")).to.equal(kVK_ANSI_V); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"W")).to.equal(kVK_ANSI_W); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"X")).to.equal(kVK_ANSI_X); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Y")).to.equal(kVK_ANSI_Y); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Z")).to.equal(kVK_ANSI_Z); }); it(@"should convert numeric key bindings to key codes", ^{ expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"0")).to.equal(kVK_ANSI_0); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"1")).to.equal(kVK_ANSI_1); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"2")).to.equal(kVK_ANSI_2); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"3")).to.equal(kVK_ANSI_3); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"4")).to.equal(kVK_ANSI_4); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"5")).to.equal(kVK_ANSI_5); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"6")).to.equal(kVK_ANSI_6); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"7")).to.equal(kVK_ANSI_7); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"8")).to.equal(kVK_ANSI_8); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"9")).to.equal(kVK_ANSI_9); }); it(@"should convert alphabetical key bindings with modifiers to key codes", ^{ expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"cmd+a")).to.equal(kVK_ANSI_A); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"shift+cmd+b")).to.equal(kVK_ANSI_B); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"alt+shift+cmd+c")).to.equal(kVK_ANSI_C); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"ctrl+alt+shift+cmd+d")).to.equal(kVK_ANSI_D); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"command+e")).to.equal(kVK_ANSI_E); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"shift+command+f")).to.equal(kVK_ANSI_F); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"option+shift+command+g")).to.equal(kVK_ANSI_G); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"control+option+shift+command+h")).to.equal(kVK_ANSI_H); }); it(@"should convert alphanumeric key bindings with whitespace to key codes", ^{ expect(SpectacleConvertShortcutKeyBindingToKeyCode(@" cmd + a ")).to.equal(kVK_ANSI_A); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@" shift + cmd + 0 ")).to.equal(kVK_ANSI_0); }); it(@"should convert named key bindings to key codes", ^{ expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"f1")).to.equal(kVK_F1); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"f2")).to.equal(kVK_F2); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"f3")).to.equal(kVK_F3); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"f4")).to.equal(kVK_F4); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"f5")).to.equal(kVK_F5); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"f6")).to.equal(kVK_F6); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"f7")).to.equal(kVK_F7); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"f8")).to.equal(kVK_F8); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"f9")).to.equal(kVK_F9); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"f10")).to.equal(kVK_F10); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"f11")).to.equal(kVK_F11); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"f12")).to.equal(kVK_F12); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"f13")).to.equal(kVK_F13); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"f14")).to.equal(kVK_F14); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"f15")).to.equal(kVK_F15); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"f16")).to.equal(kVK_F16); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"f17")).to.equal(kVK_F17); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"f18")).to.equal(kVK_F18); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"f19")).to.equal(kVK_F19); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"f20")).to.equal(kVK_F20); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"keypaddecimal")).to.equal(kVK_ANSI_KeypadDecimal); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"keypadmultiply")).to.equal(kVK_ANSI_KeypadMultiply); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"keypadplus")).to.equal(kVK_ANSI_KeypadPlus); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"keypadclear")).to.equal(kVK_ANSI_KeypadClear); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"keypaddivide")).to.equal(kVK_ANSI_KeypadDivide); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"keypadenter")).to.equal(kVK_ANSI_KeypadEnter); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"keypadminus")).to.equal(kVK_ANSI_KeypadMinus); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"keypadequals")).to.equal(kVK_ANSI_KeypadEquals); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"keypad0")).to.equal(kVK_ANSI_Keypad0); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"keypad1")).to.equal(kVK_ANSI_Keypad1); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"keypad2")).to.equal(kVK_ANSI_Keypad2); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"keypad3")).to.equal(kVK_ANSI_Keypad3); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"keypad4")).to.equal(kVK_ANSI_Keypad4); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"keypad5")).to.equal(kVK_ANSI_Keypad5); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"keypad6")).to.equal(kVK_ANSI_Keypad6); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"keypad7")).to.equal(kVK_ANSI_Keypad7); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"keypad8")).to.equal(kVK_ANSI_Keypad8); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"keypad9")).to.equal(kVK_ANSI_Keypad9); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"return")).to.equal(kVK_Return); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"tab")).to.equal(kVK_Tab); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"space")).to.equal(kVK_Space); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"delete")).to.equal(kVK_Delete); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"escape")).to.equal(kVK_Escape); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"command")).to.equal(kVK_Command); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"shift")).to.equal(kVK_Shift); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"capslock")).to.equal(kVK_CapsLock); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"option")).to.equal(kVK_Option); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"control")).to.equal(kVK_Control); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"rightshift")).to.equal(kVK_RightShift); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"rightoption")).to.equal(kVK_RightOption); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"rightcontrol")).to.equal(kVK_RightControl); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"function")).to.equal(kVK_Function); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"volumeup")).to.equal(kVK_VolumeUp); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"volumedown")).to.equal(kVK_VolumeDown); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"mute")).to.equal(kVK_Mute); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"help")).to.equal(kVK_Help); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"home")).to.equal(kVK_Home); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"pageup")).to.equal(kVK_PageUp); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"forwarddelete")).to.equal(kVK_ForwardDelete); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"end")).to.equal(kVK_End); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"pagedown")).to.equal(kVK_PageDown); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"left")).to.equal(kVK_LeftArrow); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"right")).to.equal(kVK_RightArrow); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"down")).to.equal(kVK_DownArrow); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"up")).to.equal(kVK_UpArrow); }); it(@"should convert mixed case named key bindings to key codes", ^{ expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"F1")).to.equal(kVK_F1); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"F2")).to.equal(kVK_F2); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"F3")).to.equal(kVK_F3); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"F4")).to.equal(kVK_F4); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"F5")).to.equal(kVK_F5); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"F6")).to.equal(kVK_F6); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"F7")).to.equal(kVK_F7); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"F8")).to.equal(kVK_F8); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"F9")).to.equal(kVK_F9); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"F10")).to.equal(kVK_F10); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"F11")).to.equal(kVK_F11); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"F12")).to.equal(kVK_F12); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"F13")).to.equal(kVK_F13); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"F14")).to.equal(kVK_F14); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"F15")).to.equal(kVK_F15); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"F16")).to.equal(kVK_F16); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"F17")).to.equal(kVK_F17); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"F18")).to.equal(kVK_F18); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"F19")).to.equal(kVK_F19); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"F20")).to.equal(kVK_F20); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"KeypadDecimal")).to.equal(kVK_ANSI_KeypadDecimal); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"KeypadMultiply")).to.equal(kVK_ANSI_KeypadMultiply); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"KeypadPlus")).to.equal(kVK_ANSI_KeypadPlus); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"KeypadClear")).to.equal(kVK_ANSI_KeypadClear); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"KeypadDivide")).to.equal(kVK_ANSI_KeypadDivide); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"KeypadEnter")).to.equal(kVK_ANSI_KeypadEnter); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"KeypadMinus")).to.equal(kVK_ANSI_KeypadMinus); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"KeypadEquals")).to.equal(kVK_ANSI_KeypadEquals); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Keypad0")).to.equal(kVK_ANSI_Keypad0); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Keypad1")).to.equal(kVK_ANSI_Keypad1); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Keypad2")).to.equal(kVK_ANSI_Keypad2); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Keypad3")).to.equal(kVK_ANSI_Keypad3); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Keypad4")).to.equal(kVK_ANSI_Keypad4); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Keypad5")).to.equal(kVK_ANSI_Keypad5); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Keypad6")).to.equal(kVK_ANSI_Keypad6); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Keypad7")).to.equal(kVK_ANSI_Keypad7); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Keypad8")).to.equal(kVK_ANSI_Keypad8); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Keypad9")).to.equal(kVK_ANSI_Keypad9); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Return")).to.equal(kVK_Return); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Tab")).to.equal(kVK_Tab); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Space")).to.equal(kVK_Space); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Delete")).to.equal(kVK_Delete); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Escape")).to.equal(kVK_Escape); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Command")).to.equal(kVK_Command); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Shift")).to.equal(kVK_Shift); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"CapsLock")).to.equal(kVK_CapsLock); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Option")).to.equal(kVK_Option); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Control")).to.equal(kVK_Control); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"RightShift")).to.equal(kVK_RightShift); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"RightOption")).to.equal(kVK_RightOption); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"RightControl")).to.equal(kVK_RightControl); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Function")).to.equal(kVK_Function); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"VolumeUp")).to.equal(kVK_VolumeUp); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"VolumeDown")).to.equal(kVK_VolumeDown); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Mute")).to.equal(kVK_Mute); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Help")).to.equal(kVK_Help); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Home")).to.equal(kVK_Home); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"PageUp")).to.equal(kVK_PageUp); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"ForwardDelete")).to.equal(kVK_ForwardDelete); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"End")).to.equal(kVK_End); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"PageDown")).to.equal(kVK_PageDown); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Left")).to.equal(kVK_LeftArrow); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Right")).to.equal(kVK_RightArrow); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Down")).to.equal(kVK_DownArrow); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"Up")).to.equal(kVK_UpArrow); }); it(@"should convert named key bindings with modifiers to key codes", ^{ expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"cmd+up")).to.equal(kVK_UpArrow); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"shift+cmd+down")).to.equal(kVK_DownArrow); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"alt+shift+cmd+left")).to.equal(kVK_LeftArrow); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"ctrl+alt+shift+cmd+right")).to.equal(kVK_RightArrow); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"command+space")).to.equal(kVK_Space); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"shift+command+f1")).to.equal(kVK_F1); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"option+shift+command+keypad0")).to.equal(kVK_ANSI_Keypad0); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"control+option+shift+command+escape")).to.equal(kVK_Escape); }); it(@"should convert mixed case named key bindings with modifiers to key codes", ^{ expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"cmd+Up")).to.equal(kVK_UpArrow); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"shift+cmd+Down")).to.equal(kVK_DownArrow); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"alt+shift+cmd+left")).to.equal(kVK_LeftArrow); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"ctrl+alt+shift+cmd+Right")).to.equal(kVK_RightArrow); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"command+SPACE")).to.equal(kVK_Space); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"shift+command+F1")).to.equal(kVK_F1); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"option+shift+command+KEYPAD0")).to.equal(kVK_ANSI_Keypad0); expect(SpectacleConvertShortcutKeyBindingToKeyCode(@"control+option+shift+command+ESCAPE")).to.equal(kVK_Escape); }); it(@"should convert empty shortcuts to nil key bindings", ^{ expect(SpectacleConvertShortcutToKeyBinding(shortcutForKeyBinding(nil))).to.equal(nil); }); it(@"should convert shortcuts to key bindings", ^{ expect(SpectacleConvertShortcutToKeyBinding(shortcutForKeyBinding(@"alt+cmd+c"))).to.equal(@"alt+cmd+c"); expect(SpectacleConvertShortcutToKeyBinding(shortcutForKeyBinding(@"alt+cmd+f"))).to.equal(@"alt+cmd+f"); expect(SpectacleConvertShortcutToKeyBinding(shortcutForKeyBinding(@"alt+cmd+left"))).to.equal(@"alt+cmd+left"); expect(SpectacleConvertShortcutToKeyBinding(shortcutForKeyBinding(@"alt+cmd+right"))).to.equal(@"alt+cmd+right"); expect(SpectacleConvertShortcutToKeyBinding(shortcutForKeyBinding(@"alt+cmd+up"))).to.equal(@"alt+cmd+up"); expect(SpectacleConvertShortcutToKeyBinding(shortcutForKeyBinding(@"alt+cmd+down"))).to.equal(@"alt+cmd+down"); expect(SpectacleConvertShortcutToKeyBinding(shortcutForKeyBinding(@"ctrl+cmd+left"))).to.equal(@"ctrl+cmd+left"); expect(SpectacleConvertShortcutToKeyBinding(shortcutForKeyBinding(@"ctrl+shift+cmd+left"))).to.equal(@"ctrl+shift+cmd+left"); expect(SpectacleConvertShortcutToKeyBinding(shortcutForKeyBinding(@"ctrl+cmd+right"))).to.equal(@"ctrl+cmd+right"); expect(SpectacleConvertShortcutToKeyBinding(shortcutForKeyBinding(@"ctrl+shift+cmd+right"))).to.equal(@"ctrl+shift+cmd+right"); expect(SpectacleConvertShortcutToKeyBinding(shortcutForKeyBinding(@"ctrl+alt+cmd+right"))).to.equal(@"ctrl+alt+cmd+right"); expect(SpectacleConvertShortcutToKeyBinding(shortcutForKeyBinding(@"ctrl+alt+cmd+left"))).to.equal(@"ctrl+alt+cmd+left"); expect(SpectacleConvertShortcutToKeyBinding(shortcutForKeyBinding(@"ctrl+alt+right"))).to.equal(@"ctrl+alt+right"); expect(SpectacleConvertShortcutToKeyBinding(shortcutForKeyBinding(@"ctrl+alt+left"))).to.equal(@"ctrl+alt+left"); expect(SpectacleConvertShortcutToKeyBinding(shortcutForKeyBinding(@"ctrl+alt+shift+right"))).to.equal(@"ctrl+alt+shift+right"); expect(SpectacleConvertShortcutToKeyBinding(shortcutForKeyBinding(@"ctrl+alt+shift+left"))).to.equal(@"ctrl+alt+shift+left"); expect(SpectacleConvertShortcutToKeyBinding(shortcutForKeyBinding(@"alt+cmd+z"))).to.equal(@"alt+cmd+z"); expect(SpectacleConvertShortcutToKeyBinding(shortcutForKeyBinding(@"alt+shift+cmd+z"))).to.equal(@"alt+shift+cmd+z"); }); }); SpecEnd static SpectacleShortcut *shortcutForKeyBinding(NSString *keyBinding) { return [[SpectacleShortcut alloc] initWithShortcutName:nil shortcutKeyBinding:keyBinding]; } ================================================ FILE: SpectacleSpecs/Sources/SpectacleShortcutSpec.m ================================================ #import #import #import #import #import "SpectacleShortcut.h" SpecBegin(SpectacleShortcut) describe(@"SpectacleShortcut", ^{ it(@"should be initialized with a key binding", ^{ SpectacleShortcut *shortcut = [[SpectacleShortcut alloc] initWithShortcutName:@"MoveToCenter" shortcutKeyBinding:@"option+command+c"]; expect(shortcut.shortcutKeyCode).to.equal(kVK_ANSI_C); expect(shortcut.shortcutModifiers).to.equal(optionKey | cmdKey); }); it(@"should be copied with a new shortcut action", ^{ SpectacleShortcut *shortcut = [[SpectacleShortcut alloc] initWithShortcutName:@"MoveToCenter" shortcutKeyCode:kVK_ANSI_C shortcutModifiers:NSAlternateKeyMask | NSCommandKeyMask]; expect(shortcut.shortcutAction).to.beFalsy; expect([shortcut copyWithShortcutAction:^(SpectacleShortcut *shortcut) {}].shortcutAction).to.beTruthy; }); it(@"should trigger shortcut actions", ^{ __block BOOL shortcutActionTriggered; SpectacleShortcut *shortcut = [[SpectacleShortcut alloc] initWithShortcutName:@"MoveToCenter" shortcutKeyCode:kVK_ANSI_C shortcutModifiers:NSAlternateKeyMask | NSCommandKeyMask shortcutAction:^(SpectacleShortcut *shortcut) { shortcutActionTriggered = YES; }]; [shortcut triggerShortcutAction]; expect(shortcutActionTriggered).to.beTruthy; }); it(@"should determine if the shortcut is cleared", ^{ expect([[[SpectacleShortcut alloc] initWithShortcutName:nil shortcutKeyBinding:nil] isClearedShortcut]).to.beTruthy; expect([[[SpectacleShortcut alloc] initWithShortcutName:nil shortcutKeyBinding:@""] isClearedShortcut]).to.beTruthy; expect([[[SpectacleShortcut alloc] initWithShortcutName:nil shortcutKeyCode:-1 shortcutModifiers:0] isClearedShortcut]).to.beTruthy; expect([[[SpectacleShortcut alloc] initWithShortcutName:@"MoveToCenter" shortcutKeyCode:-1 shortcutModifiers:0] isClearedShortcut]).to.beTruthy; }); it(@"should provide a display string", ^{ expect([[[SpectacleShortcut alloc] initWithShortcutName:@"MoveToCenter" shortcutKeyCode:kVK_ANSI_C shortcutModifiers:NSAlternateKeyMask | NSCommandKeyMask] displayString]).to.equal(@"⌥⌘C"); }); it(@"should provide a key binding", ^{ SpectacleShortcut *shortcut = [[SpectacleShortcut alloc] initWithShortcutName:@"MoveToCenter" shortcutKeyBinding:@"alt+cmd+c"]; expect(shortcut.shortcutKeyBinding).to.equal(@"alt+cmd+c"); }); it(@"should support equality", ^{ SpectacleShortcut *shortcut1 = [[SpectacleShortcut alloc] initWithShortcutName:@"MoveToCenter" shortcutKeyCode:kVK_ANSI_C shortcutModifiers:NSAlternateKeyMask | NSCommandKeyMask]; SpectacleShortcut *shortcut2 = [[SpectacleShortcut alloc] initWithShortcutName:@"MoveToCenter" shortcutKeyCode:kVK_ANSI_C shortcutModifiers:NSAlternateKeyMask | NSCommandKeyMask]; SpectacleShortcut *shortcut3 = [[SpectacleShortcut alloc] initWithShortcutName:nil shortcutKeyCode:kVK_ANSI_C shortcutModifiers:NSAlternateKeyMask | NSCommandKeyMask]; expect(shortcut1).to.equal(shortcut2); expect(shortcut1).to.equal(shortcut3); expect(shortcut1).notTo.equal([[SpectacleShortcut alloc] initWithShortcutName:nil shortcutKeyCode:-1 shortcutModifiers:0]); }); it(@"should determine if the shortcut is contains modifiers", ^{ SpectacleShortcut *shortcut = [[SpectacleShortcut alloc] initWithShortcutName:@"MoveToCenter" shortcutKeyCode:kVK_ANSI_C shortcutModifiers:NSAlternateKeyMask | NSCommandKeyMask]; expect([shortcut containsModifiers:NSAlternateKeyMask]).to.beTruthy; expect([shortcut containsModifiers:NSAlternateKeyMask | NSCommandKeyMask]).to.beTruthy; expect([shortcut containsModifiers:NSControlKeyMask]).to.beFalsy; }); }); SpecEnd ================================================ FILE: SpectacleSpecs/Sources/SpectacleShortcutTranslationsSpec.m ================================================ #import #import #import #import "SpectacleShortcut.h" #import "SpectacleShortcutTranslations.h" static SpectacleShortcut *shortcutForKeyBinding(NSString *keyBinding); SpecBegin(SpectacleShortcutTranslations) describe(@"SpectacleShortcutTranslations", ^{ it(@"should translate alphanumeric key codes", ^{ expect(SpectacleTranslateKeyCode(kVK_ANSI_A)).to.equal(@"A"); expect(SpectacleTranslateKeyCode(kVK_ANSI_B)).to.equal(@"B"); expect(SpectacleTranslateKeyCode(kVK_ANSI_C)).to.equal(@"C"); expect(SpectacleTranslateKeyCode(kVK_ANSI_D)).to.equal(@"D"); expect(SpectacleTranslateKeyCode(kVK_ANSI_E)).to.equal(@"E"); expect(SpectacleTranslateKeyCode(kVK_ANSI_F)).to.equal(@"F"); expect(SpectacleTranslateKeyCode(kVK_ANSI_G)).to.equal(@"G"); expect(SpectacleTranslateKeyCode(kVK_ANSI_H)).to.equal(@"H"); expect(SpectacleTranslateKeyCode(kVK_ANSI_I)).to.equal(@"I"); expect(SpectacleTranslateKeyCode(kVK_ANSI_J)).to.equal(@"J"); expect(SpectacleTranslateKeyCode(kVK_ANSI_K)).to.equal(@"K"); expect(SpectacleTranslateKeyCode(kVK_ANSI_L)).to.equal(@"L"); expect(SpectacleTranslateKeyCode(kVK_ANSI_M)).to.equal(@"M"); expect(SpectacleTranslateKeyCode(kVK_ANSI_N)).to.equal(@"N"); expect(SpectacleTranslateKeyCode(kVK_ANSI_O)).to.equal(@"O"); expect(SpectacleTranslateKeyCode(kVK_ANSI_P)).to.equal(@"P"); expect(SpectacleTranslateKeyCode(kVK_ANSI_Q)).to.equal(@"Q"); expect(SpectacleTranslateKeyCode(kVK_ANSI_R)).to.equal(@"R"); expect(SpectacleTranslateKeyCode(kVK_ANSI_S)).to.equal(@"S"); expect(SpectacleTranslateKeyCode(kVK_ANSI_T)).to.equal(@"T"); expect(SpectacleTranslateKeyCode(kVK_ANSI_U)).to.equal(@"U"); expect(SpectacleTranslateKeyCode(kVK_ANSI_V)).to.equal(@"V"); expect(SpectacleTranslateKeyCode(kVK_ANSI_W)).to.equal(@"W"); expect(SpectacleTranslateKeyCode(kVK_ANSI_X)).to.equal(@"X"); expect(SpectacleTranslateKeyCode(kVK_ANSI_Y)).to.equal(@"Y"); expect(SpectacleTranslateKeyCode(kVK_ANSI_Z)).to.equal(@"Z"); expect(SpectacleTranslateKeyCode(kVK_ANSI_0)).to.equal(@"0"); expect(SpectacleTranslateKeyCode(kVK_ANSI_1)).to.equal(@"1"); expect(SpectacleTranslateKeyCode(kVK_ANSI_2)).to.equal(@"2"); expect(SpectacleTranslateKeyCode(kVK_ANSI_3)).to.equal(@"3"); expect(SpectacleTranslateKeyCode(kVK_ANSI_4)).to.equal(@"4"); expect(SpectacleTranslateKeyCode(kVK_ANSI_5)).to.equal(@"5"); expect(SpectacleTranslateKeyCode(kVK_ANSI_6)).to.equal(@"6"); expect(SpectacleTranslateKeyCode(kVK_ANSI_7)).to.equal(@"7"); expect(SpectacleTranslateKeyCode(kVK_ANSI_8)).to.equal(@"8"); expect(SpectacleTranslateKeyCode(kVK_ANSI_9)).to.equal(@"9"); }); it(@"should translate keyboard layout independent key codes", ^{ expect(SpectacleTranslateKeyCode(kVK_F1)).to.equal(@"F1"); expect(SpectacleTranslateKeyCode(kVK_F2)).to.equal(@"F2"); expect(SpectacleTranslateKeyCode(kVK_F3)).to.equal(@"F3"); expect(SpectacleTranslateKeyCode(kVK_F4)).to.equal(@"F4"); expect(SpectacleTranslateKeyCode(kVK_F5)).to.equal(@"F5"); expect(SpectacleTranslateKeyCode(kVK_F6)).to.equal(@"F6"); expect(SpectacleTranslateKeyCode(kVK_F7)).to.equal(@"F7"); expect(SpectacleTranslateKeyCode(kVK_F8)).to.equal(@"F8"); expect(SpectacleTranslateKeyCode(kVK_F9)).to.equal(@"F9"); expect(SpectacleTranslateKeyCode(kVK_F10)).to.equal(@"F10"); expect(SpectacleTranslateKeyCode(kVK_F11)).to.equal(@"F11"); expect(SpectacleTranslateKeyCode(kVK_F12)).to.equal(@"F12"); expect(SpectacleTranslateKeyCode(kVK_F13)).to.equal(@"F13"); expect(SpectacleTranslateKeyCode(kVK_F14)).to.equal(@"F14"); expect(SpectacleTranslateKeyCode(kVK_F15)).to.equal(@"F15"); expect(SpectacleTranslateKeyCode(kVK_F16)).to.equal(@"F16"); expect(SpectacleTranslateKeyCode(kVK_F17)).to.equal(@"F17"); expect(SpectacleTranslateKeyCode(kVK_F18)).to.equal(@"F18"); expect(SpectacleTranslateKeyCode(kVK_F19)).to.equal(@"F19"); expect(SpectacleTranslateKeyCode(kVK_F20)).to.equal(@"F20"); expect(SpectacleTranslateKeyCode(kVK_ANSI_KeypadDecimal)).to.equal(@"."); expect(SpectacleTranslateKeyCode(kVK_ANSI_KeypadMultiply)).to.equal(@"*"); expect(SpectacleTranslateKeyCode(kVK_ANSI_KeypadPlus)).to.equal(@"+"); expect(SpectacleTranslateKeyCode(kVK_ANSI_KeypadClear)).to.equal(@"⌧"); expect(SpectacleTranslateKeyCode(kVK_ANSI_KeypadDivide)).to.equal(@"/"); expect(SpectacleTranslateKeyCode(kVK_ANSI_KeypadEnter)).to.equal(@"⌤"); expect(SpectacleTranslateKeyCode(kVK_ANSI_KeypadMinus)).to.equal(@"-"); expect(SpectacleTranslateKeyCode(kVK_ANSI_KeypadEquals)).to.equal(@"="); expect(SpectacleTranslateKeyCode(kVK_ANSI_Keypad0)).to.equal(@"0"); expect(SpectacleTranslateKeyCode(kVK_ANSI_Keypad1)).to.equal(@"1"); expect(SpectacleTranslateKeyCode(kVK_ANSI_Keypad2)).to.equal(@"2"); expect(SpectacleTranslateKeyCode(kVK_ANSI_Keypad3)).to.equal(@"3"); expect(SpectacleTranslateKeyCode(kVK_ANSI_Keypad4)).to.equal(@"4"); expect(SpectacleTranslateKeyCode(kVK_ANSI_Keypad5)).to.equal(@"5"); expect(SpectacleTranslateKeyCode(kVK_ANSI_Keypad6)).to.equal(@"6"); expect(SpectacleTranslateKeyCode(kVK_ANSI_Keypad7)).to.equal(@"7"); expect(SpectacleTranslateKeyCode(kVK_ANSI_Keypad8)).to.equal(@"8"); expect(SpectacleTranslateKeyCode(kVK_ANSI_Keypad9)).to.equal(@"9"); expect(SpectacleTranslateKeyCode(kVK_Return)).to.equal(@"↩"); expect(SpectacleTranslateKeyCode(kVK_Tab)).to.equal(@"⇥"); expect(SpectacleTranslateKeyCode(kVK_Space)).to.equal(@"␣"); expect(SpectacleTranslateKeyCode(kVK_Delete)).to.equal(@"⌫"); expect(SpectacleTranslateKeyCode(kVK_Escape)).to.equal(@"⎋"); expect(SpectacleTranslateKeyCode(kVK_Command)).to.equal(@"⌘"); expect(SpectacleTranslateKeyCode(kVK_Shift)).to.equal(@"⇧"); expect(SpectacleTranslateKeyCode(kVK_CapsLock)).to.equal(@"⇪"); expect(SpectacleTranslateKeyCode(kVK_Option)).to.equal(@"⌥"); expect(SpectacleTranslateKeyCode(kVK_Control)).to.equal(@"⌃"); expect(SpectacleTranslateKeyCode(kVK_RightShift)).to.equal(@""); expect(SpectacleTranslateKeyCode(kVK_RightOption)).to.equal(@""); expect(SpectacleTranslateKeyCode(kVK_RightControl)).to.equal(@""); expect(SpectacleTranslateKeyCode(kVK_Function)).to.equal(@""); expect(SpectacleTranslateKeyCode(kVK_VolumeUp)).to.equal(@""); expect(SpectacleTranslateKeyCode(kVK_VolumeDown)).to.equal(@""); expect(SpectacleTranslateKeyCode(kVK_Mute)).to.equal(@""); expect(SpectacleTranslateKeyCode(kVK_Help)).to.equal(@""); expect(SpectacleTranslateKeyCode(kVK_Home)).to.equal(@"↖"); expect(SpectacleTranslateKeyCode(kVK_PageUp)).to.equal(@"⇞"); expect(SpectacleTranslateKeyCode(kVK_ForwardDelete)).to.equal(@"⌦"); expect(SpectacleTranslateKeyCode(kVK_End)).to.equal(@"↘"); expect(SpectacleTranslateKeyCode(kVK_PageDown)).to.equal(@"⇟"); expect(SpectacleTranslateKeyCode(kVK_LeftArrow)).to.equal(@"←"); expect(SpectacleTranslateKeyCode(kVK_RightArrow)).to.equal(@"→"); expect(SpectacleTranslateKeyCode(kVK_DownArrow)).to.equal(@"↓"); expect(SpectacleTranslateKeyCode(kVK_UpArrow)).to.equal(@"↑"); }); it(@"should translate empty modifiers", ^{ expect(SpectacleTranslateModifiers(0)).to.equal(@""); }); it(@"should translate modifiers", ^{ expect(SpectacleTranslateModifiers(NSControlKeyMask)).to.equal(@"⌃"); expect(SpectacleTranslateModifiers(NSAlternateKeyMask)).to.equal(@"⌥"); expect(SpectacleTranslateModifiers(NSShiftKeyMask)).to.equal(@"⇧"); expect(SpectacleTranslateModifiers(NSCommandKeyMask)).to.equal(@"⌘"); }); it(@"should translate shortcuts", ^{ expect(SpectacleTranslateShortcut(shortcutForKeyBinding(@"option+command+c"))).to.equal(@"⌥⌘C"); expect(SpectacleTranslateShortcut(shortcutForKeyBinding(@"option+command+f"))).to.equal(@"⌥⌘F"); expect(SpectacleTranslateShortcut(shortcutForKeyBinding(@"option+command+left"))).to.equal(@"⌥⌘←"); expect(SpectacleTranslateShortcut(shortcutForKeyBinding(@"option+command+right"))).to.equal(@"⌥⌘→"); expect(SpectacleTranslateShortcut(shortcutForKeyBinding(@"option+command+up"))).to.equal(@"⌥⌘↑"); expect(SpectacleTranslateShortcut(shortcutForKeyBinding(@"option+command+down"))).to.equal(@"⌥⌘↓"); expect(SpectacleTranslateShortcut(shortcutForKeyBinding(@"control+command+left"))).to.equal(@"⌃⌘←"); expect(SpectacleTranslateShortcut(shortcutForKeyBinding(@"control+shift+command+left"))).to.equal(@"⌃⇧⌘←"); expect(SpectacleTranslateShortcut(shortcutForKeyBinding(@"control+command+right"))).to.equal(@"⌃⌘→"); expect(SpectacleTranslateShortcut(shortcutForKeyBinding(@"control+shift+command+right"))).to.equal(@"⌃⇧⌘→"); expect(SpectacleTranslateShortcut(shortcutForKeyBinding(@"control+option+command+right"))).to.equal(@"⌃⌥⌘→"); expect(SpectacleTranslateShortcut(shortcutForKeyBinding(@"control+option+command+left"))).to.equal(@"⌃⌥⌘←"); expect(SpectacleTranslateShortcut(shortcutForKeyBinding(@"control+option+right"))).to.equal(@"⌃⌥→"); expect(SpectacleTranslateShortcut(shortcutForKeyBinding(@"control+option+left"))).to.equal(@"⌃⌥←"); expect(SpectacleTranslateShortcut(shortcutForKeyBinding(@"control+option+shift+right"))).to.equal(@"⌃⌥⇧→"); expect(SpectacleTranslateShortcut(shortcutForKeyBinding(@"control+option+shift+left"))).to.equal(@"⌃⌥⇧←"); expect(SpectacleTranslateShortcut(shortcutForKeyBinding(@"option+command+z"))).to.equal(@"⌥⌘Z"); expect(SpectacleTranslateShortcut(shortcutForKeyBinding(@"option+shift+command+z"))).to.equal(@"⌥⇧⌘Z"); }); it(@"should convert empty modifiers", ^{ expect(SpectacleConvertCocoaModifiersToCarbon(0)).to.equal(0); expect(SpectacleConvertCarbonModifiersToCocoa(0)).to.equal(0); }); it(@"should convert invalid modifiers", ^{ expect(SpectacleConvertCocoaModifiersToCarbon(42)).to.equal(0); expect(SpectacleConvertCarbonModifiersToCocoa(42)).to.equal(0); }); it(@"should convert Cocoa modifiers to Carbon modifiers", ^{ expect(SpectacleConvertCocoaModifiersToCarbon(NSControlKeyMask)).to.equal(controlKey); expect(SpectacleConvertCocoaModifiersToCarbon(NSAlternateKeyMask)).to.equal(optionKey); expect(SpectacleConvertCocoaModifiersToCarbon(NSShiftKeyMask)).to.equal(shiftKey); expect(SpectacleConvertCocoaModifiersToCarbon(NSCommandKeyMask)).to.equal(cmdKey); }); it(@"should convert Carbon modifiers to Cocoa modifiers", ^{ expect(SpectacleConvertCarbonModifiersToCocoa(controlKey)).to.equal(NSControlKeyMask); expect(SpectacleConvertCarbonModifiersToCocoa(optionKey)).to.equal(NSAlternateKeyMask); expect(SpectacleConvertCarbonModifiersToCocoa(shiftKey)).to.equal(NSShiftKeyMask); expect(SpectacleConvertCarbonModifiersToCocoa(cmdKey)).to.equal(NSCommandKeyMask); }); it(@"should convert modifiers to Carbon modifiers if necessary", ^{ expect(SpectacleConvertModifiersToCarbonIfNecessary(controlKey)).to.equal(controlKey); expect(SpectacleConvertModifiersToCarbonIfNecessary(optionKey)).to.equal(optionKey); expect(SpectacleConvertModifiersToCarbonIfNecessary(shiftKey)).to.equal(shiftKey); expect(SpectacleConvertModifiersToCarbonIfNecessary(cmdKey)).to.equal(cmdKey); expect(SpectacleConvertModifiersToCarbonIfNecessary(NSControlKeyMask)).to.equal(controlKey); expect(SpectacleConvertModifiersToCarbonIfNecessary(NSAlternateKeyMask)).to.equal(optionKey); expect(SpectacleConvertModifiersToCarbonIfNecessary(NSShiftKeyMask)).to.equal(shiftKey); expect(SpectacleConvertModifiersToCarbonIfNecessary(NSCommandKeyMask)).to.equal(cmdKey); }); it(@"should convert modifiers to Cocoa modifiers if necessary", ^{ expect(SpectacleConvertModifiersToCocoaIfNecessary(NSControlKeyMask)).to.equal(NSControlKeyMask); expect(SpectacleConvertModifiersToCocoaIfNecessary(NSAlternateKeyMask)).to.equal(NSAlternateKeyMask); expect(SpectacleConvertModifiersToCocoaIfNecessary(NSShiftKeyMask)).to.equal(NSShiftKeyMask); expect(SpectacleConvertModifiersToCocoaIfNecessary(NSCommandKeyMask)).to.equal(NSCommandKeyMask); expect(SpectacleConvertModifiersToCocoaIfNecessary(controlKey)).to.equal(NSControlKeyMask); expect(SpectacleConvertModifiersToCocoaIfNecessary(optionKey)).to.equal(NSAlternateKeyMask); expect(SpectacleConvertModifiersToCocoaIfNecessary(shiftKey)).to.equal(NSShiftKeyMask); expect(SpectacleConvertModifiersToCocoaIfNecessary(cmdKey)).to.equal(NSCommandKeyMask); }); }); SpecEnd static SpectacleShortcut *shortcutForKeyBinding(NSString *keyBinding) { return [[SpectacleShortcut alloc] initWithShortcutName:nil shortcutKeyBinding:keyBinding]; } ================================================ FILE: SpectacleSpecs/Sources/SpectacleSmallerWindowCalculationSpec.m ================================================ #import #import #import "SpectacleWindowPositionCalculationResult.h" #import "SpectacleWindowPositionCalculator.h" SpecBegin(SpectacleSmallerWindowCalculation) describe(@"SpectacleSmallerWindowCalculation", ^{ CGRect visibleFrameSourceScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); CGRect visibleFrameDestinationScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); SpectacleWindowPositionCalculator *windowPositionCalculator = [[SpectacleWindowPositionCalculator alloc] initWithErrorHandler:^(NSString *message) { EXP_failure(message); }]; it(@"should calculate a window's smaller CGRect when centered in the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(315.0f, 177.0f, 810.0f, 526.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(330.0f, 192.0f, 780.0f, 496.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(330.0f, 192.0f, 780.0f, 496.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(345.0f, 207.0f, 750.0f, 466.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(345.0f, 207.0f, 750.0f, 466.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(360.0f, 222.0f, 720.0f, 436.0f)); }); it(@"should calculate a window's smaller CGRect when against the top edge of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(357.0f, 351.0f, 810.0f, 526.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(372.0f, 381.0f, 780.0f, 496.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(372.0f, 381.0f, 780.0f, 496.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(387.0f, 411.0f, 750.0f, 466.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(387.0f, 411.0f, 750.0f, 466.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(402.0f, 441.0f, 720.0f, 436.0f)); }); it(@"should calculate a window's smaller CGRect when against the bottom edge of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(193.0f, 4.0f, 810.0f, 526.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(208.0f, 4.0f, 780.0f, 496.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(208.0f, 4.0f, 780.0f, 496.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(223.0f, 4.0f, 750.0f, 466.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(223.0f, 4.0f, 750.0f, 466.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(238.0f, 4.0f, 720.0f, 436.0f)); }); it(@"should calculate a window's smaller CGRect when against the left edge of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 205.0f, 810.0f, 526.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 220.0f, 780.0f, 496.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 220.0f, 780.0f, 496.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 235.0f, 750.0f, 466.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 235.0f, 750.0f, 466.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 250.0f, 720.0f, 436.0f)); }); it(@"should calculate a window's smaller CGRect when against the right edge of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(630.0f, 258.0f, 810.0f, 526.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(660.0f, 273.0f, 780.0f, 496.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(660.0f, 273.0f, 780.0f, 496.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(690.0f, 288.0f, 750.0f, 466.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(690.0f, 288.0f, 750.0f, 466.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(720.0f, 303.0f, 720.0f, 436.0f)); }); it(@"should calculate a window's smaller CGRect when against the top and left edges of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 351.0f, 810.0f, 526.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 381.0f, 780.0f, 496.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 381.0f, 780.0f, 496.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 411.0f, 750.0f, 466.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 411.0f, 750.0f, 466.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 441.0f, 720.0f, 436.0f)); }); it(@"should calculate a window's smaller CGRect when against the top and right edges of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(630.0f, 351.0f, 810.0f, 526.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(660.0f, 381.0f, 780.0f, 496.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(660.0f, 381.0f, 780.0f, 496.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(690.0f, 411.0f, 750.0f, 466.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(690.0f, 411.0f, 750.0f, 466.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(720.0f, 441.0f, 720.0f, 436.0f)); }); it(@"should calculate a window's smaller CGRect when against the bottom and left edges of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 4.0f, 810.0f, 526.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 4.0f, 780.0f, 496.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 4.0f, 780.0f, 496.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 4.0f, 750.0f, 466.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 4.0f, 750.0f, 466.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 4.0f, 720.0f, 436.0f)); }); it(@"should calculate a window's smaller CGRect when against the bottom and right edges of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(630.0f, 4.0f, 810.0f, 526.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(660.0f, 4.0f, 780.0f, 496.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(660.0f, 4.0f, 780.0f, 496.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(690.0f, 4.0f, 750.0f, 466.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(690.0f, 4.0f, 750.0f, 466.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(720.0f, 4.0f, 720.0f, 436.0f)); }); it(@"should calculate a window's smaller CGRect when against the top and bottom edges of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(299.0f, 4.0f, 810.0f, 873.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(314.0f, 4.0f, 780.0f, 873.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(314.0f, 4.0f, 780.0f, 873.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(329.0f, 4.0f, 750.0f, 873.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(329.0f, 4.0f, 750.0f, 873.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(344.0f, 4.0f, 720.0f, 873.0f)); }); it(@"should calculate a window's smaller CGRect when against the left and right edges of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 240.0f, 1440.0f, 536.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 255.0f, 1440.0f, 506.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 255.0f, 1440.0f, 506.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 270.0f, 1440.0f, 476.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 270.0f, 1440.0f, 476.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 285.0f, 1440.0f, 446.0f)); }); it(@"should calculate a window's smaller CGRect when against all edges of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(15.0f, 19.0f, 1410.0f, 843.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(15.0f, 19.0f, 1410.0f, 843.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(30.0f, 34.0f, 1380.0f, 813.0f)); result = [windowPositionCalculator calculateWindowRect:CGRectMake(30.0f, 34.0f, 1380.0f, 813.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionSmaller]; expect(result.windowRect).to.equal(CGRectMake(45.0f, 49.0f, 1350.0f, 783.0f)); }); }); SpecEnd ================================================ FILE: SpectacleSpecs/Sources/SpectacleTopHalfWindowCalculationSpec.m ================================================ #import #import #import "SpectacleWindowPositionCalculationResult.h" #import "SpectacleWindowPositionCalculator.h" SpecBegin(SpectacleTopHalfWindowCalculation) describe(@"SpectacleTopHalfWindowCalculation", ^{ CGRect visibleFrameSourceScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); CGRect visibleFrameDestinationScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); SpectacleWindowPositionCalculator *windowPositionCalculator = [[SpectacleWindowPositionCalculator alloc] initWithErrorHandler:^(NSString *message) { EXP_failure(message); }]; it(@"should calculate a window's CGRect in the top half of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(165.0f, 245.0f, 564.0f, 384.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionTopHalf]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 441.0f, 1440.0f, 436.0f)); }); it(@"should calculate a window's CGRect in the top 2/3 of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 441.0f, 1440.0f, 436.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionTopHalf]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 295.0f, 1440.0f, 582.0f)); }); it(@"should calculate a window's CGRect in the top 1/3 of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 295.0f, 1440.0f, 582.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionTopHalf]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 586.0f, 1440.0f, 291.0f)); }); }); SpecEnd ================================================ FILE: SpectacleSpecs/Sources/SpectacleUpperLeftWindowCalculationSpec.m ================================================ #import #import #import "SpectacleWindowPositionCalculationResult.h" #import "SpectacleWindowPositionCalculator.h" SpecBegin(SpectacleUpperLeftWindowCalculation) describe(@"SpectacleUpperLeftWindowCalculation", ^{ CGRect visibleFrameSourceScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); CGRect visibleFrameDestinationScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); SpectacleWindowPositionCalculator *windowPositionCalculator = [[SpectacleWindowPositionCalculator alloc] initWithErrorHandler:^(NSString *message) { EXP_failure(message); }]; it(@"should calculate a window's CGRect in the upper left corner of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(165.0f, 245.0f, 564.0f, 384.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionUpperLeft]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 441.0f, 720.0f, 436.0f)); }); it(@"should calculate a window's CGRect in the left 2/3 of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 441.0f, 720.0f, 436.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionUpperLeft]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 441.0f, 960.0f, 436.0f)); }); it(@"should calculate a window's CGRect in the left 1/3 of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(0.0f, 441.0f, 960.0f, 436.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionUpperLeft]; expect(result.windowRect).to.equal(CGRectMake(0.0f, 441.0f, 480.0f, 436.0f)); }); }); SpecEnd ================================================ FILE: SpectacleSpecs/Sources/SpectacleUpperRightWindowCalculationSpec.m ================================================ #import #import #import "SpectacleWindowPositionCalculationResult.h" #import "SpectacleWindowPositionCalculator.h" SpecBegin(SpectacleUpperRightWindowCalculation) describe(@"SpectacleUpperRightWindowCalculation", ^{ CGRect visibleFrameSourceScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); CGRect visibleFrameDestinationScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); SpectacleWindowPositionCalculator *windowPositionCalculator = [[SpectacleWindowPositionCalculator alloc] initWithErrorHandler:^(NSString *message) { EXP_failure(message); }]; it(@"should calculate a window's CGRect in the upper right corner of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(165.0f, 245.0f, 564.0f, 384.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionUpperRight]; expect(result.windowRect).to.equal(CGRectMake(720.0f, 441.0f, 720.0f, 436.0f)); }); it(@"should calculate a window's CGRect in the upper right 2/3 corner of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(720.0f, 441.0f, 720.0f, 436.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionUpperRight]; expect(result.windowRect).to.equal(CGRectMake(480.0f, 441.0f, 960.0f, 436.0f)); }); it(@"should calculate a window's CGRect in the upper right 1/3 corner of the screen", ^{ SpectacleWindowPositionCalculationResult *result = [windowPositionCalculator calculateWindowRect:CGRectMake(480.0f, 441.0f, 960.0f, 436.0f) visibleFrameOfSourceScreen:visibleFrameSourceScreen visibleFrameOfDestinationScreen:visibleFrameDestinationScreen action:kSpectacleWindowActionUpperRight]; expect(result.windowRect).to.equal(CGRectMake(960.0f, 441.0f, 480.0f, 436.0f)); }); }); SpecEnd ================================================ FILE: SpectacleSpecs/Sources/SpectacleWindowPositionManagerSpec.m ================================================ #import #import #import #import #import "SpectacleAccessibilityElement.h" #import "SpectacleScreenDetectionResult.h" #import "SpectacleScreenDetector.h" #import "SpectacleWindowPositionCalculationResult.h" #import "SpectacleWindowPositionCalculator.h" #import "SpectacleWindowPositionManager.h" SpecBegin(SpectacleWindowPositionManager) describe(@"SpectacleWindowPositionManager", ^{ CGRect frameOfScreen = CGRectMake(0.0f, 0.0f, 1440.0f, 900.0f); CGRect visibleFrameOfScreen = CGRectMake(0.0f, 4.0f, 1440.0f, 873.0f); CGRect frontmostWindowRect = CGRectMake(165.0f, 245.0f, 564.0f, 384.0f); __block NSScreen *mockMainScreen; __block SpectacleScreenDetector *mockScreenDetector; __block SpectacleWindowPositionCalculator *mockWindowPositionCalculator; __block NSWorkspace *mockSharedWorkspace; __block NSRunningApplication *mockFrontmostApplication; __block SpectacleWindowPositionManager *windowPositionManager; __block SpectacleWindowPositionCalculationResult *mockWindowPositionCalculationResult; beforeEach(^{ mockMainScreen = mock([NSScreen class]); mockScreenDetector = mock([SpectacleScreenDetector class]); mockWindowPositionCalculator = mock([SpectacleWindowPositionCalculator class]); mockSharedWorkspace = mock([NSWorkspace class]); mockFrontmostApplication = mock([NSRunningApplication class]); mockWindowPositionCalculationResult = mock([SpectacleWindowPositionCalculationResult class]); [given([mockMainScreen frame]) willReturnStruct:&frameOfScreen objCType:@encode(NSRect)]; [given([mockMainScreen visibleFrame]) willReturnStruct:&visibleFrameOfScreen objCType:@encode(NSRect)]; MKTOngoingStubbing *ongoingWindowPositionCalculatorStubbing = given([mockWindowPositionCalculator calculateWindowRect:CGRectNull visibleFrameOfSourceScreen:CGRectNull visibleFrameOfDestinationScreen:CGRectNull action:kSpectacleWindowActionNone]); ongoingWindowPositionCalculatorStubbing = [ongoingWindowPositionCalculatorStubbing withMatcher:anything() forArgument:0]; ongoingWindowPositionCalculatorStubbing = [ongoingWindowPositionCalculatorStubbing withMatcher:anything() forArgument:1]; ongoingWindowPositionCalculatorStubbing = [ongoingWindowPositionCalculatorStubbing withMatcher:anything() forArgument:2]; ongoingWindowPositionCalculatorStubbing = [ongoingWindowPositionCalculatorStubbing withMatcher:anything() forArgument:3]; [ongoingWindowPositionCalculatorStubbing willReturn:mockWindowPositionCalculationResult]; MKTOngoingStubbing *ongoingScreenDetectorStubbing = given([mockScreenDetector screenWithAction:kSpectacleWindowActionNone frontmostWindowElement:anything() screens:anything() mainScreen:anything()]); ongoingScreenDetectorStubbing = [ongoingScreenDetectorStubbing withMatcher:anything() forArgument:0]; ongoingScreenDetectorStubbing = [ongoingScreenDetectorStubbing withMatcher:anything() forArgument:1]; [ongoingScreenDetectorStubbing willReturn:[SpectacleScreenDetectionResult resultWithSourceScreen:mockMainScreen destinationScreen:mockMainScreen]]; [given([mockFrontmostApplication bundleIdentifier]) willReturn:@"com.divisiblebyzero.SpectacleSpecs"]; [given([mockSharedWorkspace frontmostApplication]) willReturn:mockFrontmostApplication]; windowPositionManager = [[SpectacleWindowPositionManager alloc] initWithScreenDetector:mockScreenDetector windowPositionCalculator:mockWindowPositionCalculator sharedWorkspace:mockSharedWorkspace failureFeedback:^() {} windowMover:nil]; }); it(@"should do nothing if the frontmost window is a sheet", ^{ SpectacleAccessibilityElement *mockAccessibilityElemenet = mock([SpectacleAccessibilityElement class]); [given([mockAccessibilityElemenet rectOfElement]) willReturnStruct:&frontmostWindowRect objCType:@encode(CGRect)]; [given([mockAccessibilityElemenet isSheet]) willReturnBool:YES]; [windowPositionManager moveFrontmostWindowElement:mockAccessibilityElemenet action:kSpectacleWindowActionNone screens:@[mockMainScreen] mainScreen:mockMainScreen]; [verify(mockAccessibilityElemenet) rectOfElement]; [verify(mockAccessibilityElemenet) isSheet]; [verifyCount(mockAccessibilityElemenet, never()) isSystemDialog]; [verifyCount(mockWindowPositionCalculator, never()) calculateWindowRect:CGRectNull visibleFrameOfSourceScreen:CGRectNull visibleFrameOfDestinationScreen:CGRectNull action:kSpectacleWindowActionNone]; }); it(@"should do nothing if the frontmost window is a system dialog", ^{ SpectacleAccessibilityElement *mockAccessibilityElemenet = mock([SpectacleAccessibilityElement class]); [given([mockAccessibilityElemenet rectOfElement]) willReturnStruct:&frontmostWindowRect objCType:@encode(CGRect)]; [given([mockAccessibilityElemenet isSheet]) willReturnBool:NO]; [given([mockAccessibilityElemenet isSystemDialog]) willReturnBool:YES]; [windowPositionManager moveFrontmostWindowElement:mockAccessibilityElemenet action:kSpectacleWindowActionNone screens:@[mockMainScreen] mainScreen:mockMainScreen]; [verify(mockAccessibilityElemenet) rectOfElement]; [verify(mockAccessibilityElemenet) isSheet]; [verify(mockAccessibilityElemenet) isSystemDialog]; [verifyCount(mockWindowPositionCalculator, never()) calculateWindowRect:CGRectNull visibleFrameOfSourceScreen:CGRectNull visibleFrameOfDestinationScreen:CGRectNull action:kSpectacleWindowActionNone]; }); it(@"should do nothing if the frontmost window rect is unavailable", ^{ SpectacleAccessibilityElement *mockAccessibilityElemenet = mock([SpectacleAccessibilityElement class]); [given([mockAccessibilityElemenet rectOfElement]) willReturnStruct:&CGRectNull objCType:@encode(CGRect)]; [given([mockAccessibilityElemenet isSheet]) willReturnBool:NO]; [given([mockAccessibilityElemenet isSystemDialog]) willReturnBool:YES]; [windowPositionManager moveFrontmostWindowElement:mockAccessibilityElemenet action:kSpectacleWindowActionNone screens:@[mockMainScreen] mainScreen:mockMainScreen]; [verify(mockAccessibilityElemenet) rectOfElement]; [verify(mockAccessibilityElemenet) isSheet]; [verify(mockAccessibilityElemenet) isSystemDialog]; [verifyCount(mockWindowPositionCalculator, never()) calculateWindowRect:CGRectNull visibleFrameOfSourceScreen:CGRectNull visibleFrameOfDestinationScreen:CGRectNull action:kSpectacleWindowActionNone]; }); it(@"should do nothing if screen detection fails", ^{ SpectacleAccessibilityElement *mockAccessibilityElemenet = mock([SpectacleAccessibilityElement class]); [given([mockAccessibilityElemenet rectOfElement]) willReturnStruct:&frontmostWindowRect objCType:@encode(CGRect)]; [given([mockAccessibilityElemenet isSheet]) willReturnBool:NO]; [given([mockAccessibilityElemenet isSystemDialog]) willReturnBool:YES]; [given([mockScreenDetector screenWithAction:kSpectacleWindowActionCenter frontmostWindowElement:anything() screens:anything() mainScreen:anything()]) willReturn:nil]; [windowPositionManager moveFrontmostWindowElement:mockAccessibilityElemenet action:kSpectacleWindowActionNone screens:@[mockMainScreen] mainScreen:mockMainScreen]; [verify(mockAccessibilityElemenet) rectOfElement]; [verify(mockAccessibilityElemenet) isSheet]; [verify(mockAccessibilityElemenet) isSystemDialog]; [verifyCount(mockWindowPositionCalculator, never()) calculateWindowRect:CGRectNull visibleFrameOfSourceScreen:CGRectNull visibleFrameOfDestinationScreen:CGRectNull action:kSpectacleWindowActionNone]; }); it(@"should do nothing if the window position calculation returns the same results", ^{ SpectacleAccessibilityElement *mockAccessibilityElemenet = mock([SpectacleAccessibilityElement class]); [given([mockAccessibilityElemenet rectOfElement]) willReturnStruct:&frontmostWindowRect objCType:@encode(CGRect)]; [given([mockAccessibilityElemenet isSheet]) willReturnBool:NO]; [given([mockAccessibilityElemenet isSystemDialog]) willReturnBool:NO]; [given([mockWindowPositionCalculationResult action]) willReturn:kSpectacleWindowActionCenter]; CGRect flippedFrontmostWindowRect = frontmostWindowRect; [given([mockWindowPositionCalculationResult windowRect]) willReturnStruct:&flippedFrontmostWindowRect objCType:@encode(CGRect)]; [windowPositionManager moveFrontmostWindowElement:mockAccessibilityElemenet action:kSpectacleWindowActionCenter screens:@[mockMainScreen] mainScreen:mockMainScreen]; [(SpectacleWindowPositionCalculationResult *)verify(mockWindowPositionCalculationResult) action]; [verify(mockWindowPositionCalculationResult) windowRect]; [[verifyCount(mockAccessibilityElemenet, never()) withMatcher:anything()] setRectOfElement:CGRectNull]; }); }); SpecEnd ================================================ FILE: SpectacleSpecs/Supporting Files/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1