diff --git a/MTMR.xcodeproj/project.pbxproj b/MTMR.xcodeproj/project.pbxproj index 4251a02..8830654 100644 --- a/MTMR.xcodeproj/project.pbxproj +++ b/MTMR.xcodeproj/project.pbxproj @@ -61,6 +61,7 @@ B0B17439207D6B590004B740 /* Vox.nowPlaying.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B1742F207D6B590004B740 /* Vox.nowPlaying.scpt */; }; B0B1743A207D6B590004B740 /* iTunes.nowPlaying.scpt in Resources */ = {isa = PBXBuildFile; fileRef = B0B17430207D6B590004B740 /* iTunes.nowPlaying.scpt */; }; B0F3112520C9E35F0076BB88 /* SupportNSTouchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0F3112420C9E35F0076BB88 /* SupportNSTouchBar.swift */; }; + B0F54A7A2295AC7D00B4C509 /* DarkModeBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */; }; B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */; }; /* End PBXBuildFile section */ @@ -142,6 +143,7 @@ B0B1742F207D6B590004B740 /* Vox.nowPlaying.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Vox.nowPlaying.scpt; sourceTree = ""; }; B0B17430207D6B590004B740 /* iTunes.nowPlaying.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = iTunes.nowPlaying.scpt; sourceTree = ""; }; B0F3112420C9E35F0076BB88 /* SupportNSTouchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportNSTouchBar.swift; sourceTree = ""; }; + B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DarkModeBarItem.swift; sourceTree = ""; }; B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TouchBarSupport.m; sourceTree = ""; }; B0F8771B207AC92700D6E430 /* TouchBarSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TouchBarSupport.h; sourceTree = ""; }; /* End PBXFileReference section */ @@ -293,6 +295,7 @@ 6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */, 607EEA4A2087835F009DA5F0 /* WeatherBarItem.swift */, B08126F0217BE19000A98970 /* WidgetProtocol.swift */, + B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */, ); path = Widgets; sourceTree = ""; @@ -446,6 +449,7 @@ B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */, 6042B6A72083E03A00C525C8 /* AppScrubberTouchBarItem.swift in Sources */, B082B253205C7D8000BC04DC /* AppDelegate.swift in Sources */, + B0F54A7A2295AC7D00B4C509 /* DarkModeBarItem.swift in Sources */, B08126EF217BD0B900A98970 /* PomodoroBarItem.swift in Sources */, B081732C213739FE005D4908 /* DnDBarItem.swift in Sources */, 60C44AFD20A373A100C0EC91 /* MusicBarItem.swift in Sources */, diff --git a/MTMR/Assets.xcassets/dark-mode-off.imageset/Contents.json b/MTMR/Assets.xcassets/dark-mode-off.imageset/Contents.json new file mode 100644 index 0000000..f185c89 --- /dev/null +++ b/MTMR/Assets.xcassets/dark-mode-off.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "sun-icon-256.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/MTMR/Assets.xcassets/dark-mode-off.imageset/sun-icon-256.png b/MTMR/Assets.xcassets/dark-mode-off.imageset/sun-icon-256.png new file mode 100644 index 0000000..ddec2cd Binary files /dev/null and b/MTMR/Assets.xcassets/dark-mode-off.imageset/sun-icon-256.png differ diff --git a/MTMR/Assets.xcassets/dark-mode-on.imageset/39857.png b/MTMR/Assets.xcassets/dark-mode-on.imageset/39857.png new file mode 100644 index 0000000..4c127cc Binary files /dev/null and b/MTMR/Assets.xcassets/dark-mode-on.imageset/39857.png differ diff --git a/MTMR/Assets.xcassets/dark-mode-on.imageset/Contents.json b/MTMR/Assets.xcassets/dark-mode-on.imageset/Contents.json new file mode 100644 index 0000000..2fb03c0 --- /dev/null +++ b/MTMR/Assets.xcassets/dark-mode-on.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "39857.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/MTMR/Base.lproj/Main.storyboard b/MTMR/Base.lproj/Main.storyboard index 925123a..3f295b2 100644 --- a/MTMR/Base.lproj/Main.storyboard +++ b/MTMR/Base.lproj/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -619,7 +619,7 @@ - + diff --git a/MTMR/Info.plist b/MTMR/Info.plist index df5f206..fea29f8 100644 --- a/MTMR/Info.plist +++ b/MTMR/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.20.3 + 0.21 CFBundleVersion - 201 + 252 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/MTMR/ItemsParsing.swift b/MTMR/ItemsParsing.swift index 01095de..fb3fd1c 100644 --- a/MTMR/ItemsParsing.swift +++ b/MTMR/ItemsParsing.swift @@ -51,7 +51,7 @@ class SupportedTypesHolder { private var supportedTypes: [String: ParametersDecoder] = [ "escape": { _ in ( item: .staticButton(title: "esc"), - action: .keyPressSession(keycode: 53), + action: .keyPress(keycode: 53), longAction: .none, parameters: [.align: .align(.left)] ) }, @@ -197,9 +197,12 @@ class SupportedTypesHolder { ) }, - "dock": { _ in - ( - item: .dock(), + "dock": { decoder in + enum CodingKeys: String, CodingKey { case autoResize } + let container = try decoder.container(keyedBy: CodingKeys.self) + let autoResize = try container.decodeIfPresent(Bool.self, forKey: .autoResize) ?? false + return ( + item: .dock(autoResize: autoResize), action: .none, longAction: .none, parameters: [:] @@ -327,7 +330,7 @@ enum ItemType: Decodable { case appleScriptTitledButton(source: SourceProtocol, refreshInterval: Double) case timeButton(formatTemplate: String, timeZone: String?) case battery() - case dock() + case dock(autoResize: Bool) case volume() case brightness(refreshInterval: Double) case weather(interval: Double, units: String, api_key: String, icon_type: String) @@ -339,6 +342,7 @@ enum ItemType: Decodable { case dnd() case pomodoro(workTime: Double, restTime: Double) case network(flip: Bool) + case darkMode() private enum CodingKeys: String, CodingKey { case type @@ -360,6 +364,7 @@ enum ItemType: Decodable { case workTime case restTime case flip + case autoResize } enum ItemTypeRaw: String, Decodable { @@ -379,6 +384,7 @@ enum ItemType: Decodable { case dnd case pomodoro case network + case darkMode } init(from decoder: Decoder) throws { @@ -403,7 +409,8 @@ enum ItemType: Decodable { self = .battery() case .dock: - self = .dock() + let autoResize = try container.decodeIfPresent(Bool.self, forKey: .autoResize) ?? false + self = .dock(autoResize: autoResize) case .volume: self = .volume() @@ -451,6 +458,9 @@ enum ItemType: Decodable { case .network: let flip = try container.decodeIfPresent(Bool.self, forKey: .flip) ?? false self = .network(flip: flip) + + case .darkMode: + self = .darkMode() } } } @@ -459,7 +469,6 @@ enum ActionType: Decodable { case none case hidKey(keycode: Int32) case keyPress(keycode: Int) - case keyPressSession(keycode: Int) case appleScript(source: SourceProtocol) case shellScript(executable: String, parameters: [String]) case custom(closure: () -> Void) diff --git a/MTMR/KeyPress.swift b/MTMR/KeyPress.swift index c88b49f..1f68768 100644 --- a/MTMR/KeyPress.swift +++ b/MTMR/KeyPress.swift @@ -27,16 +27,6 @@ extension KeyPress { keyDown?.post(tap: loc) keyUp?.post(tap: loc) } - - func sendSession() { - let src = CGEventSource(stateID: .hidSystemState) - let keyDown = CGEvent(keyboardEventSource: src, virtualKey: keyCode, keyDown: true) - let keyUp = CGEvent(keyboardEventSource: src, virtualKey: keyCode, keyDown: false) - - let loc: CGEventTapLocation = .cgAnnotatedSessionEventTap - keyDown?.post(tap: loc) - keyUp?.post(tap: loc) - } } func HIDPostAuxKey(_ key: Int32) { diff --git a/MTMR/TouchBarController.swift b/MTMR/TouchBarController.swift index 43776a9..1389460 100644 --- a/MTMR/TouchBarController.swift +++ b/MTMR/TouchBarController.swift @@ -27,7 +27,7 @@ extension ItemType { return "com.toxblh.mtmr.timeButton." case .battery(): return "com.toxblh.mtmr.battery." - case .dock(): + case .dock(autoResize: _): return "com.toxblh.mtmr.dock" case .volume(): return "com.toxblh.mtmr.volume" @@ -51,6 +51,8 @@ extension ItemType { return PomodoroBarItem.identifier case .network(flip: _): return NetworkBarItem.identifier + case .darkMode(items: _): + return DarkModeBarItem.identifier } } } @@ -251,8 +253,8 @@ class TouchBarController: NSObject, NSTouchBarDelegate { barItem = TimeTouchBarItem(identifier: identifier, formatTemplate: template, timeZone: timeZone) case .battery(): barItem = BatteryBarItem(identifier: identifier) - case .dock: - barItem = AppScrubberTouchBarItem(identifier: identifier) + case let .dock(autoResize: autoResize): + barItem = AppScrubberTouchBarItem(identifier: identifier, autoResize: autoResize) case .volume: if case let .image(source)? = item.additionalParameters[.image] { barItem = VolumeViewController(identifier: identifier, image: source.image) @@ -283,6 +285,8 @@ class TouchBarController: NSObject, NSTouchBarDelegate { barItem = PomodoroBarItem(identifier: identifier, workTime: workTime, restTime: restTime) case let .network(flip: flip): barItem = NetworkBarItem(identifier: identifier, flip: flip) + case .darkMode(): + barItem = DarkModeBarItem(identifier: identifier) } if let action = self.action(forItem: item), let item = barItem as? CustomButtonTouchBarItem { @@ -319,8 +323,6 @@ class TouchBarController: NSObject, NSTouchBarDelegate { return { HIDPostAuxKey(keycode) } case let .keyPress(keycode: keycode): return { GenericKeyPress(keyCode: CGKeyCode(keycode)).send() } - case let .keyPressSession(keycode: keycode): - return { GenericKeyPress(keyCode: CGKeyCode(keycode)).sendSession() } case let .appleScript(source: source): guard let appleScript = source.appleScript else { print("cannot create apple script for item \(item)") diff --git a/MTMR/Widgets/AppScrubberTouchBarItem.swift b/MTMR/Widgets/AppScrubberTouchBarItem.swift index 8d15966..7c5cd39 100644 --- a/MTMR/Widgets/AppScrubberTouchBarItem.swift +++ b/MTMR/Widgets/AppScrubberTouchBarItem.swift @@ -17,6 +17,8 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub private let minTicks: Int = 5 private let maxTicks: Int = 20 private var lastSelected: Int = 0 + private var autoResize: Bool = false + private var widthConstraint: NSLayoutConstraint? private var persistentAppIdentifiers: [String] = [] private var runningAppsIdentifiers: [String] = [] @@ -27,17 +29,25 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub } private var applications: [DockItem] = [] + + convenience override init(identifier: NSTouchBarItem.Identifier) { + self.init(identifier: identifier, autoResize: false) + } + + static var iconWidth = 36 + static var spacingWidth = 2 - override init(identifier: NSTouchBarItem.Identifier) { + init(identifier: NSTouchBarItem.Identifier, autoResize: Bool) { super.init(identifier: identifier) - + self.autoResize = autoResize + scrubber = NSScrubber() scrubber.delegate = self scrubber.dataSource = self scrubber.mode = .free // .fixed let layout = NSScrubberFlowLayout() - layout.itemSize = NSSize(width: 36, height: 32) - layout.itemSpacing = 2 + layout.itemSize = NSSize(width: AppScrubberTouchBarItem.iconWidth, height: 32) + layout.itemSpacing = CGFloat(AppScrubberTouchBarItem.spacingWidth) scrubber.scrubberLayout = layout scrubber.selectionBackgroundStyle = .roundedBackground scrubber.showsAdditionalContentIndicators = true @@ -79,9 +89,22 @@ class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrub applications = newApplications applications += getDockPersistentAppsList() scrubber.reloadData() + updateSize() scrubber.selectedIndex = index ?? 0 } + + func updateSize() { + if self.autoResize { + if let constraint: NSLayoutConstraint = self.widthConstraint { + constraint.isActive = false + self.scrubber.removeConstraint(constraint) + } + let width = (AppScrubberTouchBarItem.iconWidth + AppScrubberTouchBarItem.spacingWidth) * self.applications.count - AppScrubberTouchBarItem.spacingWidth + self.widthConstraint = self.scrubber.widthAnchor.constraint(equalToConstant: CGFloat(width)) + self.widthConstraint!.isActive = true + } + } public func numberOfItems(for _: NSScrubber) -> Int { return applications.count diff --git a/MTMR/Widgets/DarkModeBarItem.swift b/MTMR/Widgets/DarkModeBarItem.swift new file mode 100644 index 0000000..60fecb6 --- /dev/null +++ b/MTMR/Widgets/DarkModeBarItem.swift @@ -0,0 +1,57 @@ +import Foundation + +class DarkModeBarItem: CustomButtonTouchBarItem, Widget { + static var name: String = "darkmode" + static var identifier: String = "com.toxblh.mtmr.darkmode" + + private var timer: Timer! + + init(identifier: NSTouchBarItem.Identifier) { + super.init(identifier: identifier, title: "") + isBordered = false + setWidth(value: 24) + + tapClosure = { [weak self] in self?.DarkModeToggle() } + + timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(refresh), userInfo: nil, repeats: true) + + refresh() + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func DarkModeToggle() { + DarkMode.isEnabled = !DarkMode.isEnabled + refresh() + } + + @objc func refresh() { + image = DarkMode.isEnabled ? #imageLiteral(resourceName: "dark-mode-on") : #imageLiteral(resourceName: "dark-mode-off") + } +} + + +struct DarkMode { + private static let prefix = "tell application \"System Events\" to tell appearance preferences to" + + static var isEnabled: Bool { + get { + return UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark" + } + set { + toggle(force: newValue) + } + } + + static func toggle(force: Bool? = nil) { + let value = force.map(String.init) ?? "not dark mode" + runAppleScript("\(prefix) set dark mode to \(value)") + } +} + +func runAppleScript(_ source: String) -> String? { + return NSAppleScript(source: source)?.executeAndReturnError(nil).stringValue +} + diff --git a/MTMRTests/ParseConfigTests.swift b/MTMRTests/ParseConfigTests.swift index ebea8a5..a3321cc 100644 --- a/MTMRTests/ParseConfigTests.swift +++ b/MTMRTests/ParseConfigTests.swift @@ -40,7 +40,7 @@ class ParseConfig: XCTestCase { XCTFail() return } - guard case .keyPressSession(keycode: 53)? = result?.first?.action else { + guard case .keyPress(keycode: 53)? = result?.first?.action else { XCTFail() return } @@ -55,7 +55,7 @@ class ParseConfig: XCTestCase { XCTFail() return } - guard case .keyPressSession(keycode: 53)? = result?.first?.action else { + guard case .keyPress(keycode: 53)? = result?.first?.action else { XCTFail() return } diff --git a/README.md b/README.md index 4fd8a4b..fb51603 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ The pre-installed configuration contains less or more than you'll probably want, - dock (half-long click to open app, full-long click to kill app) - nightShift - dnd (Don't disturb) +- darkMode - pomodoro - network @@ -205,6 +206,15 @@ To close a group, use the button: }, ``` +#### `dock` +> Dock plugin +```js +{ + "type": "dock", + "autoResize": true +}, +``` + ## Actions: - `hidKey` > https://github.com/aosm/IOHIDFamily/blob/master/IOHIDSystem/IOKit/hidsystem/ev_keymap.h use only numbers