Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd99e9d73d | ||
|
|
58beb5a213 | ||
|
|
7da9ca2c68 | ||
|
|
88a4ce82db | ||
|
|
d39b4c0c31 | ||
|
|
5e609c2446 | ||
|
|
14301c4dbd | ||
|
|
a879498e4c | ||
|
|
36bf749a46 | ||
|
|
ac0e44db4d | ||
|
|
26ad83be70 | ||
|
|
d199bbd852 | ||
|
|
352bf4887c | ||
|
|
211ca4be32 | ||
|
|
d270a7bbcd | ||
|
|
44732e8ad6 | ||
|
|
8c57342070 | ||
|
|
3add660d72 | ||
|
|
eb617ff31b | ||
| 3e82676008 | |||
| a2ad47c7ba |
2
.github/FUNDING.yml
vendored
@ -3,4 +3,4 @@
|
||||
issuehunt: Toxblh
|
||||
patreon: toxblh
|
||||
ko_fi: toxblh
|
||||
custom: https://www.paypal.me/toxblh
|
||||
custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=WUAAG2HH58WE4
|
||||
|
||||
@ -21,6 +21,8 @@
|
||||
36FEF872235A1CFC00A0ABCE /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36FEF871235A1CFC00A0ABCE /* AppSettings.swift */; };
|
||||
4CC9FEDC22FDEA65001512EB /* AMR_ANSIEscapeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */; };
|
||||
4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */; };
|
||||
4CF222EB26CC00470025BDA7 /* CPUBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF222EA26CC00470025BDA7 /* CPUBarItem.swift */; };
|
||||
4CF222ED26CC43D50025BDA7 /* CPU.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF222EC26CC43D40025BDA7 /* CPU.swift */; };
|
||||
4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */; };
|
||||
5DC6CA02241F92CB005CD5E8 /* Music.nowPlaying.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 5DC6CA00241F92CB005CD5E8 /* Music.nowPlaying.scpt */; };
|
||||
5DC6CA03241F92CB005CD5E8 /* Music.next.scpt in Resources */ = {isa = PBXBuildFile; fileRef = 5DC6CA01241F92CB005CD5E8 /* Music.next.scpt */; };
|
||||
@ -104,6 +106,8 @@
|
||||
4CC9FEDA22FDEA65001512EB /* AMR_ANSIEscapeHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AMR_ANSIEscapeHelper.h; sourceTree = "<group>"; };
|
||||
4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AMR_ANSIEscapeHelper.m; sourceTree = "<group>"; };
|
||||
4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellScriptTouchBarItem.swift; sourceTree = "<group>"; };
|
||||
4CF222EA26CC00470025BDA7 /* CPUBarItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CPUBarItem.swift; sourceTree = "<group>"; };
|
||||
4CF222EC26CC43D40025BDA7 /* CPU.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CPU.swift; sourceTree = "<group>"; };
|
||||
4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YandexWeatherBarItem.swift; sourceTree = "<group>"; };
|
||||
5DC6CA00241F92CB005CD5E8 /* Music.nowPlaying.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Music.nowPlaying.scpt; sourceTree = "<group>"; };
|
||||
5DC6CA01241F92CB005CD5E8 /* Music.next.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Music.next.scpt; sourceTree = "<group>"; };
|
||||
@ -239,6 +243,7 @@
|
||||
B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */,
|
||||
36FEF871235A1CFC00A0ABCE /* AppSettings.swift */,
|
||||
B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */,
|
||||
4CF222EC26CC43D40025BDA7 /* CPU.swift */,
|
||||
36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */,
|
||||
BAF5AB5824317CAF00B04904 /* SwipeItem.swift */,
|
||||
4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */,
|
||||
@ -310,6 +315,7 @@
|
||||
6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */,
|
||||
B04B7BB62087398C00C835D0 /* BatteryBarItem.swift */,
|
||||
6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */,
|
||||
4CF222EA26CC00470025BDA7 /* CPUBarItem.swift */,
|
||||
607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */,
|
||||
B081732B213739FE005D4908 /* DnDBarItem.swift */,
|
||||
60669B4220AD8FA80074E817 /* GroupBarItem.swift */,
|
||||
@ -478,12 +484,14 @@
|
||||
B0846A752220C968000288A7 /* NetworkBarItem.swift in Sources */,
|
||||
B0F8771A207AC1EA00D6E430 /* TouchBarSupport.m in Sources */,
|
||||
B08126F1217BE19000A98970 /* WidgetProtocol.swift in Sources */,
|
||||
4CF222EB26CC00470025BDA7 /* CPUBarItem.swift in Sources */,
|
||||
B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */,
|
||||
6042B6A72083E03A00C525C8 /* AppScrubberTouchBarItem.swift in Sources */,
|
||||
BAF5AB5924317CAF00B04904 /* SwipeItem.swift in Sources */,
|
||||
B082B253205C7D8000BC04DC /* AppDelegate.swift in Sources */,
|
||||
B0F54A7A2295AC7D00B4C509 /* DarkModeBarItem.swift in Sources */,
|
||||
B08126EF217BD0B900A98970 /* PomodoroBarItem.swift in Sources */,
|
||||
4CF222ED26CC43D50025BDA7 /* CPU.swift in Sources */,
|
||||
B081732C213739FE005D4908 /* DnDBarItem.swift in Sources */,
|
||||
60C44AFD20A373A100C0EC91 /* MusicBarItem.swift in Sources */,
|
||||
B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */,
|
||||
|
||||
@ -25,7 +25,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true] as NSDictionary)
|
||||
|
||||
TouchBarController.shared.setupControlStripPresence()
|
||||
HapticFeedbackUpdate()
|
||||
|
||||
if let button = statusItem.button {
|
||||
button.image = #imageLiteral(resourceName: "StatusImage")
|
||||
@ -41,10 +40,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
func applicationWillTerminate(_: Notification) {}
|
||||
|
||||
func HapticFeedbackUpdate() {
|
||||
HapticFeedback.shared = AppSettings.hapticFeedbackState ? HapticFeedback() : nil
|
||||
}
|
||||
|
||||
@objc func updateIsBlockedApp() {
|
||||
if let frontmostAppId = TouchBarController.shared.frontmostApplicationIdentifier {
|
||||
isBlockedApp = AppSettings.blacklistedAppIds.firstIndex(of: frontmostAppId) != nil
|
||||
@ -86,7 +81,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
@objc func toggleHapticFeedback(_ item: NSMenuItem) {
|
||||
item.state = item.state == .on ? .off : .on
|
||||
AppSettings.hapticFeedbackState = item.state == .on
|
||||
HapticFeedbackUpdate()
|
||||
}
|
||||
|
||||
@objc func toggleMultitouch(_ item: NSMenuItem) {
|
||||
|
||||
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 667 B |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 2.7 KiB |
21
MTMR/Assets.xcassets/cpu.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "cpu.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
MTMR/Assets.xcassets/cpu.imageset/cpu.png
vendored
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
191
MTMR/CPU.swift
Normal file
@ -0,0 +1,191 @@
|
||||
//
|
||||
// CPU.swift
|
||||
// Pods
|
||||
//
|
||||
// Created by zixun on 2016/12/5.
|
||||
// https://github.com/zixun/SystemEye
|
||||
// MIT License
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
private let HOST_CPU_LOAD_INFO_COUNT : mach_msg_type_number_t =
|
||||
UInt32(MemoryLayout<host_cpu_load_info_data_t>.size / MemoryLayout<integer_t>.size)
|
||||
|
||||
/// CPU Class
|
||||
public class CPU: NSObject {
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// MARK: OPEN PROPERTY
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
// /// Number of physical cores on this machine.
|
||||
// public static var physicalCores: Int {
|
||||
// get {
|
||||
// return Int(System.hostBasicInfo.physical_cpu)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /// Number of logical cores on this machine. Will be equal to physicalCores
|
||||
// /// unless it has hyper-threading, in which case it will be double.
|
||||
// public static var logicalCores: Int {
|
||||
// get {
|
||||
// return Int(System.hostBasicInfo.logical_cpu)
|
||||
// }
|
||||
// }
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// MARK: OPEN FUNCTIONS
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/// Get CPU usage of hole system (system, user, idle, nice). Determined by the delta between
|
||||
/// the current and last call.
|
||||
public static func systemUsage() -> (system: Double,
|
||||
user: Double,
|
||||
idle: Double,
|
||||
nice: Double) {
|
||||
let load = self.hostCPULoadInfo
|
||||
|
||||
let userDiff = Double(load.cpu_ticks.0 - loadPrevious.cpu_ticks.0)
|
||||
let sysDiff = Double(load.cpu_ticks.1 - loadPrevious.cpu_ticks.1)
|
||||
let idleDiff = Double(load.cpu_ticks.2 - loadPrevious.cpu_ticks.2)
|
||||
let niceDiff = Double(load.cpu_ticks.3 - loadPrevious.cpu_ticks.3)
|
||||
|
||||
let totalTicks = sysDiff + userDiff + niceDiff + idleDiff
|
||||
|
||||
let sys = sysDiff / totalTicks * 100.0
|
||||
let user = userDiff / totalTicks * 100.0
|
||||
let idle = idleDiff / totalTicks * 100.0
|
||||
let nice = niceDiff / totalTicks * 100.0
|
||||
|
||||
loadPrevious = load
|
||||
|
||||
return (sys, user, idle, nice)
|
||||
}
|
||||
|
||||
|
||||
/// Get CPU usage of application,get from all thread
|
||||
open class func applicationUsage() -> Double {
|
||||
let threads = self.threadBasicInfos()
|
||||
var result : Double = 0.0
|
||||
threads.forEach { (thread:thread_basic_info) in
|
||||
if self.flag(thread) {
|
||||
result += Double.init(thread.cpu_usage) / Double.init(TH_USAGE_SCALE);
|
||||
}
|
||||
}
|
||||
return result * 100
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// MARK: PRIVATE PROPERTY
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/// previous load of cpu
|
||||
private static var loadPrevious = host_cpu_load_info()
|
||||
|
||||
static var hostCPULoadInfo: host_cpu_load_info {
|
||||
get {
|
||||
var size = HOST_CPU_LOAD_INFO_COUNT
|
||||
var hostInfo = host_cpu_load_info()
|
||||
let result = withUnsafeMutablePointer(to: &hostInfo) {
|
||||
$0.withMemoryRebound(to: integer_t.self, capacity: Int(size)) {
|
||||
host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, $0, &size)
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
if result != KERN_SUCCESS {
|
||||
fatalError("ERROR - \(#file):\(#function) - kern_result_t = "
|
||||
+ "\(result)")
|
||||
}
|
||||
#endif
|
||||
|
||||
return hostInfo
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// MARK: PRIVATE FUNCTION
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
private class func flag(_ thread:thread_basic_info) -> Bool {
|
||||
let foo = thread.flags & TH_FLAGS_IDLE
|
||||
let number = NSNumber.init(value: foo)
|
||||
return !Bool.init(truncating: number)
|
||||
}
|
||||
|
||||
private class func threadActPointers() -> [thread_act_t] {
|
||||
var threads_act = [thread_act_t]()
|
||||
|
||||
var threads_array: thread_act_array_t? = nil
|
||||
var count = mach_msg_type_number_t()
|
||||
|
||||
let result = task_threads(mach_task_self_, &(threads_array), &count)
|
||||
|
||||
guard result == KERN_SUCCESS else {
|
||||
return threads_act
|
||||
}
|
||||
|
||||
guard let array = threads_array else {
|
||||
return threads_act
|
||||
}
|
||||
|
||||
for i in 0..<count {
|
||||
threads_act.append(array[Int(i)])
|
||||
}
|
||||
|
||||
let krsize = count * UInt32.init(MemoryLayout<thread_t>.size)
|
||||
_ = vm_deallocate(mach_task_self_, vm_address_t(array.pointee), vm_size_t(krsize));
|
||||
return threads_act
|
||||
}
|
||||
|
||||
private class func threadBasicInfos() -> [thread_basic_info] {
|
||||
var result = [thread_basic_info]()
|
||||
|
||||
let thinfo : thread_info_t = thread_info_t.allocate(capacity: Int(THREAD_INFO_MAX))
|
||||
let thread_info_count = UnsafeMutablePointer<mach_msg_type_number_t>.allocate(capacity: 128)
|
||||
var basic_info_th: thread_basic_info_t? = nil
|
||||
|
||||
for act_t in self.threadActPointers() {
|
||||
thread_info_count.pointee = UInt32(THREAD_INFO_MAX);
|
||||
let kr = thread_info(act_t ,thread_flavor_t(THREAD_BASIC_INFO),thinfo, thread_info_count);
|
||||
if (kr != KERN_SUCCESS) {
|
||||
return [thread_basic_info]();
|
||||
}
|
||||
basic_info_th = withUnsafePointer(to: &thinfo.pointee, { (ptr) -> thread_basic_info_t in
|
||||
let int8Ptr = unsafeBitCast(ptr, to: thread_basic_info_t.self)
|
||||
return int8Ptr
|
||||
})
|
||||
if basic_info_th != nil {
|
||||
result.append(basic_info_th!.pointee)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
//TODO: this function is used for get cpu usage of all thread,and this is in developing
|
||||
private class func threadIdentifierInfos() -> [thread_identifier_info] {
|
||||
var result = [thread_identifier_info]()
|
||||
let thinfo : thread_info_t = thread_info_t.allocate(capacity: Int(THREAD_INFO_MAX))
|
||||
let thread_info_count = UnsafeMutablePointer<mach_msg_type_number_t>.allocate(capacity: 128)
|
||||
var identifier_info_th: thread_identifier_info_t? = nil
|
||||
|
||||
for act_t in self.threadActPointers() {
|
||||
thread_info_count.pointee = UInt32(THREAD_INFO_MAX);
|
||||
let kr = thread_info(act_t ,thread_flavor_t(THREAD_IDENTIFIER_INFO),thinfo, thread_info_count);
|
||||
if (kr != KERN_SUCCESS) {
|
||||
return [thread_identifier_info]();
|
||||
}
|
||||
identifier_info_th = withUnsafePointer(to: &thinfo.pointee, { (ptr) -> thread_identifier_info_t in
|
||||
let int8Ptr = unsafeBitCast(ptr, to: thread_identifier_info_t.self)
|
||||
return int8Ptr
|
||||
})
|
||||
if identifier_info_th != nil {
|
||||
result.append(identifier_info_th!.pointee)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
@ -240,12 +240,12 @@ final class MultiClickGestureRecognizer: NSClickGestureRecognizer {
|
||||
}
|
||||
|
||||
override func touchesBegan(with event: NSEvent) {
|
||||
HapticFeedback.shared?.tap(strong: 2)
|
||||
HapticFeedback.instance.tap(type: .click)
|
||||
super.touchesBegan(with: event)
|
||||
}
|
||||
|
||||
override func touchesEnded(with event: NSEvent) {
|
||||
HapticFeedback.shared?.tap(strong: 1)
|
||||
HapticFeedback.instance.tap(type: .back)
|
||||
super.touchesEnded(with: event)
|
||||
_clickCount += 1
|
||||
|
||||
@ -322,7 +322,7 @@ class LongPressGestureRecognizer: NSPressGestureRecognizer {
|
||||
@objc private func onTimer() {
|
||||
if let target = self.target, let action = self.action {
|
||||
target.performSelector(onMainThread: action, with: self, waitUntilDone: false)
|
||||
HapticFeedback.shared?.tap(strong: 6)
|
||||
HapticFeedback.instance.tap(type: .strong)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,81 +9,92 @@
|
||||
import IOKit
|
||||
|
||||
class HapticFeedback {
|
||||
static var shared: HapticFeedback?
|
||||
|
||||
|
||||
// Here we have list of possible IDs for Haptic Generator Device. They are not constant
|
||||
// To find deviceID, you will need IORegistryExplorer app from Additional Tools for Xcode dmg
|
||||
// which you can download from https://developer.apple.com/download/more/?=Additional%20Tools
|
||||
// Open IORegistryExplorer app, search for AppleMultitouchDevice and get "Multitouch ID"
|
||||
// Open IORegistryExplorer app, search for "AppleMultitouchDevice" and get "Multitouch ID"
|
||||
// or "AppleMultitouchTrackpadHIDEventDriver" and get "mt-device-id"
|
||||
// There should be programmatic way to get it but I can't find, no docs for macOS :(
|
||||
private let possibleDeviceIDs: [UInt64] = [
|
||||
0x200_0000_0100_0000, // MacBook Pro 2016/2017
|
||||
0x300000080500000 // MacBook Pro 2019 (possibly 2018 as well)
|
||||
0x200_0000_0100_0000, // MacBook Pro 2016/2017
|
||||
0x300_0000_8050_0000, // MacBook Pro 2019/2018
|
||||
0x200_0000_0000_0024, // MacBook Pro (13-inch, M1, 2020)
|
||||
0x200_0000_0000_0023 // MacBook Pro M1 13-Inch 2020 with 1tb
|
||||
// 0x300000080500000,
|
||||
]
|
||||
private var correctDeviceID: UInt64?
|
||||
private var actuatorRef: CFTypeRef?
|
||||
|
||||
init() {
|
||||
recreateDevice()
|
||||
|
||||
// you can get a plist `otool -s __TEXT __tpad_act_plist /System/Library/PrivateFrameworks/MultitouchSupport.framework/Versions/Current/MultitouchSupport|tail -n +3|awk -F'\t' '{print $2}'|xxd -r -p`
|
||||
enum HapticType: Int32, CaseIterable {
|
||||
case back = 1
|
||||
case click = 2
|
||||
case weak = 3
|
||||
case medium = 4
|
||||
case weakMedium = 5
|
||||
case strong = 6
|
||||
case reserved1 = 15
|
||||
case reserved2 = 16
|
||||
}
|
||||
|
||||
// Don't know how to do strong is enum one of
|
||||
// 1 like back Click
|
||||
// 2 like Click
|
||||
// 3 week
|
||||
// 4 medium
|
||||
// 5 week medium
|
||||
// 6 strong
|
||||
// 15 nothing
|
||||
// 16 nothing
|
||||
// you can get a plist `otool -s __TEXT __tpad_act_plist /System/Library/PrivateFrameworks/MultitouchSupport.framework/Versions/Current/MultitouchSupport|tail -n +3|awk -F'\t' '{print $2}'|xxd -r -p`
|
||||
private var actuatorRef: CFTypeRef?
|
||||
|
||||
func tap(strong: Int32) {
|
||||
guard correctDeviceID != nil, actuatorRef != nil else {
|
||||
static var instance = HapticFeedback()
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
private init() {
|
||||
self.recreateDevice()
|
||||
}
|
||||
|
||||
private func recreateDevice() {
|
||||
if let actuatorRef = self.actuatorRef {
|
||||
MTActuatorClose(actuatorRef)
|
||||
self.actuatorRef = nil // just in case %)
|
||||
}
|
||||
|
||||
guard self.actuatorRef == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
// Let's find our Haptic device
|
||||
self.possibleDeviceIDs.forEach {(deviceID) in
|
||||
let actuatorRef = MTActuatorCreateFromDeviceID(deviceID).takeRetainedValue()
|
||||
|
||||
if actuatorRef != nil {
|
||||
self.actuatorRef = actuatorRef
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Tap action
|
||||
|
||||
private func getActuatorIfPosible() -> CFTypeRef? {
|
||||
guard AppSettings.hapticFeedbackState else { return nil }
|
||||
guard let actuatorRef = self.actuatorRef else {
|
||||
print("guard actuatorRef == nil (no haptic device found?)")
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
var result: IOReturn
|
||||
|
||||
result = MTActuatorOpen(actuatorRef!)
|
||||
guard result == kIOReturnSuccess else {
|
||||
guard MTActuatorOpen(actuatorRef) == kIOReturnSuccess else {
|
||||
print("guard MTActuatorOpen")
|
||||
recreateDevice()
|
||||
return
|
||||
self.recreateDevice()
|
||||
return nil
|
||||
}
|
||||
|
||||
result = MTActuatorActuate(actuatorRef!, strong, 0, 0, 0)
|
||||
guard result == kIOReturnSuccess else {
|
||||
return actuatorRef
|
||||
}
|
||||
|
||||
func tap(type: HapticType) {
|
||||
guard let actuator = getActuatorIfPosible() else { return }
|
||||
|
||||
guard MTActuatorActuate(actuator, type.rawValue, 0, 0, 0) == kIOReturnSuccess else {
|
||||
print("guard MTActuatorActuate")
|
||||
return
|
||||
}
|
||||
|
||||
result = MTActuatorClose(actuatorRef!)
|
||||
guard result == kIOReturnSuccess else {
|
||||
guard MTActuatorClose(actuator) == kIOReturnSuccess else {
|
||||
print("guard MTActuatorClose")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private func recreateDevice() {
|
||||
if let actuatorRef = actuatorRef {
|
||||
MTActuatorClose(actuatorRef)
|
||||
self.actuatorRef = nil // just in case %)
|
||||
}
|
||||
|
||||
if let correctDeviceID = correctDeviceID {
|
||||
actuatorRef = MTActuatorCreateFromDeviceID(correctDeviceID).takeRetainedValue()
|
||||
} else {
|
||||
// Let's find our Haptic device
|
||||
possibleDeviceIDs.forEach {(deviceID) in
|
||||
guard correctDeviceID == nil else {return}
|
||||
actuatorRef = MTActuatorCreateFromDeviceID(deviceID).takeRetainedValue()
|
||||
|
||||
if actuatorRef != nil {
|
||||
correctDeviceID = deviceID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.27</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>429</string>
|
||||
<string>448</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.utilities</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
||||
@ -261,6 +261,7 @@ enum ItemType: Decodable {
|
||||
case shellScriptTitledButton(source: SourceProtocol, refreshInterval: Double)
|
||||
case timeButton(formatTemplate: String, timeZone: String?, locale: String?)
|
||||
case battery
|
||||
case cpu(refreshInterval: Double)
|
||||
case dock(autoResize: Bool, filter: String?)
|
||||
case volume
|
||||
case brightness(refreshInterval: Double)
|
||||
@ -273,7 +274,7 @@ enum ItemType: Decodable {
|
||||
case nightShift
|
||||
case dnd
|
||||
case pomodoro(workTime: Double, restTime: Double)
|
||||
case network(flip: Bool)
|
||||
case network(flip: Bool, units: String)
|
||||
case darkMode
|
||||
case swipe(direction: String, fingers: Int, minOffset: Float, sourceApple: SourceProtocol?, sourceBash: SourceProtocol?)
|
||||
case upnext(from: Double, to: Double, maxToShow: Int, autoResize: Bool)
|
||||
@ -317,6 +318,7 @@ enum ItemType: Decodable {
|
||||
case shellScriptTitledButton
|
||||
case timeButton
|
||||
case battery
|
||||
case cpu
|
||||
case dock
|
||||
case volume
|
||||
case brightness
|
||||
@ -362,6 +364,10 @@ enum ItemType: Decodable {
|
||||
|
||||
case .battery:
|
||||
self = .battery
|
||||
|
||||
case .cpu:
|
||||
let refreshInterval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 5.0
|
||||
self = .cpu(refreshInterval: refreshInterval)
|
||||
|
||||
case .dock:
|
||||
let autoResize = try container.decodeIfPresent(Bool.self, forKey: .autoResize) ?? false
|
||||
@ -418,7 +424,8 @@ enum ItemType: Decodable {
|
||||
|
||||
case .network:
|
||||
let flip = try container.decodeIfPresent(Bool.self, forKey: .flip) ?? false
|
||||
self = .network(flip: flip)
|
||||
let units = try container.decodeIfPresent(String.self, forKey: .units) ?? "dynamic"
|
||||
self = .network(flip: flip, units: units)
|
||||
|
||||
case .darkMode:
|
||||
self = .darkMode
|
||||
@ -652,6 +659,7 @@ enum GeneralParameter {
|
||||
case bordered(_: Bool)
|
||||
case background(_: NSColor)
|
||||
case title(_: String)
|
||||
case matchAppId(_: String)
|
||||
}
|
||||
|
||||
struct GeneralParameters: Decodable {
|
||||
@ -664,6 +672,7 @@ struct GeneralParameters: Decodable {
|
||||
case bordered
|
||||
case background
|
||||
case title
|
||||
case matchAppId
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
@ -693,6 +702,10 @@ struct GeneralParameters: Decodable {
|
||||
result[.title] = .title(title)
|
||||
}
|
||||
|
||||
if let matchAppId = try container.decodeIfPresent(String.self, forKey: .matchAppId) {
|
||||
result[.matchAppId] = .matchAppId(matchAppId)
|
||||
}
|
||||
|
||||
parameters = result
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,6 +12,11 @@ class ShellScriptTouchBarItem: CustomButtonTouchBarItem {
|
||||
private let source: String
|
||||
private var forceHideConstraint: NSLayoutConstraint!
|
||||
|
||||
struct ScriptResult: Decodable {
|
||||
var title: String?
|
||||
var image: Source?
|
||||
}
|
||||
|
||||
init?(identifier: NSTouchBarItem.Identifier, source: SourceProtocol, interval: TimeInterval) {
|
||||
self.interval = interval
|
||||
self.source = source.string ?? "echo No \"source\""
|
||||
@ -31,12 +36,25 @@ class ShellScriptTouchBarItem: CustomButtonTouchBarItem {
|
||||
func refreshAndSchedule() {
|
||||
// Execute script and get result
|
||||
let scriptResult = execute(source)
|
||||
|
||||
var rawTitle: String, image: NSImage?
|
||||
var json: Bool
|
||||
|
||||
do {
|
||||
let decoder = JSONDecoder()
|
||||
let result = try decoder.decode(ScriptResult.self, from: scriptResult.data(using: .utf8)!)
|
||||
json = true
|
||||
rawTitle = result.title ?? ""
|
||||
image = result.image?.image
|
||||
} catch {
|
||||
json = false
|
||||
rawTitle = scriptResult
|
||||
}
|
||||
|
||||
// Apply returned text attributes (if they were returned) to our result string
|
||||
let helper = AMR_ANSIEscapeHelper.init()
|
||||
helper.defaultStringColor = NSColor.white
|
||||
helper.font = "1".defaultTouchbarAttributedString.attribute(.font, at: 0, effectiveRange: nil) as? NSFont
|
||||
let title = NSMutableAttributedString.init(attributedString: helper.attributedString(withANSIEscapedString: scriptResult) ?? NSAttributedString(string: ""))
|
||||
let title = NSMutableAttributedString.init(attributedString: helper.attributedString(withANSIEscapedString: rawTitle) ?? NSAttributedString(string: ""))
|
||||
title.addAttributes([.baselineOffset: 1], range: NSRange(location: 0, length: title.length))
|
||||
let newBackgoundColor: NSColor? = title.length != 0 ? title.attribute(.backgroundColor, at: 0, effectiveRange: nil) as? NSColor : nil
|
||||
|
||||
@ -46,6 +64,9 @@ class ShellScriptTouchBarItem: CustomButtonTouchBarItem {
|
||||
self?.backgroundColor = newBackgoundColor
|
||||
}
|
||||
self?.attributedTitle = title
|
||||
if json {
|
||||
self?.image = image
|
||||
}
|
||||
self?.forceHideConstraint.isActive = scriptResult == ""
|
||||
}
|
||||
|
||||
@ -57,7 +78,11 @@ class ShellScriptTouchBarItem: CustomButtonTouchBarItem {
|
||||
|
||||
func execute(_ command: String) -> String {
|
||||
let task = Process()
|
||||
task.launchPath = "/bin/bash"
|
||||
if let shell = getenv("SHELL") {
|
||||
task.launchPath = String.init(cString: shell)
|
||||
} else {
|
||||
task.launchPath = "/bin/bash"
|
||||
}
|
||||
task.arguments = ["-c", command]
|
||||
|
||||
let pipe = Pipe()
|
||||
|
||||
@ -51,7 +51,11 @@ class SwipeItem: NSCustomTouchBarItem {
|
||||
if scriptBash != nil {
|
||||
DispatchQueue.shellScriptQueue.async {
|
||||
let task = Process()
|
||||
task.launchPath = "/bin/bash"
|
||||
if let shell = getenv("SHELL") {
|
||||
task.launchPath = String.init(cString: shell)
|
||||
} else {
|
||||
task.launchPath = "/bin/bash"
|
||||
}
|
||||
task.arguments = ["-c", self.scriptBash!]
|
||||
task.launch()
|
||||
task.waitUntilExit()
|
||||
@ -63,4 +67,12 @@ class SwipeItem: NSCustomTouchBarItem {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isEqual(_ object: AnyObject?) -> Bool {
|
||||
if let object = object as? SwipeItem {
|
||||
return self.scriptApple?.source as String? == object.scriptApple?.source as String? && self.scriptBash == object.scriptBash && self.direction == object.direction && self.fingers == object.fingers && self.minOffset == object.minOffset
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,6 +29,8 @@ extension ItemType {
|
||||
return "com.toxblh.mtmr.timeButton."
|
||||
case .battery:
|
||||
return "com.toxblh.mtmr.battery."
|
||||
case .cpu(refreshInterval: _):
|
||||
return "com.toxblh.mtmr.cpu."
|
||||
case .dock(autoResize: _, filter: _):
|
||||
return "com.toxblh.mtmr.dock"
|
||||
case .volume:
|
||||
@ -132,17 +134,56 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
touchBar = NSTouchBar()
|
||||
jsonItems = newJsonItems
|
||||
itemDefinitions = [:]
|
||||
items = [:]
|
||||
|
||||
loadItemDefinitions(jsonItems: jsonItems)
|
||||
|
||||
updateActiveApp()
|
||||
}
|
||||
|
||||
func didItemsChange(prevItems: [NSTouchBarItem.Identifier: NSTouchBarItem], prevSwipeItems: [SwipeItem]) -> Bool {
|
||||
var changed = items.count != prevItems.count || swipeItems.count != prevSwipeItems.count
|
||||
|
||||
if !changed {
|
||||
for (item, prevItem) in zip(items, prevItems) {
|
||||
if item.key != prevItem.key {
|
||||
changed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !changed {
|
||||
for (swipeItem, prevSwipeItem) in zip(swipeItems, prevSwipeItems) {
|
||||
if !swipeItem.isEqual(prevSwipeItem) {
|
||||
changed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changed
|
||||
}
|
||||
|
||||
func prepareTouchBar() {
|
||||
let prevItems = items
|
||||
let prevSwipeItems = swipeItems
|
||||
|
||||
createItems()
|
||||
|
||||
let changed = didItemsChange(prevItems: prevItems, prevSwipeItems: prevSwipeItems)
|
||||
|
||||
if !changed {
|
||||
return
|
||||
}
|
||||
|
||||
let centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
|
||||
items[identifier]
|
||||
})
|
||||
|
||||
let centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString))
|
||||
let scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems)
|
||||
|
||||
basicViewIdentifier = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollView.".appending(UUID().uuidString))
|
||||
|
||||
touchBar.delegate = self
|
||||
touchBar.defaultItemIdentifiers = [basicViewIdentifier]
|
||||
@ -156,8 +197,6 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
|
||||
basicView = BasicView(identifier: basicViewIdentifier, items:leftItems + [scrollArea] + rightItems, swipeItems: swipeItems)
|
||||
basicView?.legacyGesturesEnabled = AppSettings.multitouchGestures
|
||||
|
||||
updateActiveApp()
|
||||
}
|
||||
|
||||
@objc func activeApplicationChanged(_: Notification) {
|
||||
@ -168,9 +207,18 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
if frontmostApplicationIdentifier != nil && blacklistAppIdentifiers.firstIndex(of: frontmostApplicationIdentifier!) != nil {
|
||||
dismissTouchBar()
|
||||
} else {
|
||||
presentTouchBar()
|
||||
prepareTouchBar()
|
||||
if touchBarContainsAnyItems() {
|
||||
presentTouchBar()
|
||||
} else {
|
||||
dismissTouchBar()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func touchBarContainsAnyItems() -> Bool {
|
||||
return items.count != 0 || swipeItems.count != 0
|
||||
}
|
||||
|
||||
func reloadStandardConfig() {
|
||||
let presetPath = standardConfigPath
|
||||
@ -210,12 +258,29 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
}
|
||||
|
||||
func createItems() {
|
||||
items = [:]
|
||||
swipeItems = []
|
||||
|
||||
for (identifier, definition) in itemDefinitions {
|
||||
let item = createItem(forIdentifier: identifier, definition: definition)
|
||||
if item is SwipeItem {
|
||||
swipeItems.append(item as! SwipeItem)
|
||||
} else {
|
||||
items[identifier] = item
|
||||
var show = true
|
||||
|
||||
if let frontApp = frontmostApplicationIdentifier {
|
||||
if case let .matchAppId(regexString)? = definition.additionalParameters[.matchAppId] {
|
||||
let regex = try! NSRegularExpression(pattern: regexString)
|
||||
let range = NSRange(location: 0, length: frontApp.count)
|
||||
if regex.firstMatch(in: frontApp, range: range) == nil {
|
||||
show = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if show {
|
||||
let item = createItem(forIdentifier: identifier, definition: definition)
|
||||
if item is SwipeItem {
|
||||
swipeItems.append(item as! SwipeItem)
|
||||
} else {
|
||||
items[identifier] = item
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -229,26 +294,29 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
}
|
||||
|
||||
func updateControlStripPresence() {
|
||||
DFRElementSetControlStripPresenceForIdentifier(.controlStripItem, true)
|
||||
let showMtmrButtonOnControlStrip = touchBarContainsAnyItems()
|
||||
DFRElementSetControlStripPresenceForIdentifier(.controlStripItem, showMtmrButtonOnControlStrip)
|
||||
}
|
||||
|
||||
@objc private func presentTouchBar() {
|
||||
if AppSettings.showControlStripState {
|
||||
updateControlStripPresence()
|
||||
presentSystemModal(touchBar, systemTrayItemIdentifier: .controlStripItem)
|
||||
} else {
|
||||
presentSystemModal(touchBar, placement: 1, systemTrayItemIdentifier: .controlStripItem)
|
||||
}
|
||||
updateControlStripPresence()
|
||||
}
|
||||
|
||||
@objc private func dismissTouchBar() {
|
||||
minimizeSystemModal(touchBar)
|
||||
if touchBarContainsAnyItems() {
|
||||
minimizeSystemModal(touchBar)
|
||||
}
|
||||
updateControlStripPresence()
|
||||
}
|
||||
|
||||
@objc func resetControlStrip() {
|
||||
dismissTouchBar()
|
||||
presentTouchBar()
|
||||
updateActiveApp()
|
||||
}
|
||||
|
||||
func touchBar(_: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
|
||||
@ -272,6 +340,8 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
barItem = TimeTouchBarItem(identifier: identifier, formatTemplate: template, timeZone: timeZone, locale: locale)
|
||||
case .battery:
|
||||
barItem = BatteryBarItem(identifier: identifier)
|
||||
case let .cpu(refreshInterval: refreshInterval):
|
||||
barItem = CPUBarItem(identifier: identifier, refreshInterval: refreshInterval)
|
||||
case let .dock(autoResize: autoResize, filter: regexString):
|
||||
if let regexString = regexString {
|
||||
guard let regex = try? NSRegularExpression(pattern: regexString, options: []) else {
|
||||
@ -312,8 +382,8 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
barItem = DnDBarItem(identifier: identifier)
|
||||
case let .pomodoro(workTime: workTime, restTime: restTime):
|
||||
barItem = PomodoroBarItem(identifier: identifier, workTime: workTime, restTime: restTime)
|
||||
case let .network(flip: flip):
|
||||
barItem = NetworkBarItem(identifier: identifier, flip: flip)
|
||||
case let .network(flip: flip, units: units):
|
||||
barItem = NetworkBarItem(identifier: identifier, flip: flip, units: units)
|
||||
case .darkMode:
|
||||
barItem = DarkModeBarItem(identifier: identifier)
|
||||
case let .swipe(direction: direction, fingers: fingers, minOffset: minOffset, sourceApple: sourceApple, sourceBash: sourceBash):
|
||||
|
||||
82
MTMR/Widgets/CPUBarItem.swift
Normal file
@ -0,0 +1,82 @@
|
||||
//
|
||||
// CPUBarItem.swift
|
||||
// MTMR
|
||||
//
|
||||
// Created by bobrosoft on 17/08/2021.
|
||||
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class CPUBarItem: CustomButtonTouchBarItem {
|
||||
private let refreshInterval: TimeInterval
|
||||
private var refreshQueue: DispatchQueue? = DispatchQueue(label: "mtmr.cpu")
|
||||
private let defaultSingleTapScript: NSAppleScript! = "activate application \"Activity Monitor\"\rtell application \"System Events\"\r\ttell process \"Activity Monitor\"\r\t\ttell radio button \"CPU\" of radio group 1 of group 2 of toolbar 1 of window 1 to perform action \"AXPress\"\r\tend tell\rend tell".appleScript
|
||||
|
||||
init(identifier: NSTouchBarItem.Identifier, refreshInterval: TimeInterval) {
|
||||
self.refreshInterval = refreshInterval
|
||||
super.init(identifier: identifier, title: "⏳")
|
||||
|
||||
// Set default image
|
||||
if self.image == nil {
|
||||
self.image = #imageLiteral(resourceName: "cpu").resize(maxSize: NSSize(width: 24, height: 24));
|
||||
}
|
||||
|
||||
// Set default action
|
||||
if actions.filter({ $0.trigger == .singleTap }).isEmpty {
|
||||
actions.append(ItemAction(
|
||||
trigger: .singleTap,
|
||||
defaultTapAction
|
||||
))
|
||||
}
|
||||
|
||||
refreshAndSchedule()
|
||||
}
|
||||
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func refreshAndSchedule() {
|
||||
DispatchQueue.main.async {
|
||||
// Get CPU load
|
||||
let usage = 100 - CPU.systemUsage().idle
|
||||
guard usage.isFinite else {
|
||||
return
|
||||
}
|
||||
|
||||
// Choose color based on CPU load
|
||||
var color: NSColor? = nil
|
||||
var bgColor: NSColor? = nil
|
||||
if usage > 70 {
|
||||
color = .black
|
||||
bgColor = .yellow
|
||||
} else if usage > 30 {
|
||||
color = .yellow
|
||||
}
|
||||
|
||||
// Update layout
|
||||
let attrTitle = NSMutableAttributedString.init(attributedString: String(format: "%.1f%%", usage).defaultTouchbarAttributedString)
|
||||
if let color = color {
|
||||
attrTitle.addAttributes([.foregroundColor: color], range: NSRange(location: 0, length: attrTitle.length))
|
||||
}
|
||||
self.attributedTitle = attrTitle
|
||||
self.backgroundColor = bgColor
|
||||
}
|
||||
|
||||
refreshQueue?.asyncAfter(deadline: .now() + refreshInterval) { [weak self] in
|
||||
self?.refreshAndSchedule()
|
||||
}
|
||||
}
|
||||
|
||||
func defaultTapAction() {
|
||||
refreshQueue?.async { [weak self] in
|
||||
self?.defaultSingleTapScript.executeAndReturnError(nil)
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
refreshQueue?.suspend()
|
||||
refreshQueue = nil
|
||||
}
|
||||
}
|
||||
@ -38,6 +38,13 @@ class CurrencyBarItem: CustomButtonTouchBarItem {
|
||||
"BTC": "฿",
|
||||
"LTC": "Ł",
|
||||
"ETH": "Ξ",
|
||||
"SOL": "◎",
|
||||
"DOT": "●",
|
||||
"DOGE": "Ð",
|
||||
"XMR": "ɱ",
|
||||
"ADA": "₳",
|
||||
"PLN": "zł",
|
||||
"UAH": "₴",
|
||||
]
|
||||
private let decimals = [
|
||||
"USD": 4,
|
||||
@ -54,9 +61,13 @@ class CurrencyBarItem: CustomButtonTouchBarItem {
|
||||
"MXN": 2,
|
||||
"SGD": 4,
|
||||
"CHF": 4,
|
||||
"BTC": 2,
|
||||
"BTC": 3,
|
||||
"LTC": 2,
|
||||
"ETH": 2,
|
||||
"DOT": 3,
|
||||
"DOGE": 4,
|
||||
"ADA": 3,
|
||||
"USDT": 3
|
||||
]
|
||||
|
||||
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, from: String, to: String, full: Bool) {
|
||||
|
||||
@ -13,9 +13,11 @@ class NetworkBarItem: CustomButtonTouchBarItem, Widget {
|
||||
static var identifier: String = "com.toxblh.mtmr.network"
|
||||
|
||||
private let flip: Bool
|
||||
private let units: String
|
||||
|
||||
init(identifier: NSTouchBarItem.Identifier, flip: Bool = false) {
|
||||
init(identifier: NSTouchBarItem.Identifier, flip: Bool = false, units: String) {
|
||||
self.flip = flip
|
||||
self.units = units
|
||||
super.init(identifier: identifier, title: " ")
|
||||
startMonitoringProcess()
|
||||
}
|
||||
@ -86,15 +88,42 @@ class NetworkBarItem: CustomButtonTouchBarItem, Widget {
|
||||
|
||||
func getHumanizeSize(speed: UInt64) -> String {
|
||||
let humanText: String
|
||||
|
||||
if speed < 1024 {
|
||||
humanText = String(format: "%.0f", Double(speed)) + " B/s"
|
||||
} else if speed < (1024 * 1024) {
|
||||
humanText = String(format: "%.1f", Double(speed) / 1024) + " KB/s"
|
||||
} else if speed < (1024 * 1024 * 1024) {
|
||||
humanText = String(format: "%.1f", Double(speed) / (1024 * 1024)) + " MB/s"
|
||||
} else {
|
||||
humanText = String(format: "%.2f", Double(speed) / (1024 * 1024 * 1024)) + " GB/s"
|
||||
|
||||
func speedB(speed: UInt64)-> String {
|
||||
return String(format: "%.0f", Double(speed)) + " B/s"
|
||||
}
|
||||
|
||||
func speedKB(speed: UInt64)-> String {
|
||||
return String(format: "%.1f", Double(speed) / 1024) + " KB/s"
|
||||
}
|
||||
|
||||
func speedMB(speed: UInt64)-> String {
|
||||
return String(format: "%.1f", Double(speed) / (1024 * 1024)) + " MB/s"
|
||||
}
|
||||
|
||||
func speedGB(speed: UInt64)-> String {
|
||||
return String(format: "%.2f", Double(speed) / (1024 * 1024 * 1024)) + " GB/s"
|
||||
}
|
||||
|
||||
switch self.units {
|
||||
case "B/s":
|
||||
humanText = speedB(speed: speed)
|
||||
case "KB/s":
|
||||
humanText = speedKB(speed: speed)
|
||||
case "MB/s":
|
||||
humanText = speedMB(speed: speed)
|
||||
case "GB/s":
|
||||
humanText = speedGB(speed: speed)
|
||||
default:
|
||||
if speed < 1024 {
|
||||
humanText = speedB(speed: speed)
|
||||
} else if speed < (1024 * 1024) {
|
||||
humanText = speedKB(speed: speed)
|
||||
} else if speed < (1024 * 1024 * 1024) {
|
||||
humanText = speedMB(speed: speed)
|
||||
} else {
|
||||
humanText = speedGB(speed: speed)
|
||||
}
|
||||
}
|
||||
|
||||
return humanText
|
||||
|
||||
@ -5,18 +5,11 @@ import CoreAudio
|
||||
|
||||
class VolumeViewController: NSCustomTouchBarItem {
|
||||
private(set) var sliderItem: CustomSlider!
|
||||
private var currentDeviceId: AudioObjectID = AudioObjectID(0)
|
||||
|
||||
init(identifier: NSTouchBarItem.Identifier, image: NSImage? = nil) {
|
||||
super.init(identifier: identifier)
|
||||
|
||||
var forPropertyAddress = AudioObjectPropertyAddress(
|
||||
mSelector: kAudioHardwareServiceDeviceProperty_VirtualMasterVolume,
|
||||
mScope: kAudioDevicePropertyScopeOutput,
|
||||
mElement: kAudioObjectPropertyElementMaster
|
||||
)
|
||||
|
||||
AudioObjectAddPropertyListenerBlock(defaultDeviceID, &forPropertyAddress, nil, audioObjectPropertyListenerBlock)
|
||||
|
||||
if image == nil {
|
||||
sliderItem = CustomSlider()
|
||||
} else {
|
||||
@ -29,6 +22,49 @@ class VolumeViewController: NSCustomTouchBarItem {
|
||||
sliderItem.floatValue = getInputGain() * 100
|
||||
|
||||
view = sliderItem
|
||||
|
||||
currentDeviceId = defaultDeviceID
|
||||
self.addAudioRouteChangedListener()
|
||||
self.addCurrentAudioVolumeChangedListener()
|
||||
}
|
||||
|
||||
private func addAudioRouteChangedListener() {
|
||||
let audioId = AudioObjectID(bitPattern: kAudioObjectSystemObject)
|
||||
var forPropertyAddress = AudioObjectPropertyAddress(
|
||||
mSelector: kAudioHardwarePropertyDefaultOutputDevice,
|
||||
mScope: kAudioObjectPropertyScopeGlobal,
|
||||
mElement: kAudioObjectPropertyElementMaster)
|
||||
AudioObjectAddPropertyListenerBlock(audioId, &forPropertyAddress, nil, audioRouteChanged)
|
||||
}
|
||||
|
||||
|
||||
func audioRouteChanged(numberAddresses _: UInt32, addresses _: UnsafePointer<AudioObjectPropertyAddress>) {
|
||||
self.removeLastAudioVolumeChangeListener()
|
||||
currentDeviceId = defaultDeviceID
|
||||
self.addCurrentAudioVolumeChangedListener()
|
||||
DispatchQueue.main.async {
|
||||
self.sliderItem.floatValue = self.getInputGain() * 100
|
||||
}
|
||||
}
|
||||
|
||||
private func addCurrentAudioVolumeChangedListener() {
|
||||
var forPropertyAddress = AudioObjectPropertyAddress(
|
||||
mSelector: kAudioHardwareServiceDeviceProperty_VirtualMainVolume,
|
||||
mScope: kAudioDevicePropertyScopeOutput,
|
||||
mElement: kAudioObjectPropertyElementMaster
|
||||
)
|
||||
|
||||
AudioObjectAddPropertyListenerBlock(defaultDeviceID, &forPropertyAddress, nil, audioObjectPropertyListenerBlock)
|
||||
}
|
||||
|
||||
private func removeLastAudioVolumeChangeListener() {
|
||||
var forPropertyAddress = AudioObjectPropertyAddress(
|
||||
mSelector: kAudioHardwareServiceDeviceProperty_VirtualMainVolume,
|
||||
mScope: kAudioDevicePropertyScopeOutput,
|
||||
mElement: kAudioObjectPropertyElementMaster
|
||||
)
|
||||
|
||||
AudioObjectRemovePropertyListenerBlock(currentDeviceId, &forPropertyAddress, nil, audioObjectPropertyListenerBlock)
|
||||
}
|
||||
|
||||
func audioObjectPropertyListenerBlock(numberAddresses _: UInt32, addresses _: UnsafePointer<AudioObjectPropertyAddress>) {
|
||||
@ -66,7 +102,7 @@ class VolumeViewController: NSCustomTouchBarItem {
|
||||
var volume: Float32 = 0.5
|
||||
var size: UInt32 = UInt32(MemoryLayout.size(ofValue: volume))
|
||||
var address: AudioObjectPropertyAddress = AudioObjectPropertyAddress()
|
||||
address.mSelector = AudioObjectPropertySelector(kAudioHardwareServiceDeviceProperty_VirtualMasterVolume)
|
||||
address.mSelector = AudioObjectPropertySelector(kAudioHardwareServiceDeviceProperty_VirtualMainVolume)
|
||||
address.mScope = AudioObjectPropertyScope(kAudioDevicePropertyScopeOutput)
|
||||
address.mElement = AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)
|
||||
AudioObjectGetPropertyData(defaultDeviceID, &address, 0, nil, &size, &volume)
|
||||
@ -86,7 +122,7 @@ class VolumeViewController: NSCustomTouchBarItem {
|
||||
var address: AudioObjectPropertyAddress = AudioObjectPropertyAddress()
|
||||
address.mScope = AudioObjectPropertyScope(kAudioDevicePropertyScopeOutput)
|
||||
address.mElement = AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)
|
||||
address.mSelector = AudioObjectPropertySelector(kAudioHardwareServiceDeviceProperty_VirtualMasterVolume)
|
||||
address.mSelector = AudioObjectPropertySelector(kAudioHardwareServiceDeviceProperty_VirtualMainVolume)
|
||||
return AudioObjectSetPropertyData(defaultDeviceID, &address, 0, nil, size, &inputVolume)
|
||||
}
|
||||
|
||||
|
||||
@ -13,8 +13,21 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate
|
||||
private let activity: NSBackgroundActivityScheduler
|
||||
private let unitsStr = "°C"
|
||||
private let iconsSource = [
|
||||
"Ясно": "☀️", "Малооблачно": "🌤", "Облачно с прояснениями": "⛅️", "Пасмурно": "☁️", "Небольшой дождь": "🌦", "Дождь": "🌧", "Ливень": "⛈", "Гроза": "🌩", "Дождь с грозой": "⛈", "Дождь со снегом": "☔️", "Небольшой снег": "❄️", "Снег": "🌨", "Туман": "🌫",
|
||||
"Clear": "☀️", "Mostly clear": "🌤", "Partly cloudy": "⛅️", "Overcast": "☁️", "Light rain": "🌦", "Rain": "🌧", "Heavy rain": "⛈", "Storm": "🌩", "Thunderstorm with rain": "⛈", "Sleet": "☔️", "Light snow": "❄️", "Snow": "🌨", "Fog": "🌫"
|
||||
"clear": "☀️",
|
||||
"mostly-clear": "🌤",
|
||||
"partly-cloudy": "⛅️",
|
||||
"overcast": "☁️",
|
||||
"cloudy": "☁️",
|
||||
"light-rain": "🌦",
|
||||
"drizzle": "💦",
|
||||
"rain": "🌧",
|
||||
"heavy-rain": "⛈",
|
||||
"storm": "🌩",
|
||||
"thunderstorm-with-rain": "⛈",
|
||||
"sleet": "☔️",
|
||||
"light-snow": "❄️",
|
||||
"snow": "🌨",
|
||||
"fog": "🌫"
|
||||
]
|
||||
private var location: CLLocation!
|
||||
private var prevLocation: CLLocation!
|
||||
@ -80,7 +93,7 @@ class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate
|
||||
temperature = matches.first?.item(at: 1)
|
||||
|
||||
var icon: String?
|
||||
matches = response.matchingStrings(regex: "link__condition.*?>(.*?)<")
|
||||
matches = response.matchingStrings(regex: "\"condition\":\"(.*?)\"")
|
||||
icon = matches.first?.item(at: 1)
|
||||
if let _ = icon, let test = self.iconsSource[icon!] {
|
||||
icon = test
|
||||
|
||||
73
README.md
@ -14,11 +14,11 @@ My idea is to create a platform for creating plugins to customize the TouchBar.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://discord.gg/CmNcDuQ"><img height="20px" src="https://camo.githubusercontent.com/88f53948f291c54736bf08f5fd7b037a848dfc62/68747470733a2f2f646973636f72646170702e636f6d2f6173736574732f30376463613830613130326434313439653937333664346231363263666636662e69636f"> Discord</a>
|
||||
<a href="https://discord.gg/CmNcDuQ"><img height="20px" src="https://assets-global.website-files.com/6257adef93867e50d84d30e2/62fddf0fde45a8baedcc7ee5_847541504914fd33810e70a0ea73177e%20(2)-1.png"> Discord</a>
|
||||
<a href="https://t.me/joinchat/AmVYGg8vW38c13_3MxdE_g"><img height="20px" src="https://telegram.org/img/t_logo.png" /> Telegram</a>
|
||||
</p>
|
||||
|
||||
<p align="center"><a href="https://www.paypal.me/toxblh/10" title="Donate via Paypal"><img height="36px" src="Resources/support_paypal.svg" alt="PayPal donate button" /></a>
|
||||
<p align="center"><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=WUAAG2HH58WE4" title="Donate via Paypal"><img height="36px" src="Resources/support_paypal.svg" alt="PayPal donate button" /></a>
|
||||
<a href="https://www.buymeacoffee.com/toxblh" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" height="36px" ></a>
|
||||
<a href="https://www.patreon.com/bePatron?u=9900748"><img height="36px" src="https://c5.patreon.com/external/logo/become_a_patron_button.png" srcset="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png 2x"></a>
|
||||
<a href="https://www.producthunt.com/posts/my-touchbar-my-rules-mtmr">
|
||||
@ -27,11 +27,11 @@ My idea is to create a platform for creating plugins to customize the TouchBar.
|
||||
|
||||
## Installation
|
||||
|
||||
- Download lastest [release](https://github.com/Toxblh/MTMR/releases) (.dmg) from github
|
||||
- Or via Homebrew `brew cask install mtmr`
|
||||
- Download latest [release](https://github.com/Toxblh/MTMR/releases) (.dmg) from github
|
||||
- Or via Homebrew `brew install --cask mtmr`
|
||||
- [Dario Prski](https://medium.com/@urdigitalpulse) has written a [fantastic article on medium](https://medium.com/@urdigitalpulse/customise-your-macbook-pro-touch-bar-966998e606b5) that goes into more detail on installing MTMR
|
||||
|
||||
**On first install** you need to allow access for MTMR in Accessibility otherwise buttons like <kbd>Esc</kbd>, <kbd>Volume</kbd>, <kbd>Brightness</kbd> and other system keys won't work
|
||||
**On first install** you need to allow access for MTMR in Accessibility otherwise buttons like <kbd>Esc</kbd>, <kbd>Volume</kbd>, <kbd>Brightness</kbd> and other system keys won't work.
|
||||
|
||||
<p align="center">
|
||||
<img width="450" alt="screenshot 2019-02-24 at 23 19 20" src="https://user-images.githubusercontent.com/2198153/53307057-2b078200-388c-11e9-8212-8c2b1aff0aa6.png">
|
||||
@ -51,7 +51,7 @@ My idea is to create a platform for creating plugins to customize the TouchBar.
|
||||
|
||||
## Customization
|
||||
|
||||
MTMR preferences are stored under `~/Library/Application\ Support/MTMR/items.json`.
|
||||
MTMR preferences are stored in `~/Library/Application\ Support/MTMR/items.json`.
|
||||
|
||||
The pre-installed configuration contains less or more than you'll probably want, try to configure:
|
||||
|
||||
@ -73,6 +73,7 @@ The pre-installed configuration contains less or more than you'll probably want,
|
||||
|
||||
- timeButton
|
||||
- battery
|
||||
- cpu
|
||||
- currency
|
||||
- weather
|
||||
- yandexWeather
|
||||
@ -158,8 +159,8 @@ You may create as many `swipe` objects in the preset as you want.
|
||||
}
|
||||
```
|
||||
|
||||
> Note: appleScriptTitledButton can change its icon. To do it, you need to do the following things:
|
||||
1. Declarate dictionary of icons in `alternativeImages` field
|
||||
> Note: You can change appleScriptTitledButton's icon by following these steps:
|
||||
1. Declare dictionary of icons in `alternativeImages` field
|
||||
2. Make you script return array of two values - `{"TITLE", "IMAGE_LABEL"}`
|
||||
3. Make sure that your `IMAGE_LABEL` is declared in `alternativeImages` field
|
||||
|
||||
@ -186,10 +187,10 @@ Example:
|
||||
```
|
||||
|
||||
#### `shellScriptTitledButton`
|
||||
> Note: script may return also colors using escape sequences (read more here https://misc.flogisoft.com/bash/tip_colors_and_formatting)
|
||||
> Only "16 Colors" mode supported atm. If background color returned, button will pick it up as own background color.
|
||||
> Note: script may also use escape sequences to return colors (read https://misc.flogisoft.com/bash/tip_colors_and_formatting for more information)
|
||||
> "16 Colors" is the only mode supported presently. Buttons will set their own background color to the color returned.
|
||||
|
||||
Example of "CPU load" button which also changes color based on load value.
|
||||
Example of "CPU load" button which also changes color based on load value (Note: The native `cpu` plugin runs runs better):
|
||||
```js
|
||||
{
|
||||
"type": "shellScriptTitledButton",
|
||||
@ -246,9 +247,22 @@ To close a group, use the button:
|
||||
|
||||
## Native plugins
|
||||
|
||||
#### `cpu`
|
||||
|
||||
> Shows current CPU load in percent, changes color based on load value.
|
||||
> Has lower power consumption and higher stability than the shell-based solution.
|
||||
|
||||
```js
|
||||
{
|
||||
"type": "cpu",
|
||||
"refreshInterval": 3,
|
||||
"width": 80
|
||||
}
|
||||
```
|
||||
|
||||
#### `timeButton`
|
||||
|
||||
> Attention! Works not all: https://en.wikipedia.org/wiki/List_of_time_zone_abbreviations
|
||||
> NOTE: Some values don't work properly: https://en.wikipedia.org/wiki/List_of_time_zone_abbreviations
|
||||
|
||||
> formatTemplate examples: https://www.datetimeformatter.com/how-to-format-date-time-in-swift/
|
||||
|
||||
@ -266,22 +280,22 @@ To close a group, use the button:
|
||||
#### `weather`
|
||||
|
||||
> Provider: https://openweathermap.org \
|
||||
> Note: you need to register on https://openweathermap.org to get your API key \
|
||||
> Note: you may need to wait for near 20 mins until your API key will be activated by Openweathermap \
|
||||
> Note: you need to allow using "Location Services" in your Mac OS "Security & Privacy" settings for MTMR
|
||||
> Note: Register at https://openweathermap.org to get your API key \
|
||||
> Note: Wait for 20 minutes or so for Openweathermap to activate your API key.\
|
||||
> Note: Enable MTMR in "Location Services" in the "Security & Privacy" System Preferences pane
|
||||
|
||||
```js
|
||||
"type": "weather",
|
||||
"refreshInterval": 600, // in seconds
|
||||
"units": "metric", // or imperial
|
||||
"icon_type": "text" // or images
|
||||
"icon_type": "text", // or images
|
||||
"api_key": "" // you can get the key on openweather
|
||||
```
|
||||
|
||||
#### `yandexWeather` (experimental)
|
||||
|
||||
> Provider: https://yandex.ru/pogoda. One click to open up weather forecast in your browser. \
|
||||
> Note: you need to allow using "Location Services" in your Mac OS "Security & Privacy" settings for MTMR
|
||||
> Note: Enable MTMR in "Location Services" in the "Security & Privacy" System Preferences pane
|
||||
|
||||
```js
|
||||
"type": "yandexWeather",
|
||||
@ -316,7 +330,7 @@ To close a group, use the button:
|
||||
|
||||
#### `pomodoro`
|
||||
|
||||
> Pomodoro plugin. One click to start the work timer, longclick to start the rest timer. Click in progress for reset.
|
||||
> Pomodoro plugin. One tap starts the work timer, long-press to start the rest timer. Tap an in-progress timer to reset.
|
||||
|
||||
```js
|
||||
{
|
||||
@ -328,12 +342,13 @@ To close a group, use the button:
|
||||
|
||||
#### `network`
|
||||
|
||||
> Network plugin. The plugin to show usage a network
|
||||
> Network plugin. The plugin to show network usage
|
||||
|
||||
```js
|
||||
{
|
||||
"type": "network",
|
||||
"flip": true
|
||||
"flip": true,
|
||||
"units": "dynamic" // or B/s, KB/s, MB/s, GB/s
|
||||
},
|
||||
```
|
||||
|
||||
@ -351,15 +366,15 @@ To close a group, use the button:
|
||||
|
||||
#### `upnext`
|
||||
|
||||
> Calender next event plugin
|
||||
Displays upcoming events from MacOS Calendar. Does not display current event.
|
||||
> Calendar next event plugin
|
||||
Displays upcoming events from macOS Calendar. Does not display current event.
|
||||
|
||||
```js
|
||||
{
|
||||
"type": "upnext",
|
||||
"from": 0, // Lower bound of search range for next event in hours. Default 0 (current time)(can be negative to view events in the past)
|
||||
"to": 12, // Upper bounds of search range for next event in hours. Default 12 (12 hours in the future)
|
||||
"maxToShow": 3 // Limits the maximum number of events displayed. Default 3 (the first 3 upcoming events)
|
||||
"maxToShow": 3, // Limits the maximum number of events displayed. Default 3 (the first 3 upcoming events)
|
||||
"autoResize": false // If true, widget will expand to display all events. Default false (scrollable view within "width")
|
||||
},
|
||||
```
|
||||
@ -398,6 +413,7 @@ Displays upcoming events from MacOS Calendar. Does not display current event.
|
||||
```
|
||||
|
||||
- `keyPress`
|
||||
> https://eastmanreference.com/complete-list-of-applescript-key-codes
|
||||
|
||||
```json
|
||||
"action": "keyPress",
|
||||
@ -474,14 +490,21 @@ by using background with color "#000000" and bordered == false you can create bu
|
||||
}
|
||||
```
|
||||
|
||||
- `matchAppId` displays the button only when active app's id matches given regexp
|
||||
|
||||
```json
|
||||
"matchAppId": "Safari"
|
||||
```
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
#### If you can't open preferences:
|
||||
- Opening another program which can't edit text
|
||||
1. Open Terminal.app
|
||||
2. Put `open -a TextEdit ~/Library/Application\ Support/MTMR/items.json` command and press <kbd>Enter</kbd>
|
||||
|
||||
|
||||
|
||||
|
||||
#### Buttons or gestures doesn't work:
|
||||
- "After the last update my mtmr is not working anymore!"
|
||||
- "Buttons sometimes do not trigger action"
|
||||
|
||||
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 68 KiB |
1
Sparkle.framework/Autoupdate
Symbolic link
@ -0,0 +1 @@
|
||||
Versions/Current/Autoupdate
|
||||
1
Sparkle.framework/Updater.app
Symbolic link
@ -0,0 +1 @@
|
||||
Versions/Current/Updater.app
|
||||
@ -1,25 +0,0 @@
|
||||
//
|
||||
// SPUDownloader.h
|
||||
// Downloader
|
||||
//
|
||||
// Created by Mayur Pawashe on 4/1/16.
|
||||
// Copyright © 2016 Sparkle Project. All rights reserved.
|
||||
//
|
||||
|
||||
#if __has_feature(modules)
|
||||
@import Foundation;
|
||||
#else
|
||||
#import <Foundation/Foundation.h>
|
||||
#endif
|
||||
#import "SPUDownloaderProtocol.h"
|
||||
|
||||
@protocol SPUDownloaderDelegate;
|
||||
|
||||
// This object implements the protocol which we have defined. It provides the actual behavior for the service. It is 'exported' by the service to make it available to the process hosting the service over an NSXPCConnection.
|
||||
@interface SPUDownloader : NSObject <SPUDownloaderProtocol>
|
||||
|
||||
// Due to XPC remote object reasons, this delegate is strongly referenced
|
||||
// Invoke cleanup when done with this instance
|
||||
- (instancetype)initWithDelegate:(id <SPUDownloaderDelegate>)delegate;
|
||||
|
||||
@end
|
||||
@ -1,38 +0,0 @@
|
||||
//
|
||||
// SPUDownloaderDelegate.h
|
||||
// Sparkle
|
||||
//
|
||||
// Created by Mayur Pawashe on 4/1/16.
|
||||
// Copyright © 2016 Sparkle Project. All rights reserved.
|
||||
//
|
||||
|
||||
#if __has_feature(modules)
|
||||
@import Foundation;
|
||||
#else
|
||||
#import <Foundation/Foundation.h>
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class SPUDownloadData;
|
||||
|
||||
@protocol SPUDownloaderDelegate <NSObject>
|
||||
|
||||
// This is only invoked for persistent downloads
|
||||
- (void)downloaderDidSetDestinationName:(NSString *)destinationName temporaryDirectory:(NSString *)temporaryDirectory;
|
||||
|
||||
// Under rare cases, this may be called more than once, in which case the current progress should be reset back to 0
|
||||
// This is only invoked for persistent downloads
|
||||
- (void)downloaderDidReceiveExpectedContentLength:(int64_t)expectedContentLength;
|
||||
|
||||
// This is only invoked for persistent downloads
|
||||
- (void)downloaderDidReceiveDataOfLength:(uint64_t)length;
|
||||
|
||||
// downloadData is nil if this is a persisent download, otherwise it's non-nil if it's a temporary download
|
||||
- (void)downloaderDidFinishWithTemporaryDownloadData:(SPUDownloadData * _Nullable)downloadData;
|
||||
|
||||
- (void)downloaderDidFailWithError:(NSError *)error;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -1,13 +0,0 @@
|
||||
//
|
||||
// SPUDownloaderDeprecated.h
|
||||
// Sparkle
|
||||
//
|
||||
// Created by Deadpikle on 12/20/17.
|
||||
// Copyright © 2017 Sparkle Project. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SPUDownloader.h"
|
||||
|
||||
@interface SPUDownloaderDeprecated : SPUDownloader <SPUDownloaderProtocol>
|
||||
|
||||
@end
|
||||
@ -1,34 +0,0 @@
|
||||
//
|
||||
// SPUDownloaderProtocol.h
|
||||
// PersistentDownloader
|
||||
//
|
||||
// Created by Mayur Pawashe on 4/1/16.
|
||||
// Copyright © 2016 Sparkle Project. All rights reserved.
|
||||
//
|
||||
|
||||
#if __has_feature(modules)
|
||||
@import Foundation;
|
||||
#else
|
||||
#import <Foundation/Foundation.h>
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class SPUURLRequest;
|
||||
|
||||
// The protocol that this service will vend as its API. This header file will also need to be visible to the process hosting the service.
|
||||
@protocol SPUDownloaderProtocol
|
||||
|
||||
- (void)startPersistentDownloadWithRequest:(SPUURLRequest *)request bundleIdentifier:(NSString *)bundleIdentifier desiredFilename:(NSString *)desiredFilename;
|
||||
|
||||
- (void)startTemporaryDownloadWithRequest:(SPUURLRequest *)request;
|
||||
|
||||
- (void)downloadDidFinish;
|
||||
|
||||
- (void)cleanup;
|
||||
|
||||
- (void)cancel;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -1,20 +0,0 @@
|
||||
//
|
||||
// SPUDownloaderSession.h
|
||||
// Sparkle
|
||||
//
|
||||
// Created by Deadpikle on 12/20/17.
|
||||
// Copyright © 2017 Sparkle Project. All rights reserved.
|
||||
//
|
||||
|
||||
#if __has_feature(modules)
|
||||
@import Foundation;
|
||||
#else
|
||||
#import <Foundation/Foundation.h>
|
||||
#endif
|
||||
#import "SPUDownloader.h"
|
||||
#import "SPUDownloaderProtocol.h"
|
||||
|
||||
NS_CLASS_AVAILABLE(NSURLSESSION_AVAILABLE, 7_0)
|
||||
@interface SPUDownloaderSession : SPUDownloader <SPUDownloaderProtocol>
|
||||
|
||||
@end
|
||||
@ -1,35 +0,0 @@
|
||||
//
|
||||
// SPUURLRequest.h
|
||||
// Sparkle
|
||||
//
|
||||
// Created by Mayur Pawashe on 5/19/16.
|
||||
// Copyright © 2016 Sparkle Project. All rights reserved.
|
||||
//
|
||||
|
||||
#if __has_feature(modules)
|
||||
@import Foundation;
|
||||
#else
|
||||
#import <Foundation/Foundation.h>
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// A class that wraps NSURLRequest and implements NSSecureCoding
|
||||
// This class exists because NSURLRequest did not support NSSecureCoding in macOS 10.8
|
||||
// I have not verified if NSURLRequest in 10.9 implements NSSecureCoding or not
|
||||
@interface SPUURLRequest : NSObject <NSSecureCoding>
|
||||
|
||||
// Creates a new URL request
|
||||
// Only these properties are currently tracked:
|
||||
// * URL
|
||||
// * Cache policy
|
||||
// * Timeout interval
|
||||
// * HTTP header fields
|
||||
// * networkServiceType
|
||||
+ (instancetype)URLRequestWithRequest:(NSURLRequest *)request;
|
||||
|
||||
@property (nonatomic, readonly) NSURLRequest *request;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -1,35 +0,0 @@
|
||||
//
|
||||
// SUAppcast.h
|
||||
// Sparkle
|
||||
//
|
||||
// Created by Andy Matuschak on 3/12/06.
|
||||
// Copyright 2006 Andy Matuschak. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef SUAPPCAST_H
|
||||
#define SUAPPCAST_H
|
||||
|
||||
#if __has_feature(modules)
|
||||
@import Foundation;
|
||||
#else
|
||||
#import <Foundation/Foundation.h>
|
||||
#endif
|
||||
#import "SUExport.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class SUAppcastItem;
|
||||
SU_EXPORT @interface SUAppcast : NSObject
|
||||
|
||||
@property (copy, nullable) NSString *userAgentString;
|
||||
@property (copy, nullable) NSDictionary<NSString *, NSString *> *httpHeaders;
|
||||
|
||||
- (void)fetchAppcastFromURL:(NSURL *)url inBackground:(BOOL)bg completionBlock:(void (^)(NSError *_Nullable))err;
|
||||
- (SUAppcast *)copyWithoutDeltaUpdates;
|
||||
|
||||
@property (readonly, copy, nullable) NSArray *items;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif
|
||||
@ -1,51 +0,0 @@
|
||||
//
|
||||
// SUAppcastItem.h
|
||||
// Sparkle
|
||||
//
|
||||
// Created by Andy Matuschak on 3/12/06.
|
||||
// Copyright 2006 Andy Matuschak. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef SUAPPCASTITEM_H
|
||||
#define SUAPPCASTITEM_H
|
||||
|
||||
#if __has_feature(modules)
|
||||
@import Foundation;
|
||||
#else
|
||||
#import <Foundation/Foundation.h>
|
||||
#endif
|
||||
#import "SUExport.h"
|
||||
|
||||
SU_EXPORT @interface SUAppcastItem : NSObject
|
||||
@property (copy, readonly) NSString *title;
|
||||
@property (copy, readonly) NSString *dateString;
|
||||
@property (copy, readonly) NSString *itemDescription;
|
||||
@property (strong, readonly) NSURL *releaseNotesURL;
|
||||
@property (copy, readonly) NSString *DSASignature;
|
||||
@property (copy, readonly) NSString *minimumSystemVersion;
|
||||
@property (copy, readonly) NSString *maximumSystemVersion;
|
||||
@property (strong, readonly) NSURL *fileURL;
|
||||
@property (nonatomic, readonly) uint64_t contentLength;
|
||||
@property (copy, readonly) NSString *versionString;
|
||||
@property (copy, readonly) NSString *osString;
|
||||
@property (copy, readonly) NSString *displayVersionString;
|
||||
@property (copy, readonly) NSDictionary *deltaUpdates;
|
||||
@property (strong, readonly) NSURL *infoURL;
|
||||
|
||||
// Initializes with data from a dictionary provided by the RSS class.
|
||||
- (instancetype)initWithDictionary:(NSDictionary *)dict;
|
||||
- (instancetype)initWithDictionary:(NSDictionary *)dict failureReason:(NSString **)error;
|
||||
|
||||
@property (getter=isDeltaUpdate, readonly) BOOL deltaUpdate;
|
||||
@property (getter=isCriticalUpdate, readonly) BOOL criticalUpdate;
|
||||
@property (getter=isMacOsUpdate, readonly) BOOL macOsUpdate;
|
||||
@property (getter=isInformationOnlyUpdate, readonly) BOOL informationOnlyUpdate;
|
||||
|
||||
// Returns the dictionary provided in initWithDictionary; this might be useful later for extensions.
|
||||
@property (readonly, copy) NSDictionary *propertiesDictionary;
|
||||
|
||||
- (NSURL *)infoURL;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@ -1,22 +0,0 @@
|
||||
//
|
||||
// SUCodeSigningVerifier.h
|
||||
// Sparkle
|
||||
//
|
||||
// Created by Andy Matuschak on 7/5/12.
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef SUCODESIGNINGVERIFIER_H
|
||||
#define SUCODESIGNINGVERIFIER_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "SUExport.h"
|
||||
|
||||
SU_EXPORT @interface SUCodeSigningVerifier : NSObject
|
||||
+ (BOOL)codeSignatureAtBundleURL:(NSURL *)oldBundlePath matchesSignatureAtBundleURL:(NSURL *)newBundlePath error:(NSError **)error;
|
||||
+ (BOOL)codeSignatureIsValidAtBundleURL:(NSURL *)bundlePath error:(NSError **)error;
|
||||
+ (BOOL)bundleAtURLIsCodeSigned:(NSURL *)bundlePath;
|
||||
+ (NSDictionary *)codeSignatureInfoAtBundleURL:(NSURL *)bundlePath;
|
||||
@end
|
||||
|
||||
#endif
|
||||
@ -1,56 +0,0 @@
|
||||
//
|
||||
// SUErrors.h
|
||||
// Sparkle
|
||||
//
|
||||
// Created by C.W. Betts on 10/13/14.
|
||||
// Copyright (c) 2014 Sparkle Project. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef SUERRORS_H
|
||||
#define SUERRORS_H
|
||||
|
||||
#if __has_feature(modules)
|
||||
@import Foundation;
|
||||
#else
|
||||
#import <Foundation/Foundation.h>
|
||||
#endif
|
||||
#import "SUExport.h"
|
||||
|
||||
/**
|
||||
* Error domain used by Sparkle
|
||||
*/
|
||||
SU_EXPORT extern NSString *const SUSparkleErrorDomain;
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wc++98-compat"
|
||||
typedef NS_ENUM(OSStatus, SUError) {
|
||||
// Appcast phase errors.
|
||||
SUAppcastParseError = 1000,
|
||||
SUNoUpdateError = 1001,
|
||||
SUAppcastError = 1002,
|
||||
SURunningFromDiskImageError = 1003,
|
||||
|
||||
// Download phase errors.
|
||||
SUTemporaryDirectoryError = 2000,
|
||||
SUDownloadError = 2001,
|
||||
|
||||
// Extraction phase errors.
|
||||
SUUnarchivingError = 3000,
|
||||
SUSignatureError = 3001,
|
||||
|
||||
// Installation phase errors.
|
||||
SUFileCopyFailure = 4000,
|
||||
SUAuthenticationFailure = 4001,
|
||||
SUMissingUpdateError = 4002,
|
||||
SUMissingInstallerToolError = 4003,
|
||||
SURelaunchError = 4004,
|
||||
SUInstallationError = 4005,
|
||||
SUDowngradeError = 4006,
|
||||
SUInstallationCancelledError = 4007,
|
||||
|
||||
// System phase errors
|
||||
SUSystemPowerOffError = 5000
|
||||
};
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
#endif
|
||||
@ -1,52 +0,0 @@
|
||||
//
|
||||
// SUStandardVersionComparator.h
|
||||
// Sparkle
|
||||
//
|
||||
// Created by Andy Matuschak on 12/21/07.
|
||||
// Copyright 2007 Andy Matuschak. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef SUSTANDARDVERSIONCOMPARATOR_H
|
||||
#define SUSTANDARDVERSIONCOMPARATOR_H
|
||||
|
||||
#if __has_feature(modules)
|
||||
@import Foundation;
|
||||
#else
|
||||
#import <Foundation/Foundation.h>
|
||||
#endif
|
||||
#import "SUExport.h"
|
||||
#import "SUVersionComparisonProtocol.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/*!
|
||||
Sparkle's default version comparator.
|
||||
|
||||
This comparator is adapted from MacPAD, by Kevin Ballard.
|
||||
It's "dumb" in that it does essentially string comparison,
|
||||
in components split by character type.
|
||||
*/
|
||||
SU_EXPORT @interface SUStandardVersionComparator : NSObject <SUVersionComparison>
|
||||
|
||||
/*!
|
||||
Initializes a new instance of the standard version comparator.
|
||||
*/
|
||||
- (instancetype)init;
|
||||
|
||||
/*!
|
||||
Returns a singleton instance of the comparator.
|
||||
|
||||
It is usually preferred to alloc/init new a comparator instead.
|
||||
*/
|
||||
+ (SUStandardVersionComparator *)defaultComparator;
|
||||
|
||||
/*!
|
||||
Compares version strings through textual analysis.
|
||||
|
||||
See the implementation for more details.
|
||||
*/
|
||||
- (NSComparisonResult)compareVersion:(NSString *)versionA toVersion:(NSString *)versionB;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
#endif
|
||||
@ -1,33 +0,0 @@
|
||||
//
|
||||
// Sparkle.h
|
||||
// Sparkle
|
||||
//
|
||||
// Created by Andy Matuschak on 3/16/06. (Modified by CDHW on 23/12/07)
|
||||
// Copyright 2006 Andy Matuschak. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef SPARKLE_H
|
||||
#define SPARKLE_H
|
||||
|
||||
// This list should include the shared headers. It doesn't matter if some of them aren't shared (unless
|
||||
// there are name-space collisions) so we can list all of them to start with:
|
||||
|
||||
#import "SUAppcast.h"
|
||||
#import "SUAppcastItem.h"
|
||||
#import "SUStandardVersionComparator.h"
|
||||
#import "SUUpdater.h"
|
||||
#import "SUUpdaterDelegate.h"
|
||||
#import "SUVersionComparisonProtocol.h"
|
||||
#import "SUVersionDisplayProtocol.h"
|
||||
#import "SUErrors.h"
|
||||
|
||||
#import "SPUDownloader.h"
|
||||
#import "SPUDownloaderDelegate.h"
|
||||
#import "SPUDownloaderDeprecated.h"
|
||||
#import "SPUDownloadData.h"
|
||||
#import "SPUDownloaderProtocol.h"
|
||||
#import "SPUDownloaderSession.h"
|
||||
#import "SPUURLRequest.h"
|
||||
#import "SUCodeSigningVerifier.h"
|
||||
|
||||
#endif
|
||||
@ -1,21 +0,0 @@
|
||||
//
|
||||
// SUUnarchiver.h
|
||||
// Sparkle
|
||||
//
|
||||
// Created by Andy Matuschak on 3/16/06.
|
||||
// Copyright 2006 Andy Matuschak. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol SUUnarchiverProtocol;
|
||||
|
||||
@interface SUUnarchiver : NSObject
|
||||
|
||||
+ (nullable id <SUUnarchiverProtocol>)unarchiverForPath:(NSString *)path updatingHostBundlePath:(nullable NSString *)hostPath decryptionPassword:(nullable NSString *)decryptionPassword;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
BIN
Sparkle.framework/Versions/A/Resources/SUStatus.nib
generated
@ -1 +0,0 @@
|
||||
fr.lproj
|
||||