From ac8a05d2e5f9fa0484ceab7fa14541424fefa2da Mon Sep 17 00:00:00 2001 From: ad Date: Sun, 15 Apr 2018 22:55:20 +0300 Subject: [PATCH 1/7] + show running apps (like dock) --- MTMR.xcodeproj/project.pbxproj | 10 + MTMR/AppScrubberTouchBarItem.swift | 265 +++++++++++++++++++++ MTMR/AppleScriptTouchBarItem.swift | 2 +- MTMR/CBridge/DeprecatedCarbonAPI.c | 45 ++++ MTMR/CBridge/DeprecatedCarbonAPI.h | 27 +++ MTMR/CBridge/TouchBarPrivateApi-Bridging.h | 1 + MTMR/CustomButtonTouchBarItem.swift | 1 + MTMR/ItemsParsing.swift | 7 + MTMR/TouchBarController.swift | 4 + 9 files changed, 361 insertions(+), 1 deletion(-) create mode 100644 MTMR/AppScrubberTouchBarItem.swift create mode 100644 MTMR/CBridge/DeprecatedCarbonAPI.c create mode 100644 MTMR/CBridge/DeprecatedCarbonAPI.h diff --git a/MTMR.xcodeproj/project.pbxproj b/MTMR.xcodeproj/project.pbxproj index fac7ea6..bd54938 100644 --- a/MTMR.xcodeproj/project.pbxproj +++ b/MTMR.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 36C2ECE0207CB1B0003CDA33 /* defaultPreset.json in Resources */ = {isa = PBXBuildFile; fileRef = 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */; }; 6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */; }; 6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */; }; + 6042B6A72083E03A00C525C8 /* AppScrubberTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */; }; + 6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */ = {isa = PBXBuildFile; fileRef = 6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */; }; B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0008E542080286C003AD4DD /* SupportHelpers.swift */; }; B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D621205E03F5006E6B86 /* TouchBarController.swift */; }; B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */; }; @@ -60,6 +62,9 @@ 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = defaultPreset.json; sourceTree = ""; }; 6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrightnessViewController.swift; sourceTree = ""; }; 6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VolumeViewController.swift; sourceTree = ""; }; + 6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScrubberTouchBarItem.swift; sourceTree = ""; }; + 6042B6A82083E1F500C525C8 /* DeprecatedCarbonAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeprecatedCarbonAPI.h; sourceTree = ""; }; + 6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = DeprecatedCarbonAPI.c; sourceTree = ""; }; B0008E542080286C003AD4DD /* SupportHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportHelpers.swift; sourceTree = ""; }; B059D621205E03F5006E6B86 /* TouchBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchBarController.swift; sourceTree = ""; }; B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButtonTouchBarItem.swift; sourceTree = ""; }; @@ -163,6 +168,7 @@ 6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */, 6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */, 368EDDE620812A1D00E10953 /* ScrollViewItem.swift */, + 6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */, ); path = MTMR; sourceTree = ""; @@ -200,6 +206,8 @@ B059D62A205F0E7D006E6B86 /* TouchBarPrivateApi-Bridging.h */, B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */, B0F8771B207AC92700D6E430 /* TouchBarSupport.h */, + 6042B6A82083E1F500C525C8 /* DeprecatedCarbonAPI.h */, + 6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */, ); path = CBridge; sourceTree = ""; @@ -324,9 +332,11 @@ 36C2ECD9207B74B4003CDA33 /* AppleScriptTouchBarItem.swift in Sources */, B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */, B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */, + 6042B6A72083E03A00C525C8 /* AppScrubberTouchBarItem.swift in Sources */, B082B253205C7D8000BC04DC /* AppDelegate.swift in Sources */, B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */, 6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */, + 6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */, B09EB1E4207C082000D5C1E0 /* HapticFeedback.swift in Sources */, 36C2ECDB207C3FE7003CDA33 /* ItemsParsing.swift in Sources */, B0A7E9AA205D6AA400EEF070 /* KeyPress.swift in Sources */, diff --git a/MTMR/AppScrubberTouchBarItem.swift b/MTMR/AppScrubberTouchBarItem.swift new file mode 100644 index 0000000..cd24c4c --- /dev/null +++ b/MTMR/AppScrubberTouchBarItem.swift @@ -0,0 +1,265 @@ +// +// AppScrubberTouchBarItem.swift +// +// This file is part of TouchDock +// Copyright (C) 2017 Xander Deng +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +import Cocoa + +let activateKeyIndex = "ActivateKeyIndex" +let appScrubberOrderIndex = "AppScrubberOrderIndex" +let appScrubberModeIndex = "AppScrubberModeIndex" + +class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrubberDataSource { + + var scrubber: NSScrubber! + + var runningApplications: [NSRunningApplication] = [] + + override init(identifier: NSTouchBarItem.Identifier) { + super.init(identifier: identifier) + + scrubber = NSScrubber().then { + $0.delegate = self + $0.dataSource = self + $0.mode = UserDefaults.standard.appScrubberMode + let layout = NSScrubberFlowLayout().then { + $0.itemSize = NSSize(width: 50, height: 30) + } + $0.scrubberLayout = layout + $0.selectionBackgroundStyle = .roundedBackground + } + view = scrubber + + scrubber.register(NSScrubberImageItemView.self, forItemIdentifier: .scrubberApplicationsItem) + + NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didLaunchApplicationNotification, object: nil) + NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didTerminateApplicationNotification, object: nil) + NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didActivateApplicationNotification, object: nil) + UserDefaults.standard.addObserver(self, forKeyPath: appScrubberOrderIndex, context: nil) + + updateRunningApplication(animated: false) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc func activeApplicationChanged(n: Notification) { + updateRunningApplication(animated: true) + } + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + updateRunningApplication(animated: false) + } + + func updateRunningApplication(animated: Bool) { + let isDockOrder = UserDefaults.standard.integer(forKey: appScrubberOrderIndex) != 0 + let newApplications = (isDockOrder ? dockPersistentApplications() : launchedApplications()).filter { + !$0.isTerminated && $0.bundleIdentifier != nil + } + let frontmost = NSWorkspace.shared.frontmostApplication + let index = newApplications.index { + $0.processIdentifier == frontmost?.processIdentifier + } + if animated { + scrubber.performSequentialBatchUpdates { +// print("-----update-----") + for (index, app) in newApplications.enumerated() { + while runningApplications[safe:index].map(newApplications.contains) == false { + scrubber.removeItems(at: [index]) + let r = runningApplications.remove(at: index) +// print("remove \(r.localizedName!) at \(index)") + } + if let oldIndex = runningApplications.index(of: app) { + guard oldIndex != index else { + return + } + scrubber.moveItem(at: oldIndex, to: index) + runningApplications.move(at: oldIndex, to: index) +// print("move \(app.localizedName!) at \(oldIndex) to \(index)") + } else { + scrubber.insertItems(at: [index]) + runningApplications.insert(app, at: index) +// print("insert \(app.localizedName!) to \(index)") + } + } + assert(runningApplications == newApplications) + } + } else { + runningApplications = newApplications + scrubber.reloadData() + } + scrubber.selectedIndex = index ?? 0 + } + + // MARK: - NSScrubberDataSource + + public func numberOfItems(for scrubber: NSScrubber) -> Int { + return runningApplications.count + } + + public func scrubber(_ scrubber: NSScrubber, viewForItemAt index: Int) -> NSScrubberItemView { + let item = scrubber.makeItem(withIdentifier: .scrubberApplicationsItem, owner: self) as? NSScrubberImageItemView ?? NSScrubberImageItemView() + item.imageView.imageScaling = .scaleProportionallyDown + if let icon = runningApplications[index].icon { + item.image = icon + } + return item + } + + public func didFinishInteracting(with scrubber: NSScrubber) { + guard scrubber.selectedIndex > 0 else { + return + } + runningApplications[scrubber.selectedIndex].activate(options: .activateIgnoringOtherApps) + } + +} + +// MARK: - Applications + +private func launchedApplications() -> [NSRunningApplication] { + let asns = _LSCopyApplicationArrayInFrontToBackOrder(~0)?.takeRetainedValue() + return (0.. [NSRunningApplication] { + let apps = NSWorkspace.shared.runningApplications.filter { + $0.activationPolicy == .regular + } + + guard let dockDefaults = UserDefaults(suiteName: "com.apple.dock"), + let persistentApps = dockDefaults.array(forKey: "persistent-apps") as [AnyObject]?, + let bundleIDs = persistentApps.flatMap({ $0.value(forKeyPath: "tile-data.bundle-identifier") }) as? [String] else { + return apps + } + + return apps.sorted { (lhs, rhs) in + if lhs.bundleIdentifier == "com.apple.finder" { + return true + } + if rhs.bundleIdentifier == "com.apple.finder" { + return false + } + switch ((bundleIDs.index(of: lhs.bundleIdentifier!)), bundleIDs.index(of: rhs.bundleIdentifier!)) { + case (nil, _): + return false; + case (_?, nil): + return true + case let (i1?, i2?): + return i1 < i2; + } + } +} + +public protocol Then {} + +extension Then where Self: Any { + + /// Makes it available to set properties with closures just after initializing and copying the value types. + /// + /// let frame = CGRect().with { + /// $0.origin.x = 10 + /// $0.size.width = 100 + /// } + public func with(_ block: (inout Self) throws -> Void) rethrows -> Self { + var copy = self + try block(©) + return copy + } + + /// Makes it available to execute something with closures. + /// + /// UserDefaults.standard.do { + /// $0.set("devxoul", forKey: "username") + /// $0.set("devxoul@gmail.com", forKey: "email") + /// $0.synchronize() + /// } + public func `do`(_ block: (Self) throws -> Void) rethrows { + try block(self) + } + +} + +extension Then where Self: AnyObject { + + /// Makes it available to set properties with closures just after initializing. + /// + /// let label = UILabel().then { + /// $0.textAlignment = .Center + /// $0.textColor = UIColor.blackColor() + /// $0.text = "Hello, World!" + /// } + public func then(_ block: (Self) throws -> Void) rethrows -> Self { + try block(self) + return self + } + +} + +extension NSObject: Then {} + +extension CGPoint: Then {} +extension CGRect: Then {} +extension CGSize: Then {} +extension CGVector: Then {} + +extension UserDefaults { + + var activateKey: NSEvent.ModifierFlags? { + let keys: [NSEvent.ModifierFlags?] = [.command, .option, .control, .shift, nil] + let index = integer(forKey: activateKeyIndex) + return keys[index] + } + + var appScrubberMode: NSScrubber.Mode { + let actions: [NSScrubber.Mode] = [.fixed, .free] + let index = integer(forKey: appScrubberModeIndex) + return actions[index] + } +} + +extension NSUserInterfaceItemIdentifier { + + static let scrubberApplicationsItem = NSUserInterfaceItemIdentifier("ScrubberApplicationsItemReuseIdentifier") +} + +extension RangeReplaceableCollection { + + mutating func move(at oldIndex: Self.Index, to newIndex: Self.Index) { + guard oldIndex != newIndex else { + return + } + let item = remove(at: oldIndex) + insert(item, at: newIndex) + } +} + +extension Collection { + + subscript(safe index: Self.Index) -> Self.Iterator.Element? { + guard index < endIndex else { + return nil + } + return self[index] + } +} diff --git a/MTMR/AppleScriptTouchBarItem.swift b/MTMR/AppleScriptTouchBarItem.swift index 1e71b04..c49914b 100644 --- a/MTMR/AppleScriptTouchBarItem.swift +++ b/MTMR/AppleScriptTouchBarItem.swift @@ -49,7 +49,7 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem { let output = script.executeAndReturnError(&error) if let error = error { print(error) - return "error" + return "" } return output.stringValue ?? "" } diff --git a/MTMR/CBridge/DeprecatedCarbonAPI.c b/MTMR/CBridge/DeprecatedCarbonAPI.c new file mode 100644 index 0000000..92cf1dc --- /dev/null +++ b/MTMR/CBridge/DeprecatedCarbonAPI.c @@ -0,0 +1,45 @@ +// +// DeprecatedCarbonAPI.c +// +// This file is part of TouchDock +// Copyright (C) 2017 Xander Deng +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "DeprecatedCarbonAPI.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +CFStringRef kPidKey = CFSTR("pid"); + +pid_t pidFromASN(void const *asn) { + pid_t pid = -1; + ProcessSerialNumber psn = {kNoProcess, kNoProcess}; + if (CFGetTypeID(asn) == _LSASNGetTypeID()) { + _LSASNExtractHighAndLowParts(asn, &psn.highLongOfPSN, &psn.lowLongOfPSN); + CFDictionaryRef processInfo = ProcessInformationCopyDictionary(&psn, kProcessDictionaryIncludeAllInformationMask); + if (processInfo) { + CFNumberRef pidNumber = CFDictionaryGetValue(processInfo, kPidKey); + if (pidNumber) { + CFNumberGetValue(pidNumber, kCFNumberSInt32Type, &pid); + } + CFRelease(processInfo); + } + } + return pid; +} + +#pragma GCC diagnostic pop diff --git a/MTMR/CBridge/DeprecatedCarbonAPI.h b/MTMR/CBridge/DeprecatedCarbonAPI.h new file mode 100644 index 0000000..a863ef1 --- /dev/null +++ b/MTMR/CBridge/DeprecatedCarbonAPI.h @@ -0,0 +1,27 @@ +// +// DeprecatedCarbonAPI.h +// +// This file is part of TouchDock +// Copyright (C) 2017 Xander Deng +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#import + +extern CFArrayRef _LSCopyApplicationArrayInFrontToBackOrder(uint32_t sessionID); +extern void _LSASNExtractHighAndLowParts(void const* asn, UInt32* psnHigh, UInt32* psnLow); +extern CFTypeID _LSASNGetTypeID(void); + +pid_t pidFromASN(void const *asn); diff --git a/MTMR/CBridge/TouchBarPrivateApi-Bridging.h b/MTMR/CBridge/TouchBarPrivateApi-Bridging.h index 8c25a7d..1955001 100644 --- a/MTMR/CBridge/TouchBarPrivateApi-Bridging.h +++ b/MTMR/CBridge/TouchBarPrivateApi-Bridging.h @@ -8,6 +8,7 @@ #import "TouchBarPrivateApi.h" #import "TouchBarSupport.h" +#import "DeprecatedCarbonAPI.h" NS_ASSUME_NONNULL_BEGIN diff --git a/MTMR/CustomButtonTouchBarItem.swift b/MTMR/CustomButtonTouchBarItem.swift index d3ba783..7b969e7 100644 --- a/MTMR/CustomButtonTouchBarItem.swift +++ b/MTMR/CustomButtonTouchBarItem.swift @@ -16,6 +16,7 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem { self.tapClosure = callback super.init(identifier: identifier) button = NSButton(title: title, target: self, action: #selector(didTapped)) + button.font = .systemFont(ofSize: CGFloat(13.0)) self.view = button } diff --git a/MTMR/ItemsParsing.swift b/MTMR/ItemsParsing.swift index 2755489..5851f67 100644 --- a/MTMR/ItemsParsing.swift +++ b/MTMR/ItemsParsing.swift @@ -80,6 +80,9 @@ class SupportedTypesHolder { let item = ItemType.appleScriptTitledButton(source: Source(filePath: scriptPath), refreshInterval: interval ?? 1800.0) return (item: item, action: .none, parameters: [:]) }, + "dock": { decoder in + return (item: .dock(), action: .none, parameters: [:]) + }, "volume": { decoder in enum CodingKeys: String, CodingKey { case image } let container = try decoder.container(keyedBy: CodingKeys.self) @@ -134,6 +137,7 @@ enum ItemType: Decodable { case staticButton(title: String) case appleScriptTitledButton(source: SourceProtocol, refreshInterval: Double) case timeButton(formatTemplate: String) + case dock() case volume() case brightness(refreshInterval: Double) @@ -150,6 +154,7 @@ enum ItemType: Decodable { case staticButton case appleScriptTitledButton case timeButton + case dock case volume case brightness } @@ -168,6 +173,8 @@ enum ItemType: Decodable { case .timeButton: let template = try container.decodeIfPresent(String.self, forKey: .formatTemplate) ?? "HH:mm" self = .timeButton(formatTemplate: template) + case .dock: + self = .dock() case .volume: self = .volume() case .brightness: diff --git a/MTMR/TouchBarController.swift b/MTMR/TouchBarController.swift index 1647f52..adee8b6 100644 --- a/MTMR/TouchBarController.swift +++ b/MTMR/TouchBarController.swift @@ -23,6 +23,8 @@ extension ItemType { return "com.toxblh.mtmr.appleScriptButton." case .timeButton(formatTemplate: _): return "com.toxblh.mtmr.timeButton." + case .dock(): + return "com.toxblh.mtmr.dock" case .volume(): return "com.toxblh.mtmr.volume" case .brightness(refreshInterval: _): @@ -140,6 +142,8 @@ class TouchBarController: NSObject, NSTouchBarDelegate { barItem = AppleScriptTouchBarItem(identifier: identifier, source: source, interval: interval, onTap: action) case .timeButton(formatTemplate: let template): barItem = TimeTouchBarItem(identifier: identifier, formatTemplate: template) + case .dock: + barItem = AppScrubberTouchBarItem(identifier: identifier) case .volume: if case .image(let source)? = item.additionalParameters[.image] { barItem = VolumeViewController(identifier: identifier, image: source.image) From 3d7bcd2d47a3b1b8a39e28d869abf6d30e6dbee2 Mon Sep 17 00:00:00 2001 From: ad Date: Mon, 16 Apr 2018 15:36:15 +0300 Subject: [PATCH 2/7] * --- MTMR/AppScrubberTouchBarItem.swift | 66 +++--------------------------- 1 file changed, 5 insertions(+), 61 deletions(-) diff --git a/MTMR/AppScrubberTouchBarItem.swift b/MTMR/AppScrubberTouchBarItem.swift index cd24c4c..b374326 100644 --- a/MTMR/AppScrubberTouchBarItem.swift +++ b/MTMR/AppScrubberTouchBarItem.swift @@ -20,10 +20,6 @@ import Cocoa -let activateKeyIndex = "ActivateKeyIndex" -let appScrubberOrderIndex = "AppScrubberOrderIndex" -let appScrubberModeIndex = "AppScrubberModeIndex" - class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrubberDataSource { var scrubber: NSScrubber! @@ -36,9 +32,9 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub scrubber = NSScrubber().then { $0.delegate = self $0.dataSource = self - $0.mode = UserDefaults.standard.appScrubberMode + $0.mode = .free // .fixed let layout = NSScrubberFlowLayout().then { - $0.itemSize = NSSize(width: 50, height: 30) + $0.itemSize = NSSize(width: 44, height: 30) } $0.scrubberLayout = layout $0.selectionBackgroundStyle = .roundedBackground @@ -50,7 +46,6 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didLaunchApplicationNotification, object: nil) NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didTerminateApplicationNotification, object: nil) NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didActivateApplicationNotification, object: nil) - UserDefaults.standard.addObserver(self, forKeyPath: appScrubberOrderIndex, context: nil) updateRunningApplication(animated: false) } @@ -68,7 +63,7 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub } func updateRunningApplication(animated: Bool) { - let isDockOrder = UserDefaults.standard.integer(forKey: appScrubberOrderIndex) != 0 + let isDockOrder = false let newApplications = (isDockOrder ? dockPersistentApplications() : launchedApplications()).filter { !$0.isTerminated && $0.bundleIdentifier != nil } @@ -78,12 +73,10 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub } if animated { scrubber.performSequentialBatchUpdates { -// print("-----update-----") for (index, app) in newApplications.enumerated() { while runningApplications[safe:index].map(newApplications.contains) == false { scrubber.removeItems(at: [index]) let r = runningApplications.remove(at: index) -// print("remove \(r.localizedName!) at \(index)") } if let oldIndex = runningApplications.index(of: app) { guard oldIndex != index else { @@ -91,11 +84,9 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub } scrubber.moveItem(at: oldIndex, to: index) runningApplications.move(at: oldIndex, to: index) -// print("move \(app.localizedName!) at \(oldIndex) to \(index)") } else { scrubber.insertItems(at: [index]) runningApplications.insert(app, at: index) -// print("insert \(app.localizedName!) to \(index)") } } assert(runningApplications == newApplications) @@ -107,8 +98,6 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub scrubber.selectedIndex = index ?? 0 } - // MARK: - NSScrubberDataSource - public func numberOfItems(for scrubber: NSScrubber) -> Int { return runningApplications.count } @@ -123,16 +112,11 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub } public func didFinishInteracting(with scrubber: NSScrubber) { - guard scrubber.selectedIndex > 0 else { - return - } - runningApplications[scrubber.selectedIndex].activate(options: .activateIgnoringOtherApps) + runningApplications[scrubber.selectedIndex].activate(options: [ .activateIgnoringOtherApps ]) } } -// MARK: - Applications - private func launchedApplications() -> [NSRunningApplication] { let asns = _LSCopyApplicationArrayInFrontToBackOrder(~0)?.takeRetainedValue() return (0.. [NSRunningApplication] { public protocol Then {} extension Then where Self: Any { - - /// Makes it available to set properties with closures just after initializing and copying the value types. - /// - /// let frame = CGRect().with { - /// $0.origin.x = 10 - /// $0.size.width = 100 - /// } public func with(_ block: (inout Self) throws -> Void) rethrows -> Self { var copy = self try block(©) return copy } - - /// Makes it available to execute something with closures. - /// - /// UserDefaults.standard.do { - /// $0.set("devxoul", forKey: "username") - /// $0.set("devxoul@gmail.com", forKey: "email") - /// $0.synchronize() - /// } + public func `do`(_ block: (Self) throws -> Void) rethrows { try block(self) } @@ -201,14 +171,6 @@ extension Then where Self: Any { } extension Then where Self: AnyObject { - - /// Makes it available to set properties with closures just after initializing. - /// - /// let label = UILabel().then { - /// $0.textAlignment = .Center - /// $0.textColor = UIColor.blackColor() - /// $0.text = "Hello, World!" - /// } public func then(_ block: (Self) throws -> Void) rethrows -> Self { try block(self) return self @@ -223,28 +185,11 @@ extension CGRect: Then {} extension CGSize: Then {} extension CGVector: Then {} -extension UserDefaults { - - var activateKey: NSEvent.ModifierFlags? { - let keys: [NSEvent.ModifierFlags?] = [.command, .option, .control, .shift, nil] - let index = integer(forKey: activateKeyIndex) - return keys[index] - } - - var appScrubberMode: NSScrubber.Mode { - let actions: [NSScrubber.Mode] = [.fixed, .free] - let index = integer(forKey: appScrubberModeIndex) - return actions[index] - } -} - extension NSUserInterfaceItemIdentifier { - static let scrubberApplicationsItem = NSUserInterfaceItemIdentifier("ScrubberApplicationsItemReuseIdentifier") } extension RangeReplaceableCollection { - mutating func move(at oldIndex: Self.Index, to newIndex: Self.Index) { guard oldIndex != newIndex else { return @@ -255,7 +200,6 @@ extension RangeReplaceableCollection { } extension Collection { - subscript(safe index: Self.Index) -> Self.Iterator.Element? { guard index < endIndex else { return nil From 69b3f9d238fbe7fdb60c47e53a824069b00f8714 Mon Sep 17 00:00:00 2001 From: ad Date: Mon, 16 Apr 2018 15:56:04 +0300 Subject: [PATCH 3/7] * removed animation --- MTMR/AppScrubberTouchBarItem.swift | 36 +++++++----------------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/MTMR/AppScrubberTouchBarItem.swift b/MTMR/AppScrubberTouchBarItem.swift index b374326..178fab3 100644 --- a/MTMR/AppScrubberTouchBarItem.swift +++ b/MTMR/AppScrubberTouchBarItem.swift @@ -47,7 +47,7 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didTerminateApplicationNotification, object: nil) NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didActivateApplicationNotification, object: nil) - updateRunningApplication(animated: false) + updateRunningApplication() } required init?(coder: NSCoder) { @@ -55,14 +55,14 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub } @objc func activeApplicationChanged(n: Notification) { - updateRunningApplication(animated: true) + updateRunningApplication() } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { - updateRunningApplication(animated: false) + updateRunningApplication() } - func updateRunningApplication(animated: Bool) { + func updateRunningApplication() { let isDockOrder = false let newApplications = (isDockOrder ? dockPersistentApplications() : launchedApplications()).filter { !$0.isTerminated && $0.bundleIdentifier != nil @@ -71,30 +71,10 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub let index = newApplications.index { $0.processIdentifier == frontmost?.processIdentifier } - if animated { - scrubber.performSequentialBatchUpdates { - for (index, app) in newApplications.enumerated() { - while runningApplications[safe:index].map(newApplications.contains) == false { - scrubber.removeItems(at: [index]) - let r = runningApplications.remove(at: index) - } - if let oldIndex = runningApplications.index(of: app) { - guard oldIndex != index else { - return - } - scrubber.moveItem(at: oldIndex, to: index) - runningApplications.move(at: oldIndex, to: index) - } else { - scrubber.insertItems(at: [index]) - runningApplications.insert(app, at: index) - } - } - assert(runningApplications == newApplications) - } - } else { - runningApplications = newApplications - scrubber.reloadData() - } + + runningApplications = newApplications + scrubber.reloadData() + scrubber.selectedIndex = index ?? 0 } From 9db9df56411cb6f67e8cf7280ade08a235582468 Mon Sep 17 00:00:00 2001 From: ad Date: Mon, 16 Apr 2018 22:37:53 +0300 Subject: [PATCH 4/7] * code cleanup --- MTMR/AppScrubberTouchBarItem.swift | 104 +++++++++-------------------- 1 file changed, 30 insertions(+), 74 deletions(-) diff --git a/MTMR/AppScrubberTouchBarItem.swift b/MTMR/AppScrubberTouchBarItem.swift index 178fab3..3e34467 100644 --- a/MTMR/AppScrubberTouchBarItem.swift +++ b/MTMR/AppScrubberTouchBarItem.swift @@ -29,16 +29,15 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub override init(identifier: NSTouchBarItem.Identifier) { super.init(identifier: identifier) - scrubber = NSScrubber().then { - $0.delegate = self - $0.dataSource = self - $0.mode = .free // .fixed - let layout = NSScrubberFlowLayout().then { - $0.itemSize = NSSize(width: 44, height: 30) - } - $0.scrubberLayout = layout - $0.selectionBackgroundStyle = .roundedBackground - } + scrubber = NSScrubber(); + scrubber.delegate = self + scrubber.dataSource = self + scrubber.mode = .free // .fixed + let layout = NSScrubberFlowLayout(); + layout.itemSize = NSSize(width: 44, height: 30) + scrubber.scrubberLayout = layout + scrubber.selectionBackgroundStyle = .roundedBackground + view = scrubber scrubber.register(NSScrubberImageItemView.self, forItemIdentifier: .scrubberApplicationsItem) @@ -93,13 +92,25 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub public func didFinishInteracting(with scrubber: NSScrubber) { runningApplications[scrubber.selectedIndex].activate(options: [ .activateIgnoringOtherApps ]) + + // NB: if you can't open app which on another space, try to check mark + // "When switching to an application, switch to a Space with open windows for the application" + // in Mission control settings + + // TODO: deminiaturize app +// if let info = CGWindowListCopyWindowInfo(.optionOnScreenOnly, kCGNullWindowID) as? [[ String : Any]] { +// for dict in info { +// if dict["kCGWindowOwnerName"] as! String == runningApplications[scrubber.selectedIndex].localizedName { +// print(dict["kCGWindowNumber"], dict["kCGWindowOwnerName"]) +// } +// } +// } } - } private func launchedApplications() -> [NSRunningApplication] { let asns = _LSCopyApplicationArrayInFrontToBackOrder(~0)?.takeRetainedValue() - return (0.. [NSRunningApplication] { guard let dockDefaults = UserDefaults(suiteName: "com.apple.dock"), let persistentApps = dockDefaults.array(forKey: "persistent-apps") as [AnyObject]?, - let bundleIDs = persistentApps.flatMap({ $0.value(forKeyPath: "tile-data.bundle-identifier") }) as? [String] else { + let bundleIDs = persistentApps.compactMap({ $0.value(forKeyPath: "tile-data.bundle-identifier") }) as? [String] else { return apps } return apps.sorted { (lhs, rhs) in - if lhs.bundleIdentifier == "com.apple.finder" { - return true - } - if rhs.bundleIdentifier == "com.apple.finder" { - return false - } switch ((bundleIDs.index(of: lhs.bundleIdentifier!)), bundleIDs.index(of: rhs.bundleIdentifier!)) { - case (nil, _): - return false; - case (_?, nil): - return true - case let (i1?, i2?): - return i1 < i2; + case (nil, _): + return false; + case (_?, nil): + return true + case let (i1?, i2?): + return i1 < i2; } } } -public protocol Then {} - -extension Then where Self: Any { - public func with(_ block: (inout Self) throws -> Void) rethrows -> Self { - var copy = self - try block(©) - return copy - } - - public func `do`(_ block: (Self) throws -> Void) rethrows { - try block(self) - } - -} - -extension Then where Self: AnyObject { - public func then(_ block: (Self) throws -> Void) rethrows -> Self { - try block(self) - return self - } - -} - -extension NSObject: Then {} - -extension CGPoint: Then {} -extension CGRect: Then {} -extension CGSize: Then {} -extension CGVector: Then {} - extension NSUserInterfaceItemIdentifier { static let scrubberApplicationsItem = NSUserInterfaceItemIdentifier("ScrubberApplicationsItemReuseIdentifier") } - -extension RangeReplaceableCollection { - mutating func move(at oldIndex: Self.Index, to newIndex: Self.Index) { - guard oldIndex != newIndex else { - return - } - let item = remove(at: oldIndex) - insert(item, at: newIndex) - } -} - -extension Collection { - subscript(safe index: Self.Index) -> Self.Iterator.Element? { - guard index < endIndex else { - return nil - } - return self[index] - } -} From 0ce5a491726cdc888b4aa0687bd42b7aeaf4bb40 Mon Sep 17 00:00:00 2001 From: ad Date: Mon, 16 Apr 2018 22:43:06 +0300 Subject: [PATCH 5/7] * removed buggy "recent apps" --- MTMR/AppScrubberTouchBarItem.swift | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/MTMR/AppScrubberTouchBarItem.swift b/MTMR/AppScrubberTouchBarItem.swift index 3e34467..8584627 100644 --- a/MTMR/AppScrubberTouchBarItem.swift +++ b/MTMR/AppScrubberTouchBarItem.swift @@ -62,8 +62,7 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub } func updateRunningApplication() { - let isDockOrder = false - let newApplications = (isDockOrder ? dockPersistentApplications() : launchedApplications()).filter { + let newApplications = launchedApplications().filter { !$0.isTerminated && $0.bundleIdentifier != nil } let frontmost = NSWorkspace.shared.frontmostApplication @@ -117,29 +116,6 @@ private func launchedApplications() -> [NSRunningApplication] { } } -private func dockPersistentApplications() -> [NSRunningApplication] { - let apps = NSWorkspace.shared.runningApplications.filter { - $0.activationPolicy == .regular - } - - guard let dockDefaults = UserDefaults(suiteName: "com.apple.dock"), - let persistentApps = dockDefaults.array(forKey: "persistent-apps") as [AnyObject]?, - let bundleIDs = persistentApps.compactMap({ $0.value(forKeyPath: "tile-data.bundle-identifier") }) as? [String] else { - return apps - } - - return apps.sorted { (lhs, rhs) in - switch ((bundleIDs.index(of: lhs.bundleIdentifier!)), bundleIDs.index(of: rhs.bundleIdentifier!)) { - case (nil, _): - return false; - case (_?, nil): - return true - case let (i1?, i2?): - return i1 < i2; - } - } -} - extension NSUserInterfaceItemIdentifier { static let scrubberApplicationsItem = NSUserInterfaceItemIdentifier("ScrubberApplicationsItemReuseIdentifier") } From 7a3c4811ac5eaef995be1af9db9d0fb3544d0836 Mon Sep 17 00:00:00 2001 From: ad Date: Mon, 16 Apr 2018 22:53:34 +0300 Subject: [PATCH 6/7] * finally --- MTMR/AppScrubberTouchBarItem.swift | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/MTMR/AppScrubberTouchBarItem.swift b/MTMR/AppScrubberTouchBarItem.swift index 8584627..3c5a80a 100644 --- a/MTMR/AppScrubberTouchBarItem.swift +++ b/MTMR/AppScrubberTouchBarItem.swift @@ -40,7 +40,7 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub view = scrubber - scrubber.register(NSScrubberImageItemView.self, forItemIdentifier: .scrubberApplicationsItem) + scrubber.register(NSScrubberImageItemView.self, forItemIdentifier: NSUserInterfaceItemIdentifier(rawValue: "ScrubberApplicationsItemReuseIdentifier")) NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didLaunchApplicationNotification, object: nil) NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didTerminateApplicationNotification, object: nil) @@ -81,7 +81,7 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub } public func scrubber(_ scrubber: NSScrubber, viewForItemAt index: Int) -> NSScrubberItemView { - let item = scrubber.makeItem(withIdentifier: .scrubberApplicationsItem, owner: self) as? NSScrubberImageItemView ?? NSScrubberImageItemView() + let item = scrubber.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "ScrubberApplicationsItemReuseIdentifier"), owner: self) as? NSScrubberImageItemView ?? NSScrubberImageItemView() item.imageView.imageScaling = .scaleProportionallyDown if let icon = runningApplications[index].icon { item.image = icon @@ -116,6 +116,3 @@ private func launchedApplications() -> [NSRunningApplication] { } } -extension NSUserInterfaceItemIdentifier { - static let scrubberApplicationsItem = NSUserInterfaceItemIdentifier("ScrubberApplicationsItemReuseIdentifier") -} From 893e9f698201e0fffc0ad55cace47d5c82e99acf Mon Sep 17 00:00:00 2001 From: ad Date: Mon, 16 Apr 2018 23:07:15 +0300 Subject: [PATCH 7/7] * removed local changes --- MTMR/AppleScriptTouchBarItem.swift | 2 +- MTMR/CustomButtonTouchBarItem.swift | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/MTMR/AppleScriptTouchBarItem.swift b/MTMR/AppleScriptTouchBarItem.swift index c49914b..1e71b04 100644 --- a/MTMR/AppleScriptTouchBarItem.swift +++ b/MTMR/AppleScriptTouchBarItem.swift @@ -49,7 +49,7 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem { let output = script.executeAndReturnError(&error) if let error = error { print(error) - return "" + return "error" } return output.stringValue ?? "" } diff --git a/MTMR/CustomButtonTouchBarItem.swift b/MTMR/CustomButtonTouchBarItem.swift index d40c966..e85fa44 100644 --- a/MTMR/CustomButtonTouchBarItem.swift +++ b/MTMR/CustomButtonTouchBarItem.swift @@ -16,11 +16,10 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem { self.tapClosure = callback super.init(identifier: identifier) button = NSButton(title: title, target: self, action: #selector(didTapped)) - button.font = .systemFont(ofSize: CGFloat(13.0)) button.title = title self.view = button } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }