mirror of
https://github.com/Toxblh/MTMR.git
synced 2026-01-10 17:08:39 +00:00
commit
71ea94590b
@ -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 */; };
|
||||
B05600D32083E9BB00EB218D /* CustomSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B05600D22083E9BB00EB218D /* CustomSlider.swift */; };
|
||||
B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D621205E03F5006E6B86 /* TouchBarController.swift */; };
|
||||
@ -61,6 +63,9 @@
|
||||
36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = defaultPreset.json; sourceTree = "<group>"; };
|
||||
6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrightnessViewController.swift; sourceTree = "<group>"; };
|
||||
6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VolumeViewController.swift; sourceTree = "<group>"; };
|
||||
6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScrubberTouchBarItem.swift; sourceTree = "<group>"; };
|
||||
6042B6A82083E1F500C525C8 /* DeprecatedCarbonAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeprecatedCarbonAPI.h; sourceTree = "<group>"; };
|
||||
6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = DeprecatedCarbonAPI.c; sourceTree = "<group>"; };
|
||||
B0008E542080286C003AD4DD /* SupportHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportHelpers.swift; sourceTree = "<group>"; };
|
||||
B05600D22083E9BB00EB218D /* CustomSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSlider.swift; sourceTree = "<group>"; };
|
||||
B059D621205E03F5006E6B86 /* TouchBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchBarController.swift; sourceTree = "<group>"; };
|
||||
@ -165,6 +170,7 @@
|
||||
6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */,
|
||||
6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */,
|
||||
368EDDE620812A1D00E10953 /* ScrollViewItem.swift */,
|
||||
6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */,
|
||||
B05600D22083E9BB00EB218D /* CustomSlider.swift */,
|
||||
);
|
||||
path = MTMR;
|
||||
@ -203,6 +209,8 @@
|
||||
B059D62A205F0E7D006E6B86 /* TouchBarPrivateApi-Bridging.h */,
|
||||
B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */,
|
||||
B0F8771B207AC92700D6E430 /* TouchBarSupport.h */,
|
||||
6042B6A82083E1F500C525C8 /* DeprecatedCarbonAPI.h */,
|
||||
6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */,
|
||||
);
|
||||
path = CBridge;
|
||||
sourceTree = "<group>";
|
||||
@ -328,9 +336,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 */,
|
||||
|
||||
118
MTMR/AppScrubberTouchBarItem.swift
Normal file
118
MTMR/AppScrubberTouchBarItem.swift
Normal file
@ -0,0 +1,118 @@
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrubberDataSource {
|
||||
|
||||
var scrubber: NSScrubber!
|
||||
|
||||
var runningApplications: [NSRunningApplication] = []
|
||||
|
||||
override init(identifier: NSTouchBarItem.Identifier) {
|
||||
super.init(identifier: identifier)
|
||||
|
||||
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: 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)
|
||||
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didActivateApplicationNotification, object: nil)
|
||||
|
||||
updateRunningApplication()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc func activeApplicationChanged(n: Notification) {
|
||||
updateRunningApplication()
|
||||
}
|
||||
|
||||
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||
updateRunningApplication()
|
||||
}
|
||||
|
||||
func updateRunningApplication() {
|
||||
let newApplications = launchedApplications().filter {
|
||||
!$0.isTerminated && $0.bundleIdentifier != nil
|
||||
}
|
||||
let frontmost = NSWorkspace.shared.frontmostApplication
|
||||
let index = newApplications.index {
|
||||
$0.processIdentifier == frontmost?.processIdentifier
|
||||
}
|
||||
|
||||
runningApplications = newApplications
|
||||
scrubber.reloadData()
|
||||
|
||||
scrubber.selectedIndex = index ?? 0
|
||||
}
|
||||
|
||||
public func numberOfItems(for scrubber: NSScrubber) -> Int {
|
||||
return runningApplications.count
|
||||
}
|
||||
|
||||
public func scrubber(_ scrubber: NSScrubber, viewForItemAt index: Int) -> NSScrubberItemView {
|
||||
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
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
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..<CFArrayGetCount(asns)).compactMap { index in
|
||||
let asn = CFArrayGetValueAtIndex(asns, index)
|
||||
let pid = pidFromASN(asn)
|
||||
return NSRunningApplication(processIdentifier: pid)
|
||||
}
|
||||
}
|
||||
|
||||
45
MTMR/CBridge/DeprecatedCarbonAPI.c
Normal file
45
MTMR/CBridge/DeprecatedCarbonAPI.c
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#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
|
||||
27
MTMR/CBridge/DeprecatedCarbonAPI.h
Normal file
27
MTMR/CBridge/DeprecatedCarbonAPI.h
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#import <Carbon/Carbon.h>
|
||||
|
||||
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);
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
#import "TouchBarPrivateApi.h"
|
||||
#import "TouchBarSupport.h"
|
||||
#import "DeprecatedCarbonAPI.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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: _):
|
||||
@ -150,6 +152,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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user