Skip to content

Commit

Permalink
Merge branch 'devel'
Browse files Browse the repository at this point in the history
  • Loading branch information
pnoqable committed Jan 15, 2023
2 parents a34d812 + 2bca5d4 commit 081f93d
Show file tree
Hide file tree
Showing 8 changed files with 373 additions and 198 deletions.
24 changes: 20 additions & 4 deletions PinchBar.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
8459492227FA287800DBB447 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8459492127FA287800DBB447 /* AppDelegate.swift */; };
8459492427FA287C00DBB447 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8459492327FA287C00DBB447 /* Assets.xcassets */; };
846AF3CC2804F5FC00B3E06F /* Repository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846AF3CB2804F5FC00B3E06F /* Repository.swift */; };
84B832422966CAD10083FB73 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B832412966CAD10083FB73 /* StatusMenu.swift */; };
84C256C3294228DC0087E286 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C256C2294228DC0087E286 /* Settings.swift */; };
84CAEABE2929B55600407DD3 /* EventMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAEABD2929B55600407DD3 /* EventMapping.swift */; };
84CAEAC0292A75E000407DD3 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAEABF292A75E000407DD3 /* Extensions.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand All @@ -21,6 +25,10 @@
8459492E27FA28A100DBB447 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
846AF3CB2804F5FC00B3E06F /* Repository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Repository.swift; sourceTree = "<group>"; };
84A304C828C686C70087F4F6 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
84B832412966CAD10083FB73 /* StatusMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMenu.swift; sourceTree = "<group>"; };
84C256C2294228DC0087E286 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
84CAEABD2929B55600407DD3 /* EventMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMapping.swift; sourceTree = "<group>"; };
84CAEABF292A75E000407DD3 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
84D7DA58290085BF0013E257 /* LICENSE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -58,9 +66,13 @@
children = (
8459492127FA287800DBB447 /* AppDelegate.swift */,
8459492327FA287C00DBB447 /* Assets.xcassets */,
84CAEABD2929B55600407DD3 /* EventMapping.swift */,
8403F28B27FF191600CAAC3C /* EventTap.swift */,
84CAEABF292A75E000407DD3 /* Extensions.swift */,
8459492E27FA28A100DBB447 /* Info.plist */,
846AF3CB2804F5FC00B3E06F /* Repository.swift */,
84C256C2294228DC0087E286 /* Settings.swift */,
84B832412966CAD10083FB73 /* StatusMenu.swift */,
);
path = PinchBar;
sourceTree = "<group>";
Expand Down Expand Up @@ -133,9 +145,13 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
84C256C3294228DC0087E286 /* Settings.swift in Sources */,
84CAEAC0292A75E000407DD3 /* Extensions.swift in Sources */,
846AF3CC2804F5FC00B3E06F /* Repository.swift in Sources */,
84B832422966CAD10083FB73 /* StatusMenu.swift in Sources */,
8403F28C27FF191600CAAC3C /* EventTap.swift in Sources */,
8459492227FA287800DBB447 /* AppDelegate.swift in Sources */,
84CAEABE2929B55600407DD3 /* EventMapping.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -271,7 +287,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = MW6A6MU64S;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
Expand All @@ -283,7 +299,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 0.1;
MARKETING_VERSION = 0.2;
PRODUCT_BUNDLE_IDENTIFIER = com.pnoqable.PinchBar;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand All @@ -300,7 +316,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = MW6A6MU64S;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
Expand All @@ -312,7 +328,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 0.1;
MARKETING_VERSION = 0.2;
PRODUCT_BUNDLE_IDENTIFIER = com.pnoqable.PinchBar;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down
102 changes: 27 additions & 75 deletions PinchBar/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,98 +1,50 @@
import Cocoa

@main class AppDelegate: NSObject, NSApplicationDelegate, EventTapDelegate {
var statusItem: NSStatusItem!
var menuItemPreferences: NSMenuItem!
var menuItemConfigure: NSMenuItem!
private let unknownApp: String = "unknown Application"

@main class AppDelegate: NSObject, NSApplicationDelegate {
var activeApp: String = unknownApp

let eventTap = EventTap()
let repository = Repository()
let settings = Settings()

let statusMenu = StatusMenu()

func applicationDidFinishLaunching(_ aNotification: Notification) {
NSLog("PinchBar \(repository.version), enabled for: \(eventTap.apps.keys)")

statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
statusItem.button?.image = NSImage(named: "StatusIcon")
statusItem.button?.toolTip = "PinchBar"
statusItem.behavior = .removalAllowed
statusItem.menu = NSMenu()
statusItem.menu?.autoenablesItems = false

let menuItemAbout = NSMenuItem()
menuItemAbout.title = "About PinchBar " + repository.version
menuItemAbout.target = self
menuItemAbout.action = #selector(openGitHub)
statusItem.menu?.addItem(menuItemAbout)

let menuItemUpdate = NSMenuItem()
menuItemUpdate.title = "Check for Updates..."
menuItemUpdate.target = self
menuItemUpdate.action = #selector(checkForUpdates)
statusItem.menu?.addItem(menuItemUpdate)
NSLog("PinchBar \(repository.version), configured for: \(settings.appNames)")

statusItem.menu?.addItem(NSMenuItem.separator())
statusMenu.callWhenPresetSelected = { [weak self] p in self?.changePreset(to: p) }
statusMenu.create(repository: repository, settings: settings)

menuItemPreferences = NSMenuItem()
menuItemPreferences.title = "Enable Pinchbar in Accessibility"
menuItemPreferences.target = self
menuItemPreferences.action = #selector(accessibility)
statusItem.menu?.addItem(menuItemPreferences)

menuItemConfigure = NSMenuItem()
menuItemConfigure.title = "Enable for " + eventTap.currentApp
menuItemConfigure.target = self
menuItemConfigure.action = #selector(configure)
menuItemConfigure.isEnabled = false
statusItem.menu?.addItem(menuItemConfigure)

statusItem.menu?.addItem(NSMenuItem.separator())

let menuItemQuit = NSMenuItem()
menuItemQuit.title = "Quit"
menuItemQuit.target = NSApplication.shared
menuItemQuit.action = #selector(NSApplication.stop)
statusItem.menu?.addItem(menuItemQuit)

eventTap.delegate = self
eventTap.callWhenCreated = { [weak statusMenu] in statusMenu?.enableSubmenu() }
eventTap.start()

repository.openUpdateLink = openGitHub
repository.checkForUpdates(verbose: false)

NSWorkspace.shared.notificationCenter
.addObserver(self, selector: #selector(activeAppChanged),
name: NSWorkspace.didActivateApplicationNotification, object: nil)

activeAppChanged()
}

func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
statusItem.isVisible = true
statusMenu.statusItem.isVisible = true
return true
}

@objc func openGitHub() {
let url = "https://github.com/pnoqable/PinchBar"
NSWorkspace.shared.open(URL(string: url)!)
}

@objc func checkForUpdates() {
repository.checkForUpdates(verbose: true)
}

@objc func accessibility() {
let url = "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"
NSWorkspace.shared.open(URL(string: url)!)
@objc func activeAppChanged() {
activeApp = NSWorkspace.shared.frontmostApplication?.localizedName ?? unknownApp
changePreset(to: settings.appPresets[activeApp])
}

@objc func configure() {
eventTap.toggleApp()
}

func eventTapCreated(_: EventTap) {
menuItemPreferences.state = .on
menuItemPreferences.isEnabled = false
menuItemConfigure.isEnabled = true
}

func eventTapUpdated(_ eventTap: EventTap) {
statusItem.button?.appearsDisabled = !eventTap.isEnabled
menuItemConfigure.title = "Enable for " + eventTap.currentApp
menuItemConfigure.state = eventTap.isEnabled ? .on : .off
func changePreset(to newPreset: String?) {
settings.appPresets[activeApp] = newPreset
settings.save()

eventTap.preset = settings.preset(named: newPreset)
statusMenu.updateSubmenu(activeApp: activeApp, activePreset: newPreset)
}

static func main() {
Expand Down
71 changes: 71 additions & 0 deletions PinchBar/EventMapping.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import Cocoa

struct EventMapping: Codable {
enum Replacement: Codable {
case wheel
case keys(codeA: CGKeyCode, codeB: CGKeyCode)
}

var replaceWith: Replacement?
var flags: CGEventFlags
var sensivity: Double

func canTap(_ event: CGEvent) -> Bool { event.subType == .magnification }

private static var remainder: Double = 0 // subpixel residue of sent (integer) scroll events

func tap(_ event: CGEvent, proxy: CGEventTapProxy) -> Unmanaged<CGEvent>? {
assert(event.subType == .magnification)

// when event is not to be replaced, just apply flags and sensivity:
guard let replacement = replaceWith else {
event.flags = flags
event.magnification *= sensivity
return .passUnretained(event)
}

if event.phase == .began {
Self.remainder = 0
}

let magnification = sensivity * event.magnification + Self.remainder
let steps = round(magnification)
Self.remainder = magnification - steps

guard steps != 0 else { return nil }

switch replacement {
case .wheel:
let event = CGEvent(scrollWheelEvent2Source: nil, units: .pixel,
wheelCount: 1, wheel1: Int32(steps), wheel2: 0, wheel3: 0)!
event.flags = flags
return .passRetained(event)
case .keys(let codeA, let codeB):
let code = steps < 0 ? codeA : codeB
sendKey(code, down: true, proxy: proxy)
sendKey(code, down: false, proxy: proxy)
return nil
}
}

private func sendKey(_ code: CGKeyCode, down: Bool, proxy: CGEventTapProxy) {
let event = CGEvent(keyboardEventSource: nil, virtualKey: code, keyDown: down)!
event.flags = flags
event.tapPostEvent(proxy)
}
}

extension EventMapping {
static func pinchToPinch(flags: CGEventFlags = .maskNoFlags, sensivity: Double = 1) -> Self {
Self(replaceWith: nil, flags: flags, sensivity: sensivity)
}

static func pinchToWheel(flags: CGEventFlags = .maskCommand, sensivity: Double = 200) -> Self {
Self(replaceWith: .wheel, flags: flags, sensivity: sensivity)
}

static func pinchToKeys(flags: CGEventFlags = .maskCommand, sensivity: Double = 5,
codeA: CGKeyCode = 44, codeB: CGKeyCode = 30) -> Self {
Self(replaceWith: .keys(codeA: codeA, codeB: codeB), flags: flags, sensivity: sensivity)
}
}
83 changes: 15 additions & 68 deletions PinchBar/EventTap.swift
Original file line number Diff line number Diff line change
@@ -1,36 +1,16 @@
import Cocoa

protocol EventTapDelegate: AnyObject {
func eventTapCreated(_ eventTap: EventTap)
func eventTapUpdated(_ eventTap: EventTap)
}

class EventTap {
static let unknownApp: String = "unknown Application"

private var eventTap: CFMachPort?

private(set) var apps: [String:Bool] = ["Cubase":true]
private(set) var currentApp: String = EventTap.unknownApp
private(set) var isEnabled: Bool = false
var preset: Settings.Preset?

weak var delegate: EventTapDelegate?

init() {
UserDefaults.standard.register(defaults: ["apps":apps])
if let apps = UserDefaults.standard.object(forKey: "apps") as? [String:Bool] {
self.apps = apps
}

NSWorkspace.shared.notificationCenter
.addObserver(self, selector: #selector(updateTap),
name: NSWorkspace.didActivateApplicationNotification, object: nil)
}
var callWhenCreated: Callback?

func start() {
let adapter: CGEventTapCallBack = { _, type, event, userInfo in
let adapter: CGEventTapCallBack = { proxy, type, event, userInfo in
let mySelf = Unmanaged<EventTap>.fromOpaque(userInfo!).takeUnretainedValue()
return mySelf.tap(type: type, event: event)
return mySelf.tap(proxy: proxy, type: type, event: event)
}

let mySelf = Unmanaged.passUnretained(self).toOpaque()
Expand All @@ -42,55 +22,22 @@ class EventTap {
callback: adapter,
userInfo: mySelf)

if let eventTap = eventTap {
let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
delegate?.eventTapCreated(self)
} else {
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: start)
}

updateTap()
}

func toggleApp() {
if apps.keys.contains(currentApp) {
apps.removeValue(forKey: currentApp)
} else {
apps[currentApp] = true
}

UserDefaults.standard.set(apps, forKey: "apps")

updateTap()
}

@objc private func updateTap() {
currentApp = NSWorkspace.shared.frontmostApplication?.localizedName ?? EventTap.unknownApp
isEnabled = apps.keys.contains(currentApp)

if let eventTap = eventTap {
if isEnabled != CGEvent.tapIsEnabled(tap: eventTap) {
CGEvent.tapEnable(tap: eventTap, enable: isEnabled)
}
} else {
isEnabled = false
guard let eventTap else {
return DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: start)
}

delegate?.eventTapUpdated(self)
let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
callWhenCreated?()
}

private func tap(type: CGEventType, event: CGEvent) -> Unmanaged<CGEvent>? {
if type == .tapDisabledByTimeout || type == .tapDisabledByUserInput {
updateTap()
} else if let nsEvent = NSEvent.init(cgEvent: event), nsEvent.type == .magnify {
let amount = Int32(round(nsEvent.deltaZ))
let newEvent = CGEvent.init(scrollWheelEvent2Source: nil, units: .pixel,
wheelCount: 1, wheel1: amount, wheel2: 0, wheel3: 0)!
newEvent.flags = .maskCommand
return Unmanaged.passRetained(newEvent)
private func tap(proxy: CGEventTapProxy, type: CGEventType, event: CGEvent) -> Unmanaged<CGEvent>? {
if type == .tapDisabledByTimeout {
CGEvent.tapEnable(tap: eventTap!, enable: true)
} else if let mapping = preset?[event.flags.purified], mapping.canTap(event) {
return mapping.tap(event, proxy: proxy)
}

return Unmanaged.passUnretained(event)
return .passUnretained(event)
}
}
Loading

0 comments on commit 081f93d

Please sign in to comment.