Showing preview only (399K chars total). Download the full file or copy to clipboard to get everything.
Repository: keycastr/keycastr
Branch: main
Commit: 58133a7bca58
Files: 66
Total size: 380.1 KB
Directory structure:
gitextract_mzq7mwf8/
├── .gitignore
├── .gitmodules
├── DEVELOPING.md
├── LICENSE.md
├── README.md
└── keycastr/
├── Base.lproj/
│ └── MainMenu.nib/
│ ├── designable.nib
│ └── keyedobjects.nib
├── DisplayIcon.tif
├── English.lproj/
│ └── MainMenu.nib/
│ ├── designable.nib
│ └── keyedobjects.nib
├── GeneralIcon.tif
├── Info.plist
├── KCAppController.h
├── KCAppController.m
├── KCColorValueTransformer.h
├── KCColorValueTransformer.m
├── KCDefaultVisualizer-Info.plist
├── KCDefaultVisualizer.h
├── KCDefaultVisualizer.m
├── KCDefaultVisualizer.nib/
│ ├── designable.nib
│ └── keyedobjects.nib
├── KCEventTap.h
├── KCEventTap.m
├── KCEventTransformer.h
├── KCEventTransformer.m
├── KCKeycastrEvent.h
├── KCKeycastrEvent.m
├── KCKeystroke.h
├── KCKeystroke.m
├── KCMouseEvent.h
├── KCMouseEvent.m
├── KCMouseEventVisualizer.h
├── KCMouseEventVisualizer.m
├── KCPrefsWindowController.h
├── KCPrefsWindowController.m
├── KCUserDefaultsMigration.h
├── KCUserDefaultsMigration.m
├── KCVisualizer-Info.plist
├── KCVisualizer.h
├── KCVisualizer.m
├── KCVisualizerTests/
│ ├── Info.plist
│ ├── KCDefaultVisualizerTests.m
│ ├── KCKeystrokeConversionTests.m
│ ├── KCMouseEventVisualizerTests.m
│ └── KCUserDefaultsMigrationTests.m
├── KeyCastr.dmgCanvas
├── KeyCastr.entitlements
├── KeyCastr.icns
├── KeyCastr.xcodeproj/
│ ├── .gitignore
│ ├── project.pbxproj
│ └── project.xcworkspace/
│ └── contents.xcworkspacedata
├── KeyCastrAbout.qtz
├── KeyCastrInactive.icns
├── KeyCastr_Prefix.pch
├── NSBezierPath+RoundedRect.h
├── NSBezierPath+RoundedRect.m
├── NSUserDefaults+Utility.h
├── NSUserDefaults+Utility.m
├── Svelte-Info.plist
├── Svelte.nib/
│ ├── designable.nib
│ └── keyedobjects.nib
├── SvelteVisualizer.h
├── SvelteVisualizer.m
├── UpdateIcon.tif
├── en.lproj/
│ └── InfoPlist.strings
└── main.m
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.DS_Store
build
xcuserdata
xcshareddata
.idea
================================================
FILE: .gitmodules
================================================
[submodule "keycastr/Externals/ShortcutRecorder"]
path = keycastr/Externals/ShortcutRecorder
url = git@github.com:Kentzo/ShortcutRecorder.git
[submodule "keycastr/Externals/Sparkle"]
path = keycastr/Externals/Sparkle
url = git@github.com:sparkle-project/Sparkle.git
================================================
FILE: DEVELOPING.md
================================================
# Developer's notes
## Running and Debugging
After cloning the repository, open the project directory in a terminal or git client and run the following command: `git submodule update --init --recursive`
In order to run KeyCastr in the debugger the built product will need accessibility or input monitoring permissions just as the released app does. Remove any existing references to KeyCastr from the Accessibility and/or Input Monitoring sections in the Security & Privacy pane within the System Preferences.app and build & run the app within Xcode. Enable the newly-created reference to the app in the settings pane within the System Preferences.app. Click 'Later' in the prompt asking whether to restart the app, and use ctrl-cmd-R in Xcode to run the same build again.
## Creating a Release
- Update app version metadata in `Info.plist` by updating MARKETING_VERSION in the Xcode project file
- note that Sparkle uses kCFBundleVersionKey for its update version comparison, which shows up in the "Build" field in Xcode
- Developer ID must be set up for code signing and notarization
- Archive the app and follow Apple's instructions for uploading a release to be notarized
- Upon receiving the success notification from Apple, export the notarized build from the project
- Verify exported artifact is notarized (w/ shell command)
- Update the zip file in the `bin/` folder to contain the new .app build
- Commit the `Info.plist` and updated app zip and create a tag for this version, i.e. `git tag -am'Version <NEW_VERSION>' v<NEW_VERSION>`
- Push the commits and tags with `git push origin head && git push --tags`
- Create the release on GitHub from the recently pushed tag and attach the zipped copy of the app
- Update `https://keycastr.github.io/appcast.xml` using the `generate_appcast` tool bundled with Sparkle
- Update Homebrew cask using `brew bump-cask-pr` (a community member sometimes gets to it first :) )
================================================
FILE: LICENSE.md
================================================
Copyright (c) 2009 Stephen Deken.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name KeyCastr nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
# KeyCastr
KeyCastr, an open source keystroke visualizer.

KeyCastr enables you to share your keystrokes when creating screencasts, presenting, or collaborating with others. You can choose to display command keys, all modified keys, or all keystrokes, and there is also an option to include mouse clicks.
It is also possible to develop your own visualizer on top of KeyCastr -- pull requests are welcome!

