tibodog 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -70,9 +70,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
70
70
|
private var panel: NotchPanel?
|
|
71
71
|
private var geometry: NotchGeometry?
|
|
72
72
|
private var observers: [Any] = []
|
|
73
|
+
private var signalSources: [DispatchSourceSignal] = []
|
|
73
74
|
|
|
74
75
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
|
75
76
|
NSApp.setActivationPolicy(.accessory)
|
|
77
|
+
quitOnSignals()
|
|
76
78
|
let store = UsageStore.shared
|
|
77
79
|
store.start()
|
|
78
80
|
rebuildPanel()
|
|
@@ -139,6 +141,17 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
139
141
|
return out
|
|
140
142
|
}
|
|
141
143
|
|
|
144
|
+
/// `pkill`, Ctrl-C or a logout quit like the menu's Quit, so the Now Playing helper stops with us.
|
|
145
|
+
private func quitOnSignals() {
|
|
146
|
+
for sig in [SIGTERM, SIGINT, SIGHUP] {
|
|
147
|
+
signal(sig, SIG_IGN)
|
|
148
|
+
let source = DispatchSource.makeSignalSource(signal: sig, queue: .main)
|
|
149
|
+
source.setEventHandler { NSApp.terminate(nil) }
|
|
150
|
+
source.resume()
|
|
151
|
+
signalSources.append(source)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
142
155
|
func applicationWillTerminate(_ notification: Notification) {
|
|
143
156
|
NowPlayingMonitor.shared.stop()
|
|
144
157
|
}
|
|
@@ -77,6 +77,21 @@ final class NowPlayingMonitor: ObservableObject {
|
|
|
77
77
|
return nil
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/// Runs the adapter (same pid, via exec) with a tiny watcher beside it that ends the adapter once
|
|
81
|
+
/// TiboDog is gone, so a crash or `kill -9` never leaves a perl process streaming to nobody.
|
|
82
|
+
static let watchdog = #"""
|
|
83
|
+
my $app = getppid(); my $adapter = $$;
|
|
84
|
+
if (fork() == 0) {
|
|
85
|
+
open STDOUT, '>', '/dev/null'; open STDIN, '<', '/dev/null';
|
|
86
|
+
while (1) {
|
|
87
|
+
sleep 1;
|
|
88
|
+
exit 0 unless kill 0, $adapter;
|
|
89
|
+
if (!kill 0, $app) { kill 'TERM', $adapter; exit 0 }
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
exec '/usr/bin/perl', @ARGV or die "exec: $!";
|
|
93
|
+
"""#
|
|
94
|
+
|
|
80
95
|
// MARK: Lifecycle
|
|
81
96
|
|
|
82
97
|
func start() {
|
|
@@ -100,7 +115,7 @@ final class NowPlayingMonitor: ObservableObject {
|
|
|
100
115
|
}
|
|
101
116
|
let proc = Process()
|
|
102
117
|
proc.executableURL = URL(fileURLWithPath: "/usr/bin/perl")
|
|
103
|
-
proc.arguments = [paths.script.path, paths.framework.path, "stream", "--debounce=150"]
|
|
118
|
+
proc.arguments = ["-e", Self.watchdog, paths.script.path, paths.framework.path, "stream", "--debounce=150"]
|
|
104
119
|
let p = Pipe()
|
|
105
120
|
proc.standardOutput = p
|
|
106
121
|
proc.standardError = FileHandle.nullDevice
|
|
@@ -77,6 +77,16 @@ final class ResetTracker: ObservableObject {
|
|
|
77
77
|
guard !redeeming else { return }
|
|
78
78
|
redeeming = true
|
|
79
79
|
lastOutcome = nil
|
|
80
|
+
if UsageStore.mock {
|
|
81
|
+
// Demos and screenshots: look like Codex said "reset", never spend a real credit.
|
|
82
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.9) {
|
|
83
|
+
self.redeeming = false
|
|
84
|
+
self.lastOutcome = "reset"
|
|
85
|
+
self.credits = max(0, (self.credits ?? 1) - 1)
|
|
86
|
+
UsageStore.shared.mockReset()
|
|
87
|
+
}
|
|
88
|
+
return
|
|
89
|
+
}
|
|
80
90
|
let key = UUID().uuidString
|
|
81
91
|
Task.detached(priority: .userInitiated) {
|
|
82
92
|
var outcome = (try? CodexUsageAPI.consumeResetCredit(idempotencyKey: key)) ?? "error"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Foundation
|
|
2
|
+
import SwiftUI
|
|
2
3
|
import Combine
|
|
3
4
|
import ServiceManagement
|
|
4
5
|
|
|
@@ -180,6 +181,18 @@ final class UsageStore: ObservableObject {
|
|
|
180
181
|
return fresh
|
|
181
182
|
}
|
|
182
183
|
|
|
184
|
+
/// TIBODOG_MOCK=1: what spending a reset credit looks like, for demos (both windows start over).
|
|
185
|
+
func mockReset() {
|
|
186
|
+
guard Self.mock || Self.mockUsageOnly else { return }
|
|
187
|
+
withAnimation(.easeOut(duration: 0.6)) {
|
|
188
|
+
usage?.fiveHour?.utilization = 0
|
|
189
|
+
usage?.fiveHour?.resetsAt = Date().addingTimeInterval(5 * 3600)
|
|
190
|
+
usage?.sevenDay?.utilization = 0
|
|
191
|
+
usage?.sevenDay?.resetsAt = Date().addingTimeInterval(7 * 86400)
|
|
192
|
+
}
|
|
193
|
+
lastUpdated = Date()
|
|
194
|
+
}
|
|
195
|
+
|
|
183
196
|
/// Short text for the menu bar item, e.g. "42% · 18%".
|
|
184
197
|
var menuBarTitle: String {
|
|
185
198
|
guard let u = usage else { return errorMessage == nil ? "…" : "!" }
|
package/package.json
CHANGED