Skip to content

Commit 672a198

Browse files
committed
refactor: replace shell commands with Unix socket communication
- Rename project from 'app' to 'BattGUI' - Implement direct Unix socket communication with batt daemon - Replace shell command execution with socket API for setting battery limits - Add proper error handling and nullability annotations - Fix display of battery power and amperage values - Improve threshold checks in power flow visualization - Move battery info updates to run before async tasks - Fix default values for various measurements
1 parent bd7b703 commit 672a198

File tree

12 files changed

+254
-76
lines changed

12 files changed

+254
-76
lines changed
Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
/* End PBXContainerItemProxy section */
2525

2626
/* Begin PBXFileReference section */
27-
9DB688042D8E7A53007083F0 /* app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = app.app; sourceTree = BUILT_PRODUCTS_DIR; };
27+
9DB688042D8E7A53007083F0 /* BattGUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BattGUI.app; sourceTree = BUILT_PRODUCTS_DIR; };
2828
9DB688152D8E7A54007083F0 /* appTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = appTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
2929
9DB6881F2D8E7A54007083F0 /* appUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = appUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3030
/* End PBXFileReference section */
@@ -85,7 +85,7 @@
8585
9DB688052D8E7A53007083F0 /* Products */ = {
8686
isa = PBXGroup;
8787
children = (
88-
9DB688042D8E7A53007083F0 /* app.app */,
88+
9DB688042D8E7A53007083F0 /* BattGUI.app */,
8989
9DB688152D8E7A54007083F0 /* appTests.xctest */,
9090
9DB6881F2D8E7A54007083F0 /* appUITests.xctest */,
9191
);
@@ -95,9 +95,9 @@
9595
/* End PBXGroup section */
9696

9797
/* Begin PBXNativeTarget section */
98-
9DB688032D8E7A53007083F0 /* app */ = {
98+
9DB688032D8E7A53007083F0 /* BattGUI */ = {
9999
isa = PBXNativeTarget;
100-
buildConfigurationList = 9DB688292D8E7A54007083F0 /* Build configuration list for PBXNativeTarget "app" */;
100+
buildConfigurationList = 9DB688292D8E7A54007083F0 /* Build configuration list for PBXNativeTarget "BattGUI" */;
101101
buildPhases = (
102102
9DB688002D8E7A53007083F0 /* Sources */,
103103
9DB688012D8E7A53007083F0 /* Frameworks */,
@@ -110,11 +110,11 @@
110110
fileSystemSynchronizedGroups = (
111111
9DB688062D8E7A53007083F0 /* app */,
112112
);
113-
name = app;
113+
name = BattGUI;
114114
packageProductDependencies = (
115115
);
116116
productName = app;
117-
productReference = 9DB688042D8E7A53007083F0 /* app.app */;
117+
productReference = 9DB688042D8E7A53007083F0 /* BattGUI.app */;
118118
productType = "com.apple.product-type.application";
119119
};
120120
9DB688142D8E7A54007083F0 /* appTests */ = {
@@ -187,7 +187,7 @@
187187
};
188188
};
189189
};
190-
buildConfigurationList = 9DB687FF2D8E7A53007083F0 /* Build configuration list for PBXProject "app" */;
190+
buildConfigurationList = 9DB687FF2D8E7A53007083F0 /* Build configuration list for PBXProject "BattGUI" */;
191191
developmentRegion = en;
192192
hasScannedForEncodings = 0;
193193
knownRegions = (
@@ -202,7 +202,7 @@
202202
projectDirPath = "";
203203
projectRoot = "";
204204
targets = (
205-
9DB688032D8E7A53007083F0 /* app */,
205+
9DB688032D8E7A53007083F0 /* BattGUI */,
206206
9DB688142D8E7A54007083F0 /* appTests */,
207207
9DB6881E2D8E7A54007083F0 /* appUITests */,
208208
);
@@ -260,12 +260,12 @@
260260
/* Begin PBXTargetDependency section */
261261
9DB688172D8E7A54007083F0 /* PBXTargetDependency */ = {
262262
isa = PBXTargetDependency;
263-
target = 9DB688032D8E7A53007083F0 /* app */;
263+
target = 9DB688032D8E7A53007083F0 /* BattGUI */;
264264
targetProxy = 9DB688162D8E7A54007083F0 /* PBXContainerItemProxy */;
265265
};
266266
9DB688212D8E7A54007083F0 /* PBXTargetDependency */ = {
267267
isa = PBXTargetDependency;
268-
target = 9DB688032D8E7A53007083F0 /* app */;
268+
target = 9DB688032D8E7A53007083F0 /* BattGUI */;
269269
targetProxy = 9DB688202D8E7A54007083F0 /* PBXContainerItemProxy */;
270270
};
271271
/* End PBXTargetDependency section */
@@ -398,6 +398,7 @@
398398
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
399399
CLANG_ENABLE_MODULES = YES;
400400
CODE_SIGN_ENTITLEMENTS = app/app.entitlements;
401+
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
401402
CODE_SIGN_STYLE = Automatic;
402403
COMBINE_HIDPI_IMAGES = YES;
403404
CURRENT_PROJECT_VERSION = 1;
@@ -431,6 +432,7 @@
431432
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
432433
CLANG_ENABLE_MODULES = YES;
433434
CODE_SIGN_ENTITLEMENTS = app/app.entitlements;
435+
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
434436
CODE_SIGN_STYLE = Automatic;
435437
COMBINE_HIDPI_IMAGES = YES;
436438
CURRENT_PROJECT_VERSION = 1;
@@ -527,7 +529,7 @@
527529
/* End XCBuildConfiguration section */
528530

529531
/* Begin XCConfigurationList section */
530-
9DB687FF2D8E7A53007083F0 /* Build configuration list for PBXProject "app" */ = {
532+
9DB687FF2D8E7A53007083F0 /* Build configuration list for PBXProject "BattGUI" */ = {
531533
isa = XCConfigurationList;
532534
buildConfigurations = (
533535
9DB688272D8E7A54007083F0 /* Debug */,
@@ -536,7 +538,7 @@
536538
defaultConfigurationIsVisible = 0;
537539
defaultConfigurationName = Debug;
538540
};
539-
9DB688292D8E7A54007083F0 /* Build configuration list for PBXNativeTarget "app" */ = {
541+
9DB688292D8E7A54007083F0 /* Build configuration list for PBXNativeTarget "BattGUI" */ = {
540542
isa = XCConfigurationList;
541543
buildConfigurations = (
542544
9DB6882A2D8E7A54007083F0 /* Debug */,

app.xcodeproj/project.xcworkspace/contents.xcworkspacedata renamed to BattGUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata

File renamed without changes.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict/>
5+
</plist>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>BuildLocationStyle</key>
6+
<string>UseAppPreferences</string>
7+
<key>CustomBuildLocationType</key>
8+
<string>RelativeToDerivedData</string>
9+
<key>DerivedDataLocationStyle</key>
10+
<string>Default</string>
11+
<key>ShowSharedSchemesAutomaticallyEnabled</key>
12+
<true/>
13+
</dict>
14+
</plist>

app.xcodeproj/xcuserdata/tsunami.xcuserdatad/xcschemes/xcschememanagement.plist renamed to BattGUI.xcodeproj/xcuserdata/tsunami.xcuserdatad/xcschemes/xcschememanagement.plist

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
<dict>
55
<key>SchemeUserState</key>
66
<dict>
7+
<key>BattGUI.xcscheme_^#shared#^_</key>
8+
<dict>
9+
<key>orderHint</key>
10+
<integer>0</integer>
11+
</dict>
712
<key>app.xcscheme_^#shared#^_</key>
813
<dict>
914
<key>orderHint</key>
Binary file not shown.

app/Battery.swift

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,14 @@ class BatteryInfoManager: ObservableObject {
7575
// captures their output, and then calls `parseBatteryInfo` on the main thread
7676
// to update the published properties.
7777
func updateBatteryInfo() {
78+
loadwatt = Double(getRawSystemPower())
79+
inputwatt = Double(getAdapterPower())
80+
amperage = Double(getAdapterAmperage())
81+
voltage = Double(getAdapterVoltage())
82+
batteryVoltage = Double(getBatteryVoltage())
83+
batteryAmperage = Double(getBatteryAmperage())
84+
batteryPower = Double(getBatteryPower())
85+
isCharging = getChargingStatus().contains("Charging")
7886
Task {
7987
let process = Process()
8088
process.executableURL = URL(fileURLWithPath: "/bin/zsh")
@@ -97,15 +105,6 @@ class BatteryInfoManager: ObservableObject {
97105

98106
@MainActor
99107
private func parseBatteryInfo(from output: String) {
100-
loadwatt = Double(getRawSystemPower())
101-
inputwatt = Double(getAdapterPower())
102-
amperage = Double(getAdapterAmperage())
103-
voltage = Double(getAdapterVoltage())
104-
batteryVoltage = Double(getBatteryVoltage())
105-
batteryAmperage = Double(getBatteryAmperage())
106-
batteryPower = Double(getBatteryPower())
107-
isCharging = getChargingStatus().contains("Charging")
108-
109108
// --- Parse Temperature (ioreg - VirtualTemperature seems more reliable than power_info's) ---
110109
if let match = output.range(of: "\"VirtualTemperature\" = ([0-9]+)", options: .regularExpression) {
111110
let valueStr = String(output[match]).components(separatedBy: "=").last?.trimmingCharacters(in: .whitespaces) ?? "0"

app/ContentView.swift

Lines changed: 44 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ struct ContentView: View {
3232
@State private var num: Float = 70
3333
@State private var iconwidth: CGFloat = 15
3434
@State private var debounceTask: Task<Void, Never>?
35+
private let socketPath = "/var/run/batt.sock" // Define socket path constant
3536

3637
private func quitApp() {
3738
NSApplication.shared.terminate(nil)
@@ -54,17 +55,17 @@ struct ContentView: View {
5455

5556
Spacer()
5657

57-
Button(action: {}) {
58-
HStack {
59-
// Localized Text and flexible frame
60-
Text(LocalizedStringKey("enable.power.button")).font(.system(size: 13)).bold()
61-
}.frame(minWidth: 70, idealWidth: 90, maxWidth: .infinity, minHeight: 30).background( // Use minWidth
62-
RoundedRectangle(cornerRadius: 10, style: .continuous)
63-
.fill(.ultraThickMaterial) // Changed to transparent
64-
)
65-
}.padding(EdgeInsets(top: 10, leading: 0, bottom: 0, trailing: 0)).buttonStyle(PlainButtonStyle())
66-
67-
Spacer()
58+
// Button(action: {}) {
59+
// HStack {
60+
// // Localized Text and flexible frame
61+
// Text(LocalizedStringKey("enable.power.button")).font(.system(size: 13)).bold()
62+
// }.frame(minWidth: 70, idealWidth: 90, maxWidth: .infinity, minHeight: 30).background( // Use minWidth
63+
// RoundedRectangle(cornerRadius: 10, style: .continuous)
64+
// .fill(.ultraThickMaterial) // Changed to transparent
65+
// )
66+
// }.padding(EdgeInsets(top: 10, leading: 0, bottom: 0, trailing: 0)).buttonStyle(PlainButtonStyle())
67+
//
68+
// Spacer()
6869

6970
Button(action: { quitApp() }) {
7071
HStack {
@@ -81,38 +82,39 @@ struct ContentView: View {
8182
Slider(value: $num, in: 10...99, onEditingChanged: {editing in
8283
if !editing {
8384
debounceTask?.cancel()
84-
debounceTask = Task.detached(priority: .medium) {
85-
try? await Task.sleep(nanoseconds: 000_000_000) // 500ms
86-
//guard let battURL = Bundle.main.url(forResource: "batt", withExtension: nil) else {
87-
// // Localized fatalError
88-
// fatalError(NSLocalizedString("executable.not.found.error", comment: "Error when batt executable is missing"))
89-
//}
90-
let pr = Process()
91-
//pr.launchPath = "/usr/bin/osascript"
92-
//pr.arguments = ["-e", "do shell script \"source ~/.zshrc; sudo batt install --allow-non-root-access\" with prompt "安装 daemon 辅助程序需要授权" with administrator privileges"]
93-
pr.executableURL = URL(fileURLWithPath: "/bin/zsh")
94-
//pr.arguments = ["-c", "source ~/.zshrc; \(battURL.path)
95-
//pr.arguments = ["-c", "source ~/.zshrc; \(battURL.path) limit \(await Int(num))"]
96-
pr.arguments = ["-c", "source ~/.zshrc; curl -Lv --unix-socket /var/run/batt.sock -XPUT http://localhost/limit --data \(await Int(num))"]
97-
let pi = Pipe()
98-
pr.standardOutput = pi
99-
pr.standardError = pi
85+
// Use Task for background execution, no need for .detached unless specific priority is crucial
86+
debounceTask = Task {
87+
// Define error pointer for C function
88+
var nsError: NSError?
89+
90+
// Call the C function with the defined path as a C string
91+
let result = sendCommandToUnixSocket(Int(num), socketPath.cString(using: .utf8)!, &nsError)
10092

101-
do {
102-
try pr.run()
103-
pr.waitUntilExit()
104-
let dt = pi.fileHandleForReading.readDataToEndOfFile()
105-
let re = String(data: dt, encoding: .utf8) ?? ""
106-
if let _ = extractSet(from: re) {
107-
//print(sn)
93+
// Switch back to the main thread to handle UI or print logs
94+
await MainActor.run {
95+
if let error = nsError {
96+
// An error occurred during the socket operation
97+
// Use localized string with formatting
98+
print(String(format: NSLocalizedString("socket.command.failed.error", comment: "Error message when socket command fails"), error.localizedDescription))
99+
// TODO: Add UI feedback for the error (e.g., show an alert)
100+
} else if let response = result {
101+
// Successfully sent command and got response
102+
print("Socket command successful. Response: \(response)")
103+
// Optional: Parse response using extractSet(from: response) if needed
104+
if let limitSet = extractSet(from: response) {
105+
print("Successfully set limit to \(limitSet)%")
106+
// TODO: Add UI feedback for success if desired
107+
} else {
108+
// Use localized string if available, otherwise generic message
109+
// Consider adding a specific localized string for this case
110+
print(NSLocalizedString("limit.set.confirmation.parse.error", comment: "Could not parse confirmation from daemon response: \(response)"))
111+
// TODO: Add UI feedback for parsing failure if desired
112+
}
108113
} else {
109-
// Localized print
110-
print(NSLocalizedString("cannot.set.charge.limit.error", comment: "Error message when setting charge limit fails"))
114+
// Should not happen if ObjC code is correct and no error is set, but handle defensively
115+
print(NSLocalizedString("socket.command.unknown.error", comment: "Unknown error during socket command"))
116+
// TODO: Add UI feedback for this unexpected case
111117
}
112-
113-
} catch {
114-
// Localized print with formatting
115-
print(String(format: NSLocalizedString("execution.failed.error", comment: "Generic execution failure message"), error.localizedDescription))
116118
}
117119
}
118120
}
@@ -193,13 +195,13 @@ struct ContentView: View {
193195
Image(systemName: Icons.iconLight).frame(width: iconwidth, alignment: .center)
194196
Text(LocalizedStringKey("battery.power.label")).font(.system(size: 13)).bold() // Localized
195197
Spacer()
196-
Text(String(format: "%.2f", batteryManager.batteryPower) + " W").font(.system(size: 13)).frame(alignment: .trailing)
198+
Text(String(format: "%.2f", batteryManager.batteryPower < 0 ? batteryManager.batteryPower * -1 : batteryManager.batteryPower) + " W").font(.system(size: 13)).frame(alignment: .trailing)
197199
}).padding(EdgeInsets(top: 0, leading: 15, bottom: 1, trailing: 15))
198200
HStack(content: {
199201
Image(systemName: Icons.normLight).frame(width: iconwidth, alignment: .center)
200202
Text(LocalizedStringKey("battery.amperage.label")).font(.system(size: 13)).bold() // Localized
201203
Spacer()
202-
Text(String(format: "%.3f", batteryManager.batteryAmperage) + " A").font(.system(size: 13)).frame(alignment: .trailing)
204+
Text(String(format: "%.3f", batteryManager.batteryAmperage < 0 ? batteryManager.batteryAmperage * -1 : batteryManager.batteryAmperage) + " A").font(.system(size: 13)).frame(alignment: .trailing)
203205
}).padding(EdgeInsets(top: 0, leading: 15, bottom: 1, trailing: 15))
204206
HStack(content: {
205207
Image(systemName: Icons.slash).frame(width: iconwidth, alignment: .center)

app/PowerFlowView.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ struct PowerFlowView: View {
7777
var body: some View {
7878
HStack(spacing: spacing) {
7979
VStack(alignment: .leading, spacing: flowHeight, content: {
80-
if inputPower > 0 {
80+
if inputPower > 0.01 {
8181
ZStack {
8282
// corners: [TL, TR, BR, BL]
8383
generateSquircle(width: iconSize + 20, height: batteryPower > 0 ? flowHeight * 1.5 : flowHeight, radius: cornerRadius, corners: [true, false, false, true])
@@ -106,7 +106,7 @@ struct PowerFlowView: View {
106106

107107
// Middle Flow Section - Measure width using PreferenceKey
108108
Group {
109-
if inputPower > 0 {
109+
if inputPower > 0.01 {
110110
if batteryPower == 0 {
111111
ZStack {
112112
ZStack(alignment: .leading, content: {
@@ -300,15 +300,15 @@ struct PowerFlowView: View {
300300
// System Load Icon (Laptop)
301301
ZStack {
302302
// corners: [TL, TR, BR, BL]
303-
generateSquircle(width: iconSize + 20, height: (batteryPower < 0 && inputPower > 0) ? flowHeight * 1.5 : flowHeight, radius: cornerRadius, corners: [false, true, true, false])
303+
generateSquircle(width: iconSize + 20, height: (batteryPower < 0 && inputPower > 0.01) ? flowHeight * 1.5 : flowHeight, radius: cornerRadius, corners: [false, true, true, false])
304304
.fill(.ultraThickMaterial)
305305

306306
Image(systemName: "laptopcomputer")
307307
.font(.system(size: iconSize))
308308
.foregroundColor(animateLoad ? Color.blue : Color.black)
309309
.offset(x: -1, y: 0)
310310
}
311-
.frame(width: iconSize + 20, height: (batteryPower < 0 && inputPower > 0) ? flowHeight * 1.5 : flowHeight) // Apply frame to the ZStack
311+
.frame(width: iconSize + 20, height: (batteryPower < 0 && inputPower > 0.01) ? flowHeight * 1.5 : flowHeight) // Apply frame to the ZStack
312312
}).frame(width: iconSize + 20)
313313

314314
}

0 commit comments

Comments
 (0)