Hey folks. First time writing here!I'm writing a (simple?) macOS application (in Swift) to apply text macros to selected text in the open application (e.g. indent, deindent, capitalize selection).I've found that I can read the attribute value for `"AXSelectedText"`, edit it, and write out the value back to `"AXSelectedText"`. However, this only works for plain text. If I have some formatted text selected (e.g. a portion of an RTF file in TextEdit), all the formatting is stripped lost.Is there some way to retrieve/save an `NSAttributedString` for the `"AXSelectedText"` attribute?Alternatively, is there a way to just tell the application "delete this range of text", "insert this text here", etc., without losing formatting?(And yes I'm aware that automator services can achieve this, but they're not as powerful as generalized Swift code, they're slow to load/run, and they don't have the UX I'm looking for)
Post
Replies
Boosts
Views
Activity
Hi there,
I'm working on an app that contains a mini system monitoring utility. I would like to list the top CPU-using processes.
As Quinn “The Eskimo!” has repeatedly cautioned, relying on private frameworks is just begging for maintenance effort in the future. Ideally, I want to go through public headers/frameworks.
I've gone to great lengths to try to find this information myself, and at this point I'm just struggling. I detail my research below.
Any pointers in the right direction would be much appreciated!
Attempts
Libproc
First I looked at libproc. Using proc_pidinfo with PROC_PIDTHREADINFO, I'm able to get each thread of an app, with its associated CPU usage percentage. Summing these, I could get the total for an app.
Unfortunately, this has two downsides:
Listing a table of processes now takes O(proces_count) rather than just O(process_count), and causes way more syscalls to be made
It doesn't work for processes owned by other users. Perhaps running as root could alleviate that, but that would involve making a priviliedged helper akin to the existing sysmond that Activity Monitor.app uses. I'm a little scared of that, because I don't want to put my users at risk.
Sysctl
Using the keys [CTL_KERN, KERN_PROC, KERN_PROC_PID, someProcessID], I'm able to get a kinfo_proc - https://github.com/apple-opensource/xnu/blob/24525736ba5b8a67ce3a8a017ced469abe101ad5/bsd/sys/sysctl.h#L750-L776 instance. Accessing its .kp_proc - https://github.com/apple-opensource/xnu/blob/24525736ba5b8a67ce3a8a017ced469abe101ad5/bsd/sys/proc.h#L96-L150.p_pctcpu - https://github.com/apple-opensource/xnu/blob/24525736ba5b8a67ce3a8a017ced469abe101ad5/bsd/sys/proc.h#L123 looked really promising, but that value is always zero.
Digging deeper, I found the kernel code that fills this struct in (fill_user64_externproc - https://github.com/apple-opensource/xnu/blob/c76cff20e09b8d61688d1c3dfb8cc855cccb93ad/bsd/kern/kern_sysctl.c#L1121-L1168). The assignment of p_pctcpu - https://github.com/apple-opensource/xnu/blob/c76cff20e09b8d61688d1c3dfb8cc855cccb93ad/bsd/kern/kern_sysctl.c#L1149 is in a conditional region, relying on the _PROC_HAS_SCHEDINFO_ flag. Disassembling the kernel on my mac, I could confirm that the assignment of that field never happens (thus _PROC_HAS_SCHEDINFO_ wasn't set during compilation, and the value will always stay zero)
Reverse engineering Activity Monitor.app
Activity Monitor.app makes proc_info and sysctl system calls, but from looking at the disassembly, it doesn't look like that's where its CPU figures come from. From what I can tell, it's using private functions from /usr/lib/libsysmon.dylib.
That's a user library which wraps an XPC connection to sysmond (/usr/libexec/sysmond), allowing you to create requests (sysmon_request_create), add specific attributes you want to retrieve (sysmon_request_add_attribute), and then functions to query that data out (sysmon_row_get_value).
Getting the data "striaght from the horses mouth" like this sounds ideal. But unfortunately, the only documentation/usage I can find of sysmond is from bug databases demonstrating a privilege escalation vulnerability lol. There are some partial reverse engineered header files floating around, but they're incomplete, and have the usual fragility/upkeep issues associated with using private APIs.
On one hand, I don't want to depend on a private API, because that takes a lot of time to reverse engineer, keep up with changes, etc. On the other, making my own similar privileged helper would be duplicating effort, and expose a bigger attack surface. Needless to say, I have no confidence in being able to make a safer privileged helper than Apple's engineers lol
Reverse engineering iStat Menus
Looks like they're using proc_pid_rusage - https://github.com/apple-opensource/xnu/blob/24525736ba5b8a67ce3a8a017ced469abe101ad5/libsyscall/wrappers/libproc/libproc.h#L103-L108 .
However, I don't know how to convert the cpu_*_time fields of the resulting struct rusage_info_v4 - https://github.com/apple-opensource/xnu/blob/24525736ba5b8a67ce3a8a017ced469abe101ad5/bsd/sys/resource.h#L306-L343 to compute a "simple" percentage. Even if I came up with some formula that produces plausible looking results, I have no real guarantee it's correct or equivalent to what Activity Monitor shows.
I'm writing a utility that configures some UserDefaults values for me, such as for initializing new Macs to my personal tastes.
It reads a set of configurations from an input file. It then applies the specified preferences to various applications using the CFPreferencesSetValue API. I use it instead of NSUserDefaults, because the latter doesn't let you write to NSGlobalDomain (/Library/Preferences/.GlobalPreferences.plist).
It works well, but here's a twist: I want to be able to detect which applications have had their preferences' values actually change, and which ones already have the desired preferences in place. This allows me to automatically restart "dirty" applications such as Finder, Dock, etc. only if necessary.
I achieve that by doing this:
Reading the existing preferences' values
Comparing the just-read values from the desired values as read from the config file
Noting down which domains have values need changing
Applying the preferences changes
Restarting the apps corresponding to the dirty domains.
The problem is that I have a last-write-wins scenario. Without any kind of transaction/locking mechanism, it's entirely possible that someone will change the preferences under my feet, and I'll overwrite their change. This wouldn't be an issue if I was only changing the preferences for my own app, but given that I'll be changing NSGlobalDomain (which is relatively higher "traffic"), this is a bug in waiting.
Is there a way to safely make this change, without a risk of last-write-wins clobbering other changes?
Hey there. I'm struggling to deal with the two-phase initialization pattern that Storyboards push you into.
Some of my AppDelegate's methods open new windows. They do this by first by instantiating a new MainWindowController from a Storyboard, initializing some properties on it, and then calling showWindow.
When initializing the window controller, the storyboard calls an initializer whose signature I can't control (init?(coder:)). Similarly, when the storyboard automatically instantiates the MainWindowContentViewController. This is the two-stage initialization pattern I mentioned earlier. In attempt to work with it, I came up with this convention:
Each window/view controller has a custom "initialize" method with supplementary parameters that couldn't have been passed to init?(coder:). Since this isn't the real init, all fields set from this psuedo-initilizer are forced to be implicitly unwrapped optionals.
Each custom initialize method is responsible to call similar initialize methods of descendant views.
During viewDidLoad, a view controller can expect that its custom initialize method has already been called by the parent, and that the IUOs are safe to access.
By the time viewWillAppear, most of the the heavy lifting is done, so (for example) presentation of tab views can be very snappy.
This works for the most part, except for one big fly in the ointment: NSWindowController.windowDidLoad() is called after all subviews were loaded. If I hook into it to start this top-down initializer call chain, any fields set by those calls won't be ready in time for viewDidLoads (since those already fired before we even started). I can't find any earlier lifecycle methods on NSWindowController I can hook into to start my top-down initialization pattern. In practice, this means that I don't have a way to pass data from my MainWindowController to my MainWindowContentViewController in a way that makes it available by the time viewDidLoad is called.
I've made a minimal demo project that illustrates my issue. In that example, I compare passing a toolbar button driven Publisher from the window to its content view. Because windowDidLoad is called after viewDidLoad of the content view, I had to move my content view's self-initializing logic to viewDidAppear (I actually meant to use viewWillAppear, but mixed that up).
Fundamentally, I'm trying to use dependency injection rather than singletons (or global variables in general), so that my AppDelegate can pass some objects to the window controller which can pass it to its content view's view controller, which can pass it down to its descedant views' view controllers, and so on.
What' s the right way to achieve this?
No matter what I try to do to quit/kill my XPC service while its processing a message, my client only ever gets XPC_ERROR_CONNECTION_INTERRUPTED and never XPC_ERROR_TERMINATION_IMMINENT. To be specific, I tried all these things:
Quitting the service while it's idle (SIGTERM)
Force-quitting the service while it's idle (SIGKILL)
Quitting the service while it's in the middle of processing a message (SIGTERM)
Force-quitting the service while it's in the middle of processing a message (SIGKILL)
Making the service quit itself in the middle of processing a message (exit(EXIT_SUCCESS))
Making the service cancel the connection in the middle of processing a message (xpc_connection_cancel(connection))
Hoping to find some examples of its usage, I searched around GitHub, and haven't really found anyone do much at all in response to this message. Some parts look like they come up from a common ancestor (probably some guide or template): https://github.com/search?q=%22The+client+process+on+the+other+end+of+the+connection+has+either%22&type=Code
Even Apple's own WebKit2 doesn't seem to do much with it: https://github.com/apple-opensource/WebKit2/search?q=XPC_ERROR_TERMINATION_IMMINENT
When does XPC_ERROR_TERMINATION_IMMINENT ever come into play?
Hey there,
I'm trying to employ the same pattern as demonstrated in the EvenBetterAuthorizationSample: an unsandboxed XPC service calls SMJobBless to install a privileged helper service on behalf a sandboxed main app (which isn't allowed to call SMJobBless). It then starts an XPC connection to the Mach service hosted by the privileged service, and hands over the connection back to the main app, along with the XPC service's connection to the security server (AuthorizationRef).
When I try to do call SMJobBless from my XPC service, I get these messages:
info authd Process /usr/libexec/smd (PID 28881) evaluates 1 rights with flags 00000003 (engine 629): (
"com.apple.ServiceManagement.blesshelper"
)
error authd Fatal: interaction not allowed (session has no ui access) (engine 629)
default authd Failed to authorize right 'com.apple.ServiceManagement.blesshelper' by client '/usr/libexec/smd' [28881] for authorization created by '/MyApp.app/Contents/XPCServices/IntermediatorXPCService.xpc' [29325] (3,0) (-60007) (engine 629)
error authd copy_rights: authorization failed
This seems reasonable to me, because I wouldn't expect an XPC service to be capable of running graphics. However, this works just fine in the "App-Sandboxed" app in the EvenBetterAuthorizationSample project.
I poked around the available open source code, and found out that this message is logged when the processes' audit session doesn't have AU_SESSION_FLAG_HAS_GRAPHIC_ACCESS set.
if (!(session_get_attributes(auth_token_get_session(engine->auth)) & AU_SESSION_FLAG_HAS_GRAPHIC_ACCESS)) {
os_log_error(AUTHD_LOG, "Fatal: interaction not allowed (session has no ui access) (engine %lld)", engine->engine_index);
return errAuthorizationInteractionNotAllowed;
}
Out of curiosity, I compared the audit sessions of my XPC service to the one in EBAS using this code:
auditinfo_addr_t auditInfo;
int result = getaudit_addr(&auditInfo, sizeof(auditInfo));
assert(result == 0 );
if (auditInfo.ai_flags & AU_SESSION_FLAG_IS_INITIAL) {
NSLog(@"AU_SESSION_FLAG_IS_INITIAL");
}
if (auditInfo.ai_flags & AU_SESSION_FLAG_HAS_GRAPHIC_ACCESS) {
NSLog(@"AU_SESSION_FLAG_HAS_GRAPHIC_ACCESS");
}
if (auditInfo.ai_flags & AU_SESSION_FLAG_HAS_TTY) {
NSLog(@"AU_SESSION_FLAG_HAS_TTY");
}
if (auditInfo.ai_flags & AU_SESSION_FLAG_IS_REMOTE) {
NSLog(@"AU_SESSION_FLAG_IS_REMOTE");
}
if (auditInfo.ai_flags & AU_SESSION_FLAG_HAS_CONSOLE_ACCESS) {
NSLog(@"AU_SESSION_FLAG_HAS_CONSOLE_ACCESS");
}
if (auditInfo.ai_flags & AU_SESSION_FLAG_HAS_AUTHENTICATED) {
NSLog(@"AU_SESSION_FLAG_HAS_AUTHENTICATED");
}
Sure enough, I got different results.
EBAS:
2021-11-20 18:45:52.792512-0500 com.example.apple-samplecode.EBAS.XPCService[25296:592874] result: 0
2021-11-20 18:45:52.792527-0500 com.example.apple-samplecode.EBAS.XPCService[25296:592874] AU_SESSION_FLAG_HAS_GRAPHIC_ACCESS
2021-11-20 18:45:52.792539-0500 com.example.apple-samplecode.EBAS.XPCService[25296:592874] AU_SESSION_FLAG_HAS_TTY
2021-11-20 18:45:52.792549-0500 com.example.apple-samplecode.EBAS.XPCService[25296:592874] AU_SESSION_FLAG_HAS_CONSOLE_ACCESS
(lldb) p auditInfo
(auditinfo_addr_t) $0 = {
ai_auid = 501
ai_mask = (am_success = 4294967295, am_failure = 4294967295)
ai_termid = {
at_port = 50331650
at_type = 4
at_addr = ([0] = 0, [1] = 0, [2] = 0, [3] = 0)
}
ai_asid = 100019
ai_flags = 8240
}
My XPC service:
2021-11-20 21:33:44.355007-0500 IntermediatorXPCService[29325:698278] result: 0
(lldb) p auditInfo
▿ __C.auditinfo_addr
- ai_auid: 4294967295
▿ ai_mask: __C.au_mask
- am_success: 4294967295
- am_failure: 4294967295
▿ ai_termid: __C.au_tid_addr
- at_port: 0
- at_type: 4
▿ at_addr: (4 elements)
- .0: 0
- .1: 0
- .2: 0
- .3: 0
- ai_asid: 102293
- ai_flags: 0
It looks like ai_flags is all 0. Any ideas why that might be? What is making EBAS special?
And also, how can AU_SESSION_FLAG_HAS_TTY and AU_SESSION_FLAG_HAS_CONSOLE_ACCESS be false? I'm reading these logs from the console?!
(Another curious observation: audit_session_flags is imported into Swift as RawRepresentable, but not as an OptionSet)