## Installation
Download the latest release from [GitHub](https://github.com/keycastr/keycastr/releases)
Or, install using [homebrew](http://brew.sh/)
```console
brew install --cask keycastr
```
## Granting Permissions
KeyCastr requires your permission to receive events from macOS in order to broadcast your keystrokes and mouse clicks.
On newer versions of macOS (10.15+) there is an Input Monitoring menu under Security & Privacy within the System Preferences app, and KeyCastr will appear there automatically the first time you run it. Simply unlock this menu and check the box next to KeyCastr to enable it.

On older versions of macOS, or if for some reason the app doesn't appear under the Input Monitoring menu (or if you want to pre-enable it) then you may manually add it to the list of apps in the Accessibility menu.

To add KeyCastr to the list click the <kbd>+</kbd> button and select KeyCastr from the file system, or drag it in using Finder.
If KeyCastr is already in the list you can remove it with the <kbd>−</kbd> button and add it again in order to be certain that the right copy of the application is specified.
### Troubleshooting permissions
There are two likely causes for the app seeming not to work:
1. Not receiving keystroke events from macOS due to security settings
1. The window being offscreen.
Sometimes switching from the Default to the Svelte visualizer can help you
figure out which problem it is.
To troubleshoot, We recommend that you:
- Quit the KeyCastr app
- Remove KeyCastr from any of the Privacy areas in the Security & Privacy
preferences panel (typically the Accessibility or Input Monitoring areas)
- Start the KeyCastr app
- When you see the "Keystroke Receiving" dialog from macOS, click "Open System
Preferences" or open the Preferences app yourself
- Under Input Monitoring, enable KeyCastr by unlocking your preferences and
clicking the box next to KeyCastr which should have been automatically added
to the list
- When macOS prompts you, restart KeyCastr. Or you can do it manually.
## Position on Screen
The default position is on the bottom left of your display. To modify the position of displayed keystrokes, click and drag the text like so:

## Security Concerns
Any application in the Accessibility or Input Monitoring sections of the Security & Privacy pane in macOS's Preferences.app is capable of receiving all your input events. We encourage you to inspect these lists carefully, remove applications which you don't believe need to monitor your input, and ask tough questions of the companies which produce the software you use.
KeyCastr is completely free and open source, and does not employ any networking mechanisms other than that included with the ubiquitous [Sparkle framework](https://sparkle-project.org/) for managing application updates. KeyCastr will never receive or display your passwords, so long as the website or application you are using treats password entry as secure, e.g. `<input type="password">` or equivalent.
## History and Credits
- [sdeken](https://github.com/sdeken) wrote the original version.
- [akitchen](https://github.com/akitchen) occasional development and maintenance.
- [elia](https://github.com/elia) created `keycastr` organization and forked into it.
- [lqez](https://github.com/lqez) added a new menu bar icon.
- [QuintB](https://github.com/QuintB) designed an updated application icon to match the icon style in modern versions of macOS.
## License
[BSD 3-Clause](https://opensource.org/licenses/BSD-3-Clause)
KeyCastr has been freely available for the Mac since 2009.
================================================
FILE: keycastr/Base.lproj/MainMenu.nib/designable.nib
================================================
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="23727" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23727"/>
<plugIn identifier="com.apple.QuartzComposerIBPlugin" version="23727"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication"/>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject">
<connections>
<outlet property="delegate" destination="375" id="376"/>
<outlet property="dockMenu" destination="514" id="518"/>
</connections>
</customObject>
<customObject id="375" customClass="KCAppController">
<connections>
<outlet property="aboutQCView" destination="386" id="ppu-GG-BnQ"/>
<outlet property="aboutWindow" destination="384" id="395"/>
<outlet property="dockShortcutItem" destination="516" id="521"/>
<outlet property="preferencesWindow" destination="371" id="394"/>
<outlet property="prefsWindowController" destination="400" id="413"/>
<outlet property="shortcutRecorder" destination="J1Q-sG-Zms" id="pua-mu-3sG"/>
<outlet property="statusMenu" destination="377" id="381"/>
<outlet property="statusShortcutItem" destination="438" id="474"/>
</connections>
</customObject>
<customObject id="400" userLabel="Prefs Controller" customClass="KCPrefsWindowController">
<connections>
<outlet property="appController" destination="375" id="439"/>
<outlet property="prefsWindow" destination="371" id="401"/>
<outlet property="tabView" destination="402" id="407"/>
</connections>
</customObject>
<customObject id="496" userLabel="Sparkle" customClass="SUUpdater"/>
<menu title="AMainMenu" systemMenu="main" id="29" userLabel="Main Menu">
<items>
<menuItem title="KeyCastr" id="56">
<menu key="submenu" title="KeyCastr" systemMenu="apple" id="57">
<items>
<menuItem title="About KeyCastr" id="58">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontKeyCastrAboutPanel:" target="375" id="398"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="236">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Check For Updates" id="512">
<connections>
<action selector="checkForUpdates:" target="496" id="513"/>
</connections>
</menuItem>
<menuItem title="Preferences…" keyEquivalent="," id="129" userLabel="121">
<connections>
<action selector="orderFrontKeyCastrPreferencesPanel:" target="375" id="399"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="143">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Services" id="131">
<menu key="submenu" title="Services" systemMenu="services" id="130"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="144">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Hide KeyCastr" keyEquivalent="h" id="134">
<connections>
<action selector="hide:" target="-1" id="367"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="145">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="368"/>
</connections>
</menuItem>
<menuItem title="Show All" id="150">
<connections>
<action selector="unhideAllApplications:" target="-1" id="370"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="149">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Quit KeyCastr" keyEquivalent="q" id="136" userLabel="1111">
<connections>
<action selector="terminate:" target="-1" id="369"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="File" id="480">
<menu key="submenu" title="File" id="481">
<items>
<menuItem title="Close" keyEquivalent="w" id="486">
<connections>
<action selector="orderOut:" target="-1" id="495"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
<point key="canvasLocation" x="152" y="-291"/>
</menu>
<menu id="377" userLabel="Status Menu">
<items>
<menuItem title="About KeyCastr" id="378">
<connections>
<action selector="orderFrontKeyCastrAboutPanel:" target="375" id="396"/>
</connections>
</menuItem>
<menuItem title="Preferences..." keyEquivalent="," id="379">
<connections>
<action selector="orderFrontKeyCastrPreferencesPanel:" target="375" id="397"/>
</connections>
</menuItem>
<menuItem title="Start Casting" keyEquivalent="S" id="438">
<modifierMask key="keyEquivalentModifierMask" shift="YES" option="YES"/>
<connections>
<action selector="pretendToDoSomethingImportant:" target="375" id="520"/>
</connections>
</menuItem>
<menuItem title="Quit KeyCastr" id="380">
<connections>
<action selector="terminate:" target="-3" id="382"/>
</connections>
</menuItem>
</items>
<point key="canvasLocation" x="171" y="-134"/>
</menu>
<menu id="514">
<items>
<menuItem title="Start Casting" keyEquivalent="S" id="516">
<attributedString key="attributedTitle"/>
<modifierMask key="keyEquivalentModifierMask" shift="YES" option="YES"/>
<connections>
<action selector="toggleRecording:" target="375" id="519"/>
</connections>
</menuItem>
</items>
<point key="canvasLocation" x="183" y="73"/>
</menu>
<window title="KeyCastr" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" toolbarStyle="preference" id="371" userLabel="Prefs">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="335" y="390" width="471" height="466"/>
<rect key="screenRect" x="0.0" y="0.0" width="1728" height="1079"/>
<view key="contentView" id="372">
<rect key="frame" x="0.0" y="0.0" width="471" height="466"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tabView translatesAutoresizingMaskIntoConstraints="NO" id="402">
<rect key="frame" x="13" y="2" width="445" height="458"/>
<font key="font" metaFont="system"/>
<tabViewItems>
<tabViewItem label="General" identifier="1" id="403">
<view key="view" id="406">
<rect key="frame" x="10" y="33" width="425" height="412"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="410">
<rect key="frame" x="18" y="275" width="389" height="117"/>
<subviews>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="446">
<rect key="frame" x="18" y="50" width="139" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Toggle capturing:" id="447">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button imageHugsTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="444">
<rect key="frame" x="163" y="19" width="206" height="18"/>
<buttonCell key="cell" type="check" title="Show preferences at launch" bezelStyle="regularSquare" imagePosition="leading" alignment="left" state="on" inset="2" id="445">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="462" name="value" keyPath="values.alwaysShowPrefs" id="463"/>
</connections>
</button>
<popUpButton imageHugsTitle="YES" verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="420">
<rect key="frame" x="162" y="75" width="196" height="25"/>
<popUpButtonCell key="cell" type="push" title="In the Menu Bar and Dock" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="3" inset="2" selectedItem="425" id="421">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="message"/>
<menu key="menu" title="OtherViews" id="422">
<items>
<menuItem title="In the Menu Bar" tag="1" id="423"/>
<menuItem title="In the Dock" tag="2" id="424"/>
<menuItem title="In the Menu Bar and Dock" state="on" tag="3" id="425"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="changeIconPreference:" target="375" id="479"/>
<binding destination="462" name="selectedTag" keyPath="values.displayIcon" id="467"/>
</connections>
</popUpButton>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="418">
<rect key="frame" x="17" y="81" width="140" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Display KeyCastr icon:" id="419">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<customView verifyAmbiguity="ignoreSizes" translatesAutoresizingMaskIntoConstraints="NO" id="J1Q-sG-Zms" customClass="SRRecorderControl">
<rect key="frame" x="165" y="46" width="112" height="25"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="25" id="eK4-Fh-hia"/>
<constraint firstAttribute="height" constant="25" id="hLq-xG-0AL"/>
<constraint firstAttribute="width" constant="112" id="nQT-sS-wpQ"/>
</constraints>
<connections>
<outlet property="delegate" destination="375" id="F1v-ra-SAD"/>
</connections>
</customView>
</subviews>
<constraints>
<constraint firstItem="420" firstAttribute="leading" secondItem="418" secondAttribute="trailing" constant="10" id="ASO-R4-kP5"/>
<constraint firstItem="418" firstAttribute="top" secondItem="410" secondAttribute="top" constant="20" symbolic="YES" id="AhR-0d-QJW"/>
<constraint firstItem="446" firstAttribute="trailing" secondItem="418" secondAttribute="trailing" id="B0l-8L-Veq"/>
<constraint firstAttribute="bottom" secondItem="444" secondAttribute="bottom" constant="20" symbolic="YES" id="K1i-2f-vZ5"/>
<constraint firstItem="J1Q-sG-Zms" firstAttribute="leading" secondItem="420" secondAttribute="leading" id="KcZ-du-wqH"/>
<constraint firstItem="446" firstAttribute="top" secondItem="418" secondAttribute="bottom" constant="15" id="Ki8-5m-GAo"/>
<constraint firstAttribute="trailing" secondItem="444" secondAttribute="trailing" constant="20" symbolic="YES" id="Lb9-NW-tv1"/>
<constraint firstItem="420" firstAttribute="centerY" secondItem="418" secondAttribute="centerY" id="MKc-AT-Q0J"/>
<constraint firstItem="444" firstAttribute="top" secondItem="J1Q-sG-Zms" secondAttribute="bottom" constant="10" id="WKQ-Xm-f9J"/>
<constraint firstItem="J1Q-sG-Zms" firstAttribute="centerY" secondItem="446" secondAttribute="centerY" id="YnR-do-Ets"/>
<constraint firstItem="418" firstAttribute="leading" secondItem="410" secondAttribute="leading" constant="19" id="frE-vs-mvf"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="420" secondAttribute="trailing" constant="20" symbolic="YES" id="pVd-0x-Mux"/>
<constraint firstItem="444" firstAttribute="leading" secondItem="J1Q-sG-Zms" secondAttribute="leading" id="qEJ-kF-57p"/>
<constraint firstItem="446" firstAttribute="leading" secondItem="410" secondAttribute="leading" constant="20" symbolic="YES" id="r9m-A4-bWf"/>
</constraints>
</customView>
</subviews>
<constraints>
<constraint firstItem="410" firstAttribute="leading" secondItem="406" secondAttribute="leading" constant="18" id="2Yw-fp-kHD"/>
<constraint firstAttribute="trailing" secondItem="410" secondAttribute="trailing" constant="18" id="DdU-FN-2jL"/>
<constraint firstItem="410" firstAttribute="top" secondItem="406" secondAttribute="top" constant="20" symbolic="YES" id="XpO-Rg-CHJ"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="Display" identifier="2" id="404">
<view key="view" id="405">
<rect key="frame" x="10" y="33" width="427" height="412"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="411">
<rect key="frame" x="20" y="20" width="387" height="372"/>
<subviews>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="426" userLabel="Selected Visualizer Container View">
<rect key="frame" x="0.0" y="312" width="387" height="61"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="427">
<rect key="frame" x="35" y="22" width="124" height="17"/>
<constraints>
<constraint firstAttribute="height" constant="17" id="Zba-BR-Ul0"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Selected Visualizer:" id="428">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton imageHugsTitle="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="429" userLabel="Visualizers">
<rect key="frame" x="162" y="16" width="209" height="26"/>
<popUpButtonCell key="cell" type="push" title="Item 1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" inset="2" selectedItem="432" id="430">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="message"/>
<menu key="menu" title="OtherViews" id="431">
<items>
<menuItem title="Item 1" state="on" id="432"/>
<menuItem title="Item 2" id="433"/>
<menuItem title="Item 3" id="434"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<binding destination="375" name="contentValues" keyPath="availableVisualizerNames" id="440"/>
<binding destination="375" name="selectedValue" keyPath="currentVisualizerName" previousBinding="440" id="443"/>
</connections>
</popUpButton>
<box autoresizesSubviews="NO" verticalHuggingPriority="750" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="435">
<rect key="frame" x="0.0" y="-2" width="387" height="5"/>
</box>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="435" secondAttribute="trailing" id="3hr-79-CV9"/>
<constraint firstItem="427" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="426" secondAttribute="leading" constant="20" symbolic="YES" id="5gK-Ro-QwY"/>
<constraint firstItem="427" firstAttribute="centerY" secondItem="426" secondAttribute="centerY" id="5mV-qL-dYW"/>
<constraint firstAttribute="bottom" secondItem="435" secondAttribute="bottom" id="BhL-cf-fcu"/>
<constraint firstItem="429" firstAttribute="baseline" secondItem="427" secondAttribute="firstBaseline" id="HPa-YT-TBr"/>
<constraint firstItem="435" firstAttribute="leading" secondItem="426" secondAttribute="leading" id="Thn-8A-gmu"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="429" secondAttribute="trailing" constant="20" symbolic="YES" id="VML-hV-YTt"/>
<constraint firstItem="429" firstAttribute="top" secondItem="426" secondAttribute="top" constant="20" id="VQy-N3-cqU"/>
<constraint firstItem="429" firstAttribute="leading" secondItem="427" secondAttribute="trailing" constant="8" symbolic="YES" id="dny-iF-2eO"/>
<constraint firstItem="429" firstAttribute="centerY" secondItem="427" secondAttribute="centerY" id="e4U-9v-C4m"/>
</constraints>
</customView>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="eb6-kp-O9f" userLabel="Mouse Options Container View">
<rect key="frame" x="0.0" y="252" width="387" height="61"/>
<subviews>
<box autoresizesSubviews="NO" verticalHuggingPriority="750" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="cVt-cZ-Yk1">
<rect key="frame" x="0.0" y="-2" width="387" height="5"/>
</box>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="i4K-FF-bg4">
<rect key="frame" x="18" y="22" width="141" height="17"/>
<constraints>
<constraint firstAttribute="height" constant="17" id="Ar2-T7-Vk2"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Display Mouse Events:" id="rrC-bj-WMM">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="M4E-xg-OPh" userLabel="Mouse Display Options">
<rect key="frame" x="162" y="17" width="209" height="25"/>
<popUpButtonCell key="cell" type="push" title="with pointer and visualizer" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="b75-G6-B4y" id="7h2-NY-2W2">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="message"/>
<menu key="menu" id="r7L-ll-0VM">
<items>
<menuItem title="with pointer and visualizer" state="on" id="b75-G6-B4y"/>
<menuItem title="Item 2" id="Q6p-K6-yk8"/>
<menuItem title="Item 3" id="yqL-M9-9ZR"/>
</items>
</menu>
</popUpButtonCell>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="202" id="p39-R6-Fxu"/>
</constraints>
<connections>
<binding destination="375" name="contentValues" keyPath="availableMouseDisplayOptionNames" id="o5O-Wp-pDz"/>
<binding destination="375" name="selectedValue" keyPath="currentMouseDisplayOptionName" previousBinding="o5O-Wp-pDz" id="oNY-D3-xbk"/>
</connections>
</popUpButton>
</subviews>
<constraints>
<constraint firstItem="M4E-xg-OPh" firstAttribute="centerY" secondItem="eb6-kp-O9f" secondAttribute="centerY" id="7Pe-er-u6y"/>
<constraint firstItem="M4E-xg-OPh" firstAttribute="leading" secondItem="i4K-FF-bg4" secondAttribute="trailing" constant="8" symbolic="YES" id="Cq3-2R-pcl"/>
<constraint firstItem="cVt-cZ-Yk1" firstAttribute="leading" secondItem="eb6-kp-O9f" secondAttribute="leading" id="Eh8-4A-q9r"/>
<constraint firstAttribute="bottom" secondItem="cVt-cZ-Yk1" secondAttribute="bottom" id="beM-Gf-hXe"/>
<constraint firstAttribute="trailing" secondItem="M4E-xg-OPh" secondAttribute="trailing" constant="20" symbolic="YES" id="jEI-iC-aeW"/>
<constraint firstItem="i4K-FF-bg4" firstAttribute="leading" secondItem="eb6-kp-O9f" secondAttribute="leading" constant="20" symbolic="YES" id="mKh-Df-ITg"/>
<constraint firstAttribute="trailing" secondItem="cVt-cZ-Yk1" secondAttribute="trailing" id="qBs-y2-jtp"/>
<constraint firstItem="i4K-FF-bg4" firstAttribute="centerY" secondItem="eb6-kp-O9f" secondAttribute="centerY" id="xTr-7r-Syp"/>
</constraints>
</customView>
</subviews>
<constraints>
<constraint firstItem="426" firstAttribute="top" secondItem="411" secondAttribute="top" constant="-1" id="1Ab-H7-bCx"/>
<constraint firstItem="426" firstAttribute="leading" secondItem="411" secondAttribute="leading" id="7Fk-Cn-60D"/>
<constraint firstItem="426" firstAttribute="leading" secondItem="411" secondAttribute="leading" id="7Xe-N7-6ra"/>
<constraint firstItem="eb6-kp-O9f" firstAttribute="trailing" secondItem="426" secondAttribute="trailing" id="FNg-LN-KJQ"/>
<constraint firstAttribute="trailing" secondItem="426" secondAttribute="trailing" id="L0z-xl-lkI"/>
<constraint firstAttribute="trailing" secondItem="eb6-kp-O9f" secondAttribute="trailing" id="LiA-gp-fpA"/>
<constraint firstItem="eb6-kp-O9f" firstAttribute="leading" secondItem="426" secondAttribute="leading" id="P0K-Hi-dPV"/>
<constraint firstItem="M4E-xg-OPh" firstAttribute="leading" secondItem="429" secondAttribute="leading" id="So5-P7-BXR"/>
<constraint firstItem="eb6-kp-O9f" firstAttribute="top" secondItem="411" secondAttribute="top" constant="59" id="Zec-vm-rAh"/>
<constraint firstItem="429" firstAttribute="width" secondItem="M4E-xg-OPh" secondAttribute="width" id="auR-yA-s7a"/>
<constraint firstItem="eb6-kp-O9f" firstAttribute="height" secondItem="426" secondAttribute="height" id="drL-t1-XcD"/>
<constraint firstAttribute="trailing" secondItem="426" secondAttribute="trailing" id="lIh-gp-Fjd"/>
<constraint firstItem="eb6-kp-O9f" firstAttribute="leading" secondItem="411" secondAttribute="leading" id="rXA-cP-0dy"/>
</constraints>
</customView>
</subviews>
<constraints>
<constraint firstItem="411" firstAttribute="leading" secondItem="405" secondAttribute="leading" constant="20" symbolic="YES" id="Jhu-xo-GLe"/>
<constraint firstAttribute="bottom" secondItem="411" secondAttribute="bottom" priority="750" constant="20" symbolic="YES" id="Y7b-qq-GYd"/>
<constraint firstAttribute="trailing" secondItem="411" secondAttribute="trailing" constant="20" symbolic="YES" id="bnn-6m-ZfU"/>
<constraint firstItem="411" firstAttribute="top" secondItem="405" secondAttribute="top" constant="20" symbolic="YES" id="det-dK-dTU"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="Update" identifier="Item 2" id="408">
<view key="view" id="409">
<rect key="frame" x="10" y="33" width="422" height="412"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="412">
<rect key="frame" x="20" y="292" width="382" height="100"/>
<subviews>
<button imageHugsTitle="YES" horizontalHuggingPriority="1000" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="451">
<rect key="frame" x="18" y="63" width="200" height="18"/>
<buttonCell key="cell" type="check" title="Check for updates at startup" bezelStyle="regularSquare" imagePosition="leading" alignment="left" state="on" inset="2" id="452">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="496" name="value" keyPath="automaticallyChecksForUpdates" id="501"/>
</connections>
</button>
<button imageHugsTitle="YES" horizontalHuggingPriority="1000" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="459">
<rect key="frame" x="18" y="41" width="232" height="18"/>
<buttonCell key="cell" type="check" title="Include anonymous system profile" bezelStyle="regularSquare" imagePosition="leading" alignment="left" enabled="NO" inset="2" id="460">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="496" name="enabled" keyPath="automaticallyChecksForUpdates" id="511"/>
<binding destination="496" name="value" keyPath="sendsSystemProfile" id="499"/>
</connections>
</button>
<button imageHugsTitle="YES" horizontalHuggingPriority="1000" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="453">
<rect key="frame" x="280" y="12" width="88" height="27"/>
<buttonCell key="cell" type="push" title="Check Now" bezelStyle="rounded" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="454">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>
<connections>
<action selector="checkForUpdates:" target="496" id="497"/>
</connections>
</button>
<textField horizontalHuggingPriority="1000" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="457">
<rect key="frame" x="98" y="20" width="37" height="14"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="Never" id="458">
<dateFormatter key="formatter" formatterBehavior="custom10_4" dateStyle="short" id="509"/>
<font key="font" metaFont="smallSystemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="496" name="value" keyPath="lastUpdateCheckDate" id="508">
<dictionary key="options">
<integer key="NSAllowsEditingMultipleValuesSelection" value="1"/>
<integer key="NSAlwaysPresentsApplicationModalAlerts" value="0"/>
<integer key="NSConditionallySetsEditable" value="0"/>
<integer key="NSConditionallySetsEnabled" value="0"/>
<integer key="NSConditionallySetsHidden" value="0"/>
<integer key="NSContinuouslyUpdatesValue" value="0"/>
<string key="NSMultipleValuesPlaceholder"></string>
<string key="NSNoSelectionPlaceholder"></string>
<string key="NSNotApplicablePlaceholder"></string>
<string key="NSNullPlaceholder">Never</string>
<integer key="NSRaisesForNotApplicableKeys" value="1"/>
<integer key="NSValidatesImmediately" value="0"/>
</dictionary>
</binding>
</connections>
</textField>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="455">
<rect key="frame" x="18" y="20" width="78" height="14"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Last checked:" id="456">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="457" firstAttribute="centerY" secondItem="455" secondAttribute="centerY" id="4CT-k5-sTJ"/>
<constraint firstItem="459" firstAttribute="leading" secondItem="412" secondAttribute="leading" constant="20" symbolic="YES" id="51x-bl-ZnQ"/>
<constraint firstItem="453" firstAttribute="centerY" secondItem="455" secondAttribute="centerY" id="8G3-0g-iye"/>
<constraint firstItem="455" firstAttribute="leading" secondItem="412" secondAttribute="leading" constant="20" symbolic="YES" id="9UJ-O8-nHJ"/>
<constraint firstItem="453" firstAttribute="leading" relation="lessThanOrEqual" secondItem="457" secondAttribute="trailing" priority="750" constant="165" id="Bwd-mK-EDY"/>
<constraint firstAttribute="bottom" secondItem="455" secondAttribute="bottom" priority="750" constant="20" symbolic="YES" id="C9f-X2-aHf"/>
<constraint firstAttribute="trailing" secondItem="453" secondAttribute="trailing" constant="20" symbolic="YES" id="MEG-U3-HHf"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="451" secondAttribute="trailing" constant="20" symbolic="YES" id="MWa-M8-HeB"/>
<constraint firstItem="451" firstAttribute="leading" secondItem="412" secondAttribute="leading" constant="20" symbolic="YES" id="Wdg-09-OeY"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="459" secondAttribute="trailing" constant="20" symbolic="YES" id="lVd-PZ-Pb0"/>
<constraint firstItem="457" firstAttribute="leading" secondItem="455" secondAttribute="trailing" constant="6" id="lhO-MH-0Y5"/>
<constraint firstItem="455" firstAttribute="top" secondItem="459" secondAttribute="bottom" constant="8" symbolic="YES" id="n42-Rv-hV1"/>
<constraint firstItem="459" firstAttribute="top" secondItem="451" secondAttribute="bottom" constant="6" id="uuu-lo-b8g"/>
<constraint firstItem="451" firstAttribute="top" secondItem="412" secondAttribute="top" constant="20" symbolic="YES" id="xoq-Gs-Tx9"/>
</constraints>
</customView>
</subviews>
<constraints>
<constraint firstItem="412" firstAttribute="top" secondItem="409" secondAttribute="top" constant="20" symbolic="YES" id="UYV-aj-X82"/>
<constraint firstItem="412" firstAttribute="leading" secondItem="409" secondAttribute="leading" constant="20" symbolic="YES" id="ZFN-97-9xf"/>
<constraint firstAttribute="trailing" secondItem="412" secondAttribute="trailing" constant="20" symbolic="YES" id="mB7-Kk-Qb2"/>
</constraints>
</view>
</tabViewItem>
</tabViewItems>
</tabView>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="402" secondAttribute="trailing" constant="20" symbolic="YES" id="0kG-h1-NYo"/>
<constraint firstItem="402" firstAttribute="centerY" secondItem="372" secondAttribute="centerY" id="1hh-jm-bCH"/>
<constraint firstItem="402" firstAttribute="top" secondItem="372" secondAttribute="top" constant="12" symbolic="YES" id="Ohd-N8-e97"/>
<constraint firstItem="402" firstAttribute="leading" secondItem="372" secondAttribute="leading" constant="20" symbolic="YES" id="Umm-iE-YI9"/>
</constraints>
</view>
<connections>
<outlet property="delegate" destination="375" id="477"/>
</connections>
<point key="canvasLocation" x="-262.5" y="-49"/>
</window>
<window allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="384" userLabel="About">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="194" width="256" height="337"/>
<rect key="screenRect" x="0.0" y="0.0" width="1728" height="1079"/>
<view key="contentView" id="385">
<rect key="frame" x="0.0" y="0.0" width="256" height="337"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<box autoresizesSubviews="NO" verticalHuggingPriority="750" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="387">
<rect key="frame" x="0.0" y="78" width="258" height="5"/>
</box>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="388">
<rect key="frame" x="18" y="56" width="83" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="Version 0.8.0" id="389">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField preferredMaxLayoutWidth="226" translatesAutoresizingMaskIntoConstraints="NO" id="392">
<rect key="frame" x="18" y="20" width="222" height="28"/>
<textFieldCell key="cell" controlSize="small" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="left" id="393">
<font key="font" metaFont="smallSystem"/>
<string key="title" base64-UTF8="YES">
Q29weXJpZ2h0IAPCqSAyMDA5LTIwMjUgU3RlcGhlbiBEZWtlbiwgQW5kcmV3IEtpdGNoZW4sIGFuZCBj
b250cmlidXRvcnM
</string>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<qcView autostartsRendering="YES" translatesAutoresizingMaskIntoConstraints="NO" id="386">
<rect key="frame" x="0.0" y="81" width="256" height="256"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="256" id="Ava-98-nwW"/>
<constraint firstAttribute="height" constant="256" id="hLn-Dy-hdQ"/>
<constraint firstAttribute="width" constant="256" id="tOE-mZ-U4T"/>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="256" id="vIp-Xy-p2X"/>
</constraints>
<color key="eraseColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
</qcView>
</subviews>
<constraints>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="388" secondAttribute="trailing" constant="20" symbolic="YES" id="248-AJ-Ggs"/>
<constraint firstItem="387" firstAttribute="leading" secondItem="385" secondAttribute="leading" id="4LD-sS-nls"/>
<constraint firstItem="392" firstAttribute="top" secondItem="388" secondAttribute="bottom" constant="8" symbolic="YES" id="9tx-aX-Hxd"/>
<constraint firstAttribute="trailing" secondItem="392" secondAttribute="trailing" constant="18" id="AvG-ah-dWf"/>
<constraint firstAttribute="trailing" secondItem="387" secondAttribute="trailing" constant="-2" id="D0c-GO-EmZ"/>
<constraint firstItem="392" firstAttribute="leading" secondItem="385" secondAttribute="leading" constant="20" symbolic="YES" id="E6j-q0-oTt"/>
<constraint firstAttribute="bottom" secondItem="392" secondAttribute="bottom" constant="20" symbolic="YES" id="HXc-z2-vge"/>
<constraint firstItem="386" firstAttribute="top" secondItem="385" secondAttribute="top" id="Jj4-dI-NKr"/>
<constraint firstItem="387" firstAttribute="top" secondItem="386" secondAttribute="bottom" id="MEY-n5-Y4k"/>
<constraint firstItem="386" firstAttribute="leading" secondItem="385" secondAttribute="leading" id="UQw-nQ-vWY"/>
<constraint firstItem="392" firstAttribute="leading" secondItem="388" secondAttribute="leading" id="YcI-9r-inR"/>
<constraint firstItem="388" firstAttribute="top" secondItem="387" secondAttribute="bottom" constant="8" symbolic="YES" id="myd-3l-Exp"/>
<constraint firstAttribute="trailing" secondItem="386" secondAttribute="trailing" id="rmf-TA-eIa"/>
</constraints>
</view>
<point key="canvasLocation" x="-763" y="16"/>
</window>
<userDefaultsController representsSharedInstance="YES" id="462"/>
</objects>
</document>
================================================
FILE: keycastr/English.lproj/MainMenu.nib/designable.nib
================================================
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
<plugIn identifier="com.apple.QuartzComposerIBPlugin" version="18122"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication"/>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject">
<connections>
<outlet property="delegate" destination="375" id="376"/>
<outlet property="dockMenu" destination="514" id="518"/>
</connections>
</customObject>
<customObject id="375" customClass="KCAppController">
<connections>
<outlet property="aboutQCView" destination="386" id="ppu-GG-BnQ"/>
<outlet property="aboutWindow" destination="384" id="395"/>
<outlet property="dockShortcutItem" destination="516" id="521"/>
<outlet property="preferencesWindow" destination="371" id="394"/>
<outlet property="prefsWindowController" destination="400" id="413"/>
<outlet property="shortcutRecorder" destination="J1Q-sG-Zms" id="pua-mu-3sG"/>
<outlet property="statusMenu" destination="377" id="381"/>
<outlet property="statusShortcutItem" destination="438" id="474"/>
</connections>
</customObject>
<customObject id="400" userLabel="Prefs Controller" customClass="KCPrefsWindowController">
<connections>
<outlet property="appController" destination="375" id="439"/>
<outlet property="prefsWindow" destination="371" id="401"/>
<outlet property="tabView" destination="402" id="407"/>
</connections>
</customObject>
<customObject id="496" userLabel="Sparkle" customClass="SUUpdater"/>
<menu title="AMainMenu" systemMenu="main" id="29" userLabel="Main Menu">
<items>
<menuItem title="KeyCastr" id="56">
<menu key="submenu" title="KeyCastr" systemMenu="apple" id="57">
<items>
<menuItem title="About KeyCastr" id="58">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontKeyCastrAboutPanel:" target="375" id="398"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="236">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Check For Updates" id="512">
<connections>
<action selector="checkForUpdates:" target="496" id="513"/>
</connections>
</menuItem>
<menuItem title="Preferences…" keyEquivalent="," id="129" userLabel="121">
<connections>
<action selector="orderFrontKeyCastrPreferencesPanel:" target="375" id="399"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="143">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Services" id="131">
<menu key="submenu" title="Services" systemMenu="services" id="130"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="144">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Hide KeyCastr" keyEquivalent="h" id="134">
<connections>
<action selector="hide:" target="-1" id="367"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="145">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="368"/>
</connections>
</menuItem>
<menuItem title="Show All" id="150">
<connections>
<action selector="unhideAllApplications:" target="-1" id="370"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="149">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Quit KeyCastr" keyEquivalent="q" id="136" userLabel="1111">
<connections>
<action selector="terminate:" target="-1" id="369"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="File" id="480">
<menu key="submenu" title="File" id="481">
<items>
<menuItem title="Close" keyEquivalent="w" id="486">
<connections>
<action selector="orderOut:" target="-1" id="495"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
<point key="canvasLocation" x="-136" y="154"/>
</menu>
<menu id="377" userLabel="Status Menu">
<items>
<menuItem title="About KeyCastr" id="378">
<connections>
<action selector="orderFrontKeyCastrAboutPanel:" target="375" id="396"/>
</connections>
</menuItem>
<menuItem title="Preferences..." keyEquivalent="," id="379">
<connections>
<action selector="orderFrontKeyCastrPreferencesPanel:" target="375" id="397"/>
</connections>
</menuItem>
<menuItem title="Start Casting" keyEquivalent="S" id="438">
<modifierMask key="keyEquivalentModifierMask" shift="YES" option="YES"/>
<connections>
<action selector="pretendToDoSomethingImportant:" target="375" id="520"/>
</connections>
</menuItem>
<menuItem title="Quit KeyCastr" id="380">
<connections>
<action selector="terminate:" target="-3" id="382"/>
</connections>
</menuItem>
</items>
<point key="canvasLocation" x="-208" y="535"/>
</menu>
<menu id="514">
<items>
<menuItem title="Start Casting" keyEquivalent="S" id="516">
<attributedString key="attributedTitle"/>
<modifierMask key="keyEquivalentModifierMask" shift="YES" option="YES"/>
<connections>
<action selector="toggleRecording:" target="375" id="519"/>
</connections>
</menuItem>
</items>
<point key="canvasLocation" x="-68" y="686"/>
</menu>
<window title="KeyCastr" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="371" userLabel="Prefs">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="335" y="390" width="480" height="360"/>
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1175"/>
<view key="contentView" id="372">
<rect key="frame" x="0.0" y="0.0" width="480" height="360"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<tabView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="402">
<rect key="frame" x="23" y="10" width="454" height="344"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<font key="font" metaFont="system"/>
<tabViewItems>
<tabViewItem label="General" identifier="1" id="403">
<view key="view" id="406">
<rect key="frame" x="10" y="33" width="434" height="298"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="410">
<rect key="frame" x="18" y="174" width="398" height="120"/>
<subviews>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="446">
<rect key="frame" x="47" y="53" width="110" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Toggle capturing:" id="447">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button imageHugsTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="444">
<rect key="frame" x="163" y="16" width="195" height="18"/>
<buttonCell key="cell" type="check" title="Show preferences at launch" bezelStyle="regularSquare" imagePosition="leading" alignment="left" state="on" inset="2" id="445">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="462" name="value" keyPath="values.alwaysShowPrefs" id="463"/>
</connections>
</button>
<popUpButton verticalHuggingPriority="750" imageHugsTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="420">
<rect key="frame" x="162" y="78" width="195" height="25"/>
<popUpButtonCell key="cell" type="push" title="In the menu bar and Dock" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="3" inset="2" selectedItem="425" id="421">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" title="OtherViews" id="422">
<items>
<menuItem title="In the menu bar" tag="1" id="423"/>
<menuItem title="In the Dock" tag="2" id="424"/>
<menuItem title="In the menu bar and Dock" state="on" tag="3" id="425"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="changeIconPreference:" target="375" id="479"/>
<binding destination="462" name="selectedTag" keyPath="values.displayIcon" id="467"/>
</connections>
</popUpButton>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="418">
<rect key="frame" x="17" y="84" width="140" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Display KeyCastr icon:" id="419">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<customView verifyAmbiguity="ignoreSizes" translatesAutoresizingMaskIntoConstraints="NO" id="J1Q-sG-Zms" customClass="SRRecorderControl">
<rect key="frame" x="165" y="49" width="112" height="25"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="25" id="eK4-Fh-hia"/>
<constraint firstAttribute="height" constant="25" id="hLq-xG-0AL"/>
<constraint firstAttribute="width" constant="112" id="nQT-sS-wpQ"/>
</constraints>
<connections>
<outlet property="delegate" destination="375" id="F1v-ra-SAD"/>
</connections>
</customView>
</subviews>
<constraints>
<constraint firstItem="420" firstAttribute="leading" secondItem="418" secondAttribute="trailing" constant="10" id="ASO-R4-kP5"/>
<constraint firstItem="418" firstAttribute="top" secondItem="410" secondAttribute="top" constant="20" symbolic="YES" id="AhR-0d-QJW"/>
<constraint firstItem="446" firstAttribute="trailing" secondItem="418" secondAttribute="trailing" id="B0l-8L-Veq"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="420" secondAttribute="trailing" constant="20" symbolic="YES" id="Gc3-NV-ODD"/>
<constraint firstItem="J1Q-sG-Zms" firstAttribute="leading" secondItem="420" secondAttribute="leading" id="KcZ-du-wqH"/>
<constraint firstItem="446" firstAttribute="top" secondItem="418" secondAttribute="bottom" constant="15" id="Ki8-5m-GAo"/>
<constraint firstItem="420" firstAttribute="centerY" secondItem="418" secondAttribute="centerY" id="MKc-AT-Q0J"/>
<constraint firstItem="444" firstAttribute="top" secondItem="J1Q-sG-Zms" secondAttribute="bottom" constant="16" id="WKQ-Xm-f9J"/>
<constraint firstItem="J1Q-sG-Zms" firstAttribute="centerY" secondItem="446" secondAttribute="centerY" id="YnR-do-Ets"/>
<constraint firstItem="418" firstAttribute="leading" secondItem="410" secondAttribute="leading" constant="19" id="frE-vs-mvf"/>
<constraint firstItem="444" firstAttribute="leading" secondItem="J1Q-sG-Zms" secondAttribute="leading" id="qEJ-kF-57p"/>
</constraints>
</customView>
</subviews>
<constraints>
<constraint firstItem="410" firstAttribute="leading" secondItem="406" secondAttribute="leading" constant="18" id="2Yw-fp-kHD"/>
<constraint firstAttribute="trailing" secondItem="410" secondAttribute="trailing" constant="18" id="DdU-FN-2jL"/>
<constraint firstItem="410" firstAttribute="top" secondItem="406" secondAttribute="top" constant="4" id="XpO-Rg-CHJ"/>
<constraint firstAttribute="bottom" secondItem="410" secondAttribute="bottom" constant="174" id="waU-gj-tj2"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="Display" identifier="2" id="404">
<view key="view" id="405">
<rect key="frame" x="10" y="33" width="434" height="298"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="411">
<rect key="frame" x="27" y="31" width="390" height="246"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews>
<customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="426">
<rect key="frame" x="0.0" y="192" width="390" height="54"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews>
<box autoresizesSubviews="NO" verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="435">
<rect key="frame" x="0.0" y="-2" width="390" height="5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
</box>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" imageHugsTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="429">
<rect key="frame" x="143" y="10" width="230" height="26"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Item 1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" inset="2" selectedItem="432" id="430">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" title="OtherViews" id="431">
<items>
<menuItem title="Item 1" state="on" id="432"/>
<menuItem title="Item 2" id="433"/>
<menuItem title="Item 3" id="434"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<binding destination="375" name="contentValues" keyPath="availableVisualizerNames" id="440"/>
<binding destination="375" name="selectedValue" keyPath="currentVisualizerName" previousBinding="440" id="443"/>
</connections>
</popUpButton>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="427">
<rect key="frame" x="17" y="16" width="124" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Selected visualizer:" id="428">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
</customView>
</subviews>
</customView>
</subviews>
</view>
</tabViewItem>
<tabViewItem label="Update" identifier="Item 2" id="408">
<view key="view" id="409">
<rect key="frame" x="10" y="33" width="434" height="298"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="412">
<rect key="frame" x="17" y="178" width="331" height="117"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<button fixedFrame="YES" imageHugsTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="459">
<rect key="frame" x="18" y="18" width="269" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Include anonymous system profile" bezelStyle="regularSquare" imagePosition="leading" alignment="left" enabled="NO" inset="2" id="460">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="496" name="enabled" keyPath="automaticallyChecksForUpdates" id="511"/>
<binding destination="496" name="value" keyPath="sendsSystemProfile" id="499"/>
</connections>
</button>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="457">
<rect key="frame" x="221" y="59" width="90" height="14"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="Never" id="458">
<dateFormatter key="formatter" formatterBehavior="custom10_4" dateStyle="short" id="509"/>
<font key="font" metaFont="smallSystemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="496" name="value" keyPath="lastUpdateCheckDate" id="508">
<dictionary key="options">
<integer key="NSAllowsEditingMultipleValuesSelection" value="1"/>
<integer key="NSAlwaysPresentsApplicationModalAlerts" value="0"/>
<integer key="NSConditionallySetsEditable" value="0"/>
<integer key="NSConditionallySetsEnabled" value="0"/>
<integer key="NSConditionallySetsHidden" value="0"/>
<integer key="NSContinuouslyUpdatesValue" value="0"/>
<string key="NSMultipleValuesPlaceholder"></string>
<string key="NSNoSelectionPlaceholder"></string>
<string key="NSNotApplicablePlaceholder"></string>
<string key="NSNullPlaceholder">Never</string>
<integer key="NSRaisesForNotApplicableKeys" value="1"/>
<integer key="NSValidatesImmediately" value="0"/>
</dictionary>
</binding>
</connections>
</textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="455">
<rect key="frame" x="136" y="59" width="83" height="14"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Last checked:" id="456">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" fixedFrame="YES" imageHugsTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="453">
<rect key="frame" x="34" y="51" width="96" height="28"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Check Now" bezelStyle="rounded" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="454">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>
<connections>
<action selector="checkForUpdates:" target="496" id="497"/>
</connections>
</button>
<button fixedFrame="YES" imageHugsTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="451">
<rect key="frame" x="18" y="81" width="269" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Check for updates at startup" bezelStyle="regularSquare" imagePosition="leading" alignment="left" state="on" inset="2" id="452">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="496" name="value" keyPath="automaticallyChecksForUpdates" id="501"/>
</connections>
</button>
</subviews>
</customView>
</subviews>
</view>
</tabViewItem>
</tabViewItems>
</tabView>
</subviews>
</view>
<connections>
<outlet property="delegate" destination="375" id="477"/>
</connections>
<point key="canvasLocation" x="-305" y="-120"/>
</window>
<window allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="384" userLabel="About">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="194" width="256" height="316"/>
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1175"/>
<view key="contentView" id="385">
<rect key="frame" x="0.0" y="0.0" width="256" height="316"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<qcView fixedFrame="YES" autostartsRendering="YES" translatesAutoresizingMaskIntoConstraints="NO" id="386">
<rect key="frame" x="0.0" y="68" width="256" height="256"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<color key="eraseColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
</qcView>
<box autoresizesSubviews="NO" verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="387">
<rect key="frame" x="-5" y="65" width="280" height="5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</box>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="388">
<rect key="frame" x="17" y="38" width="222" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Version 0.8.0" id="389">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="392">
<rect key="frame" x="18" y="17" width="210" height="14"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="clipping" sendsActionOnEndEditing="YES" id="393">
<font key="font" metaFont="smallSystem"/>
<string key="title" base64-UTF8="YES">
Q29weXJpZ2h0IAPCqTIwMDkgU3RlcGhlbiBEZWtlbg
</string>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
</view>
<point key="canvasLocation" x="67" y="436"/>
</window>
<userDefaultsController representsSharedInstance="YES" id="462"/>
</objects>
<designables>
<designable name="J1Q-sG-Zms">
<size key="intrinsicContentSize" width="102" height="25"/>
</designable>
</designables>
</document>
================================================
FILE: keycastr/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en_US</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string>KeyCastr</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(MARKETING_VERSION)</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.productivity</string>
<key>LSUIElement</key>
<string>1</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
<key>SUFeedURL</key>
<string>https://keycastr.github.io/appcast.xml</string>
<key>SUPublicEDKey</key>
<string>Ddy5wePDO7kDtpd0s2Y8nGBgVR3HLs1p96Xvhg24/Vc=</string>
</dict>
</plist>
================================================
FILE: keycastr/KCAppController.h
================================================
// Copyright (c) 2009 Stephen Deken
// Copyright (c) 2020-2024 Andrew Kitchen
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name KeyCastr nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Cocoa/Cocoa.h>
@protocol KCVisualizer;
@interface KCAppController : NSObject <NSApplicationDelegate>
@property (nonatomic) BOOL isCapturing;
-(IBAction) orderFrontKeyCastrAboutPanel:(id)sender;
-(IBAction) orderFrontKeyCastrPreferencesPanel:(id)sender;
-(IBAction) toggleRecording:(id)sender;
-(IBAction) pretendToDoSomethingImportant:(id)sender;
-(IBAction) changeIconPreference:(id)sender;
-(void) registerVisualizers;
-(NSArray*) availableVisualizerNames;
-(NSString*) currentVisualizerName;
-(void) setCurrentVisualizerName:(NSString*)visualizerName;
-(id<KCVisualizer>) currentVisualizer;
-(void) setCurrentVisualizer:(id<KCVisualizer>)newVisualizer;
- (NSArray *)availableMouseDisplayOptionNames;
- (NSString *)currentMouseDisplayOptionName;
- (void)setCurrentMouseDisplayOptionName:(NSString *)displayOptionName;
@end
================================================
FILE: keycastr/KCAppController.m
================================================
// Copyright (c) 2009 Stephen Deken
// Copyright (c) 2014-2025 Andrew Kitchen
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name KeyCastr nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#if !__has_feature(objc_arc)
#error "ARC is required for this file -- enable with -fobjc-arc"
#endif
#import <Quartz/Quartz.h>
#import <ShortcutRecorder/ShortcutRecorder.h>
#import "KCAppController.h"
#import "KCEventTap.h"
#import "KCKeystroke.h"
#import "KCMouseEventVisualizer.h"
#import "KCPrefsWindowController.h"
#import "KCUserDefaultsMigration.h"
#import "KCVisualizer.h"
typedef struct _KeyCombo {
unsigned int flags; // 0 for no flags
signed short code; // -1 for no code
} KeyCombo;
static NSString* kKCPrefCapturingHotKey = @"capturingHotKey";
static NSString* kKCPrefVisibleAtLaunch = @"alwaysShowPrefs";
static NSString* kKCPrefDisplayIcon = @"displayIcon";
static NSString* kKCPrefSelectedVisualizer = @"selectedVisualizer";
static NSString* kKCSupplementalAlertText = @"\n\nPlease grant KeyCastr access to the Accessibility and/or Input Monitoring API in order to broadcast your keyboard inputs.\n\nWithin the System Preferences application, open the Security & Privacy preferences and add KeyCastr to the Accessibility and/or Input Monitoring list within the Privacy tab. If KeyCastr is already listed under the menus, please remove it and try again.\n";
static NSInteger kKCPrefDisplayIconInMenuBar = 0x01;
static NSInteger kKCPrefDisplayIconInDock = 0x02;
@interface KCAppController () <KCEventTapDelegate, KCMouseEventVisualizerDelegate>
@property (nonatomic, strong) KCEventTap *eventTap;
@property (nonatomic, strong) NSStatusItem *statusItem;
@property (nonatomic, assign) NSInteger prefDisplayIcon;
@property (nonatomic, assign) BOOL showInDock;
@property (nonatomic, assign) BOOL showInMenuBar;
@property (nonatomic, strong) SRShortcut *toggleCastingShortcut;
@property (nonatomic, assign) IBOutlet NSMenu *statusMenu;
@property (nonatomic, strong) IBOutlet NSWindow *aboutWindow;
@property (nonatomic, strong) IBOutlet QCView *aboutQCView;
@property (nonatomic, assign) IBOutlet NSWindow *preferencesWindow;
@property (nonatomic, assign) IBOutlet KCPrefsWindowController *prefsWindowController;
@property (nonatomic, assign) IBOutlet SRRecorderControl *shortcutRecorder;
@property (nonatomic, assign) IBOutlet NSMenuItem *statusShortcutItem;
@property (nonatomic, assign) IBOutlet NSMenuItem *dockShortcutItem;
@property (nonatomic, strong) KCMouseEventVisualizer *mouseEventVisualizer;
@property (nonatomic, strong) id<KCVisualizer> currentVisualizer;
@end
@implementation KCAppController {
BOOL _isCapturing;
}
@synthesize eventTap, statusItem, statusMenu, aboutWindow, aboutQCView, preferencesWindow, prefsWindowController, shortcutRecorder, dockShortcutItem, statusShortcutItem, mouseEventVisualizer, currentVisualizer;
@synthesize toggleCastingShortcut = _toggleCastingShortcut;
#pragma mark -
#pragma mark Startup Procedures
- (id)init {
if (!(self = [super init]))
return nil;
eventTap = [KCEventTap new];
eventTap.delegate = self;
[self registerVisualizers];
[self registerDefaults];
mouseEventVisualizer = [KCMouseEventVisualizer new];
mouseEventVisualizer.delegate = self;
[NSColor setIgnoresAlpha:NO];
return self;
}
- (void)awakeFromNib {
[self setIsCapturing:NO];
// Set current visualizer from user preferences
[self setCurrentVisualizerName:[[NSUserDefaults standardUserDefaults] objectForKey:kKCPrefSelectedVisualizer]];
[shortcutRecorder setObjectValue:self.toggleCastingShortcut];
[self updateToggleShortcutDisplay:self.toggleCastingShortcut];
[prefsWindowController nudge];
[self updateAboutPanel];
[[NSUserDefaults standardUserDefaults] addObserver:self
forKeyPath:kKCPrefDisplayIcon
options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew)
context:nil];
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
[NSApp activateIgnoringOtherApps:YES];
if (![self installTap]) {
[self setIsCapturing:NO];
return;
}
[self setIsCapturing:YES];
if ([[NSUserDefaults standardUserDefaults] boolForKey:kKCPrefVisibleAtLaunch]) {
[preferencesWindow center];
[preferencesWindow makeKeyAndOrderFront:self];
}
}
- (void)applicationWillTerminate:(NSNotification *)notification {
[eventTap removeTap];
}
- (SRShortcut *)toggleCastingShortcut {
if (_toggleCastingShortcut == nil) {
KeyCombo toggleShortcutKey;
NSData *toggleShortcutKeyData = [[NSUserDefaults standardUserDefaults] dataForKey:kKCPrefCapturingHotKey];
if (toggleShortcutKeyData != nil) {
[toggleShortcutKeyData getBytes:&toggleShortcutKey length:sizeof(toggleShortcutKey)];
}
_toggleCastingShortcut = [SRShortcut shortcutWithDictionary:@{SRShortcutKeyKeyCode: @(toggleShortcutKey.code),
SRShortcutKeyModifierFlags: @(toggleShortcutKey.flags)}];
}
return _toggleCastingShortcut;
}
- (void)setToggleCastingShortcut:(SRShortcut *)toggleCastingShortcut {
_toggleCastingShortcut = toggleCastingShortcut;
KeyCombo newKeyCombo;
newKeyCombo.code = toggleCastingShortcut.keyCode;
newKeyCombo.flags = (unsigned int)toggleCastingShortcut.modifierFlags; // migration needed to avoid cast
[[NSUserDefaults standardUserDefaults] setObject:[NSData dataWithBytes:&newKeyCombo length:sizeof(newKeyCombo)] forKey:kKCPrefCapturingHotKey];
}
- (void)openPrefsPane:(id)sender {
NSString *text = @"tell application \"System Preferences\" \n reveal anchor \"Privacy_Accessibility\" of pane id \"com.apple.preference.security\" \n activate \n end tell";
NSAppleScript *script = [[NSAppleScript alloc] initWithSource:text];
[script executeAndReturnError:nil];
}
- (void)displayPermissionsAlertWithError:(NSError *)error {
NSAlert *alert = [NSAlert new];
[alert addButtonWithTitle:@"Close"];
[alert addButtonWithTitle:@"Open System Preferences"];
alert.messageText = @"Additional Permissions Required";
alert.informativeText = [error.localizedDescription stringByAppendingString:kKCSupplementalAlertText];
alert.alertStyle = NSAlertStyleCritical;
switch ([alert runModal]) {
case NSAlertFirstButtonReturn:
[NSApp terminate:nil];
break;
case NSAlertSecondButtonReturn: {
[self openPrefsPane:nil];
[NSApp terminate:nil];
}
break;
}
}
- (BOOL)installTap {
NSError *error = nil;
if (![eventTap installTapWithError:&error]) {
// Only display a custom error message if we're running on macOS < 10.15
NSOperatingSystemVersion minVersion = { .majorVersion = 10, .minorVersion = 15, .patchVersion = 0 };
BOOL supportsNewPermissionsAlert = [NSProcessInfo.processInfo respondsToSelector:@selector(isOperatingSystemAtLeastVersion:)] && [NSProcessInfo.processInfo isOperatingSystemAtLeastVersion:minVersion];
if (!supportsNewPermissionsAlert) {
[self displayPermissionsAlertWithError:error];
}
return NO;
}
return YES;
}
- (void)registerDefaults
{
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[KCUserDefaultsMigration performMigration:userDefaults];
KeyCombo keyCombo;
keyCombo.code = 40;
keyCombo.flags = NSEventModifierFlagControl | NSEventModifierFlagOption | NSEventModifierFlagCommand;
NSDictionary *appDefaults = @{ kKCPrefDisplayIcon: @3,
kKCPrefSelectedVisualizer: @"Default",
kKCPrefVisibleAtLaunch: @YES,
kKCPrefCapturingHotKey: [NSData dataWithBytes:&keyCombo length:sizeof(keyCombo)] };
NSArray *factories = [KCVisualizer availableVisualizerFactories];
NSMutableDictionary *defaults = [NSMutableDictionary dictionary];
for (KCVisualizerFactory *factory in factories) {
Class visualizerClass = factory.visualizerClass;
if ([visualizerClass conformsToProtocol:@protocol(KCVisualizer)]) {
[defaults addEntriesFromDictionary:[(Class<KCVisualizer>)visualizerClass visualizerDefaults]];
}
}
[defaults addEntriesFromDictionary:appDefaults];
[userDefaults registerDefaults:defaults];
}
- (void)eventTap:(KCEventTap *)tap noteKeystroke:(KCKeystroke *)keystroke
{
if (keystroke.keyCode == self.toggleCastingShortcut.keyCode && (keystroke.modifierFlags & (NSEventModifierFlagControl | NSEventModifierFlagCommand | NSEventModifierFlagShift | NSEventModifierFlagOption)) == (self.toggleCastingShortcut.modifierFlags & (NSEventModifierFlagControl | NSEventModifierFlagCommand | NSEventModifierFlagShift | NSEventModifierFlagOption)))
{
[self toggleRecording:self];
return;
}
if (!_isCapturing) {
return;
}
if (currentVisualizer != nil) {
[currentVisualizer noteKeyEvent:keystroke];
}
}
- (void)eventTap:(KCEventTap *)tap noteFlagsChanged:(NSEventModifierFlags)flags
{
if (!_isCapturing) {
return;
}
if (currentVisualizer != nil) {
[currentVisualizer noteFlagsChanged:flags];
}
}
- (void)eventTap:(KCEventTap *)eventTap noteMouseEvent:(KCMouseEvent *)mouseEvent
{
// TODO: need to let mouseUp events through after isCapturing or mouse events are disabled, otherwise we can end up with a stuck visualizer animation
if (!_isCapturing) {
return;
}
[mouseEventVisualizer noteMouseEvent:mouseEvent];
}
- (void)mouseEventVisualizer:(KCMouseEventVisualizer *)visualizer didNoteMouseEvent:(KCMouseEvent *)mouseEvent
{
[currentVisualizer noteMouseEvent:mouseEvent];
}
-(NSStatusItem*) createStatusItem
{
if (statusItem == nil)
{
statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:30];
[statusItem setMenu:statusMenu];
[statusItem.button setImage:(_isCapturing
? [NSImage imageNamed:@"KeyCastrStatusItemActive"]
: [NSImage imageNamed:@"KeyCastrStatusItemInactive"])];
statusItem.button.cell.highlighted = YES;
}
return statusItem;
}
-(void) deleteStatusItem
{
if (statusItem != nil)
{
statusItem = nil;
}
}
-(void) registerVisualizers
{
// register visualizers from plug-in paths
[KCVisualizer loadPluginsFromDirectory:[[NSBundle mainBundle] builtInPlugInsPath]];
}
- (void)updateToggleShortcutDisplay:(SRShortcut *)shortcut
{
SRKeyCodeTransformer *transformer = [SRKeyCodeTransformer sharedTransformer];
[statusShortcutItem setKeyEquivalent:[transformer transformedValue:@(shortcut.keyCode)]];
[statusShortcutItem setKeyEquivalentModifierMask:shortcut.modifierFlags];
[dockShortcutItem setKeyEquivalent:[transformer transformedValue:@(shortcut.keyCode)]];
[dockShortcutItem setKeyEquivalentModifierMask:shortcut.modifierFlags];
}
- (void)updateAboutPanel
{
for (NSView *subview in aboutWindow.contentView.subviews) {
if ([subview isKindOfClass:[NSTextField class]]) {
NSTextField *textField = (NSTextField *)subview;
NSString *prefix = @"Version ";
if ([textField.stringValue rangeOfString:prefix].location == 0) {
[textField setStringValue:[prefix stringByAppendingString:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]]];
}
}
}
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"KeyCastrAbout" ofType:@"qtz"];
[aboutQCView loadCompositionFromFile:filePath];
}
-(void) orderFrontKeyCastrAboutPanel:(id)sender
{
[aboutWindow center];
[aboutWindow makeKeyAndOrderFront:sender];
if (!aboutQCView.isRendering) {
[aboutQCView startRendering];
}
[NSApp activateIgnoringOtherApps:YES];
}
-(void) orderFrontKeyCastrPreferencesPanel:(id)sender
{
[preferencesWindow makeKeyAndOrderFront:sender];
[NSApp activateIgnoringOtherApps:YES];
}
-(void) toggleRecording:(id)sender
{
[self setIsCapturing:![self isCapturing]];
}
-(void) stopPretending:(id)what
{
[self toggleRecording:self];
}
-(void) pretendToDoSomethingImportant:(id)sender
{
[self performSelector:@selector(stopPretending:) withObject:nil afterDelay:0.1];
}
-(NSString*) currentVisualizerName
{
if (currentVisualizer == nil)
return nil;
return [currentVisualizer visualizerName];
}
-(void) setCurrentVisualizerName:(NSString*)visualizerName
{
[[NSUserDefaults standardUserDefaults] setObject:visualizerName forKey:kKCPrefSelectedVisualizer];
id<KCVisualizer> new = [KCVisualizer visualizerWithName:visualizerName];
[self setCurrentVisualizer:new];
}
-(id<KCVisualizer>) currentVisualizer
{
return currentVisualizer;
}
- (void)setCurrentVisualizer:(id <KCVisualizer>)newVisualizer {
if (newVisualizer == nil || currentVisualizer == newVisualizer) {
return;
}
id <KCVisualizer> oldVisualizer = currentVisualizer;
if (oldVisualizer != nil) {
[oldVisualizer deactivateVisualizer:self];
}
currentVisualizer = newVisualizer;
[newVisualizer showVisualizer:self];
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:newVisualizer, @"newVisualizer", oldVisualizer, @"oldVisualizer", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:@"KCVisualizerChanged" object:self userInfo:userInfo];
}
-(NSArray*) availableVisualizerNames
{
NSArray* factories = [KCVisualizer availableVisualizerFactories];
NSMutableArray* rval = [NSMutableArray array];
int i = 0;
for (i = 0; i < [factories count]; ++i)
{
id<KCVisualizerFactory> factory = [factories objectAtIndex:i];
[rval addObject:[factory visualizerName]];
}
return rval;
}
- (NSArray *)availableMouseDisplayOptionNames {
return mouseEventVisualizer.mouseDisplayOptionNames;
}
- (NSString *)currentMouseDisplayOptionName {
return mouseEventVisualizer.currentMouseDisplayOptionName;
}
- (void)setCurrentMouseDisplayOptionName:(NSString *)displayOptionName {
mouseEventVisualizer.currentMouseDisplayOptionName = displayOptionName;
}
-(void) setIsCapturing:(BOOL)capture
{
if (capture && !eventTap.tapInstalled) {
return;
}
_isCapturing = capture;
[statusItem.button setImage:(_isCapturing
? [NSImage imageNamed:@"KeyCastrStatusItemActive"]
: [NSImage imageNamed:@"KeyCastrStatusItemInactive"])
];
[statusShortcutItem setTitle:(_isCapturing
? @"Stop Casting"
: @"Start Casting")];
[dockShortcutItem setTitle:(_isCapturing
? @"Stop Casting"
: @"Start Casting")];
[NSApp setApplicationIconImage:(_isCapturing
? [NSImage imageNamed:@"KeyCastr"]
: [NSImage imageNamed:@"KeyCastrInactive"])];
}
-(void) restartPanel:(NSAlert*)alert closedWithCode:(NSModalResponse)returnCode
{
if (returnCode == NSAlertFirstButtonReturn)
{
[[NSUserDefaults standardUserDefaults] synchronize];
NSURL *bundleURL = [[NSBundle mainBundle] bundleURL];
[[NSWorkspace sharedWorkspace] launchApplicationAtURL:bundleURL
options:NSWorkspaceLaunchNewInstance
configuration:@{}
error:NULL];
[NSApp terminate:self];
}
}
-(void) changeIconPreference:(id)sender
{
// sent from the UI. Ignore until we can update the UI to remove this event.
}
#pragma mark -
#pragma mark Observers
-(void) prefDisplayIconUpdatedTo:(NSInteger)prefDisplayIcon {
// if the pref is set such that the icon is hidden in both the menu bar and the dock,
// show the icon in the dock regardless of the user's preference.
if (0 == (prefDisplayIcon & (kKCPrefDisplayIconInMenuBar | kKCPrefDisplayIconInDock))) {
prefDisplayIcon = prefDisplayIcon | kKCPrefDisplayIconInDock;
}
ProcessSerialNumber psn = { 0, kCurrentProcess };
if (prefDisplayIcon & kKCPrefDisplayIconInDock) {
// show dock icon
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
}
else {
// hide dock icon
preferencesWindow.canHide = NO;
TransformProcessType(&psn, kProcessTransformToUIElementApplication);
}
if (prefDisplayIcon & kKCPrefDisplayIconInMenuBar) {
// show icon in menu bar
[self createStatusItem];
}
else {
// hide icon in menu bar
[self deleteStatusItem];
}
}
-(void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:kKCPrefDisplayIcon]) {
[self prefDisplayIconUpdatedTo:[[change objectForKey:NSKeyValueChangeNewKey] integerValue]];
}
else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
#pragma mark -
#pragma mark Properties
-(NSInteger) prefDisplayIcon {
return [NSUserDefaults.standardUserDefaults integerForKey:kKCPrefDisplayIcon];
}
-(void) setPrefDisplayIcon:(NSInteger)prefDisplayIcon {
[NSUserDefaults.standardUserDefaults setInteger:prefDisplayIcon forKey:kKCPrefDisplayIcon];
}
-(BOOL) showInDock {
return (self.prefDisplayIcon & kKCPrefDisplayIconInDock) == kKCPrefDisplayIconInDock;
}
-(void) setShowInDock:(BOOL)showInDock {
self.prefDisplayIcon = (self.prefDisplayIcon & ~kKCPrefDisplayIconInDock) | (showInDock ? kKCPrefDisplayIconInDock : 0);
}
-(BOOL) showInMenuBar {
return (self.prefDisplayIcon & kKCPrefDisplayIconInMenuBar) == kKCPrefDisplayIconInMenuBar;
}
-(void) setShowInMenuBar:(BOOL)showInMenuBar {
self.prefDisplayIcon = (self.prefDisplayIcon & ~kKCPrefDisplayIconInMenuBar) | (showInMenuBar ? kKCPrefDisplayIconInMenuBar : 0);
}
#pragma mark -
#pragma mark SRRecorderControlDelegate methods
- (void)shortcutRecorderDidEndRecording:(SRRecorderControl *)recorder;
{
SRShortcut *newShortcut = recorder.objectValue;
if (!newShortcut) {
[shortcutRecorder setObjectValue:self.toggleCastingShortcut];
return;
}
self.toggleCastingShortcut = newShortcut;
[shortcutRecorder setObjectValue:newShortcut];
[self updateToggleShortcutDisplay:newShortcut];
}
@end
================================================
FILE: keycastr/KCColorValueTransformer.h
================================================
// Copyright (c) 2024 Andrew Kitchen
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name KeyCastr nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
API_AVAILABLE(macos(10.14))
@interface KCColorValueTransformer : NSSecureUnarchiveFromDataTransformer
@end
NS_ASSUME_NONNULL_END
================================================
FILE: keycastr/KCColorValueTransformer.m
================================================
// Copyright (c) 2024 Andrew Kitchen
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name KeyCastr nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import "KCColorValueTransformer.h"
@implementation KCColorValueTransformer
+ (NSArray<Class> *)allowedTopLevelClasses {
return @[NSColor.class];
}
@end
================================================
FILE: keycastr/KCDefaultVisualizer-Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSMainNibFile</key>
<string>KCDefaultVisualizer</string>
<key>NSPrincipalClass</key>
<string>KCDefaultVisualizerFactory</string>
</dict>
</plist>
================================================
FILE: keycastr/KCDefaultVisualizer.h
================================================
// Copyright (c) 2009 Stephen Deken
// Copyright (c) 2014-2024 Andrew Kitchen
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name KeyCastr nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Cocoa/Cocoa.h>
#import "KCVisualizer.h"
@interface KCDefaultVisualizerFactory : KCVisualizerFactory <KCVisualizerFactory>
-(NSString*) visualizerNibName;
-(Class) visualizerClass;
-(NSString*) visualizerName;
@end
@interface KCDefaultVisualizerBezelView : NSView
{
CGFloat _maxWidth;
NSColor* _backgroundColor;
float _opacity;
NSTextStorage* _textStorage;
NSLayoutManager* _layoutManager;
NSTextContainer* _textContainer;
}
-(id) initWithMaxWidth:(CGFloat)maxWidth text:(NSString *)string backgroundColor:(NSColor *)color;
-(NSDictionary*) attributes;
-(void) maybeResize;
-(void) setAlphaValue:(float)opacity;
-(void) appendString:(NSString*)t;
-(void) scheduleFadeOut;
@end
@class KCDefaultVisualizerWindow;
@interface KCBezelAnimation : NSAnimation<NSAnimationDelegate>
@property (nonatomic, strong) KCDefaultVisualizerBezelView *bezelView;
- (KCBezelAnimation *)initWithBezelView:(KCDefaultVisualizerBezelView *)bezelView;
@end
@class KCKeystroke;
@interface KCDefaultVisualizerWindow : NSWindow
{
KCDefaultVisualizerBezelView* _currentBezelView;
NSMutableArray* _runningAnimations;
}
- (void)addKeystroke:(KCKeystroke *)keystroke;
- (void)addMouseEvent:(KCMouseEvent *)mouseEvent;
- (void)addRunningAnimation:(KCBezelAnimation *)animation;
- (instancetype)init;
@end
typedef NS_ENUM(NSInteger, KCDefaultVisualizerDisplayOption) {
KCDefaultVisualizerDisplayOptionCommandKeysOnly = 0,
KCDefaultVisualizerDisplayOptionAllModifiedKeys,
KCDefaultVisualizerDisplayOptionAllKeys,
KCDefaultVisualizerDisplayOptionDefault = KCDefaultVisualizerDisplayOptionCommandKeysOnly
};
@interface KCDefaultVisualizerPreferencesView : NSView
@property (nonatomic, assign) IBOutlet NSButton *commandKeysOnlyButton;
@property (nonatomic, assign) IBOutlet NSButton *allModifiedKeysButton;
@property (nonatomic, assign) IBOutlet NSButton *allKeysButton;
@end
@interface KCDefaultVisualizer : KCVisualizer <KCVisualizer>
{
KCDefaultVisualizerWindow* visualizerWindow;
}
@property (nonatomic, assign) IBOutlet KCDefaultVisualizerPreferencesView *preferencesView;
@property (nonatomic, assign) KCDefaultVisualizerDisplayOption displayMode;
- (IBAction)preferencesViewDidSelectDisplayOption:(id)sender;
- (BOOL)shouldOnlyDisplayCommandKeys;
- (BOOL)shouldOnlyDisplayModifiedKeys;
@end
================================================
FILE: keycastr/KCDefaultVisualizer.m
================================================
// Copyright (c) 2009 Stephen Deken
// Copyright (c) 2014-2024 Andrew Kitchen
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name KeyCastr nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#if !__has_feature(objc_arc)
#error "ARC is required for this file -- enable with -fobjc-arc"
#endif
#import "KCDefaultVisualizer.h"
#import "KCKeystroke.h"
#import "KCMouseEvent.h"
#import "NSBezierPath+RoundedRect.h"
#import "NSUserDefaults+Utility.h"
// TODO: seems this should be based on the font height, or shouldn't be needed at all
static const CGFloat kKCDefaultBezelHeight = 32.0;
static const CGFloat kKCDefaultBezelPadding = 10.0;
@implementation KCDefaultVisualizerFactory
-(NSString*) visualizerNibName
{
return @"KCDefaultVisualizer";
}
-(Class) visualizerClass
{
return [KCDefaultVisualizer class];
}
-(NSString*) visualizerName
{
return @"Default";
}
@end
@implementation KCDefaultVisualizerPreferencesView
@synthesize commandKeysOnlyButton, allModifiedKeysButton, allKeysButton;
@end
@implementation KCDefaultVisualizer
@dynamic preferencesView;
- (instancetype)init
{
if (!(self = [super init]))
return nil;
visualizerWindow = [[KCDefaultVisualizerWindow alloc] init];
return self;
}
-(NSString*) visualizerName
{
return @"Default";
}
-(void) awakeFromNib
{
[super awakeFromNib];
[self configureDisplayModeWithDefaults:NSUserDefaults.standardUserDefaults];
}
- (void)configureDisplayModeWithDefaults:(NSUserDefaults *)userDefaults
{
if ([userDefaults boolForKey:@"default.commandKeysOnly"]) {
_displayMode = KCDefaultVisualizerDisplayOptionCommandKeysOnly;
} else if ([userDefaults boolForKey:@"default.allModifiedKeys"]) {
_displayMode = KCDefaultVisualizerDisplayOptionAllModifiedKeys;
} else {
_displayMode = KCDefaultVisualizerDisplayOptionAllKeys;
}
}
- (IBAction)preferencesViewDidSelectDisplayOption:(id)sender
{
KCDefaultVisualizerDisplayOption mode = KCDefaultVisualizerDisplayOptionDefault;
if (sender == self.preferencesView.commandKeysOnlyButton) {
mode = KCDefaultVisualizerDisplayOptionCommandKeysOnly;
}
else if (sender == self.preferencesView.allModifiedKeysButton) {
mode = KCDefaultVisualizerDisplayOptionAllModifiedKeys;
}
else if (sender == self.preferencesView.allKeysButton) {
mode = KCDefaultVisualizerDisplayOptionAllKeys;
}
[self setDisplayMode:mode];
}
- (void)setDisplayMode:(KCDefaultVisualizerDisplayOption)mode
{
_displayMode = mode;
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setBool:(mode == KCDefaultVisualizerDisplayOptionCommandKeysOnly) forKey:@"default.commandKeysOnly"];
[userDefaults setBool:(mode == KCDefaultVisualizerDisplayOptionAllModifiedKeys) forKey:@"default.allModifiedKeys"];
[userDefaults setBool:(mode == KCDefaultVisualizerDisplayOptionAllKeys) forKey:@"default.allKeys"];
}
-(void) showVisualizer:(id)sender
{
[visualizerWindow orderFront:self];
}
-(void) hideVisualizer:(id)sender
{
[visualizerWindow orderOut:self];
}
-(void) deactivateVisualizer:(id)sender
{
[visualizerWindow orderOut:self];
}
- (BOOL)shouldOnlyDisplayCommandKeys
{
return _displayMode == KCDefaultVisualizerDisplayOptionCommandKeysOnly;
}
- (BOOL)shouldOnlyDisplayModifiedKeys
{
return _displayMode == KCDefaultVisualizerDisplayOptionAllModifiedKeys;
}
- (void)noteKeyEvent:(KCKeystroke *)keystroke
{
if (![keystroke isCommand] && [self shouldOnlyDisplayCommandKeys]) {
return;
}
if (![keystroke isModified] && [self shouldOnlyDisplayModifiedKeys]) {
return;
}
[visualizerWindow addKeystroke:keystroke];
}
- (void)noteMouseEvent:(KCMouseEvent *)mouseEvent
{
[visualizerWindow addMouseEvent:mouseEvent];
}
- (void)noteFlagsChanged:(NSEventModifierFlags)flags
{
// no-op; future option to display or otherwise react to bare modifier keypresses
}
+ (NSDictionary<NSString *, NSObject *> *)visualizerDefaults
{
return @{ @"default.commandKeysOnly": @YES,
@"default.fadeDelay": @2.0,
@"default.fadeDuration": @0.2,
@"default.fontSize": @16.0,
@"default.keystrokeDelay": @0.5,
@"default.bezelColor": [NSKeyedArchiver archivedDataWithRootObject:[NSColor colorWithCalibratedWhite:0 alpha:0.8]
requiringSecureCoding:NO
error:NULL],
@"default.textColor": [NSKeyedArchiver archivedDataWithRootObject:[NSColor colorWithCalibratedWhite:1 alpha:1]
requiringSecureCoding:NO
error:NULL],
@"default_displayModifiedCharacters": @NO,
};
}
@end
static NSRect KC_defaultFrame(void) {
CGFloat width = NSWidth(NSScreen.mainScreen.frame) - 2 * kKCDefaultBezelPadding;
return NSMakeRect(kKCDefaultBezelPadding, kKCDefaultBezelPadding, width, kKCDefaultBezelHeight);
}
@implementation KCDefaultVisualizerWindow {
BOOL _shouldResize;
BOOL _dragging;
}
- (instancetype)init
{
return [self initWithContentRect:KC_defaultFrame()
styleMask:NSWindowStyleMaskBorderless
backing:NSBackingStoreBuffered
defer:NO];
}
- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSWindowStyleMask)styleMask backing:(NSBackingStoreType)backing defer:(BOOL)defer
{
if (!(self = [super initWithContentRect:contentRect styleMask:styleMask backing:backing defer:defer]))
return nil;
_runningAnimations = [[NSMutableArray alloc] init];
[self setFrameUsingName:@"KCBezelWindow default.bezelWindow" force:YES];
[self setFrameAutosaveName:@"KCBezelWindow default.bezelWindow"];
[self resizePreservingHeight:NO];
CGFloat padding = 10;
NSRect boundingRect = NSInsetRect([NSScreen mainScreen].frame, padding, padding);
if (!NSPointInRect(self.frame.origin, boundingRect)) {
NSLog(@"================> Out of range: %@", NSStringFromRect(self.frame));
[self resetFrame];
}
[self setLevel:NSScreenSaverWindowLevel];
[self setOpaque:NO];
[self setBackgroundColor:[NSColor clearColor]];
[self setAlphaValue:1];
[self setMovableByWindowBackground:YES];
[self setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillTerminate:)
name:NSApplicationWillTerminateNotification
object:nil];
return self;
}
- (void)applicationWillTerminate:(NSNotification *)notification {
[self _suspendAnimations];
[self resizePreservingHeight:NO];
[self saveFrameUsingName:self.frameAutosaveName];
}
- (void)resizePreservingHeight:(BOOL)keepHeight {
NSRect screenRect = self.screen ? self.screen.frame : NSScreen.mainScreen.frame;
// Need to calculate a different width if our origin is dragged to a screen to the left
CGFloat optimalWidth;
if (NSMinX(self.frame) < NSMinX(self.screen.frame)) {
optimalWidth = NSMinX(self.screen.frame) - NSMinX(self.frame) - kKCDefaultBezelPadding;
} else {
optimalWidth = fabs(NSMaxX(screenRect) - NSMinX(self.frame)) - kKCDefaultBezelPadding;
}
CGFloat height;
if (keepHeight) {
height = fmaxf(kKCDefaultBezelHeight, NSHeight(self.frame));
} else {
height = kKCDefaultBezelHeight;
}
NSRect frame = NSMakeRect(NSMinX(self.frame), NSMinY(self.frame), optimalWidth, height);
[self setFrame:frame display:NO];
}
- (void)resetFrame {
[self setFrame:KC_defaultFrame() display:NO];
}
- (void)abandonCurrentBezelView {
_currentBezelView = nil;
}
-(void) _cancelLineBreak
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(abandonCurrentBezelView) object:nil];
}
-(void) _scheduleLineBreak
{
[self performSelector:@selector(abandonCurrentBezelView)
withObject:nil
afterDelay:[[NSUserDefaults standardUserDefaults] floatForKey:@"default.keystrokeDelay"]];
}
- (void)addMouseEvent:(KCMouseEvent *)mouseEvent
{
if (NSEventMaskFromType(mouseEvent.type) & (NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown | NSEventMaskOtherMouseDown)) {
[self abandonCurrentBezelView];
[self appendString:[mouseEvent convertToString]];
}
}
- (void)addKeystroke:(KCKeystroke *)keystroke
{
[self _cancelLineBreak];
if ([keystroke isCommand])
{
[self abandonCurrentBezelView];
}
[self appendString:[keystroke convertToString]];
}
- (void)appendString:(NSString *)string
{
if (_currentBezelView == nil)
{
if (!(NSWidth(self.frame) > 0)) {
NSLog(@"Fixing frame; width not greater than 0: %@", NSStringFromRect(self.frame));
[self resetFrame];
}
NSColor *backgroundColor = [[NSUserDefaults standardUserDefaults] colorForKey:@"default.bezelColor"];
_currentBezelView = [[KCDefaultVisualizerBezelView alloc] initWithMaxWidth:NSWidth(self.frame)
text:string
backgroundColor:backgroundColor];
[_currentBezelView setAutoresizingMask:NSViewMinYMargin];
NSRect frame = self.frame;
frame.size.height += 10 + _currentBezelView.frame.size.height;
[self setFrame:frame display:YES animate:NO];
[[self contentView] addSubview:_currentBezelView];
}
else
{
[_currentBezelView appendString:string];
}
[self _scheduleLineBreak];
}
-(void) addRunningAnimation:(KCBezelAnimation*)animation
{
[_runningAnimations addObject:animation];
[self saveFrameUsingName:self.frameAutosaveName];
if (_dragging)
[animation stopAnimation];
}
-(void) removeRunningAnimation:(KCBezelAnimation*)animation
{
[_runningAnimations removeObject:animation];
[self saveFrameUsingName:self.frameAutosaveName];
if (_runningAnimations.count == 0) {
if (_shouldResize) {
_shouldResize = NO;
[self resizePreservingHeight:YES];
}
}
}
- (void)_suspendAnimations
{
NSUInteger vc = [_runningAnimations count];
int i;
for (i = 0; i < vc; ++i)
{
NSAnimation* anim = [_runningAnimations objectAtIndex:i];
[anim stopAnimation];
}
}
- (void)_resumeAnimations
{
NSUInteger vc = [_runningAnimations count];
int i;
for (i = 0; i < vc; ++i)
{
NSAnimation* anim = [_runningAnimations objectAtIndex:i];
[anim startAnimation];
}
}
- (void)mouseDown:(NSEvent*)theEvent
{
_dragging = YES;
[self _suspendAnimations];
[super mouseDown:theEvent];
}
- (void)mouseUp:(NSEvent*)theEvent
{
_dragging = NO;
_shouldResize = YES;
[self _resumeAnimations];
[super mouseUp:theEvent];
}
@end
@implementation KCBezelAnimation
- (KCBezelAnimation *)initWithBezelView:(KCDefaultVisualizerBezelView*)bezelView
{
if (!(self = [super init]))
return nil;
_bezelView = bezelView;
return self;
}
-(void) fadeOutOverDuration:(NSTimeInterval)duration
{
if ([self isAnimating])
return;
if (duration < 0.01)
{
// just do it immediately
[self animationDidEnd:self];
return;
}
[self setDelegate:self];
[self setDuration:duration];
[self setFrameRate:30];
[self setAnimationCurve:NSAnimationLinear];
[self setAnimationBlockingMode:NSAnimationNonblocking];
[self startAnimation];
}
-(void) setCurrentProgress:(NSAnimationProgress)progress
{
[super setCurrentProgress:progress];
[_bezelView setAlphaValue:(1 - progress)];
[_bezelView setNeedsDisplay:YES];
}
-(void) animationDidEnd:(NSAnimation*)anim
{
CGFloat deltaY = [_bezelView frame].size.height + 10;
KCDefaultVisualizerWindow* w = (KCDefaultVisualizerWindow*)[_bezelView window];
[w removeRunningAnimation:self];
[_bezelView removeFromSuperview];
NSArray* a = [[w contentView] subviews];
NSUInteger vc = [a count];
int i;
for (i = 0; i < vc; ++i)
{
NSView* v = [a objectAtIndex:i];
NSRect r = [v frame];
r.origin.y += deltaY;
[v setFrame:r];
}
NSRect r = [w frame];
r.size.height -= deltaY;
if (r.size.height < 0)
r.size.height = 0;
[w setFrame:r display:YES animate:NO];
}
@end
@implementation KCDefaultVisualizerBezelView
static const int kKCBezelBorder = 6;
- (id)initWithMaxWidth:(CGFloat)maxWidth text:(NSString *)string backgroundColor:(NSColor *)color
{
if (!(self = [super initWithFrame:NSMakeRect(0, 0, maxWidth, kKCDefaultBezelHeight)]))
return nil;
_opacity = 1.0;
_maxWidth = maxWidth;
_backgroundColor = color;
_textStorage = [[NSTextStorage alloc] initWithString:string];
_textContainer = [[NSTextContainer alloc] initWithContainerSize:NSMakeSize(_maxWidth-kKCBezelBorder*2, FLT_MAX)];
_layoutManager = [[NSLayoutManager alloc] init];
[_layoutManager addTextContainer:_textContainer];
[_textStorage addLayoutManager:_layoutManager];
[_textStorage setAttributes:[self attributes] range:NSMakeRange(0, [_textStorage length])];
[self setAutoresizingMask:NSViewMinYMargin];
[self maybeResize];
[self scheduleFadeOut];
return self;
}
-(void) scheduleFadeOut
{
NSTimeInterval fadeDelay = [[NSUserDefaults standardUserDefaults] floatForKey:@"default.fadeDelay"];
if (fadeDelay == 0)
fadeDelay = 2;
SEL fadeOutSelector = @selector(beginFadeOut:);
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:fadeOutSelector object:nil];
[self performSelector:fadeOutSelector withObject:nil afterDelay:fadeDelay];
}
-(void) beginFadeOut:(id)sender
{
KCDefaultVisualizerWindow *window = (KCDefaultVisualizerWindow *)[self window];
KCBezelAnimation *animation = [[KCBezelAnimation alloc] initWithBezelView:self];
[animation fadeOutOverDuration:[[NSUserDefaults standardUserDefaults] floatForKey:@"default.fadeDuration"]];
[window addRunningAnimation:animation];
}
-(NSDictionary*) attributes
{
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSColor* c = [ud colorForKey:@"default.textColor"];
return [NSDictionary dictionaryWithObjectsAndKeys:
[NSFont systemFontOfSize:[ud floatForKey:@"default.fontSize"]], NSFontAttributeName,
[c colorWithAlphaComponent:_opacity * [c alphaComponent]], NSForegroundColorAttributeName,
nil];
}
-(void) setAlphaValue:(float)opacity
{
_opacity = opacity;
[_textStorage setAttributes:[self attributes] range:NSMakeRange(0, [_textStorage length])];
}
-(void) drawRect:(NSRect)r
{
NSRect frame = [self bounds];
NSBezierPath *bgPath = [NSBezierPath bezierPath];
[bgPath appendRoundedRect:frame radius:16];
[[_backgroundColor colorWithAlphaComponent:_opacity * [_backgroundColor alphaComponent]] setFill];
[bgPath fill];
[_layoutManager drawGlyphsForGlyphRange:NSMakeRange(0,[_textStorage length]) atPoint:NSMakePoint(kKCBezelBorder, kKCBezelBorder)];
}
-(void) maybeResize
{
NSRect frame = [self frame];
[_layoutManager glyphRangeForTextContainer:_textContainer];
NSSize size = [_layoutManager usedRectForTextContainer:_textContainer].size;
size.width += kKCBezelBorder * 2;
size.height += kKCBezelBorder * 2;
if (frame.size.width != size.width || frame.size.height != size.height)
{
[self setFrameSize:size];
if (size.height != frame.size.height)
{
float deltaY = size.height - frame.size.height;
NSWindow* w = [self window];
NSArray* a = [[w contentView] subviews];
NSUInteger vc = [a count];
int i;
for (i = 0; i < vc; ++i)
{
NSView* v = [a objectAtIndex:i];
if (v == self) continue;
NSRect f = [v frame];
if (f.origin.y > frame.origin.y)
f.origin.y += deltaY;
}
NSRect r = [w frame];
r.size.height += deltaY;
[self setAutoresizingMask:NSViewMaxYMargin];
[w setFrame:r display:YES];
[self setAutoresizingMask:NSViewMinYMargin];
}
}
}
-(void) appendString:(NSString*)t
{
[self scheduleFadeOut];
[_textStorage appendAttributedString:[[NSAttributedString alloc] initWithString:t]];
[_textStorage setAttributes:[self attributes] range:NSMakeRange(0, [_textStorage length])];
[self maybeResize];
[self setNeedsDisplay:YES];
}
-(BOOL) isFlipped
{
return YES;
}
@end
================================================
FILE: keycastr/KCDefaultVisualizer.nib/designable.nib
================================================
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22690"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="KCDefaultVisualizer">
<connections>
<outlet property="preferencesView" destination="1" id="7"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="1" userLabel="Preferences" customClass="KCDefaultVisualizerPreferencesView">
<rect key="frame" x="0.0" y="0.0" width="380" height="380"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews>
<textField focusRingType="none" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="16">
<rect key="frame" x="93" y="254" width="64" height="17"/>
<constraints>
<constraint firstAttribute="height" constant="17" id="oj0-lq-OA1"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="right" title="Font Size:" id="103">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="52">
<rect key="frame" x="43" y="230" width="110" height="24"/>
<constraints>
<constraint firstAttribute="height" constant="24" id="FYf-Uh-xbf"/>
<constraint firstAttribute="width" constant="106" id="eRQ-CZ-p1F"/>
</constraints>
<textFieldCell key="cell" controlSize="mini" sendsActionOnEndEditing="YES" alignment="right" title="Size of the keystrokes on the bezel" id="121">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="disabledControlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<slider verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="24">
<rect key="frame" x="161" y="256" width="201" height="13"/>
<sliderCell key="cell" controlSize="mini" state="on" alignment="left" minValue="8" maxValue="144" doubleValue="45.944813829787229" tickMarkPosition="above" sliderType="linear" id="107"/>
<connections>
<binding destination="9" name="value" keyPath="values.default.fontSize" id="64"/>
</connections>
</slider>
<textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="26">
<rect key="frame" x="161" y="243" width="23" height="11"/>
<textFieldCell key="cell" controlSize="mini" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Tiny" id="108">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="28">
<rect key="frame" x="334" y="243" width="28" height="11"/>
<textFieldCell key="cell" controlSize="mini" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Huge" id="109">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="18">
<rect key="frame" x="48" y="205" width="109" height="17"/>
<constraints>
<constraint firstAttribute="height" constant="17" id="bX2-H7-kTx"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="right" title="Line Break Delay:" id="104">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="48">
<rect key="frame" x="45" y="180" width="108" height="24"/>
<constraints>
<constraint firstAttribute="width" constant="104" id="lW4-c6-Sd9"/>
</constraints>
<textFieldCell key="cell" controlSize="mini" sendsActionOnEndEditing="YES" alignment="right" title="Length of time before the line breaks" id="119">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="disabledControlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<slider verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="32">
<rect key="frame" x="161" y="206" width="201" height="15"/>
<sliderCell key="cell" controlSize="mini" state="on" alignment="left" minValue="0.10000000000000001" maxValue="1" doubleValue="0.75" tickMarkPosition="above" sliderType="linear" id="112"/>
<connections>
<binding destination="9" name="value" keyPath="values.default.keystrokeDelay" id="65"/>
</connections>
</slider>
<textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="31">
<rect key="frame" x="161" y="193" width="28" height="11"/>
<textFieldCell key="cell" controlSize="mini" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Short" id="111">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="30">
<rect key="frame" x="336" y="193" width="26" height="11"/>
<textFieldCell key="cell" controlSize="mini" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Long" id="110">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="20">
<rect key="frame" x="77" y="155" width="80" height="17"/>
<constraints>
<constraint firstAttribute="height" constant="17" id="4aB-UP-YRn"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="right" title="Linger Time:" id="105">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="50">
<rect key="frame" x="49" y="129" width="104" height="24"/>
<constraints>
<constraint firstAttribute="width" constant="100" id="Rtg-nV-mBt"/>
</constraints>
<textFieldCell key="cell" controlSize="mini" sendsActionOnEndEditing="YES" alignment="right" title="Length of time before the text fades away" id="120">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="disabledControlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<slider verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="36">
<rect key="frame" x="161" y="155" width="201" height="17"/>
<sliderCell key="cell" controlSize="mini" state="on" alignment="left" minValue="0.10000000000000001" maxValue="5" doubleValue="1" tickMarkPosition="above" sliderType="linear" id="113"/>
<connections>
<binding destination="9" name="value" keyPath="values.default.fadeDelay" id="66"/>
</connections>
</slider>
<textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="38">
<rect key="frame" x="161" y="142" width="28" height="11"/>
<textFieldCell key="cell" controlSize="mini" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Short" id="115">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="37">
<rect key="frame" x="336" y="142" width="26" height="11"/>
<textFieldCell key="cell" controlSize="mini" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Long" id="114">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="22">
<rect key="frame" x="64" y="105" width="93" height="17"/>
<constraints>
<constraint firstAttribute="height" constant="17" id="Rte-ej-cs2"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="right" title="Fade Duration:" id="106">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="54">
<rect key="frame" x="77" y="80" width="76" height="24"/>
<constraints>
<constraint firstAttribute="width" constant="72" id="S53-WX-hfS"/>
</constraints>
<textFieldCell key="cell" controlSize="mini" sendsActionOnEndEditing="YES" alignment="right" title="Duration of the fade effect" id="122">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="disabledControlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<slider verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="42">
<rect key="frame" x="161" y="105" width="201" height="17"/>
<sliderCell key="cell" controlSize="mini" state="on" alignment="left" maxValue="1" doubleValue="0.25" tickMarkPosition="above" sliderType="linear" id="116"/>
<connections>
<binding destination="9" name="value" keyPath="values.default.fadeDuration" id="67"/>
</connections>
</slider>
<textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="43">
<rect key="frame" x="161" y="92" width="35" height="11"/>
<textFieldCell key="cell" controlSize="mini" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Instant" id="117">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="44">
<rect key="frame" x="322" y="92" width="40" height="11"/>
<textFieldCell key="cell" controlSize="mini" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Fast-ish" id="118">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="2">
<rect key="frame" x="79" y="53" width="78" height="17"/>
<constraints>
<constraint firstAttribute="height" constant="17" id="izL-kT-ll9"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="right" title="Bezel Color:" id="101">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<colorWell translatesAutoresizingMaskIntoConstraints="NO" id="8">
<rect key="frame" x="160" y="48" width="50" height="26"/>
<constraints>
<constraint firstAttribute="width" constant="44" id="FkX-jX-V4s"/>
<constraint firstAttribute="height" constant="22" id="InA-Ja-vdL"/>
</constraints>
<color key="color" red="0.0" green="0.0" blue="0.0" alpha="0.80000000999999998" colorSpace="calibratedRGB"/>
<connections>
<binding destination="9" name="value" keyPath="values.default.bezelColor" id="WiU-rr-09e">
<dictionary key="options">
<string key="NSValueTransformerName">KCColorValueTransformer</string>
</dictionary>
</binding>
</connections>
</colorWell>
<textField focusRingType="none" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="14">
<rect key="frame" x="87" y="23" width="70" height="17"/>
<constraints>
<constraint firstAttribute="height" constant="17" id="ywo-xr-G82"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="right" title="Text Color:" id="102">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<colorWell translatesAutoresizingMaskIntoConstraints="NO" id="13">
<rect key="frame" x="160" y="18" width="50" height="26"/>
<color key="color" white="1" alpha="1" colorSpace="calibratedWhite"/>
<connections>
<binding destination="9" name="value" keyPath="values.default.textColor" id="aIJ-FX-pPt">
<dictionary key="options">
<string key="NSValueTransformerName">KCColorValueTransformer</string>
</dictionary>
</binding>
</connections>
</colorWell>
<button verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="56">
<rect key="frame" x="161" y="347" width="152" height="18"/>
<buttonCell key="cell" type="radio" title="Command Keys Only" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="123">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="preferencesViewDidSelectDisplayOption:" target="-2" id="4O5-Cb-Cy2"/>
<binding destination="9" name="value" keyPath="values.default.commandKeysOnly" id="TEL-ve-bVh"/>
</connections>
</button>
<button tag="1" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="2Tl-2J-YBO" userLabel="All Modified Keys Button">
<rect key="frame" x="161" y="325" width="130" height="18"/>
<buttonCell key="cell" type="radio" title="All Modified Keys" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="yg3-BT-nUP">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="preferencesViewDidSelectDisplayOption:" target="-2" id="RxX-iy-y6G"/>
<binding destination="9" name="value" keyPath="values.default.allModifiedKeys" id="8uF-RA-gxC"/>
</connections>
</button>
<button tag="2" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="wva-Xt-LhR">
<rect key="frame" x="161" y="303" width="74" height="18"/>
<buttonCell key="cell" type="radio" title="All Keys" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="uCf-M1-rtX">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="preferencesViewDidSelectDisplayOption:" target="-2" id="Kt4-6J-rth"/>
<binding destination="9" name="value" keyPath="values.default.allKeys" id="dUM-9e-oZf"/>
</connections>
</button>
<textField focusRingType="none" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="8ph-mh-BBf">
<rect key="frame" x="67" y="348" width="90" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="right" title="Display Mode:" id="6mL-ee-0MX">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<box autoresizesSubviews="NO" verticalHuggingPriority="750" boxType="separator" id="8PJ-cL-aOJ">
<rect key="frame" x="6" y="283" width="384" height="5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</box>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="4FL-0E-Ykx">
<rect key="frame" x="241" y="303" width="121" height="18"/>
<buttonCell key="cell" type="check" title="Apply Modifiers" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="SEj-DO-7J0">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="9" name="value" keyPath="values.default_displayModifiedCharacters" id="sXZ-s3-9aV"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="13" firstAttribute="width" secondItem="8" secondAttribute="width" id="0J1-VP-ec6"/>
<constraint firstItem="13" firstAttribute="centerY" secondItem="14" secondAttribute="centerY" id="0Tb-r1-UIH"/>
<constraint firstItem="14" firstAttribute="top" secondItem="2" secondAttribute="bottom" constant="13" id="1J0-wQ-74V"/>
<constraint firstItem="36" firstAttribute="centerY" secondItem="20" secondAttribute="centerY" id="1uM-Wt-j4h"/>
<constraint firstItem="24" firstAttribute="centerY" secondItem="16" secondAttribute="centerY" id="3g3-uM-OOV"/>
<constraint firstItem="8ph-mh-BBf" firstAttribute="top" secondItem="1" secondAttribute="top" constant="16" id="3k0-SE-Spc"/>
<constraint firstItem="42" firstAttribute="trailing" secondItem="24" secondAttribute="trailing" id="4pm-uP-Qdn"/>
<constraint firstItem="48" firstAttribute="firstBaseline" secondItem="31" secondAttribute="firstBaseline" id="4tC-Sx-SOP"/>
<constraint firstItem="42" firstAttribute="leading" secondItem="24" secondAttribute="leading" id="5Bb-nr-cVw"/>
<constraint firstItem="44" firstAttribute="top" secondItem="42" secondAttribute="bottom" constant="4" id="6BZ-Me-45v"/>
<constraint firstItem="52" firstAttribute="firstBaseline" secondItem="26" secondAttribute="firstBaseline" id="6FJ-sB-ZjC"/>
<constraint firstAttribute="trailing" secondItem="24" secondAttribute="trailing" constant="20" symbolic="YES" id="6Wy-F0-aAR"/>
<constraint firstItem="31" firstAttribute="leading" secondItem="24" secondAttribute="leading" id="6kz-fT-hIQ"/>
<constraint firstItem="13" firstAttribute="leading" secondItem="24" secondAttribute="leading" id="8oM-ez-NrL"/>
<constraint firstItem="24" firstAttribute="leading" secondItem="56" secondAttribute="leading" id="9Ha-8p-P5q"/>
<constraint firstItem="44" firstAttribute="trailing" secondItem="24" secondAttribute="trailing" id="9iF-X9-Kw9"/>
<constraint firstItem="24" firstAttribute="leading" secondItem="16" secondAttribute="trailing" constant="8" symbolic="YES" id="A2o-k4-pLO"/>
<constraint firstItem="wva-Xt-LhR" firstAttribute="leading" secondItem="56" secondAttribute="leading" id="AaS-jK-hYa"/>
<constraint firstItem="48" firstAttribute="top" secondItem="18" secondAttribute="bottom" constant="1" id="Ays-k9-FZ5"/>
<constraint firstItem="2Tl-2J-YBO" firstAttribute="leading" secondItem="56" secondAttribute="leading" id="Brv-HQ-Xfx"/>
<constraint firstItem="8" firstAttribute="leading" secondItem="24" secondAttribute="leading" id="C8t-6g-olT"/>
<constraint firstItem="30" firstAttribute="top" secondItem="32" secondAttribute="bottom" constant="4" id="CCf-pp-ciY"/>
<constraint firstItem="36" firstAttribute="leading" secondItem="24" secondAttribute="leading" id="Cgf-fU-zhI"/>
<constraint firstItem="13" firstAttribute="height" secondItem="8" secondAttribute="height" id="ES0-bN-yud"/>
<constraint firstItem="2" firstAttribute="top" secondItem="54" secondAttribute="bottom" constant="10" id="FAc-eN-4k2"/>
<constraint firstItem="28" firstAttribute="trailing" secondItem="24" secondAttribute="trailing" id="Ffo-I8-zWE"/>
<constraint firstItem="43" firstAttribute="leading" secondItem="24" secondAttribute="leading" id="III-z4-8qi"/>
<constraint firstItem="26" firstAttribute="leading" secondItem="52" secondAttribute="trailing" constant="12" id="Is3-Wn-nea"/>
<constraint firstItem="38" firstAttribute="top" secondItem="36" secondAttribute="bottom" constant="4" id="L0Z-n6-z3I"/>
<constraint firstItem="50" firstAttribute="trailing" secondItem="52" secondAttribute="trailing" id="Lyk-kg-GxV"/>
<constraint firstItem="26" firstAttribute="top" secondItem="24" secondAttribute="bottom" constant="4" id="MR6-cj-gBL"/>
<constraint firstItem="18" firstAttribute="trailing" secondItem="16" secondAttribute="trailing" id="NJq-7k-Rxy"/>
<constraint firstItem="37" firstAttribute="top" secondItem="36" secondAttribute="bottom" constant="4" id="NQe-1q-yPh"/>
<constraint firstItem="22" firstAttribute="top" secondItem="50" secondAttribute="bottom" constant="7" id="PHP-lq-rt0"/>
<constraint firstItem="26" firstAttribute="leading" secondItem="24" secondAttribute="leading" id="Phn-P7-w7X"/>
<constraint firstItem="2" firstAttribute="trailing" secondItem="16" secondAttribute="trailing" id="Q2L-sq-5q6"/>
<constraint firstItem="38" firstAttribute="leading" secondItem="24" secondAttribute="leading" id="QZs-lU-Ck9"/>
<constraint firstItem="18" firstAttribute="top" secondItem="52" secondAttribute="bottom" constant="8" symbolic="YES" id="R8m-Lz-kp2"/>
<constraint firstItem="36" firstAttribute="trailing" secondItem="24" secondAttribute="trailing" id="StK-p0-lxO"/>
<constraint firstAttribute="bottom" secondItem="13" secondAttribute="bottom" constant="20" symbolic="YES" id="TYp-gD-Zlf"/>
<constraint firstItem="32" firstAttribute="leading" secondItem="24" secondAttribute="leading" id="U4Y-1C-OGz"/>
<constraint firstItem="4FL-0E-Ykx" firstAttribute="centerY" secondItem="wva-Xt-LhR" secondAttribute="centerY" id="UM4-7T-snu"/>
<constraint firstItem="50" firstAttribute="top" secondItem="20" secondAttribute="bottom" constant="2" id="UNa-8N-WUY"/>
<constraint firstItem="31" firstAttribute="top" secondItem="32" secondAttribute="bottom" constant="4" id="UYl-2z-5Ai"/>
<constraint firstItem="16" firstAttribute="trailing" secondItem="8ph-mh-BBf" secondAttribute="trailing" id="UmG-PP-XdV"/>
<constraint firstItem="48" firstAttribute="height" secondItem="52" secondAttribute="height" id="VHd-8j-uIW"/>
<constraint firstItem="32" firstAttribute="trailing" secondItem="24" secondAttribute="trailing" id="Vf8-r5-iET"/>
<constraint firstItem="30" firstAttribute="trailing" secondItem="24" secondAttribute="trailing" id="Yrb-Qc-UgY"/>
<constraint firstItem="54" firstAttribute="top" secondItem="22" secondAttribute="bottom" constant="1" id="a6R-GG-UVm"/>
<constraint firstItem="28" firstAttribute="top" secondItem="24" secondAttribute="bottom" constant="4" id="bYm-bf-dy7"/>
<constraint firstItem="wva-Xt-LhR" firstAttribute="top" secondItem="2Tl-2J-YBO" secondAttribute="bottom" constant="6" symbolic="YES" id="dqP-s4-VML"/>
<constraint firstItem="50" firstAttribute="firstBaseline" secondItem="38" secondAttribute="firstBaseline" id="gU5-1G-uMg"/>
<constraint firstItem="56" firstAttribute="centerY" secondItem="8ph-mh-BBf" secondAttribute="centerY" id="h7S-SL-z7q"/>
<constraint firstItem="54" firstAttribute="height" secondItem="52" secondAttribute="height" id="h9s-gK-Kwn"/>
<constraint firstItem="52" firstAttribute="leading" secondItem="1" secondAttribute="leading" constant="45" id="hCM-9H-KuC"/>
<constraint firstItem="50" firstAttribute="height" secondItem="52" secondAttribute="height" id="hLe-A5-CXh"/>
<constraint firstItem="52" firstAttribute="top" secondItem="16" secondAttribute="bottom" id="jy0-Q1-Pq0"/>
<constraint firstItem="37" firstAttribute="trailing" secondItem="24" secondAttribute="trailing" id="lT1-FQ-dzy"/>
<constraint firstItem="2Tl-2J-YBO" firstAttribute="top" secondItem="56" secondAttribute="bottom" constant="6" symbolic="YES" id="nIF-Z1-dJL"/>
<constraint firstItem="14" firstAttribute="trailing" secondItem="16" secondAttribute="trailing" id="oMo-qP-3VH"/>
<constraint firstItem="32" firstAttribute="centerY" secondItem="18" secondAttribute="centerY" id="ovi-bb-YzX"/>
<constraint firstItem="20" firstAttribute="trailing" secondItem="16" secondAttribute="trailing" id="pqR-3f-82n"/>
<constraint firstItem="20" firstAttribute="top" secondItem="48" secondAttribute="bottom" constant="8" symbolic="YES" id="qn1-NA-TWu"/>
<constraint firstItem="43" firstAttribute="top" secondItem="42" secondAttribute="bottom" constant="4" id="rqx-MA-nMm"/>
<constraint firstItem="13" firstAttribute="trailing" secondItem="8" secondAttribute="trailing" id="sUO-ag-Pdb"/>
<constraint firstItem="42" firstAttribute="centerY" secondItem="22" secondAttribute="centerY" id="saT-LU-bUE"/>
<constraint firstItem="22" firstAttribute="trailing" secondItem="16" secondAttribute="trailing" id="vLB-IC-fQg"/>
<constraint firstItem="54" firstAttribute="trailing" secondItem="52" secondAttribute="trailing" id="wgt-6B-skG"/>
<constraint firstItem="8" firstAttribute="centerY" secondItem="2" secondAttribute="centerY" id="zBO-qC-VpU"/>
<constraint firstItem="4FL-0E-Ykx" firstAttribute="leading" secondItem="wva-Xt-LhR" secondAttribute="trailing" constant="8" symbolic="YES" id="zJm-dx-dcw"/>
<constraint firstItem="48" firstAttribute="trailing" secondItem="52" secondAttribute="trailing" id="zUY-EJ-yvK"/>
</constraints>
<connections>
<outlet property="allKeysButton" destination="wva-Xt-LhR" id="AbU-Bb-Mqy"/>
<outlet property="allModifiedKeysButton" destination="2Tl-2J-YBO" id="3vh-1W-L3k"/>
<outlet property="commandKeysOnlyButton" destination="56" id="d8Y-jk-jDl"/>
</connections>
<point key="canvasLocation" x="159" y="186"/>
</customView>
<userDefaultsController representsSharedInstance="YES" id="9" userLabel="Shared User Defaults Controller"/>
</objects>
</document>
================================================
FILE: keycastr/KCEventTap.h
================================================
// Copyright (c) 2009 Stephen Deken
// Copyright (c) 2020-2023 Andrew Kitchen
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name KeyCastr nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Foundation/Foundation.h>
@class KCKeystroke, KCMouseEvent;
@protocol KCEventTapDelegate;
@interface KCEventTap : NSObject
@property (nonatomic, assign) id<KCEventTapDelegate> delegate;
@property (nonatomic, assign, readonly) BOOL tapInstalled;
- (BOOL)installTapWithError:(NSError **)error;
- (void)removeTap;
@end
@protocol KCEventTapDelegate
- (void)eventTap:(KCEventTap *)tap noteKeystroke:(KCKeystroke *)keystroke;
- (void)eventTap:(KCEventTap *)tap noteMouseEvent:(KCMouseEvent *)mouseEvent;
- (void)eventTap:(KCEventTap *)tap noteFlagsChanged:(NSEventModifierFlags)flags;
@end
================================================
FILE: keycastr/KCEventTap.m
================================================
// Copyright (c) 2009 Stephen Deken
// Copyright (c) 2014-2024 Andrew Kitchen
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name KeyCastr nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#if !__has_feature(objc_arc)
#error "ARC is required for this file -- enable with -fobjc-arc"
#endif
#import "KCEventTap.h"
#import "KCKeystroke.h"
#import "KCKeycastrEvent.h"
#import "KCMouseEvent.h"
@interface KCEventTap () {
CFMachPortRef keyEventTap;
CFMachPortRef mouseAndFlagsEventTap;
CFRunLoopSourceRef keyEventTapSource;
CFRunLoopSourceRef mouseAndFlagsEventTapSource;
}
- (void)_noteMouseEvent:(CGEventRef)eventRef;
- (void)_noteKeyEvent:(CGEventRef)eventRef;
- (void)_noteFlagsChanged:(CGEventRef)event;
@end
CGEventRef keyEventTapCallback(
CGEventTapProxy proxy,
CGEventType type,
CGEventRef event,
void *context)
{
KCEventTap *eventTap = (__bridge KCEventTap *)context;
switch (type)
{
case kCGEventKeyDown:
[eventTap _noteKeyEvent:event];
break;
case kCGEventKeyUp:
break;
default:
break;
}
return event;
}
CGEventRef mouseAndFlagsEventTapCallback(
CGEventTapProxy proxy,
CGEventType type,
CGEventRef event,
void *context)
{
KCEventTap *eventTap = (__bridge KCEventTap *)context;
switch (type)
{
case kCGEventLeftMouseDown:
case kCGEventRightMouseDown:
case kCGEventLeftMouseUp:
case kCGEventRightMouseUp:
case kCGEventLeftMouseDragged:
case kCGEventRightMouseDragged:
case kCGEventOtherMouseDown:
case kCGEventOtherMouseUp:
case kCGEventOtherMouseDragged:
[eventTap _noteMouseEvent:event];
break;
case kCGEventFlagsChanged:
[eventTap _noteFlagsChanged:event];
break;
default:
break;
}
return event;
}
@implementation KCEventTap
-(id) init
{
if (!(self = [super init]))
return nil;
return self;
}
- (void)dealloc {
if (_tapInstalled) {
[self removeTap];
}
}
-(NSError*) constructErrorWithDescription:(NSString*)description {
return [NSError errorWithDomain:NSBundle.mainBundle.bundleIdentifier
code:0
userInfo:@{
NSLocalizedDescriptionKey: NSLocalizedString(description, nil)
}];
}
-(BOOL) installTapWithError:(NSError **)error {
if (_tapInstalled) {
return YES;
}
// We have to try to tap the keydown event independently because CGEventTapCreate will succeed if it can
// install the event tap for the flags changed event, which apparently doesn't require universal access
// to be enabled. Thus, the call would succeed but KeyCastr would be, um, useless.
keyEventTap = CGEventTapCreate(kCGSessionEventTap,
kCGHeadInsertEventTap,
kCGEventTapOptionListenOnly,
CGEventMaskBit(kCGEventKeyDown)
| CGEventMaskBit(kCGEventKeyUp),
keyEventTapCallback,
(__bridge void *)self
);
if (keyEventTap == NULL) {
if (error != NULL) {
*error = [self constructErrorWithDescription:@"Could not create key event tap! Permissions needed..."];
}
return NO;
}
mouseAndFlagsEventTap = CGEventTapCreate(kCGSessionEventTap,
kCGHeadInsertEventTap,
kCGEventTapOptionListenOnly,
CGEventMaskBit(kCGEventLeftMouseDown)
| CGEventMaskBit(kCGEventLeftMouseUp)
| CGEventMaskBit(kCGEventRightMouseDown)
| CGEventMaskBit(kCGEventRightMouseUp)
| CGEventMaskBit(kCGEventLeftMouseDragged)
| CGEventMaskBit(kCGEventRightMouseDragged)
| CGEventMaskBit(kCGEventFlagsChanged)
| CGEventMaskBit(kCGEventOtherMouseDown)
| CGEventMaskBit(kCGEventOtherMouseUp)
| CGEventMaskBit(kCGEventOtherMouseDragged),
mouseAndFlagsEventTapCallback,
(__bridge void *)self
);
if (mouseAndFlagsEventTap == NULL) {
if (error != NULL) {
*error = [self constructErrorWithDescription:@"Could not create mouse and modifiers event tap!"];
}
return NO;
}
keyEventTapSource = CFMachPortCreateRunLoopSource(NULL, keyEventTap, 0);
mouseAndFlagsEventTapSource = CFMachPortCreateRunLoopSource(NULL, mouseAndFlagsEventTap, 0);
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopAddSource(runLoop, keyEventTapSource, kCFRunLoopDefaultMode);
CFRunLoopAddSource(runLoop, mouseAndFlagsEventTapSource, kCFRunLoopDefaultMode);
_tapInstalled = YES;
return YES;
}
-(void) removeTap {
if (!_tapInstalled) {
return;
}
CFRunLoopSourceInvalidate(keyEventTapSource);
CFRunLoopSourceInvalidate(mouseAndFlagsEventTapSource);
CFRelease(keyEventTapSource);
CFRelease(mouseAndFlagsEventTapSource);
CFRelease(keyEventTap);
CFRelease(mouseAndFlagsEventTap);
_tapInstalled = NO;
}
-(void) _noteFlagsChanged:(CGEventRef)event
{
NSEventModifierFlags modifiers = 0;
CGEventFlags f = CGEventGetFlags( event );
if (f & kCGEventFlagMaskShift)
modifiers |= NSEventModifierFlagShift;
if (f & kCGEventFlagMaskCommand)
modifiers |= NSEventModifierFlagCommand;
if (f & kCGEventFlagMaskControl)
modifiers |= NSEventModifierFlagControl;
if (f & kCGEventFlagMaskAlternate)
modifiers |= NSEventModifierFlagOption;
[self noteFlagsChanged:modifiers];
}
-(void) _noteKeyEvent:(CGEventRef)eventRef
{
NSEvent *event = [NSEvent eventWithCGEvent:eventRef];
KCKeystroke* keystroke = [KCKeystroke eventWithNSEvent:event];
[self noteKeystroke:keystroke];
}
- (void)_noteMouseEvent:(CGEventRef)eventRef
{
NSEvent *event = [NSEvent eventWithCGEvent:eventRef];
KCMouseEvent *mouseEvent
gitextract_mzq7mwf8/
├── .gitignore
├── .gitmodules
├── DEVELOPING.md
├── LICENSE.md
├── README.md
└── keycastr/
├── Base.lproj/
│ └── MainMenu.nib/
│ ├── designable.nib
│ └── keyedobjects.nib
├── DisplayIcon.tif
├── English.lproj/
│ └── MainMenu.nib/
│ ├── designable.nib
│ └── keyedobjects.nib
├── GeneralIcon.tif
├── Info.plist
├── KCAppController.h
├── KCAppController.m
├── KCColorValueTransformer.h
├── KCColorValueTransformer.m
├── KCDefaultVisualizer-Info.plist
├── KCDefaultVisualizer.h
├── KCDefaultVisualizer.m
├── KCDefaultVisualizer.nib/
│ ├── designable.nib
│ └── keyedobjects.nib
├── KCEventTap.h
├── KCEventTap.m
├── KCEventTransformer.h
├── KCEventTransformer.m
├── KCKeycastrEvent.h
├── KCKeycastrEvent.m
├── KCKeystroke.h
├── KCKeystroke.m
├── KCMouseEvent.h
├── KCMouseEvent.m
├── KCMouseEventVisualizer.h
├── KCMouseEventVisualizer.m
├── KCPrefsWindowController.h
├── KCPrefsWindowController.m
├── KCUserDefaultsMigration.h
├── KCUserDefaultsMigration.m
├── KCVisualizer-Info.plist
├── KCVisualizer.h
├── KCVisualizer.m
├── KCVisualizerTests/
│ ├── Info.plist
│ ├── KCDefaultVisualizerTests.m
│ ├── KCKeystrokeConversionTests.m
│ ├── KCMouseEventVisualizerTests.m
│ └── KCUserDefaultsMigrationTests.m
├── KeyCastr.dmgCanvas
├── KeyCastr.entitlements
├── KeyCastr.icns
├── KeyCastr.xcodeproj/
│ ├── .gitignore
│ ├── project.pbxproj
│ └── project.xcworkspace/
│ └── contents.xcworkspacedata
├── KeyCastrAbout.qtz
├── KeyCastrInactive.icns
├── KeyCastr_Prefix.pch
├── NSBezierPath+RoundedRect.h
├── NSBezierPath+RoundedRect.m
├── NSUserDefaults+Utility.h
├── NSUserDefaults+Utility.m
├── Svelte-Info.plist
├── Svelte.nib/
│ ├── designable.nib
│ └── keyedobjects.nib
├── SvelteVisualizer.h
├── SvelteVisualizer.m
├── UpdateIcon.tif
├── en.lproj/
│ └── InfoPlist.strings
└── main.m
SYMBOL INDEX (3 symbols across 2 files) FILE: keycastr/KCDefaultVisualizer.h function interface (line 73) | interface KCDefaultVisualizerWindow : NSWindow function interface (line 94) | interface KCDefaultVisualizerPreferencesView : NSView FILE: keycastr/KCPrefsWindowController.h function interface (line 33) | interface KCPrefsWindowController : NSObject<NSToolbarDelegate>
Condensed preview — 66 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (416K chars).
[
{
"path": ".gitignore",
"chars": 46,
"preview": ".DS_Store\nbuild\nxcuserdata\nxcshareddata\n.idea\n"
},
{
"path": ".gitmodules",
"chars": 270,
"preview": "[submodule \"keycastr/Externals/ShortcutRecorder\"]\n\tpath = keycastr/Externals/ShortcutRecorder\n\turl = git@github.com:Kent"
},
{
"path": "DEVELOPING.md",
"chars": 1941,
"preview": "# Developer's notes\n\n## Running and Debugging\n\nAfter cloning the repository, open the project directory in a terminal or"
},
{
"path": "LICENSE.md",
"chars": 1481,
"preview": "Copyright (c) 2009 Stephen Deken.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or witho"
},
{
"path": "README.md",
"chars": 4266,
"preview": "# KeyCastr\n\nKeyCastr, an open source keystroke visualizer.\n\n\n\nKeyCastr enable"
},
{
"path": "keycastr/Base.lproj/MainMenu.nib/designable.nib",
"chars": 55606,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
},
{
"path": "keycastr/English.lproj/MainMenu.nib/designable.nib",
"chars": 38589,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
},
{
"path": "keycastr/Info.plist",
"chars": 1301,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "keycastr/KCAppController.h",
"chars": 2433,
"preview": "//\tCopyright (c) 2009 Stephen Deken\n//\tCopyright (c) 2020-2024 Andrew Kitchen\n//\n//\tAll rights reserved.\n// \n//\tRedistri"
},
{
"path": "keycastr/KCAppController.m",
"chars": 19786,
"preview": "//\tCopyright (c) 2009 Stephen Deken\n//\tCopyright (c) 2014-2025 Andrew Kitchen\n//\n//\tAll rights reserved.\n//\n//\tRedistrib"
},
{
"path": "keycastr/KCColorValueTransformer.h",
"chars": 1781,
"preview": "// Copyright (c) 2024 Andrew Kitchen\n//\n// All rights reserved.\n//\n// Redistribution and use in source and binary for"
},
{
"path": "keycastr/KCColorValueTransformer.m",
"chars": 1750,
"preview": "// Copyright (c) 2024 Andrew Kitchen\n//\n// All rights reserved.\n//\n// Redistribution and use in source and binary for"
},
{
"path": "keycastr/KCDefaultVisualizer-Info.plist",
"chars": 758,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "keycastr/KCDefaultVisualizer.h",
"chars": 3914,
"preview": "//\tCopyright (c) 2009 Stephen Deken\n//\tCopyright (c) 2014-2024 Andrew Kitchen\n//\n//\tAll rights reserved.\n//\n//\tRedistrib"
},
{
"path": "keycastr/KCDefaultVisualizer.m",
"chars": 17720,
"preview": "//\tCopyright (c) 2009 Stephen Deken\n//\tCopyright (c) 2014-2024 Andrew Kitchen\n//\n//\tAll rights reserved.\n//\n//\tRedistrib"
},
{
"path": "keycastr/KCDefaultVisualizer.nib/designable.nib",
"chars": 35613,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
},
{
"path": "keycastr/KCEventTap.h",
"chars": 2186,
"preview": "//\tCopyright (c) 2009 Stephen Deken\n//\tCopyright (c) 2020-2023 Andrew Kitchen\n//\n//\tAll rights reserved.\n//\n//\tRedistrib"
},
{
"path": "keycastr/KCEventTap.m",
"chars": 8488,
"preview": "//\tCopyright (c) 2009 Stephen Deken\n//\tCopyright (c) 2014-2024 Andrew Kitchen\n//\n//\tAll rights reserved.\n//\n//\tRedistrib"
},
{
"path": "keycastr/KCEventTransformer.h",
"chars": 1975,
"preview": "//\tCopyright (c) 2009 Stephen Deken\n//\tCopyright (c) 2021-2024 Andrew Kitchen\n//\n//\tAll rights reserved.\n// \n//\tRedistri"
},
{
"path": "keycastr/KCEventTransformer.m",
"chars": 12767,
"preview": "//\tCopyright (c) 2009 Stephen Deken\n//\tCopyright (c) 2017-2024 Andrew Kitchen\n//\n//\tAll rights reserved.\n// \n//\tRedistri"
},
{
"path": "keycastr/KCKeycastrEvent.h",
"chars": 2032,
"preview": "// Copyright (c) 2021 Andrew Kitchen\n// All rights reserved.\n//\n// Redistribution and use in source and binary "
},
{
"path": "keycastr/KCKeycastrEvent.m",
"chars": 2285,
"preview": "// Copyright (c) 2021-2023 Andrew Kitchen\n// All rights reserved.\n//\n// Redistribution and use in source and binary f"
},
{
"path": "keycastr/KCKeystroke.h",
"chars": 2137,
"preview": "//\tCopyright (c) 2009 Stephen Deken\n//\tCopyright (c) 2020-2024 Andrew Kitchen\n//\n//\tAll rights reserved.\n//\n//\tRedistrib"
},
{
"path": "keycastr/KCKeystroke.m",
"chars": 2785,
"preview": "//\tCopyright (c) 2009 Stephen Deken\n//\tCopyright (c) 2017-2023 Andrew Kitchen\n//\n//\tAll rights reserved.\n// \n//\tRedistri"
},
{
"path": "keycastr/KCMouseEvent.h",
"chars": 1771,
"preview": "// Copyright (c) 2021-2023 Andrew Kitchen\n//\n// All rights reserved.\n//\n// Redistribution and use in source and binar"
},
{
"path": "keycastr/KCMouseEvent.m",
"chars": 2116,
"preview": "// Copyright (c) 2021-2023 Andrew Kitchen\n//\n// All rights reserved.\n//\n// Redistribution and use in source and binar"
},
{
"path": "keycastr/KCMouseEventVisualizer.h",
"chars": 2491,
"preview": "// Copyright (c) 2022-2023 Andrew Kitchen\n//\n// All rights reserved.\n//\n// Redistribution and use in source and binar"
},
{
"path": "keycastr/KCMouseEventVisualizer.m",
"chars": 10323,
"preview": "// Copyright (c) 2022-2023 Andrew Kitchen\n//\n// All rights reserved.\n//\n// Redistribution and use in source and binar"
},
{
"path": "keycastr/KCPrefsWindowController.h",
"chars": 2057,
"preview": "//\tCopyright (c) 2009 Stephen Deken\n//\tAll rights reserved.\n// \n//\tRedistribution and use in source and binary forms, wi"
},
{
"path": "keycastr/KCPrefsWindowController.m",
"chars": 6631,
"preview": "//\tCopyright (c) 2009 Stephen Deken\n//\tCopyright (c) 2014-2023 Andrew Kitchen\n//\n//\tAll rights reserved.\n// \n//\tRedistri"
},
{
"path": "keycastr/KCUserDefaultsMigration.h",
"chars": 2476,
"preview": "// Copyright (c) 2024 Andrew Kitchen\n//\n// All rights reserved.\n//\n// Redistribution and use in source and binary for"
},
{
"path": "keycastr/KCUserDefaultsMigration.m",
"chars": 2963,
"preview": "// Copyright (c) 2024 Andrew Kitchen\n//\n// All rights reserved.\n//\n// Redistribution and use in source and binary for"
},
{
"path": "keycastr/KCVisualizer-Info.plist",
"chars": 620,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "keycastr/KCVisualizer.h",
"chars": 2647,
"preview": "//\tCopyright (c) 2009 Stephen Deken\n//\tCopyright (c) 2024 Andrew Kitchen\n//\n//\tAll rights reserved.\n// \n//\tRedistributio"
},
{
"path": "keycastr/KCVisualizer.m",
"chars": 3743,
"preview": "//\tCopyright (c) 2009 Stephen Deken\n//\tCopyright (c) 2024 Andrew Kitchen\n//\n//\tAll rights reserved.\n//\n//\tRedistribution"
},
{
"path": "keycastr/KCVisualizerTests/Info.plist",
"chars": 727,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "keycastr/KCVisualizerTests/KCDefaultVisualizerTests.m",
"chars": 3961,
"preview": "// Copyright (c) 2024 Andrew Kitchen\n//\n// All rights reserved.\n//\n// Redistribution and use in source and binary for"
},
{
"path": "keycastr/KCVisualizerTests/KCKeystrokeConversionTests.m",
"chars": 13301,
"preview": "// Copyright (c) 2020-2024 Andrew Kitchen\n//\n// All rights reserved.\n//\n// Redistribution and use in source and binar"
},
{
"path": "keycastr/KCVisualizerTests/KCMouseEventVisualizerTests.m",
"chars": 3722,
"preview": "// Copyright (c) 2023-2024 Andrew Kitchen\n//\n// All rights reserved.\n//\n// Redistribution and use in source and binar"
},
{
"path": "keycastr/KCVisualizerTests/KCUserDefaultsMigrationTests.m",
"chars": 2708,
"preview": "// Copyright (c) 2024 Andrew Kitchen\n//\n// All rights reserved.\n//\n// Redistribution and use in source and binary for"
},
{
"path": "keycastr/KeyCastr.entitlements",
"chars": 181,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "keycastr/KeyCastr.xcodeproj/.gitignore",
"chars": 18,
"preview": "*.pbxuser\n*.mode*\n"
},
{
"path": "keycastr/KeyCastr.xcodeproj/project.pbxproj",
"chars": 78315,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 54;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "keycastr/KeyCastr.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "keycastr/KeyCastr_Prefix.pch",
"chars": 147,
"preview": "//\n// Prefix header for all source files of the 'KeyCastr' target in the 'KeyCastr' project\n//\n\n#ifdef __OBJC__\n #imp"
},
{
"path": "keycastr/NSBezierPath+RoundedRect.h",
"chars": 1669,
"preview": "//\tCopyright (c) 2009 Stephen Deken\n//\tAll rights reserved.\n// \n//\tRedistribution and use in source and binary forms, wi"
},
{
"path": "keycastr/NSBezierPath+RoundedRect.m",
"chars": 2769,
"preview": "//\tCopyright (c) 2009 Stephen Deken\n//\tAll rights reserved.\n// \n//\tRedistribution and use in source and binary forms, wi"
},
{
"path": "keycastr/NSUserDefaults+Utility.h",
"chars": 1811,
"preview": "//\tCopyright (c) 2009 Stephen Deken\n//\tAll rights reserved.\n// \n//\tRedistribution and use in source and binary forms, wi"
},
{
"path": "keycastr/NSUserDefaults+Utility.m",
"chars": 3061,
"preview": "//\tCopyright (c) 2009 Stephen Deken\n//\tCopyright (c) 2024 Andrew Kitchen\n//\n//\tAll rights reserved.\n// \n//\tRedistributio"
},
{
"path": "keycastr/Svelte-Info.plist",
"chars": 691,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "keycastr/Svelte.nib/designable.nib",
"chars": 2855,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
},
{
"path": "keycastr/SvelteVisualizer.h",
"chars": 1790,
"preview": "//\tCopyright (c) 2009 Stephen Deken\n//\tAll rights reserved.\n// \n//\tRedistribution and use in source and binary forms, wi"
},
{
"path": "keycastr/SvelteVisualizer.m",
"chars": 9903,
"preview": "//\tCopyright (c) 2009 Stephen Deken\n//\tCopyright (c) 2014-2024 Andrew Kitchen\n//\n//\tAll rights reserved.\n// \n//\tRedistri"
},
{
"path": "keycastr/main.m",
"chars": 1663,
"preview": "//\tCopyright (c) 2009 Stephen Deken\n//\tAll rights reserved.\n// \n//\tRedistribution and use in source and binary forms, wi"
}
]
// ... and 12 more files (download for full content)
About this extraction
This page contains the full source code of the keycastr/keycastr GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 66 files (380.1 KB), approximately 96.0k tokens, and a symbol index with 3 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.