Skip to content

Commit

Permalink
capsLock 깜빡임 방지하고 long press로 전환 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
youknowone committed Jan 18, 2019
1 parent 3d178e1 commit 8b91c3b
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 64 deletions.
24 changes: 10 additions & 14 deletions Gureum.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
3881869121EB2DC0004B7FDB /* Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 381DB19A21678B74005A37B9 /* Debug.swift */; };
3881869221EB2DC0004B7FDB /* InputMethodServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 871B807B2153DDFA0013EE69 /* InputMethodServer.swift */; };
3881869321EB2DC0004B7FDB /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38C2AE4C208795E700FE211A /* Configuration.swift */; };
3881869721EB2E0F004B7FDB /* IOKitUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388534FC213A740600885C87 /* IOKitUtility.swift */; };
3881869721EB2E0F004B7FDB /* IOKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388534FC213A740600885C87 /* IOKit.swift */; };
3881869D21EB3055004B7FDB /* EmoticonComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3F007652160B09700501606 /* EmoticonComposer.swift */; };
3881869E21EB3055004B7FDB /* HangulComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D3E42A21213E9700751191 /* HangulComposer.swift */; };
3881869F21EB3055004B7FDB /* HanjaComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38162D7B140F64B70077AA2D /* HanjaComposer.swift */; };
Expand All @@ -89,7 +89,7 @@
388186BF21EB9E01004B7FDB /* GureumCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3881867A21EB2D7D004B7FDB /* GureumCore.framework */; };
388186C021EB9E11004B7FDB /* Pods_OSXTestApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77EC0BFFBE505587BF06F373 /* Pods_OSXTestApp.framework */; };
388186D321EE339B004B7FDB /* Hangul.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 389A387D1423477F00A2ED88 /* Hangul.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
388534FD213A740600885C87 /* IOKitUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388534FC213A740600885C87 /* IOKitUtility.swift */; };
388534FD213A740600885C87 /* IOKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388534FC213A740600885C87 /* IOKit.swift */; };
388E2A611469249700ADBDA5 /* Hangul.strings in Resources */ = {isa = PBXBuildFile; fileRef = 388E2A601469249700ADBDA5 /* Hangul.strings */; };
38946A6B19F4F3FD00920E09 /* InputMethodKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 38946A6A19F4F3FD00920E09 /* InputMethodKit.framework */; };
38BFE80218B45419004B2B2E /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 38BFE80018B45419004B2B2E /* InfoPlist.strings */; };
Expand Down Expand Up @@ -326,7 +326,7 @@
3881867D21EB2D7D004B7FDB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
388186A721EB34FA004B7FDB /* TISInputSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TISInputSource.swift; sourceTree = "<group>"; };
388186B321EB4E4C004B7FDB /* MockApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockApp.swift; sourceTree = "<group>"; };
388534FC213A740600885C87 /* IOKitUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOKitUtility.swift; sourceTree = "<group>"; };
388534FC213A740600885C87 /* IOKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOKit.swift; sourceTree = "<group>"; };
38863C63140E64EB00A8ED76 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
38863C66140E64EB00A8ED76 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
38863C67140E64EB00A8ED76 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
Expand Down Expand Up @@ -475,7 +475,6 @@
3831983B21DB744900E20D78 /* UpdateManager.swift */,
38475AEB14129BAA0062100D /* Icons */,
38162DF7141263280077AA2D /* Supporting Files */,
388534F9213A731E00885C87 /* IOKit */,
38098F09215A3DB30001F159 /* Assets.xcassets */,
);
path = OSX;
Expand Down Expand Up @@ -608,19 +607,12 @@
38162D7B140F64B70077AA2D /* HanjaComposer.swift */,
38C75C8B1F153A74004BE02A /* RomanComposer.swift */,
E54B8381214299EB00527218 /* GureumComposer.swift */,
388534FC213A740600885C87 /* IOKit.swift */,
3835B4E21F5DAA7100896BEC /* data */,
);
path = OSXCore;
sourceTree = "<group>";
};
388534F9213A731E00885C87 /* IOKit */ = {
isa = PBXGroup;
children = (
388534FC213A740600885C87 /* IOKitUtility.swift */,
);
name = IOKit;
sourceTree = "<group>";
};
38863C4E140E62D100A8ED76 = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -887,6 +879,7 @@
};
3881867921EB2D7D004B7FDB = {
CreatedOnToolsVersion = 10.1;
LastSwiftMigration = 1010;
ProvisioningStyle = Automatic;
};
38BFE7F918B45419004B2B2E = {
Expand Down Expand Up @@ -1229,7 +1222,7 @@
3831983C21DB744900E20D78 /* UpdateManager.swift in Sources */,
388186AC21EB3FDD004B7FDB /* Debug.swift in Sources */,
E5ED2DE4213030B700BD9B13 /* GureumMenu.swift in Sources */,
388534FD213A740600885C87 /* IOKitUtility.swift in Sources */,
388534FD213A740600885C87 /* IOKit.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -1269,7 +1262,7 @@
3881869121EB2DC0004B7FDB /* Debug.swift in Sources */,
388186A121EB3055004B7FDB /* GureumComposer.swift in Sources */,
3881868921EB2D8D004B7FDB /* InputReceiver.swift in Sources */,
3881869721EB2E0F004B7FDB /* IOKitUtility.swift in Sources */,
3881869721EB2E0F004B7FDB /* IOKit.swift in Sources */,
388186A821EB34FA004B7FDB /* TISInputSource.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -1492,6 +1485,7 @@
baseConfigurationReference = A7DFB5991543F2531F28F4A5 /* Pods-OSXCore.debug.xcconfig */;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = OSX/Gureum.entitlements;
CODE_SIGN_IDENTITY = "-";
COMBINE_HIDPI_IMAGES = YES;
Expand All @@ -1517,6 +1511,7 @@
PRODUCT_NAME = GureumCore;
SDKROOT = macosx;
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
Expand All @@ -1528,6 +1523,7 @@
baseConfigurationReference = 561064BBDB0084288F87B8A1 /* Pods-OSXCore.release.xcconfig */;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = OSX/Gureum.entitlements;
CODE_SIGN_IDENTITY = "-";
COMBINE_HIDPI_IMAGES = YES;
Expand Down
1 change: 1 addition & 0 deletions GureumTests/GureumTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

@testable import GureumCore
import Hangul
import InputMethodKit
import XCTest

class GureumTests: XCTestCase {
Expand Down
2 changes: 0 additions & 2 deletions OSXCore/GureumComposer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,6 @@ class GureumComposer: DelegatedComposer {
func filterCommand(key keyCode: Int, modifiers flags: NSEvent.ModifierFlags, client _: Any) -> InputEvent? {
let configuration = Configuration.shared
let inputModifier = flags.intersection(NSEvent.ModifierFlags.deviceIndependentFlagsMask).intersection(NSEvent.ModifierFlags(rawValue: ~NSEvent.ModifierFlags.capsLock.rawValue))
var need_exchange = false
var need_candidtes = false
// if (string == nil) {
// NSUInteger modifierKey = flags & 0xff;
// if (self->lastModifier != 0 && modifierKey == 0) {
Expand Down
4 changes: 3 additions & 1 deletion OSX/IOKitUtility.swift → OSXCore/IOKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import Foundation
import IOKit
import IOKit.hid
import IOKit.hidsystem

class IOKitError: Error {
init() {}
Expand Down Expand Up @@ -50,7 +51,8 @@ public extension io_connect_t {
return state
}
set {
_ = id.setModifierLockState(kIOHIDCapsLockState, state: newValue)
let kr = id.setModifierLockState(kIOHIDCapsLockState, state: newValue)
// NSLog("set capslock state: \(newValue) \(kr == KERN_SUCCESS)")
}
}
}
Expand Down
75 changes: 55 additions & 20 deletions OSXCore/InputController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import InputMethodKit

let DEBUG_LOGGING = false
let DEBUG_INPUTCONTROLLER = false
let DEBUG_SPYING = true

/*!
@enum
Expand All @@ -34,6 +35,7 @@ struct InputResult: Equatable {

enum ChangeLayout {
case toggle
case toggleByCapsLock
case hangul
case roman
case hanja
Expand All @@ -46,19 +48,41 @@ enum InputEvent {
@objc(GureumInputController)
public class InputController: IMKInputController {
var receiver: InputReceiver!
var lastFlags: NSEvent.ModifierFlags = NSEvent.ModifierFlags(rawValue: 0)

override init!(server: IMKServer, delegate: Any!, client inputClient: Any) {
super.init(server: server, delegate: delegate, client: inputClient)
guard let inputClient = inputClient as? (IMKTextInput & IMKUnicodeTextInput) else {
return nil
}
dlog(DEBUG_INPUTCONTROLLER, "**** NEW INPUT CONTROLLER INIT **** WITH SERVER: \(server) / DELEGATE: \(delegate ?? "nil") / CLIENT: \(inputClient) \(inputClient.bundleIdentifier() ?? "nil")")
dlog(DEBUG_INPUTCONTROLLER, "**** NEW INPUT CONTROLLER INIT **** WITH SERVER: \(server) / DELEGATE: \(String(describing: delegate)) / CLIENT: \(inputClient) \(inputClient.bundleIdentifier() ?? "nil")")
assert(InputMethodServer.shared.server === server)
receiver = InputReceiver(server: server, delegate: delegate, client: inputClient, controller: self)
}

override init() {
super.init()
}

#if DEBUG
public override func responds(to aSelector: Selector) -> Bool {
let r = super.responds(to: aSelector)
dlog(DEBUG_SPYING, "controller responds to: \(aSelector) \(r)")
return r
}

public override func modes(_ sender: Any!) -> [AnyHashable: Any]! {
let modes = super.modes(sender)
dlog(DEBUG_SPYING, "modes: \(modes)")
return modes
}

public override func value(forTag tag: Int, client _: Any!) -> Any! {
let v = super.value(forTag: tag, client: client)
dlog(DEBUG_SPYING, "value: \(v) for tag: \(tag)")
return v
}
#endif
}

extension InputController {
Expand All @@ -73,33 +97,42 @@ public extension InputController { // IMKServerInputHandleEvent
// Receiving Events Directly from the Text Services Manager

public override func handle(_ event: NSEvent, client sender: Any) -> Bool {
// dlog(DEBUG_INPUTCONTROLLER, "event: \(event)")
// sender is (IMKInputText & IMKUnicodeInputText & IMTSMSupport)
let imkCandidtes = InputMethodServer.shared.candidates
let keys = imkCandidtes.selectionKeys() as! [NSNumber]
if imkCandidtes.isVisible(), keys.contains(NSNumber(value: event.keyCode)) {
if event.type == .keyDown, imkCandidtes.isVisible(), keys.contains(NSNumber(value: event.keyCode)) {
imkCandidtes.interpretKeyEvents([event])
return true
}
if event.type == .keyDown {
switch event.type {
case .keyDown:
dlog(DEBUG_INPUTCONTROLLER, "** InputController KEYDOWN -handleEvent:client: with event: %@ / key: %d / modifier: %lu / chars: %@ / chars ignoreMod: %@ / client: %@", event, event.keyCode, event.modifierFlags.rawValue, event.characters ?? "(empty)", event.charactersIgnoringModifiers ?? "(empty)", client()!.bundleIdentifier())
let result = receiver.input(text: event.characters, key: Int(event.keyCode), modifiers: event.modifierFlags, client: sender)
dlog(DEBUG_LOGGING, "LOGGING::PROCESSED::\(result)")
return result.processed
} else if event.type == .flagsChanged {
var modifierFlags = event.modifierFlags
if InputMethodServer.shared.io.testAndClearCapsLockState() {
dlog(DEBUG_IOKIT_EVENT, "controller detected capslock")
modifierFlags.formUnion(.capsLock)

dlog(DEBUG_IOKIT_EVENT, "modifierFlags by IOKit: %lx", modifierFlags.rawValue)
// dlog(DEBUG_INPUTCONTROLLER, @"** InputController FLAGCHANGED -handleEvent:client: with event: %@ / key: %d / modifier: %lu / chars: %@ / chars ignoreMod: %@ / client: %@", event, -1, modifierFlags, nil, nil, [[self client] bundleIdentifier])
receiver.input(event: .changeLayout(.toggle), client: sender)
return false
case .flagsChanged:
// dlog(DEBUG_INPUTCONTROLLER, @"** InputController FLAGCHANGED -handleEvent:client: with event: %@ / key: %d / modifier: %lu / chars: %@ / chars ignoreMod: %@ / client: %@", event, -1, modifierFlags, nil, nil, [[self client] bundleIdentifier])
let changed = lastFlags.symmetricDifference(event.modifierFlags)
lastFlags = event.modifierFlags

if changed.contains(.capsLock), Configuration.shared.enableCapslockToToggleInputMode {
if InputMethodServer.shared.io.testAndClearCapsLockState() {
dlog(DEBUG_IOKIT_EVENT, "controller detected capslock")
let result = receiver.input(event: .changeLayout(.toggleByCapsLock), client: sender)
return false
} else {
(sender as! IMKTextInput).selectMode(receiver.composer.inputMode)
}
}

dlog(DEBUG_LOGGING, "LOGGING::UNHANDLED::%@/%@", event, sender as! NSObject)
dlog(DEBUG_INPUTCONTROLLER, "** InputController -handleEvent:client: with event: %@ / sender: %@", event, sender as! NSObject)
return false
case .leftMouseDown, .leftMouseUp, .leftMouseDragged, .rightMouseDown, .rightMouseUp, .rightMouseDragged:
dlog(false, "mouse event: \(event)")
default:
dlog(DEBUG_SPYING, "unhandled event: \(event)")
}
return false
}
Expand Down Expand Up @@ -131,21 +164,23 @@ public extension InputController { // IMKServerInputHandleEvent
public extension InputController { // IMKStateSetting
//! @brief 마우스 이벤트를 잡을 수 있게 한다.
override func recognizedEvents(_ sender: Any!) -> Int {
return receiver.recognizedEvents(sender)
return Int(receiver.recognizedEvents(sender).rawValue)
}

//! @brief 자판 전환을 감지한다.
override func setValue(_ value: Any, forTag tag: Int, client sender: Any) {
receiver.setValue(value, forTag: tag, client: sender, controller: self)
}

override func activateServer(_: Any!) {
dlog(DEBUG_INPUTCONTROLLER, "server activated")
override func activateServer(_ sender: Any!) {
dlog(true, "server activated")
super.activateServer(sender)
}

override func deactivateServer(_ sender: Any!) {
dlog(DEBUG_INPUTCONTROLLER, "server deactivated")
receiver.commitComposition(sender)
dlog(true, "server deactivating")
receiver.commitCompositionEvent(sender)
super.deactivateServer(sender)
}
}

Expand All @@ -172,7 +207,7 @@ public extension InputController { // IMKServerInput
override func commitComposition(_ sender: Any!) {
dlog(DEBUG_LOGGING, "LOGGING::EVENT::COMMIT-RAW?")
_ = receiver.commitCompositionEvent(sender)
// [super commitComposition:sender]
super.commitComposition(sender)
}

override func updateComposition() {
Expand Down Expand Up @@ -319,7 +354,7 @@ public extension InputController { // IMKServerInput
public extension MockInputController { // IMKStateSetting
//! @brief 마우스 이벤트를 잡을 수 있게 한다.
public override func recognizedEvents(_ sender: Any!) -> Int {
return receiver.recognizedEvents(sender)
return Int(receiver.recognizedEvents(sender).rawValue)
}

//! @brief 자판 전환을 감지한다.
Expand Down
26 changes: 19 additions & 7 deletions OSXCore/InputMethodServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class IOKitty {
let connect: IOConnect
let manager: IOHIDManager
private var defaultCapsLockState: Bool = false
private var capsLockPressed: Bool = false
var capsLockDate: Date?

init?() {
guard let _service = try? IOService(name: kIOHIDSystemClass) else {
Expand All @@ -62,6 +62,7 @@ class IOKitty {

service = _service
connect = _connect
defaultCapsLockState = connect.capsLockState

manager = IOHIDManager.create()
manager.setDeviceMatching(page: kHIDPage_GenericDesktop, usage: kHIDUsage_GD_Keyboard)
Expand All @@ -74,17 +75,29 @@ class IOKitty {
manager.registerInputValueCallback({
inContext, _, _, value in
guard let inContext = inContext else {
dlog(DEBUG_IOKIT_EVENT, "IOKit callback inContext is nil")
dlog(true, "IOKit callback inContext is nil - impossible")
return
}

let pressed = value.integerValue > 0
dlog(DEBUG_IOKIT_EVENT, "caps lock pressed: \(pressed)")
let _self = inContext.assumingMemoryBound(to: IOKitty.self).pointee
if pressed {
_self.capsLockPressed = true
_self.capsLockDate = Date()
dlog(DEBUG_IOKIT_EVENT, "caps lock pressed set in context")
} else {
var interval = 0.0
if let capsLockDate = _self.capsLockDate {
interval = Date().timeIntervalSince(capsLockDate)
}
if _self.defaultCapsLockState || interval >= 0.5 {
_self.defaultCapsLockState = !_self.defaultCapsLockState
} else {
_self.connect.capsLockState = _self.defaultCapsLockState
}
_self.capsLockDate = nil
}
_self.connect.capsLockState = _self.defaultCapsLockState
// NSEvent.otherEvent(with: .applicationDefined, location: .zero, modifierFlags: .capsLock, timestamp: 0, windowNumber: 0, context: nil, subtype: 0, data1: 0, data2: 0)!
}, context: _self)
})
manager.schedule(runloop: .current, mode: .default)
Expand All @@ -102,9 +115,8 @@ class IOKitty {
}

func testAndClearCapsLockState() -> Bool {
let r = capsLockPressed
capsLockPressed = false
connect.capsLockState = defaultCapsLockState
let r = capsLockDate != nil
// capsLockDate = nil
return r
}
}
Expand Down
Loading

0 comments on commit 8b91c3b

Please sign in to comment.