Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions scripts/vphoned/vphoned.m
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,14 @@ static BOOL handle_client(int fd) {
continue;
}

// Generic notify_post
if ([t isEqualToString:@"notify_post"]) {
NSDictionary *resp = vp_handle_notify_post_command(msg);
if (resp && !vp_write_message(fd, resp))
break;
continue;
}

NSDictionary *resp = handle_command(msg);
if (resp && !vp_write_message(fd, resp))
break;
Expand Down
1 change: 1 addition & 0 deletions scripts/vphoned/vphoned_notify.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
#import <Foundation/Foundation.h>

NSDictionary *vp_handle_notify_command(NSDictionary *msg);
NSDictionary *vp_handle_notify_post_command(NSDictionary *msg);
43 changes: 38 additions & 5 deletions scripts/vphoned/vphoned_notify.m
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
/*
* vphoned_notify — Low power mode state sync.
* vphoned_notify — Darwin notification helpers.
*
* Sets LPM state via notify_set_state("com.apple.system.lowpowermode") + notify_post.
* All registrations for a notification name share one state value, so this
* updates what NSProcessInfo and SpringBoard read via notify_get_state —
* mirroring what powerd does internally when the user toggles Low Power Mode.
* Low power mode: Sets LPM state via notify_set_state + notify_post.
* Generic notify_post: Posts an arbitrary Darwin notification name, optionally
* setting a uint64 state value before posting.
*/

#import "vphoned_notify.h"
Expand All @@ -30,3 +29,37 @@
r[@"ok"] = @(ok);
return r;
}

NSDictionary *vp_handle_notify_post_command(NSDictionary *msg) {
id reqId = msg[@"id"];
NSString *name = msg[@"name"];
if (!name || name.length == 0) {
NSMutableDictionary *r = vp_make_response(@"err", reqId);
r[@"msg"] = @"missing notification name";
return r;
}

const char *cname = [name UTF8String];
BOOL hasState = (msg[@"state"] != nil);
BOOL ok = YES;

if (hasState) {
uint64_t state = [msg[@"state"] unsignedLongLongValue];
int token = 0;
ok = (notify_register_check(cname, &token) == NOTIFY_STATUS_OK);
if (ok) {
notify_set_state(token, state);
ok = (notify_post(cname) == NOTIFY_STATUS_OK);
notify_cancel(token);
}
NSLog(@"vphoned: notify_post %@ state=%llu -> %s", name, state,
ok ? "ok" : "failed");
} else {
ok = (notify_post(cname) == NOTIFY_STATUS_OK);
NSLog(@"vphoned: notify_post %@ -> %s", name, ok ? "ok" : "failed");
}

NSMutableDictionary *r = vp_make_response(@"notify_post", reqId);
r[@"ok"] = @(ok);
return r;
}
2 changes: 2 additions & 0 deletions sources/vphone-cli/VPhoneAppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ class VPhoneAppDelegate: NSObject, NSApplicationDelegate {
mc?.updateURLAvailability(available: caps.contains("url"))
mc?.updateClipboardAvailability(available: caps.contains("clipboard"))
mc?.updateSettingsAvailability(available: true)
mc?.updateNotifyAvailability(available: true)
if caps.contains("location") {
mc?.updateLocationCapability(available: true)
// Auto-resume if user had toggle on
Expand All @@ -166,6 +167,7 @@ class VPhoneAppDelegate: NSObject, NSApplicationDelegate {
mc?.updateURLAvailability(available: false)
mc?.updateClipboardAvailability(available: false)
mc?.updateSettingsAvailability(available: false)
mc?.updateNotifyAvailability(available: false)
provider?.stopReplay()
provider?.stopForwarding()
mc?.updateLocationCapability(available: false)
Expand Down
12 changes: 12 additions & 0 deletions sources/vphone-cli/VPhoneControl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,18 @@ class VPhoneControl {
}
}

// MARK: - Notify Post

func notifyPost(name: String, state: UInt64? = nil) async throws {
var req: [String: Any] = ["t": "notify_post", "name": name]
if let state { req["state"] = state }
let (resp, _) = try await sendRequest(req)
let ok = resp["ok"] as? Bool ?? false
if !ok {
throw ControlError.guestError("notify_post failed for \(name)")
}
}

// MARK: - Accessibility

func accessibilityTree(depth: Int = -1) async throws -> [String: Any] {
Expand Down
7 changes: 7 additions & 0 deletions sources/vphone-cli/VPhoneMenuConnect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ extension VPhoneMenuController {
menu.addItem(buildLocationSubmenu())
menu.addItem(buildBatterySubmenu())

menu.addItem(NSMenuItem.separator())

let notifyItem = makeItem("Send Darwin Notification...", action: #selector(postNotification))
notifyItem.isEnabled = false
notifyPostItem = notifyItem
menu.addItem(notifyItem)

item.submenu = menu
return item
}
Expand Down
1 change: 1 addition & 0 deletions sources/vphone-cli/VPhoneMenuController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class VPhoneMenuController {
var powerSourceRunLoopSource: CFRunLoopSource?
var powerSourceRetainedPtr: UnsafeMutableRawPointer?
var lowPowerObserver: (any NSObjectProtocol)?
var notifyPostItem: NSMenuItem?

init(keyHelper: VPhoneKeyHelper, control: VPhoneControl) {
self.keyHelper = keyHelper
Expand Down
Loading