├── icon-large-assets └── icon-1024.png ├── .gitmodules ├── .gitignore ├── KeyCast ├── Images.xcassets │ └── AppIcon.appiconset │ │ ├── icon_16x16.png │ │ ├── icon_32x32.png │ │ ├── icon_128x128.png │ │ ├── icon_256x256.png │ │ ├── icon_32x32-1.png │ │ ├── icon_512x512.png │ │ ├── icon_256x256-1.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512-1.png │ │ ├── icon_512x512@2x.png │ │ └── Contents.json ├── KeyCast-Bridging-Header.h ├── processlist.h ├── app.sdef ├── README.md ├── AboutWindowController.swift ├── Toast.swift ├── Info.plist ├── Utils.swift ├── MainView.swift ├── Accessibility.swift ├── Credits.html ├── processlist.c ├── PreferencesWindowController.swift ├── AppDelegate.swift └── Base.lproj │ └── MainMenu.xib ├── KeyCast.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── project.pbxproj ├── KeyCastTests ├── Info.plist └── KeyCastTests.swift └── README.md /icon-large-assets/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cho45/KeyCast/HEAD/icon-large-assets/icon-1024.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ShortcutRecorder"] 2 | path = ShortcutRecorder 3 | url = https://github.com/Kentzo/ShortcutRecorder.git 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | KeyCast.xcodeproj/project.xcworkspace/xcuserdata/ 2 | KeyCast.xcodeproj/xcuserdata/ 3 | KeyCast.xcodeproj/project.xcworkspace/xcshareddata/ 4 | -------------------------------------------------------------------------------- /KeyCast/Images.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cho45/KeyCast/HEAD/KeyCast/Images.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /KeyCast/Images.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cho45/KeyCast/HEAD/KeyCast/Images.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /KeyCast/Images.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cho45/KeyCast/HEAD/KeyCast/Images.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /KeyCast/Images.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cho45/KeyCast/HEAD/KeyCast/Images.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /KeyCast/Images.xcassets/AppIcon.appiconset/icon_32x32-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cho45/KeyCast/HEAD/KeyCast/Images.xcassets/AppIcon.appiconset/icon_32x32-1.png -------------------------------------------------------------------------------- /KeyCast/Images.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cho45/KeyCast/HEAD/KeyCast/Images.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /KeyCast/Images.xcassets/AppIcon.appiconset/icon_256x256-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cho45/KeyCast/HEAD/KeyCast/Images.xcassets/AppIcon.appiconset/icon_256x256-1.png -------------------------------------------------------------------------------- /KeyCast/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cho45/KeyCast/HEAD/KeyCast/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /KeyCast/Images.xcassets/AppIcon.appiconset/icon_512x512-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cho45/KeyCast/HEAD/KeyCast/Images.xcassets/AppIcon.appiconset/icon_512x512-1.png -------------------------------------------------------------------------------- /KeyCast/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cho45/KeyCast/HEAD/KeyCast/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /KeyCast/KeyCast-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "processlist.h" 6 | #import 7 | -------------------------------------------------------------------------------- /KeyCast/processlist.h: -------------------------------------------------------------------------------- 1 | #ifndef __KeyCast__processlist__ 2 | #define __KeyCast__processlist__ 3 | 4 | #include 5 | bool IsInBSDProcessList(const char name[]); 6 | 7 | 8 | #endif /* defined(__KeyCast__processlist__) */ 9 | -------------------------------------------------------------------------------- /KeyCast.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /KeyCast/app.sdef: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /KeyCastTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | net.lowreal.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /KeyCast/README.md: -------------------------------------------------------------------------------- 1 | KeyCast 2 | ======= 3 | 4 | Display keystroke for desktop screencast. 5 | 6 | Automatically Hide Password Input 7 | --------------------------------- 8 | 9 | KeyCast detect the focused input is password input. So you do not need to disable by hand to hide input for password mostly. 10 | 11 | Supported: 12 | 13 | * Native AXSecureTextField 14 | * Google Chrome's password input 15 | * Local `sudo` (hide on `sudo` is in processlist) 16 | 17 | Scripting Bridge 18 | ---------------- 19 | 20 | KeyCast also supports scripting bridge. You can control (enable or disable) KeyCast by AppleScript or JavaScript (Yosemite). 21 | 22 | eg. 23 | 24 | # enable 25 | osascript -e 'tell application "KeyCast"' -e 'set enabled to true' -e 'end tell' 26 | 27 | # disable 28 | osascript -e 'tell application "KeyCast"' -e 'set enabled to false' -e 'end tell' -------------------------------------------------------------------------------- /KeyCastTests/KeyCastTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyCastTests.swift 3 | // KeyCastTests 4 | // 5 | // Created by WATANABE HIROFUMI on 2015/02/06. 6 | // Copyright (c) 2015年 cho45. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import XCTest 11 | 12 | class KeyCastTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | XCTAssert(true, "Pass") 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measureBlock() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /KeyCast/AboutWindowController.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import WebKit 3 | 4 | class AboutWindow: NSWindow, WKNavigationDelegate { 5 | @IBOutlet weak var webview: WebView! 6 | @IBOutlet weak var labelVersion: NSTextField! 7 | 8 | let aboutURL = NSBundle.mainBundle().pathForResource("Credits", ofType: "html")! 9 | 10 | override func awakeFromNib() { 11 | webview.mainFrameURL = aboutURL 12 | webview.policyDelegate = self 13 | 14 | let info = NSBundle.mainBundle().infoDictionary! 15 | let appVersion = info["CFBundleShortVersionString"] as! String 16 | let buildVersion = info["CFBundleVersion"] as! String 17 | labelVersion.stringValue = "v\(appVersion) build \(buildVersion)" 18 | } 19 | 20 | override func webView(sender: WebView!, decidePolicyForNavigationAction actionInformation: [NSObject : AnyObject]!, request: NSURLRequest!, frame: WebFrame!, decisionListener listener: WebPolicyDecisionListener!) { 21 | 22 | if actionInformation["WebActionOriginalURLKey"] != nil { 23 | listener.ignore() 24 | NSWorkspace.sharedWorkspace().openURL(request.URL!) 25 | } else { 26 | listener.use() 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | KeyCast 2 | ======= 3 | 4 | Display keystroke for desktop screencast. (demo movie: youtube) 5 | 6 | Automatically Hide Password Input 7 | --------------------------------- 8 | 9 | KeyCast detect the focused input is password input. So you do not need to disable by hand to hide input for password mostly. 10 | 11 | Supported: 12 | 13 | * Native AXSecureTextField 14 | * Google Chrome's password input 15 | * Local `sudo` (hide on `sudo` is in processlist) 16 | 17 | Download and Install 18 | -------------------- 19 | 20 | Download .dmg from releases page: 21 | 22 | https://github.com/cho45/KeyCast/releases 23 | 24 | Copy KeyCast.app to your Application folder. 25 | 26 | Scripting Bridge 27 | ---------------- 28 | 29 | KeyCast also supports scripting bridge. You can control (enable or disable) KeyCast by AppleScript or JavaScript (Yosemite). 30 | 31 | eg. 32 | 33 | # enable 34 | osascript -e 'tell application "KeyCast"' -e 'set enabled to true' -e 'end tell' 35 | 36 | # disable 37 | osascript -e 'tell application "KeyCast"' -e 'set enabled to false' -e 'end tell' 38 | 39 | eg. (on Yosemite) 40 | 41 | # enable 42 | osascript -l JavaScript -e 'Application("KeyCast").enabled = true;' 43 | 44 | #disable 45 | osascript -l JavaScript -e 'Application("KeyCast").enabled = false;' 46 | -------------------------------------------------------------------------------- /KeyCast/Toast.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | 4 | class ToastView : NSView { 5 | override func drawRect(dirtyRect: NSRect) { 6 | NSColor.clearColor().set() 7 | NSRectFill(bounds) 8 | 9 | let path = NSBezierPath(roundedRect: bounds, xRadius: 10.0, yRadius: 10.0) 10 | NSColor(calibratedWhite: 0.0, alpha: 0.5).set() 11 | path.fill() 12 | } 13 | } 14 | 15 | class ToastWindow : NSWindow { 16 | @IBOutlet weak var label: NSTextField! 17 | 18 | var timer: NSTimer! 19 | 20 | func toast(str: String) { 21 | println("toast \(str)") 22 | label.stringValue = str 23 | alphaValue = 1.0 24 | if timer != nil { 25 | timer.invalidate() 26 | } 27 | timer = NSTimer.scheduledTimerWithTimeInterval(3.0, target: self, selector: "fadeOut", userInfo: nil, repeats: false) 28 | makeKeyAndOrderFront(nil) 29 | } 30 | 31 | override func awakeFromNib() { 32 | hasShadow = true 33 | opaque = false 34 | level = 10000 35 | movable = false 36 | movableByWindowBackground = false 37 | } 38 | 39 | func fadeOut() { 40 | animator().alphaValue = 0.0 41 | NSTimer.scheduledTimerWithTimeInterval(NSAnimationContext.currentContext().duration + 0.1, target: self, selector: "orderOut:", userInfo: nil, repeats: false) 42 | } 43 | } -------------------------------------------------------------------------------- /KeyCast/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | net.lowreal.$(PRODUCT_NAME:rfc1034identifier) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.1 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 2 25 | LSApplicationCategoryType 26 | public.app-category.utilities 27 | LSMinimumSystemVersion 28 | $(MACOSX_DEPLOYMENT_TARGET) 29 | LSUIElement 30 | 31 | NSAppleScriptEnabled 32 | 33 | NSHumanReadableCopyright 34 | Copyright © 2015 cho45. 35 | NSMainNibFile 36 | MainMenu 37 | NSPrincipalClass 38 | NSApplication 39 | OSAScriptingDefinition 40 | app.sdef 41 | 42 | 43 | -------------------------------------------------------------------------------- /KeyCast/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "icon_16x16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "icon_32x32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "icon_32x32-1.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "icon_32x32@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "icon_128x128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "icon_256x256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "icon_256x256-1.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "icon_512x512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "icon_512x512-1.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "icon_512x512@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /KeyCast/Utils.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | struct Utils { 4 | static let REPLACE_MAP: Dictionary = [ 5 | "\r" : "↵\n", 6 | "\u{1B}" : "⎋", 7 | "\t" : "⇥", 8 | "\u{19}" : "⇤", 9 | " " : "␣", 10 | "\u{7f}" : "⌫", 11 | "\u{03}" : "⌤", 12 | "\u{10}" : "⏏", 13 | "\u{F728}" : "⌦", 14 | "\u{F739}" : "⌧", 15 | "\u{F704}" : "[F1]", 16 | "\u{F705}" : "[F2]", 17 | "\u{F706}" : "[F3]", 18 | "\u{F707}" : "[F4]", 19 | "\u{F708}" : "[F5]", 20 | "\u{F709}" : "[F6]", 21 | "\u{F70A}" : "[F7]", 22 | "\u{F70B}" : "[F8]", 23 | "\u{F70C}" : "[F9]", 24 | "\u{F70D}" : "[F10]", 25 | "\u{F70E}" : "[F11]", 26 | "\u{F70F}" : "[F12]", 27 | 28 | "\u{F700}" : "↑", 29 | "\u{F701}" : "↓", 30 | "\u{F702}" : "←", 31 | "\u{F703}" : "→", 32 | "\u{F72C}" : "⇞", 33 | "\u{F72D}" : "⇟", 34 | "\u{F729}" : "↖", 35 | "\u{F72B}" : "↘", 36 | ] 37 | 38 | static func keyStringFromEvent(e: NSEvent)->(String, String) { 39 | var mod = "" 40 | if e.modifierFlags.rawValue & NSEventModifierFlags.ControlKeyMask.rawValue != 0 { 41 | mod += "⌃" 42 | } 43 | if e.modifierFlags.rawValue & NSEventModifierFlags.AlternateKeyMask.rawValue != 0 { 44 | mod += "⌥" 45 | } 46 | if e.modifierFlags.rawValue & NSEventModifierFlags.ShiftKeyMask.rawValue != 0 { 47 | mod += "⇧" 48 | } 49 | if e.modifierFlags.rawValue & NSEventModifierFlags.CommandKeyMask.rawValue != 0 { 50 | mod += "⌘" 51 | } 52 | 53 | let char = keyToReadableString(e.charactersIgnoringModifiers!.uppercaseString) 54 | 55 | return (mod, char) 56 | } 57 | 58 | static func keyToReadableString (string: String)-> String { 59 | var str = string 60 | for (k, v) in REPLACE_MAP { 61 | str = str.stringByReplacingOccurrencesOfString(k, withString: v) 62 | } 63 | return str 64 | } 65 | } -------------------------------------------------------------------------------- /KeyCast/MainView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainView.swift 3 | // KeyCast 4 | // 5 | 6 | import Cocoa 7 | 8 | class MainView : NSView { 9 | internal var log = "" 10 | var font = NSFont.boldSystemFontOfSize(24) 11 | var shadowCount = 10 12 | var maxLine : Int = 5 13 | 14 | override func drawRect(dirtyRect: NSRect) { 15 | super.drawRect(dirtyRect) 16 | NSColor.clearColor().set() 17 | NSRectFill(self.bounds) 18 | 19 | let shadow = NSShadow() 20 | shadow.shadowColor = NSColor.blackColor() 21 | shadow.shadowBlurRadius = 2 22 | shadow.shadowOffset = NSSize(width: 0, height: 0) 23 | let attrs = [ 24 | NSForegroundColorAttributeName: NSColor.whiteColor(), 25 | NSFontAttributeName: font, 26 | NSShadowAttributeName: shadow, 27 | ] 28 | 29 | var y = 0 30 | let lines = split(log, isSeparator: { $0 == "\n" }).reverse() 31 | for line in lines { 32 | let storage = NSTextStorage(string: line, attributes: attrs) 33 | let manager = NSLayoutManager() 34 | let container = NSTextContainer() 35 | 36 | manager.addTextContainer(container) 37 | storage.addLayoutManager(manager) 38 | 39 | let range = manager.glyphRangeForTextContainer(container) 40 | for i in 1...shadowCount { 41 | manager.drawGlyphsForGlyphRange(range, atPoint: NSPoint(x: 0, y: y)) 42 | } 43 | let rect = manager.boundingRectForGlyphRange(NSRange(location: 0, length: manager.numberOfGlyphs), inTextContainer: container) 44 | y += Int(rect.size.height) 45 | } 46 | } 47 | 48 | func appendLog(str: String) { 49 | log += str 50 | let lines = split(log, isSeparator: { $0 == "\n" }) 51 | if lines.count > maxLine { 52 | log = join("\n", lines[lines.count - maxLine ..< lines.count]) 53 | } 54 | self.needsDisplay = true 55 | } 56 | 57 | func clear() { 58 | log = "" 59 | self.needsDisplay = true 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /KeyCast/Accessibility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Accessibility.swift 3 | // KeyCast 4 | // 5 | // Created by Satoh on 2015/02/08. 6 | // Copyright (c) 2015年 cho45. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import ScriptingBridge 11 | 12 | // 元にないやつは optional にしないと extension でエラーになる 13 | @objc protocol SBSystemPreferencesApplication { 14 | optional var panes: SBElementArray {get} 15 | func activate() 16 | } 17 | 18 | 19 | @objc protocol SBSystemPreferencesPane { 20 | optional var anchors: SBElementArray {get} 21 | optional var id: NSString {get} 22 | 23 | } 24 | 25 | @objc protocol SBSystemPreferencesAnchor { 26 | optional var name: NSString {get} 27 | optional func reveal() -> id_t 28 | } 29 | 30 | // protocol 定義を無理矢理使えるようにする 31 | extension SBApplication : SBSystemPreferencesApplication {} 32 | extension SBObject : SBSystemPreferencesPane, SBSystemPreferencesAnchor {} 33 | 34 | struct Accessibility { 35 | static func checkAccessibilityEnabled(app: NSApplicationDelegate) { 36 | if AXIsProcessTrusted() != 1 { 37 | let alert = NSAlert() 38 | alert.messageText = "Require accessibility setting" 39 | alert.alertStyle = NSAlertStyle.CriticalAlertStyle 40 | alert.addButtonWithTitle("Open System Preference") 41 | alert.addButtonWithTitle("Quit") 42 | if alert.runModal() == 1000 { 43 | openSecurityPane() 44 | NSApplication.sharedApplication().terminate(app) 45 | } else { 46 | NSApplication.sharedApplication().terminate(app) 47 | } 48 | } 49 | } 50 | 51 | static func openSecurityPane() { 52 | // openURL 使うのが最も簡単だが、アクセシビリティの項目まで選択された状態で開くことができない 53 | // NSWorkspace.sharedWorkspace().openURL( NSURL.fileURLWithPath("/System/Library/PreferencePanes/Security.prefPane")! ) 54 | 55 | // ScriptingBridge を使い、表示したいところまで自動で移動させる 56 | // open System Preference -> Security and Privacy -> Accessibility 57 | let prefs = SBApplication.applicationWithBundleIdentifier("com.apple.systempreferences")! as! SBSystemPreferencesApplication 58 | prefs.activate() 59 | for pane_ in prefs.panes! { 60 | let pane = pane_ as! SBSystemPreferencesPane 61 | if pane.id == "com.apple.preference.security" { 62 | for anchor_ in pane.anchors! { 63 | let anchor = anchor_ as! SBSystemPreferencesAnchor 64 | if anchor.name == "Privacy_Accessibility" { 65 | println(pane, anchor) 66 | anchor.reveal!() 67 | break 68 | } 69 | } 70 | break 71 | } 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /KeyCast/Credits.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Credits 5 | 24 | 25 | 26 |

Author

27 |

28 | cho45 <cho45@lowreal.net> / www.lowreal.net 29 |

30 | 31 |

Development

32 |

33 | GitHub 34 |

35 | 36 |
37 | 38 |

Using Libraries

39 |

ShortcutRecorder

40 |
41 | Copyright (c) 2006, contributors to ShortcutRecorder. (See the contributors listed in detail later in the file, or see .)
42 | 
43 | All rights reserved.
44 | Redistribution and use in source and binary forms, with or without
45 | modification, are permitted provided that the following conditions are met:
46 | 
47 |     * Redistributions of source code must retain the above copyright
48 |       notice, this list of conditions and the following disclaimer.
49 |     * Redistributions in binary form must reproduce the above copyright
50 |       notice, this list of conditions and the following disclaimer in the
51 |       documentation and/or other materials provided with the distribution.
52 |     * The name of the contributors may not be used to endorse or promote 
53 |       products derived from this software without specific prior written
54 |       permission.
55 | 
56 | THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS "AS IS" AND ANY
57 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
58 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
59 | DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
60 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
61 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
62 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
63 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
64 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
65 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
66 | 
67 | =====================================================================
68 | 
69 | Contributors to Shortcut Recorder, in no order in particular:
70 | Jesper, waffle software, . Initial idea and concept, first shot at implementation using NSView.
71 | David Dauer, . Refinement, cleaner reimplementation, documentation, IB Palette.
72 | Jamie Kirkpatrick, Kirk Consulting Ltd, .  Further modularisation and re-factoring, and general bug fixes.
73 | Ilya Kulakov, . ShortcutRecorder 2.0 and further support.
74 | Alexander Ljungberg, . Graphics for ShortcutRecorder 2.0
75 | 
76 | =====================================================================
77 | 
78 | Some rights reserved: 
79 | 
80 | For more information, visit 
81 | 
82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /KeyCast/processlist.c: -------------------------------------------------------------------------------- 1 | #include "processlist.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | typedef struct kinfo_proc kinfo_proc; 11 | 12 | int GetBSDProcessList(kinfo_proc **procList, size_t *procCount) 13 | // Returns a list of all BSD processes on the system. This routine 14 | // allocates the list and puts it in *procList and a count of the 15 | // number of entries in *procCount. You are responsible for freeing 16 | // this list (use "free" from System framework). 17 | // On success, the function returns 0. 18 | // On error, the function returns a BSD errno value. 19 | { 20 | int err; 21 | kinfo_proc * result; 22 | bool done; 23 | static const int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }; 24 | // Declaring name as const requires us to cast it when passing it to 25 | // sysctl because the prototype doesn't include the const modifier. 26 | size_t length; 27 | 28 | assert( procList != NULL); 29 | assert(*procList == NULL); 30 | assert(procCount != NULL); 31 | 32 | *procCount = 0; 33 | 34 | // We start by calling sysctl with result == NULL and length == 0. 35 | // That will succeed, and set length to the appropriate length. 36 | // We then allocate a buffer of that size and call sysctl again 37 | // with that buffer. If that succeeds, we're done. If that fails 38 | // with ENOMEM, we have to throw away our buffer and loop. Note 39 | // that the loop causes use to call sysctl with NULL again; this 40 | // is necessary because the ENOMEM failure case sets length to 41 | // the amount of data returned, not the amount of data that 42 | // could have been returned. 43 | 44 | result = NULL; 45 | done = false; 46 | do { 47 | assert(result == NULL); 48 | 49 | // Call sysctl with a NULL buffer. 50 | 51 | length = 0; 52 | err = sysctl( (int *) name, (sizeof(name) / sizeof(*name)) - 1, 53 | NULL, &length, 54 | NULL, 0); 55 | if (err == -1) { 56 | err = errno; 57 | } 58 | 59 | // Allocate an appropriately sized buffer based on the results 60 | // from the previous call. 61 | 62 | if (err == 0) { 63 | result = malloc(length); 64 | if (result == NULL) { 65 | err = ENOMEM; 66 | } 67 | } 68 | 69 | // Call sysctl again with the new buffer. If we get an ENOMEM 70 | // error, toss away our buffer and start again. 71 | 72 | if (err == 0) { 73 | err = sysctl( (int *) name, (sizeof(name) / sizeof(*name)) - 1, 74 | result, &length, 75 | NULL, 0); 76 | if (err == -1) { 77 | err = errno; 78 | } 79 | if (err == 0) { 80 | done = true; 81 | } else if (err == ENOMEM) { 82 | assert(result != NULL); 83 | free(result); 84 | result = NULL; 85 | err = 0; 86 | } 87 | } 88 | } while (err == 0 && ! done); 89 | 90 | // Clean up and establish post conditions. 91 | 92 | if (err != 0 && result != NULL) { 93 | free(result); 94 | result = NULL; 95 | } 96 | *procList = result; 97 | if (err == 0) { 98 | *procCount = length / sizeof(kinfo_proc); 99 | } 100 | 101 | assert( (err == 0) == (*procList != NULL) ); 102 | 103 | return err; 104 | } 105 | 106 | bool IsInBSDProcessList(const char name[]) { 107 | assert(name != NULL); 108 | kinfo_proc *result = NULL; 109 | size_t count = 0; 110 | bool ret = 0; 111 | if (GetBSDProcessList(&result, &count) == 0) { 112 | for (size_t i = 0; i < count; i++) { 113 | char *p_comm = result[i].kp_proc.p_comm; 114 | // pid_t p_pid = result[i].kp_proc.p_pid; 115 | // printf("%d %s (%ld/%ld)\n", p_pid, p_comm, i, count); 116 | if (strcmp(p_comm, name) == 0) { 117 | ret = 1; 118 | break; 119 | } 120 | } 121 | free(result); 122 | } 123 | return ret; 124 | } 125 | -------------------------------------------------------------------------------- /KeyCast/PreferencesWindowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreferencesWindowController.swift 3 | // KeyCast 4 | // 5 | 6 | import Cocoa 7 | 8 | class PreferencesWindow: NSWindow { 9 | @IBOutlet weak var textSelectedFont: NSTextField! 10 | @IBOutlet weak var inputWidth: NSTextField! 11 | @IBOutlet weak var inputHeight: NSTextField! 12 | @IBOutlet weak var inputShadow: NSSlider! 13 | @IBOutlet weak var inputOpacity: NSSlider! 14 | @IBOutlet weak var inputLines: NSTextField! 15 | @IBOutlet weak var inputHideInputAutomaticaly: NSButton! 16 | @IBOutlet weak var inputHideNativePasswordInput: NSButton! 17 | @IBOutlet weak var inputHideSudoInProcessList: NSButton! 18 | @IBOutlet weak var inputHotkey: SRRecorderControl! 19 | 20 | let userDefaultsController = NSUserDefaultsController.sharedUserDefaultsController() 21 | var font = NSFont.boldSystemFontOfSize(24) 22 | 23 | var width : Int { 24 | get { 25 | return userDefaultsController.values.valueForKey("width") as! Int 26 | } 27 | } 28 | 29 | var height : Int { 30 | get { 31 | return userDefaultsController.values.valueForKey("height") as! Int 32 | } 33 | } 34 | 35 | var lines : Int { 36 | get { 37 | return userDefaultsController.values.valueForKey("lines") as! Int 38 | } 39 | } 40 | 41 | var shadow : Int { 42 | get { 43 | return userDefaultsController.values.valueForKey("shadow") as! Int 44 | } 45 | } 46 | 47 | var opacity : Int { 48 | get { 49 | return userDefaultsController.values.valueForKey("opacity")as! Int 50 | } 51 | } 52 | 53 | var hideInputAutomaticaly : Bool { 54 | get { 55 | return userDefaultsController.values.valueForKey("hideInputAutomaticaly") as! Bool 56 | } 57 | } 58 | var hideNativePasswordInput : Bool { 59 | get { 60 | return hideInputAutomaticaly && userDefaultsController.values.valueForKey("hideNativePasswordInput") as! Bool 61 | } 62 | } 63 | var hideSudoInProcessList : Bool { 64 | get { 65 | return hideInputAutomaticaly && userDefaultsController.values.valueForKey("hideSudoInProcessList") as! Bool 66 | } 67 | } 68 | 69 | var hotkey : (UInt16, NSEventModifierFlags)? { 70 | get { 71 | if let key = userDefaultsController.values.valueForKey("hotkey") as? Dictionary { 72 | let keyCode : UInt16 = numericCast(key["keyCode"]! as! UInt) 73 | let modifierFlags = NSEventModifierFlags(key["modifierFlags"] as! UInt) 74 | return (keyCode, modifierFlags) 75 | } else { 76 | return nil 77 | } 78 | } 79 | } 80 | 81 | override func awakeFromNib() { 82 | let defaults = NSUserDefaults.standardUserDefaults() 83 | defaults.registerDefaults([ 84 | "width": 800, 85 | "height": 600, 86 | "shadow": 5, 87 | "lines": 5, 88 | "opacity": 90, 89 | "hideInputAutomaticaly": true, 90 | "hideNativePasswordInput": true, 91 | "hideSudoInProcessList": true, 92 | ]) 93 | defaults.synchronize() 94 | 95 | inputWidth.bind("value", toObject: userDefaultsController, withKeyPath: "values.width", options: [ "NSContinuouslyUpdatesValue": true ]) 96 | inputHeight.bind("value", toObject: userDefaultsController, withKeyPath: "values.height", options: [ "NSContinuouslyUpdatesValue": true ]) 97 | inputLines.bind("value", toObject: userDefaultsController, withKeyPath: "values.lines", options: [ "NSContinuouslyUpdatesValue": true ]) 98 | inputShadow.bind("value", toObject: userDefaultsController, withKeyPath: "values.shadow", options: [ "NSContinuouslyUpdatesValue": true ]) 99 | inputOpacity.bind("value", toObject: userDefaultsController, withKeyPath: "values.opacity", options: [ "NSContinuouslyUpdatesValue": true ]) 100 | inputHotkey.bind("value", toObject: userDefaultsController, withKeyPath: "values.hotkey", options: nil ) 101 | inputHotkey.delegate = self 102 | inputHotkey.allowsEscapeToCancelRecording = true 103 | inputHotkey.setAllowedModifierFlags((NSEventModifierFlags.ShiftKeyMask | NSEventModifierFlags.CommandKeyMask | NSEventModifierFlags.ControlKeyMask | NSEventModifierFlags.AlternateKeyMask).rawValue, requiredModifierFlags: 0, allowsEmptyModifierFlags: false) 104 | inputHotkey.enabled = true 105 | 106 | inputHideInputAutomaticaly.bind("value", toObject: userDefaultsController, withKeyPath: "values.hideInputAutomaticaly", options: [ "NSContinuouslyUpdatesValue": true ]) 107 | inputHideNativePasswordInput.bind("value", toObject: userDefaultsController, withKeyPath: "values.hideNativePasswordInput", options: [ "NSContinuouslyUpdatesValue": true ]) 108 | inputHideSudoInProcessList.bind("value", toObject: userDefaultsController, withKeyPath: "values.hideSudoInProcessList", options: [ "NSContinuouslyUpdatesValue": true ]) 109 | 110 | let fontName = userDefaultsController.values.valueForKey("fontName") as! String? 111 | let pointSize = userDefaultsController.values.valueForKey("pointSize") as! Float? 112 | if fontName != nil && pointSize != nil { 113 | let font_ = NSFont(name: fontName!, size: CGFloat(pointSize!)) 114 | if font_ != nil { 115 | font = font_! 116 | } 117 | } 118 | updateFontInfo(font) 119 | 120 | NSNotificationCenter.defaultCenter().addObserver( 121 | self, 122 | selector: "userDefaultsDidChange:", 123 | name: NSUserDefaultsDidChangeNotification, 124 | object: nil 125 | ) 126 | userDefaultsDidChange(nil) 127 | } 128 | 129 | func userDefaultsDidChange(aNotification: NSNotification!) { 130 | println("changed") 131 | 132 | if hideInputAutomaticaly { 133 | inputHideNativePasswordInput.enabled = true 134 | inputHideSudoInProcessList.enabled = true 135 | } else { 136 | inputHideNativePasswordInput.enabled = false 137 | inputHideSudoInProcessList.enabled = false 138 | } 139 | 140 | // SRRecorderControl の bind を読むとなぜか無限ループになるのでここでは触らないようにしなければならない… 141 | } 142 | 143 | func shortcutRecorderDidEndRecording(sr: SRRecorderControl) { 144 | } 145 | 146 | override func cancelOperation(sender: AnyObject?) { 147 | close() 148 | } 149 | 150 | func updateFontInfo(f: NSFont) { 151 | font = f 152 | textSelectedFont.stringValue = String(format: "%@ %.0fpt", font.displayName!, Float(font.pointSize)) 153 | userDefaultsController.values.setValue(font.fontName, forKey: "fontName") 154 | userDefaultsController.values.setValue(Float(font.pointSize), forKey: "pointSize") 155 | } 156 | 157 | @IBAction func setScreenWidth(sender: AnyObject) { 158 | if let width = screen?.visibleFrame.size.width { 159 | userDefaultsController.values.setValue(width, forKey: "width") 160 | } 161 | } 162 | } 163 | 164 | -------------------------------------------------------------------------------- /KeyCast/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // KeyCast 4 | // 5 | // Copyright (c) 2015年 cho45. All rights reserved. 6 | // 7 | 8 | import Cocoa 9 | 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(-1) 14 | var enabled: Bool = true { 15 | didSet { 16 | menuEnabled.state = enabled ? 1 : 0 17 | updateMenuTitle() 18 | toast.toast(enabled ? "KeyCast is enabled" : "KeyCast is disabled") 19 | } 20 | } 21 | var window: NSWindow! = nil 22 | var view: MainView! = nil 23 | var prevKeyed: NSDate = NSDate() 24 | 25 | @IBOutlet weak var menu: NSMenu! 26 | @IBOutlet weak var menuEnabled: NSMenuItem! 27 | @IBOutlet weak var preferences: PreferencesWindow! 28 | @IBOutlet weak var toast: ToastWindow! 29 | @IBOutlet weak var about: AboutWindow! 30 | 31 | func applicationDidFinishLaunching(aNotification: NSNotification) { 32 | 33 | Accessibility.checkAccessibilityEnabled(self) 34 | 35 | NSEvent.addGlobalMonitorForEventsMatchingMask(NSEventMask.KeyDownMask) { (e: NSEvent!) in 36 | if let (hotkey, hotkeyflags) = self.preferences.hotkey { 37 | // println("mod") 38 | // println(String(e.modifierFlags.rawValue & NSEventModifierFlags.DeviceIndependentModifierFlagsMask.rawValue, radix: 2)) 39 | // println(String(hotkeyflags.rawValue, radix: 2)) 40 | let sameKeyCode = e.keyCode == hotkey 41 | let sameModifiers = (e.modifierFlags & NSEventModifierFlags.DeviceIndependentModifierFlagsMask).rawValue == hotkeyflags.rawValue 42 | if sameKeyCode && sameModifiers { 43 | self.toggleState(nil) 44 | } 45 | } 46 | 47 | if !self.canShowInput() { 48 | return 49 | } 50 | if e.type != NSEventType.KeyDown { 51 | return 52 | } 53 | // println(e) 54 | 55 | 56 | for c in e.charactersIgnoringModifiers!.uppercaseString.unicodeScalars { 57 | println(NSString(format: "%08X", c.value)); 58 | } 59 | 60 | let (mod, char) = Utils.keyStringFromEvent(e) 61 | 62 | if mod.isEmpty { 63 | let interval = NSDate().timeIntervalSinceDate(self.prevKeyed) 64 | if interval > 1 { 65 | self.view.appendLog("\n" + char) 66 | } else { 67 | self.view.appendLog(char) 68 | } 69 | } else { 70 | self.view.appendLog("\n" + mod + char + " ") 71 | 72 | } 73 | 74 | self.prevKeyed = NSDate() 75 | } 76 | 77 | let rect = NSRect(x: 0, y: 0, width: 800, height: 500) 78 | window = NSWindow(contentRect: rect, styleMask: NSBorderlessWindowMask, backing: NSBackingStoreType.Buffered, defer: false) 79 | window.opaque = false 80 | window.hasShadow = false 81 | window.level = 1000 82 | window.movableByWindowBackground = true 83 | window.makeKeyAndOrderFront(nil) 84 | window.orderFrontRegardless() 85 | 86 | view = MainView(frame: rect) 87 | view.font = preferences.font 88 | 89 | window.contentView = view 90 | 91 | statusItem.menu = menu 92 | // statusItem.image = NSImage(named: "icon-menu") 93 | statusItem.highlightMode = true 94 | updateMenuTitle() 95 | 96 | view.appendLog("KeyCast Initialized\nYou can drag this to the position you want") 97 | 98 | NSNotificationCenter.defaultCenter().addObserver( 99 | self, 100 | selector: "userDefaultsDidChange:", 101 | name: NSUserDefaultsDidChangeNotification, 102 | object: nil 103 | ) 104 | userDefaultsDidChange(nil) 105 | 106 | 107 | /* 108 | let callback: AXObserverCallback = (AXObserver!, AXUIElement!, CFString!, UnsafeMutablePointer) -> Void) { 109 | 110 | } 111 | var observer: Unmanaged? 112 | AXObserverCreate(NSProcessInfo.processInfo().processIdentifier, callback, &observer) 113 | AXObserverAddNotification(observer, AXUIElementCreateSystemWide().takeRetainedValue(), NSAccessibilityFocusedUIElementChangedNotification, &0) 114 | */ 115 | 116 | enableGlobalAccessibilityFeatures() 117 | toast.toast("Initialized") 118 | } 119 | 120 | // VoiceOver が起動していない限りアクセシビリティオブジェクトを作らない一部アプリケーション用 (eg. Google Chrome) に 121 | // VoiceOver がセットする属性をセットする。VoiceOver 判定のため自プロセスには設定しない 122 | func enableGlobalAccessibilityFeatures() { 123 | println("enableGlobalAccessibilityFeatures") 124 | NSWorkspace.sharedWorkspace().notificationCenter.addObserver( 125 | self, 126 | selector: "enableAccessibilityForNewApplication:", 127 | name: NSWorkspaceDidLaunchApplicationNotification, 128 | object: nil 129 | ) 130 | 131 | var ptr: Unmanaged? 132 | let pid = NSProcessInfo.processInfo().processIdentifier 133 | for application in NSWorkspace.sharedWorkspace().runningApplications { 134 | if application.processIdentifier == pid { 135 | continue 136 | } 137 | let app = AXUIElementCreateApplication(application.processIdentifier).takeRetainedValue() 138 | println("enableGlobalAccessibilityFeatures: \(app)") 139 | AXUIElementCopyAttributeValue(app, "AXEnhancedUserInterface", &ptr) 140 | AXUIElementSetAttributeValue(app, "AXEnhancedUserInterface", 1) 141 | } 142 | } 143 | 144 | // callback 145 | func enableAccessibilityForNewApplication(aNotification: NSNotification) { 146 | let pid = aNotification.userInfo!["NSApplicationProcessIdentifier"] as! Int 147 | 148 | var ptr: Unmanaged? 149 | let app = AXUIElementCreateApplication(Int32(pid)).takeRetainedValue() 150 | println("NSWorkspaceWillLaunchApplicationNotification") 151 | println(app) 152 | 153 | AXUIElementCopyAttributeValue(app, "AXEnhancedUserInterface", &ptr) 154 | AXUIElementSetAttributeValue(app, "AXEnhancedUserInterface", 1) 155 | } 156 | 157 | func disableGlobalAccessibilityFeatures() { 158 | NSWorkspace.sharedWorkspace().notificationCenter.removeObserver(self, name: NSWorkspaceWillLaunchApplicationNotification, object: nil) 159 | 160 | if isVoiceOverRunning() { 161 | return 162 | } 163 | 164 | var ptr: Unmanaged? 165 | let pid = NSProcessInfo.processInfo().processIdentifier 166 | for application in NSWorkspace.sharedWorkspace().runningApplications { 167 | if application.processIdentifier == pid { 168 | continue 169 | } 170 | let app = AXUIElementCreateApplication(application.processIdentifier).takeRetainedValue() 171 | println(app) 172 | 173 | AXUIElementCopyAttributeValue(app, "AXEnhancedUserInterface", &ptr) 174 | AXUIElementSetAttributeValue(app, "AXEnhancedUserInterface", 0) 175 | } 176 | } 177 | 178 | // 完全に起動したあとでなければ常に false を返す 179 | func isVoiceOverRunning()->Bool { 180 | var ptr: Unmanaged? 181 | let pid = NSProcessInfo.processInfo().processIdentifier 182 | let app = AXUIElementCreateApplication(pid).takeRetainedValue() 183 | AXUIElementCopyAttributeValue(app, "AXEnhancedUserInterface", &ptr) 184 | if let running = ptr?.takeRetainedValue() as? Int { 185 | if running == 1 { 186 | return true 187 | } 188 | } 189 | return false 190 | } 191 | 192 | func userDefaultsDidChange(aNotification: NSNotification!) { 193 | window.alphaValue = CGFloat(preferences.opacity) / 100.0 194 | self.resize(preferences.width, height: preferences.height) 195 | view.shadowCount = preferences.shadow 196 | view.maxLine = preferences.lines 197 | view.needsDisplay = true 198 | } 199 | 200 | func applicationWillTerminate(aNotification: NSNotification) { 201 | disableGlobalAccessibilityFeatures() 202 | } 203 | 204 | func resize (width: Int, height: Int) { 205 | let current = window.frame 206 | let rect = NSRect(x: Int(current.minX), y: Int(current.minY), width: width, height: height) 207 | window.setFrame(rect, display: true) 208 | view.setFrameSize(NSSize(width: rect.width, height: rect.height)) 209 | } 210 | 211 | func updateMenuTitle() { 212 | statusItem.title = (enabled ? "\u{2713} " : " ") + NSRunningApplication.currentApplication().localizedName! 213 | } 214 | 215 | func canShowInput()-> Bool { 216 | return enabled && canShowInputByRunningProcesses() && canShowInputByFocusedUIElement() 217 | } 218 | 219 | func canShowInputByRunningProcesses()->Bool { 220 | if !preferences.hideSudoInProcessList { 221 | return true 222 | } 223 | 224 | // sudo 実行中は入力を表示しない 225 | return !IsInBSDProcessList("sudo") 226 | } 227 | 228 | func canShowInputByFocusedUIElement()->Bool { 229 | if !preferences.hideNativePasswordInput { 230 | return true 231 | } 232 | 233 | var ptr: Unmanaged? 234 | 235 | let system = AXUIElementCreateSystemWide().takeRetainedValue() 236 | 237 | AXUIElementCopyAttributeValue(system, "AXFocusedApplication", &ptr) 238 | if ptr == nil { 239 | return true 240 | } 241 | let focusedApp = ptr!.takeRetainedValue() as! AXUIElement 242 | 243 | var pid: pid_t = 0 244 | AXUIElementGetPid(focusedApp, &pid) 245 | 246 | let bundleId_ = NSRunningApplication(processIdentifier: pid)?.bundleIdentifier 247 | if bundleId_ == nil { 248 | return true 249 | } 250 | let bundleId = bundleId_! 251 | 252 | 253 | AXUIElementCopyAttributeValue(focusedApp, NSAccessibilityFocusedUIElementAttribute, &ptr) 254 | if ptr == nil { 255 | return true 256 | } 257 | let ui = ptr!.takeRetainedValue() as! AXUIElement 258 | 259 | 260 | /* 261 | let target = focusedApp 262 | var arrayPtr: Unmanaged? 263 | AXUIElementCopyAttributeNames(target, &arrayPtr) 264 | if let array = arrayPtr?.takeRetainedValue() { 265 | for var i = 0, len = CFArrayGetCount(array); i < len; i++ { 266 | let name = unsafeBitCast(CFArrayGetValueAtIndex(array, i), CFString.self) 267 | AXUIElementCopyAttributeValue(target, name, &ptr) 268 | let value = ptr?.takeRetainedValue() 269 | if value != nil { 270 | println(name) 271 | println(value) 272 | println("") 273 | } 274 | } 275 | } 276 | */ 277 | 278 | /* 279 | if bundleId == "com.apple.Terminal" { 280 | AXUIElementCopyAttributeValue(ui, "AXValue", &ptr) 281 | if ptr != nil { 282 | let value = ptr!.takeRetainedValue() as String 283 | 284 | // Terminal.app で sudo っぽいことが起きていたらタイプを表示しない 285 | // (screen の中だと意味がない) sudo のラッパを書いたほうがいいかも 286 | let re = NSRegularExpression(pattern: "Password:\\s*$", options: .CaseInsensitive, error: nil)! 287 | let matches = re.matchesInString(value, options: nil, range: NSMakeRange(0, countElements(value))) 288 | if matches.count > 0 { 289 | return false 290 | } 291 | } 292 | } 293 | */ 294 | 295 | AXUIElementCopyAttributeValue(ui, "AXSubrole", &ptr) 296 | if ptr != nil { 297 | let value = ptr!.takeRetainedValue() as! String 298 | if value == "AXSecureTextField" { 299 | return false 300 | } 301 | } 302 | 303 | // NSAccessibilityFocusedUIElementChangedNotification 304 | return true 305 | } 306 | 307 | 308 | @IBAction func toggleState(sender: AnyObject!) { 309 | enabled = !enabled 310 | } 311 | 312 | @IBAction func clearLog(sender: AnyObject) { 313 | view.clear() 314 | } 315 | 316 | @IBAction func openPreferencesWindow(sender: AnyObject) { 317 | preferences.makeKeyAndOrderFront(nil) 318 | NSApp.activateIgnoringOtherApps!(true) 319 | } 320 | 321 | @IBAction func chooseFont(sender: AnyObject) { 322 | let fontManager = NSFontManager.sharedFontManager() 323 | fontManager.delegate = self 324 | fontManager.target = self 325 | fontManager.setSelectedFont(preferences.font, isMultiple: false) 326 | 327 | let fontPanel = fontManager.fontPanel(true) 328 | fontPanel?.makeKeyAndOrderFront(sender) 329 | } 330 | 331 | override func changeFont(sender: AnyObject?) { 332 | let fontManager = sender! as! NSFontManager 333 | println("changeFont") 334 | println(fontManager) 335 | preferences.font = fontManager.convertFont(preferences.font) 336 | view.needsDisplay = true 337 | println(preferences.font.fontName) 338 | println(preferences.font.pointSize) 339 | 340 | preferences.updateFontInfo(preferences.font) 341 | view.font = preferences.font 342 | } 343 | 344 | /* wait for Xcode 6.3... 345 | func processList() { 346 | // https://developer.apple.com/legacy/library/qa/qa2001/qa1123.html 347 | 348 | var err : Int32 349 | var nullpo = UnsafeMutablePointer.null() 350 | 351 | var names: [Int32] = [ CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 ] 352 | var length: UInt = 0 353 | var namelen: u_int = u_int(names.count) 354 | err = sysctl(&names, namelen, nullpo, &length, nullpo, 0) 355 | if (err == -1) { 356 | println("error") 357 | return 358 | } 359 | println(length) 360 | 361 | var result = UnsafeMutablePointer.alloc(Int(length)) 362 | err = sysctl(&names, namelen, result, &length, nullpo, 0) 363 | if (err == -1) { 364 | println("error") 365 | return 366 | } 367 | 368 | println(result) 369 | rkkkgg} 370 | */ 371 | 372 | // osascript -e 'tell application "KeyCast"' -e 'set enabled to true' -e 'end tell' 373 | // osascript -e 'tell application "KeyCast"' -e 'set enabled to false' -e 'end tell' 374 | override func application(sender: NSApplication, delegateHandlesKey key: String) -> Bool { 375 | if key == "enabled" { 376 | return true 377 | } 378 | return false 379 | } 380 | 381 | @IBAction func showAbout(sender: AnyObject) { 382 | about.makeKeyAndOrderFront(nil) 383 | NSApp.activateIgnoringOtherApps!(true) 384 | } 385 | } 386 | 387 | -------------------------------------------------------------------------------- /KeyCast.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 28356C791A95E82C0083749C /* Credits.html in Resources */ = {isa = PBXBuildFile; fileRef = 28356C781A95E82C0083749C /* Credits.html */; }; 11 | 28398B1E1A8D924F009BBEBE /* processlist.c in Sources */ = {isa = PBXBuildFile; fileRef = 28398B1D1A8D924F009BBEBE /* processlist.c */; }; 12 | 28485C551A84AFF000667ECB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28485C541A84AFF000667ECB /* AppDelegate.swift */; }; 13 | 28485C571A84AFF000667ECB /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 28485C561A84AFF000667ECB /* Images.xcassets */; }; 14 | 28485C5A1A84AFF000667ECB /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28485C581A84AFF000667ECB /* MainMenu.xib */; }; 15 | 28485C661A84AFF100667ECB /* KeyCastTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28485C651A84AFF100667ECB /* KeyCastTests.swift */; }; 16 | 285ED37E1A888C4E00E97321 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285ED37D1A888C4E00E97321 /* PreferencesWindowController.swift */; }; 17 | 286F3C4A1A8DF94800D56393 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 286F3C491A8DF94800D56393 /* Toast.swift */; }; 18 | 28F478381A8D933800E88A71 /* app.sdef in Resources */ = {isa = PBXBuildFile; fileRef = 28F478371A8D933800E88A71 /* app.sdef */; }; 19 | 28F4783E1A8DBB9F00E88A71 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28F4783D1A8DBB9F00E88A71 /* Utils.swift */; }; 20 | 28FD3B861A88596600D6A1C4 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FD3B851A88596600D6A1C4 /* MainView.swift */; }; 21 | 74547BE11A8A559D003A079A /* README.md in Sources */ = {isa = PBXBuildFile; fileRef = 74547BE01A8A559D003A079A /* README.md */; }; 22 | 74742F7B1A9476F800B6DD89 /* PTHotKey.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 74742F781A9476CA00B6DD89 /* PTHotKey.framework */; }; 23 | 74742F7C1A9476F800B6DD89 /* ShortcutRecorder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 74742F761A9476CA00B6DD89 /* ShortcutRecorder.framework */; }; 24 | 74742F7E1A9477C600B6DD89 /* ShortcutRecorder.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 74742F761A9476CA00B6DD89 /* ShortcutRecorder.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 25 | 74742F7F1A9477C600B6DD89 /* PTHotKey.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 74742F781A9476CA00B6DD89 /* PTHotKey.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 26 | 748E2EC91A87AF3100F5B319 /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748E2EC81A87AF3100F5B319 /* Accessibility.swift */; }; 27 | 74FE2A001A962803004F98D5 /* AboutWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74FE29FF1A962803004F98D5 /* AboutWindowController.swift */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXContainerItemProxy section */ 31 | 28485C601A84AFF100667ECB /* PBXContainerItemProxy */ = { 32 | isa = PBXContainerItemProxy; 33 | containerPortal = 28485C471A84AFF000667ECB /* Project object */; 34 | proxyType = 1; 35 | remoteGlobalIDString = 28485C4E1A84AFF000667ECB; 36 | remoteInfo = KeyCast; 37 | }; 38 | 74742F751A9476CA00B6DD89 /* PBXContainerItemProxy */ = { 39 | isa = PBXContainerItemProxy; 40 | containerPortal = 74742F6F1A9476C900B6DD89 /* ShortcutRecorder.xcodeproj */; 41 | proxyType = 2; 42 | remoteGlobalIDString = 939837800DA42965007F53F3; 43 | remoteInfo = ShortcutRecorder.framework; 44 | }; 45 | 74742F771A9476CA00B6DD89 /* PBXContainerItemProxy */ = { 46 | isa = PBXContainerItemProxy; 47 | containerPortal = 74742F6F1A9476C900B6DD89 /* ShortcutRecorder.xcodeproj */; 48 | proxyType = 2; 49 | remoteGlobalIDString = E273122E1349EC9000A84433; 50 | remoteInfo = PTHotKey.framework; 51 | }; 52 | 74742F791A9476CA00B6DD89 /* PBXContainerItemProxy */ = { 53 | isa = PBXContainerItemProxy; 54 | containerPortal = 74742F6F1A9476C900B6DD89 /* ShortcutRecorder.xcodeproj */; 55 | proxyType = 2; 56 | remoteGlobalIDString = 0D6B2468180304DE00CE1142; 57 | remoteInfo = Demo; 58 | }; 59 | /* End PBXContainerItemProxy section */ 60 | 61 | /* Begin PBXCopyFilesBuildPhase section */ 62 | 74742F7D1A94779500B6DD89 /* CopyFiles */ = { 63 | isa = PBXCopyFilesBuildPhase; 64 | buildActionMask = 2147483647; 65 | dstPath = ""; 66 | dstSubfolderSpec = 10; 67 | files = ( 68 | 74742F7E1A9477C600B6DD89 /* ShortcutRecorder.framework in CopyFiles */, 69 | 74742F7F1A9477C600B6DD89 /* PTHotKey.framework in CopyFiles */, 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | /* End PBXCopyFilesBuildPhase section */ 74 | 75 | /* Begin PBXFileReference section */ 76 | 282BCF531A8A0C1200472C8A /* KeyCast-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "KeyCast-Bridging-Header.h"; sourceTree = ""; }; 77 | 282BCF551A8A0C1300472C8A /* processlist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = processlist.h; sourceTree = ""; }; 78 | 28356C781A95E82C0083749C /* Credits.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = Credits.html; sourceTree = ""; }; 79 | 28398B1D1A8D924F009BBEBE /* processlist.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = processlist.c; sourceTree = ""; }; 80 | 28485C4F1A84AFF000667ECB /* KeyCast.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KeyCast.app; sourceTree = BUILT_PRODUCTS_DIR; }; 81 | 28485C531A84AFF000667ECB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 82 | 28485C541A84AFF000667ECB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 83 | 28485C561A84AFF000667ECB /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 84 | 28485C591A84AFF000667ECB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 85 | 28485C5F1A84AFF100667ECB /* KeyCastTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KeyCastTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 86 | 28485C641A84AFF100667ECB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 87 | 28485C651A84AFF100667ECB /* KeyCastTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyCastTests.swift; sourceTree = ""; }; 88 | 285ED37D1A888C4E00E97321 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; 89 | 286F3C491A8DF94800D56393 /* Toast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = ""; }; 90 | 28F478371A8D933800E88A71 /* app.sdef */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = app.sdef; sourceTree = ""; }; 91 | 28F4783D1A8DBB9F00E88A71 /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; 92 | 28FD3B851A88596600D6A1C4 /* MainView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; 93 | 74547BE01A8A559D003A079A /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 94 | 74742F6F1A9476C900B6DD89 /* ShortcutRecorder.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ShortcutRecorder.xcodeproj; path = ShortcutRecorder/ShortcutRecorder.xcodeproj; sourceTree = SOURCE_ROOT; }; 95 | 748E2EC81A87AF3100F5B319 /* Accessibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Accessibility.swift; sourceTree = ""; }; 96 | 74FE29FF1A962803004F98D5 /* AboutWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutWindowController.swift; sourceTree = ""; }; 97 | /* End PBXFileReference section */ 98 | 99 | /* Begin PBXFrameworksBuildPhase section */ 100 | 28485C4C1A84AFF000667ECB /* Frameworks */ = { 101 | isa = PBXFrameworksBuildPhase; 102 | buildActionMask = 2147483647; 103 | files = ( 104 | 74742F7B1A9476F800B6DD89 /* PTHotKey.framework in Frameworks */, 105 | 74742F7C1A9476F800B6DD89 /* ShortcutRecorder.framework in Frameworks */, 106 | ); 107 | runOnlyForDeploymentPostprocessing = 0; 108 | }; 109 | 28485C5C1A84AFF100667ECB /* Frameworks */ = { 110 | isa = PBXFrameworksBuildPhase; 111 | buildActionMask = 2147483647; 112 | files = ( 113 | ); 114 | runOnlyForDeploymentPostprocessing = 0; 115 | }; 116 | /* End PBXFrameworksBuildPhase section */ 117 | 118 | /* Begin PBXGroup section */ 119 | 28485C461A84AFF000667ECB = { 120 | isa = PBXGroup; 121 | children = ( 122 | 28485C511A84AFF000667ECB /* KeyCast */, 123 | 28485C621A84AFF100667ECB /* KeyCastTests */, 124 | 28485C501A84AFF000667ECB /* Products */, 125 | ); 126 | sourceTree = ""; 127 | }; 128 | 28485C501A84AFF000667ECB /* Products */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 28485C4F1A84AFF000667ECB /* KeyCast.app */, 132 | 28485C5F1A84AFF100667ECB /* KeyCastTests.xctest */, 133 | ); 134 | name = Products; 135 | sourceTree = ""; 136 | }; 137 | 28485C511A84AFF000667ECB /* KeyCast */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | 28485C541A84AFF000667ECB /* AppDelegate.swift */, 141 | 74742F6F1A9476C900B6DD89 /* ShortcutRecorder.xcodeproj */, 142 | 74547BE01A8A559D003A079A /* README.md */, 143 | 28398B1D1A8D924F009BBEBE /* processlist.c */, 144 | 282BCF551A8A0C1300472C8A /* processlist.h */, 145 | 28485C561A84AFF000667ECB /* Images.xcassets */, 146 | 28485C581A84AFF000667ECB /* MainMenu.xib */, 147 | 28485C521A84AFF000667ECB /* Supporting Files */, 148 | 748E2EC81A87AF3100F5B319 /* Accessibility.swift */, 149 | 28FD3B851A88596600D6A1C4 /* MainView.swift */, 150 | 285ED37D1A888C4E00E97321 /* PreferencesWindowController.swift */, 151 | 74FE29FF1A962803004F98D5 /* AboutWindowController.swift */, 152 | 286F3C491A8DF94800D56393 /* Toast.swift */, 153 | 28F4783D1A8DBB9F00E88A71 /* Utils.swift */, 154 | 282BCF531A8A0C1200472C8A /* KeyCast-Bridging-Header.h */, 155 | 28356C781A95E82C0083749C /* Credits.html */, 156 | 28F478371A8D933800E88A71 /* app.sdef */, 157 | ); 158 | path = KeyCast; 159 | sourceTree = ""; 160 | }; 161 | 28485C521A84AFF000667ECB /* Supporting Files */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | 28485C531A84AFF000667ECB /* Info.plist */, 165 | ); 166 | name = "Supporting Files"; 167 | sourceTree = ""; 168 | }; 169 | 28485C621A84AFF100667ECB /* KeyCastTests */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 28485C651A84AFF100667ECB /* KeyCastTests.swift */, 173 | 28485C631A84AFF100667ECB /* Supporting Files */, 174 | ); 175 | path = KeyCastTests; 176 | sourceTree = ""; 177 | }; 178 | 28485C631A84AFF100667ECB /* Supporting Files */ = { 179 | isa = PBXGroup; 180 | children = ( 181 | 28485C641A84AFF100667ECB /* Info.plist */, 182 | ); 183 | name = "Supporting Files"; 184 | sourceTree = ""; 185 | }; 186 | 74742F701A9476C900B6DD89 /* Products */ = { 187 | isa = PBXGroup; 188 | children = ( 189 | 74742F761A9476CA00B6DD89 /* ShortcutRecorder.framework */, 190 | 74742F781A9476CA00B6DD89 /* PTHotKey.framework */, 191 | 74742F7A1A9476CA00B6DD89 /* Demo.app */, 192 | ); 193 | name = Products; 194 | sourceTree = ""; 195 | }; 196 | /* End PBXGroup section */ 197 | 198 | /* Begin PBXNativeTarget section */ 199 | 28485C4E1A84AFF000667ECB /* KeyCast */ = { 200 | isa = PBXNativeTarget; 201 | buildConfigurationList = 28485C691A84AFF100667ECB /* Build configuration list for PBXNativeTarget "KeyCast" */; 202 | buildPhases = ( 203 | 28485C4B1A84AFF000667ECB /* Sources */, 204 | 28485C4C1A84AFF000667ECB /* Frameworks */, 205 | 28485C4D1A84AFF000667ECB /* Resources */, 206 | 74742F7D1A94779500B6DD89 /* CopyFiles */, 207 | ); 208 | buildRules = ( 209 | ); 210 | dependencies = ( 211 | ); 212 | name = KeyCast; 213 | productName = KeyCast; 214 | productReference = 28485C4F1A84AFF000667ECB /* KeyCast.app */; 215 | productType = "com.apple.product-type.application"; 216 | }; 217 | 28485C5E1A84AFF100667ECB /* KeyCastTests */ = { 218 | isa = PBXNativeTarget; 219 | buildConfigurationList = 28485C6C1A84AFF100667ECB /* Build configuration list for PBXNativeTarget "KeyCastTests" */; 220 | buildPhases = ( 221 | 28485C5B1A84AFF100667ECB /* Sources */, 222 | 28485C5C1A84AFF100667ECB /* Frameworks */, 223 | 28485C5D1A84AFF100667ECB /* Resources */, 224 | ); 225 | buildRules = ( 226 | ); 227 | dependencies = ( 228 | 28485C611A84AFF100667ECB /* PBXTargetDependency */, 229 | ); 230 | name = KeyCastTests; 231 | productName = KeyCastTests; 232 | productReference = 28485C5F1A84AFF100667ECB /* KeyCastTests.xctest */; 233 | productType = "com.apple.product-type.bundle.unit-test"; 234 | }; 235 | /* End PBXNativeTarget section */ 236 | 237 | /* Begin PBXProject section */ 238 | 28485C471A84AFF000667ECB /* Project object */ = { 239 | isa = PBXProject; 240 | attributes = { 241 | LastUpgradeCheck = 0610; 242 | ORGANIZATIONNAME = cho45; 243 | TargetAttributes = { 244 | 28485C4E1A84AFF000667ECB = { 245 | CreatedOnToolsVersion = 6.1.1; 246 | SystemCapabilities = { 247 | com.apple.Sandbox = { 248 | enabled = 0; 249 | }; 250 | }; 251 | }; 252 | 28485C5E1A84AFF100667ECB = { 253 | CreatedOnToolsVersion = 6.1.1; 254 | TestTargetID = 28485C4E1A84AFF000667ECB; 255 | }; 256 | }; 257 | }; 258 | buildConfigurationList = 28485C4A1A84AFF000667ECB /* Build configuration list for PBXProject "KeyCast" */; 259 | compatibilityVersion = "Xcode 3.2"; 260 | developmentRegion = English; 261 | hasScannedForEncodings = 0; 262 | knownRegions = ( 263 | en, 264 | Base, 265 | ); 266 | mainGroup = 28485C461A84AFF000667ECB; 267 | productRefGroup = 28485C501A84AFF000667ECB /* Products */; 268 | projectDirPath = ""; 269 | projectReferences = ( 270 | { 271 | ProductGroup = 74742F701A9476C900B6DD89 /* Products */; 272 | ProjectRef = 74742F6F1A9476C900B6DD89 /* ShortcutRecorder.xcodeproj */; 273 | }, 274 | ); 275 | projectRoot = ""; 276 | targets = ( 277 | 28485C4E1A84AFF000667ECB /* KeyCast */, 278 | 28485C5E1A84AFF100667ECB /* KeyCastTests */, 279 | ); 280 | }; 281 | /* End PBXProject section */ 282 | 283 | /* Begin PBXReferenceProxy section */ 284 | 74742F761A9476CA00B6DD89 /* ShortcutRecorder.framework */ = { 285 | isa = PBXReferenceProxy; 286 | fileType = wrapper.framework; 287 | path = ShortcutRecorder.framework; 288 | remoteRef = 74742F751A9476CA00B6DD89 /* PBXContainerItemProxy */; 289 | sourceTree = BUILT_PRODUCTS_DIR; 290 | }; 291 | 74742F781A9476CA00B6DD89 /* PTHotKey.framework */ = { 292 | isa = PBXReferenceProxy; 293 | fileType = wrapper.framework; 294 | path = PTHotKey.framework; 295 | remoteRef = 74742F771A9476CA00B6DD89 /* PBXContainerItemProxy */; 296 | sourceTree = BUILT_PRODUCTS_DIR; 297 | }; 298 | 74742F7A1A9476CA00B6DD89 /* Demo.app */ = { 299 | isa = PBXReferenceProxy; 300 | fileType = wrapper.application; 301 | path = Demo.app; 302 | remoteRef = 74742F791A9476CA00B6DD89 /* PBXContainerItemProxy */; 303 | sourceTree = BUILT_PRODUCTS_DIR; 304 | }; 305 | /* End PBXReferenceProxy section */ 306 | 307 | /* Begin PBXResourcesBuildPhase section */ 308 | 28485C4D1A84AFF000667ECB /* Resources */ = { 309 | isa = PBXResourcesBuildPhase; 310 | buildActionMask = 2147483647; 311 | files = ( 312 | 28F478381A8D933800E88A71 /* app.sdef in Resources */, 313 | 28356C791A95E82C0083749C /* Credits.html in Resources */, 314 | 28485C571A84AFF000667ECB /* Images.xcassets in Resources */, 315 | 28485C5A1A84AFF000667ECB /* MainMenu.xib in Resources */, 316 | ); 317 | runOnlyForDeploymentPostprocessing = 0; 318 | }; 319 | 28485C5D1A84AFF100667ECB /* Resources */ = { 320 | isa = PBXResourcesBuildPhase; 321 | buildActionMask = 2147483647; 322 | files = ( 323 | ); 324 | runOnlyForDeploymentPostprocessing = 0; 325 | }; 326 | /* End PBXResourcesBuildPhase section */ 327 | 328 | /* Begin PBXSourcesBuildPhase section */ 329 | 28485C4B1A84AFF000667ECB /* Sources */ = { 330 | isa = PBXSourcesBuildPhase; 331 | buildActionMask = 2147483647; 332 | files = ( 333 | 748E2EC91A87AF3100F5B319 /* Accessibility.swift in Sources */, 334 | 286F3C4A1A8DF94800D56393 /* Toast.swift in Sources */, 335 | 28398B1E1A8D924F009BBEBE /* processlist.c in Sources */, 336 | 28FD3B861A88596600D6A1C4 /* MainView.swift in Sources */, 337 | 28F4783E1A8DBB9F00E88A71 /* Utils.swift in Sources */, 338 | 74547BE11A8A559D003A079A /* README.md in Sources */, 339 | 74FE2A001A962803004F98D5 /* AboutWindowController.swift in Sources */, 340 | 285ED37E1A888C4E00E97321 /* PreferencesWindowController.swift in Sources */, 341 | 28485C551A84AFF000667ECB /* AppDelegate.swift in Sources */, 342 | ); 343 | runOnlyForDeploymentPostprocessing = 0; 344 | }; 345 | 28485C5B1A84AFF100667ECB /* Sources */ = { 346 | isa = PBXSourcesBuildPhase; 347 | buildActionMask = 2147483647; 348 | files = ( 349 | 28485C661A84AFF100667ECB /* KeyCastTests.swift in Sources */, 350 | ); 351 | runOnlyForDeploymentPostprocessing = 0; 352 | }; 353 | /* End PBXSourcesBuildPhase section */ 354 | 355 | /* Begin PBXTargetDependency section */ 356 | 28485C611A84AFF100667ECB /* PBXTargetDependency */ = { 357 | isa = PBXTargetDependency; 358 | target = 28485C4E1A84AFF000667ECB /* KeyCast */; 359 | targetProxy = 28485C601A84AFF100667ECB /* PBXContainerItemProxy */; 360 | }; 361 | /* End PBXTargetDependency section */ 362 | 363 | /* Begin PBXVariantGroup section */ 364 | 28485C581A84AFF000667ECB /* MainMenu.xib */ = { 365 | isa = PBXVariantGroup; 366 | children = ( 367 | 28485C591A84AFF000667ECB /* Base */, 368 | ); 369 | name = MainMenu.xib; 370 | sourceTree = ""; 371 | }; 372 | /* End PBXVariantGroup section */ 373 | 374 | /* Begin XCBuildConfiguration section */ 375 | 28485C671A84AFF100667ECB /* Debug */ = { 376 | isa = XCBuildConfiguration; 377 | buildSettings = { 378 | ALWAYS_SEARCH_USER_PATHS = NO; 379 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 380 | CLANG_CXX_LIBRARY = "libc++"; 381 | CLANG_ENABLE_MODULES = YES; 382 | CLANG_ENABLE_OBJC_ARC = YES; 383 | CLANG_WARN_BOOL_CONVERSION = YES; 384 | CLANG_WARN_CONSTANT_CONVERSION = YES; 385 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 386 | CLANG_WARN_EMPTY_BODY = YES; 387 | CLANG_WARN_ENUM_CONVERSION = YES; 388 | CLANG_WARN_INT_CONVERSION = YES; 389 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 390 | CLANG_WARN_UNREACHABLE_CODE = YES; 391 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 392 | CODE_SIGN_IDENTITY = "-"; 393 | COPY_PHASE_STRIP = NO; 394 | ENABLE_STRICT_OBJC_MSGSEND = YES; 395 | GCC_C_LANGUAGE_STANDARD = gnu99; 396 | GCC_DYNAMIC_NO_PIC = NO; 397 | GCC_OPTIMIZATION_LEVEL = 0; 398 | GCC_PREPROCESSOR_DEFINITIONS = ( 399 | "DEBUG=1", 400 | "$(inherited)", 401 | ); 402 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 403 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 404 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 405 | GCC_WARN_UNDECLARED_SELECTOR = YES; 406 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 407 | GCC_WARN_UNUSED_FUNCTION = YES; 408 | GCC_WARN_UNUSED_VARIABLE = YES; 409 | MACOSX_DEPLOYMENT_TARGET = 10.9; 410 | MTL_ENABLE_DEBUG_INFO = YES; 411 | ONLY_ACTIVE_ARCH = YES; 412 | SDKROOT = macosx; 413 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 414 | }; 415 | name = Debug; 416 | }; 417 | 28485C681A84AFF100667ECB /* Release */ = { 418 | isa = XCBuildConfiguration; 419 | buildSettings = { 420 | ALWAYS_SEARCH_USER_PATHS = NO; 421 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 422 | CLANG_CXX_LIBRARY = "libc++"; 423 | CLANG_ENABLE_MODULES = YES; 424 | CLANG_ENABLE_OBJC_ARC = YES; 425 | CLANG_WARN_BOOL_CONVERSION = YES; 426 | CLANG_WARN_CONSTANT_CONVERSION = YES; 427 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 428 | CLANG_WARN_EMPTY_BODY = YES; 429 | CLANG_WARN_ENUM_CONVERSION = YES; 430 | CLANG_WARN_INT_CONVERSION = YES; 431 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 432 | CLANG_WARN_UNREACHABLE_CODE = YES; 433 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 434 | CODE_SIGN_IDENTITY = "-"; 435 | COPY_PHASE_STRIP = YES; 436 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 437 | ENABLE_NS_ASSERTIONS = NO; 438 | ENABLE_STRICT_OBJC_MSGSEND = YES; 439 | GCC_C_LANGUAGE_STANDARD = gnu99; 440 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 441 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 442 | GCC_WARN_UNDECLARED_SELECTOR = YES; 443 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 444 | GCC_WARN_UNUSED_FUNCTION = YES; 445 | GCC_WARN_UNUSED_VARIABLE = YES; 446 | MACOSX_DEPLOYMENT_TARGET = 10.9; 447 | MTL_ENABLE_DEBUG_INFO = NO; 448 | SDKROOT = macosx; 449 | }; 450 | name = Release; 451 | }; 452 | 28485C6A1A84AFF100667ECB /* Debug */ = { 453 | isa = XCBuildConfiguration; 454 | buildSettings = { 455 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 456 | CLANG_ENABLE_MODULES = YES; 457 | COMBINE_HIDPI_IMAGES = YES; 458 | INFOPLIST_FILE = KeyCast/Info.plist; 459 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 460 | PRODUCT_NAME = "$(TARGET_NAME)"; 461 | SWIFT_OBJC_BRIDGING_HEADER = "KeyCast/KeyCast-Bridging-Header.h"; 462 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 463 | }; 464 | name = Debug; 465 | }; 466 | 28485C6B1A84AFF100667ECB /* Release */ = { 467 | isa = XCBuildConfiguration; 468 | buildSettings = { 469 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 470 | CLANG_ENABLE_MODULES = YES; 471 | COMBINE_HIDPI_IMAGES = YES; 472 | INFOPLIST_FILE = KeyCast/Info.plist; 473 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 474 | PRODUCT_NAME = "$(TARGET_NAME)"; 475 | SWIFT_OBJC_BRIDGING_HEADER = "KeyCast/KeyCast-Bridging-Header.h"; 476 | }; 477 | name = Release; 478 | }; 479 | 28485C6D1A84AFF100667ECB /* Debug */ = { 480 | isa = XCBuildConfiguration; 481 | buildSettings = { 482 | BUNDLE_LOADER = "$(TEST_HOST)"; 483 | COMBINE_HIDPI_IMAGES = YES; 484 | FRAMEWORK_SEARCH_PATHS = ( 485 | "$(DEVELOPER_FRAMEWORKS_DIR)", 486 | "$(inherited)", 487 | ); 488 | GCC_PREPROCESSOR_DEFINITIONS = ( 489 | "DEBUG=1", 490 | "$(inherited)", 491 | ); 492 | INFOPLIST_FILE = KeyCastTests/Info.plist; 493 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 494 | PRODUCT_NAME = "$(TARGET_NAME)"; 495 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/KeyCast.app/Contents/MacOS/KeyCast"; 496 | }; 497 | name = Debug; 498 | }; 499 | 28485C6E1A84AFF100667ECB /* Release */ = { 500 | isa = XCBuildConfiguration; 501 | buildSettings = { 502 | BUNDLE_LOADER = "$(TEST_HOST)"; 503 | COMBINE_HIDPI_IMAGES = YES; 504 | FRAMEWORK_SEARCH_PATHS = ( 505 | "$(DEVELOPER_FRAMEWORKS_DIR)", 506 | "$(inherited)", 507 | ); 508 | INFOPLIST_FILE = KeyCastTests/Info.plist; 509 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 510 | PRODUCT_NAME = "$(TARGET_NAME)"; 511 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/KeyCast.app/Contents/MacOS/KeyCast"; 512 | }; 513 | name = Release; 514 | }; 515 | /* End XCBuildConfiguration section */ 516 | 517 | /* Begin XCConfigurationList section */ 518 | 28485C4A1A84AFF000667ECB /* Build configuration list for PBXProject "KeyCast" */ = { 519 | isa = XCConfigurationList; 520 | buildConfigurations = ( 521 | 28485C671A84AFF100667ECB /* Debug */, 522 | 28485C681A84AFF100667ECB /* Release */, 523 | ); 524 | defaultConfigurationIsVisible = 0; 525 | defaultConfigurationName = Release; 526 | }; 527 | 28485C691A84AFF100667ECB /* Build configuration list for PBXNativeTarget "KeyCast" */ = { 528 | isa = XCConfigurationList; 529 | buildConfigurations = ( 530 | 28485C6A1A84AFF100667ECB /* Debug */, 531 | 28485C6B1A84AFF100667ECB /* Release */, 532 | ); 533 | defaultConfigurationIsVisible = 0; 534 | defaultConfigurationName = Release; 535 | }; 536 | 28485C6C1A84AFF100667ECB /* Build configuration list for PBXNativeTarget "KeyCastTests" */ = { 537 | isa = XCConfigurationList; 538 | buildConfigurations = ( 539 | 28485C6D1A84AFF100667ECB /* Debug */, 540 | 28485C6E1A84AFF100667ECB /* Release */, 541 | ); 542 | defaultConfigurationIsVisible = 0; 543 | defaultConfigurationName = Release; 544 | }; 545 | /* End XCConfigurationList section */ 546 | }; 547 | rootObject = 28485C471A84AFF000667ECB /* Project object */; 548 | } 549 | -------------------------------------------------------------------------------- /KeyCast/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | Default 583 | 584 | 585 | 586 | 587 | 588 | 589 | Left to Right 590 | 591 | 592 | 593 | 594 | 595 | 596 | Right to Left 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | Default 608 | 609 | 610 | 611 | 612 | 613 | 614 | Left to Right 615 | 616 | 617 | 618 | 619 | 620 | 621 | Right to Left 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 858 | 865 | 872 | 873 | 874 | 875 | 876 | 877 | 878 | 879 | 880 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | 901 | 902 | 903 | 904 | 905 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 921 | 922 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | 930 | 931 | 932 | 933 | 934 | 935 | 936 | 937 | 938 | 939 | 940 | 941 | 942 | 943 | 944 | 945 | 946 | 947 | 948 | 949 | 950 | 951 | 952 | 953 | 954 | 955 | 956 | 957 | 958 | 959 | 960 | 961 | 962 | 963 | 964 | 965 | 966 | 967 | 968 | 969 | 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | 985 | 986 | 987 | 988 | 989 | 990 | 991 | 992 | 993 | 994 | 995 | 996 | 997 | 998 | 999 | --------------------------------------------------------------------------------