|
| 1 | +/* |
| 2 | +
|
| 3 | +Set process title in a way compatible with Activity Monitor and other |
| 4 | +MacOS system tools. |
| 5 | +
|
| 6 | +Idea is borrowed from libuv (used by node.js) |
| 7 | +See https://github.com/libuv/libuv/blob/v1.x/src/unix/darwin-proctitle.c |
| 8 | +Implementation rewritten from scratch, fixing various libuv bugs among other things |
| 9 | +
|
| 10 | +*/ |
| 11 | + |
| 12 | +#include <CoreFoundation/CoreFoundation.h> |
| 13 | + |
| 14 | +#include <dlfcn.h> |
| 15 | +#include <pthread.h> |
| 16 | + |
| 17 | +#include "darwin_set_process_name.h" |
| 18 | + |
| 19 | +#define DONE_IF(cond) if (cond) goto done; |
| 20 | + |
| 21 | +/* Undocumented Launch Services functions */ |
| 22 | +typedef enum { |
| 23 | + kLSDefaultSessionID = -2, |
| 24 | +} LSSessionID; |
| 25 | +CFTypeRef LSGetCurrentApplicationASN(void); |
| 26 | +OSStatus LSSetApplicationInformationItem(LSSessionID, CFTypeRef, CFStringRef, CFStringRef, CFDictionaryRef*); |
| 27 | +CFDictionaryRef LSApplicationCheckIn(LSSessionID, CFDictionaryRef); |
| 28 | +void LSSetApplicationLaunchServicesServerConnectionStatus(uint64_t, void *); |
| 29 | + |
| 30 | +typedef struct { |
| 31 | + void * application_services_handle; |
| 32 | + |
| 33 | + CFBundleRef launch_services_bundle; |
| 34 | + typeof(LSGetCurrentApplicationASN) * pLSGetCurrentApplicationASN; |
| 35 | + typeof(LSSetApplicationInformationItem) * pLSSetApplicationInformationItem; |
| 36 | + typeof(LSApplicationCheckIn) * pLSApplicationCheckIn; |
| 37 | + typeof(LSSetApplicationLaunchServicesServerConnectionStatus) * pLSSetApplicationLaunchServicesServerConnectionStatus; |
| 38 | + |
| 39 | + CFStringRef * display_name_key_ptr; |
| 40 | + |
| 41 | +} launch_services_t; |
| 42 | + |
| 43 | +static bool launch_services_init(launch_services_t * it) { |
| 44 | + enum { |
| 45 | + has_nothing, |
| 46 | + has_application_services_handle |
| 47 | + } state = has_nothing; |
| 48 | + bool ret = false; |
| 49 | + |
| 50 | + it->application_services_handle = dlopen("/System/Library/Frameworks/" |
| 51 | + "ApplicationServices.framework/" |
| 52 | + "Versions/Current/ApplicationServices", |
| 53 | + RTLD_LAZY | RTLD_LOCAL); |
| 54 | + DONE_IF(!it->application_services_handle); |
| 55 | + ++state; |
| 56 | + |
| 57 | + it->launch_services_bundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices")); |
| 58 | + DONE_IF(!it->launch_services_bundle); |
| 59 | + |
| 60 | +#define LOAD_METHOD(name) \ |
| 61 | + *(void **)(&it->p ## name ) = \ |
| 62 | + CFBundleGetFunctionPointerForName(it->launch_services_bundle, CFSTR("_" #name)); \ |
| 63 | + DONE_IF(!it->p ## name); |
| 64 | + |
| 65 | + LOAD_METHOD(LSGetCurrentApplicationASN) |
| 66 | + LOAD_METHOD(LSSetApplicationInformationItem) |
| 67 | + LOAD_METHOD(LSApplicationCheckIn) |
| 68 | + LOAD_METHOD(LSSetApplicationLaunchServicesServerConnectionStatus) |
| 69 | + |
| 70 | +#undef LOAD_METHOD |
| 71 | + |
| 72 | + it->display_name_key_ptr = |
| 73 | + CFBundleGetDataPointerForName(it->launch_services_bundle, CFSTR("_kLSDisplayNameKey")); |
| 74 | + DONE_IF(!it->display_name_key_ptr || !*it->display_name_key_ptr); |
| 75 | + |
| 76 | + ret = true; |
| 77 | + |
| 78 | +done: |
| 79 | + switch(state) { |
| 80 | + case has_application_services_handle: if (!ret) dlclose(it->application_services_handle); |
| 81 | + case has_nothing: ; |
| 82 | + } |
| 83 | + return ret; |
| 84 | +} |
| 85 | + |
| 86 | +static inline void launch_services_destroy(launch_services_t * it) { |
| 87 | + dlclose(it->application_services_handle); |
| 88 | +} |
| 89 | + |
| 90 | +static bool launch_services_set_process_title(const launch_services_t * it, const char * title) { |
| 91 | + |
| 92 | + enum { |
| 93 | + has_nothing, |
| 94 | + has_cf_title |
| 95 | + } state = has_nothing; |
| 96 | + bool ret = false; |
| 97 | + |
| 98 | + static bool checked_in = false; |
| 99 | + |
| 100 | + CFTypeRef asn; |
| 101 | + CFStringRef cf_title; |
| 102 | + CFDictionaryRef info_dict; |
| 103 | + CFMutableDictionaryRef mutable_info_dict; |
| 104 | + CFStringRef LSUIElement_key; |
| 105 | + |
| 106 | + if (!checked_in) { |
| 107 | + it->pLSSetApplicationLaunchServicesServerConnectionStatus(0, NULL); |
| 108 | + |
| 109 | + // See https://github.com/dvarrazzo/py-setproctitle/issues/143 |
| 110 | + // We need to set LSUIElement (https://developer.apple.com/documentation/bundleresources/information-property-list/lsuielement) |
| 111 | + // key to true to avoid macOS > 15 displaying the Dock icon. |
| 112 | + info_dict = CFBundleGetInfoDictionary(CFBundleGetMainBundle()); |
| 113 | + mutable_info_dict = CFDictionaryCreateMutableCopy(NULL, 0, info_dict); |
| 114 | + LSUIElement_key = CFStringCreateWithCString(NULL, "LSUIElement", kCFStringEncodingUTF8); |
| 115 | + CFDictionaryAddValue(mutable_info_dict, LSUIElement_key, kCFBooleanTrue); |
| 116 | + CFRelease(LSUIElement_key); |
| 117 | + |
| 118 | + it->pLSApplicationCheckIn(kLSDefaultSessionID, mutable_info_dict); |
| 119 | + CFRelease(mutable_info_dict); |
| 120 | + |
| 121 | + checked_in = true; |
| 122 | + } |
| 123 | + |
| 124 | + asn = it->pLSGetCurrentApplicationASN(); |
| 125 | + DONE_IF(!asn); |
| 126 | + |
| 127 | + cf_title = CFStringCreateWithCString(NULL, title, kCFStringEncodingUTF8); |
| 128 | + DONE_IF(!cf_title); |
| 129 | + ++state; |
| 130 | + DONE_IF(it->pLSSetApplicationInformationItem(kLSDefaultSessionID, |
| 131 | + asn, |
| 132 | + *it->display_name_key_ptr, |
| 133 | + cf_title, |
| 134 | + NULL) != noErr); |
| 135 | + ret = true; |
| 136 | +done: |
| 137 | + switch(state) { |
| 138 | + case has_cf_title: CFRelease(cf_title); |
| 139 | + case has_nothing: ; |
| 140 | + } |
| 141 | + |
| 142 | + return ret; |
| 143 | +} |
| 144 | + |
| 145 | +static bool darwin_pthread_setname_np(const char* name) { |
| 146 | + char namebuf[64]; /* MAXTHREADNAMESIZE according to libuv */ |
| 147 | + |
| 148 | + strncpy(namebuf, name, sizeof(namebuf) - 1); |
| 149 | + namebuf[sizeof(namebuf) - 1] = '\0'; |
| 150 | + |
| 151 | + return (pthread_setname_np(namebuf) != 0); |
| 152 | +} |
| 153 | + |
| 154 | + |
| 155 | +bool darwin_set_process_title(const char * title) { |
| 156 | + |
| 157 | + enum { |
| 158 | + has_nothing, |
| 159 | + has_launch_services |
| 160 | + } state = has_nothing; |
| 161 | + bool ret = false; |
| 162 | + |
| 163 | + launch_services_t launch_services; |
| 164 | + |
| 165 | + DONE_IF(!launch_services_init(&launch_services)); |
| 166 | + ++state; |
| 167 | + |
| 168 | + DONE_IF(!launch_services_set_process_title(&launch_services, title)); |
| 169 | + |
| 170 | + (void)darwin_pthread_setname_np(title); |
| 171 | + |
| 172 | + ret = true; |
| 173 | + |
| 174 | +done: |
| 175 | + switch(state) { |
| 176 | + case has_launch_services: launch_services_destroy(&launch_services); |
| 177 | + case has_nothing: ; |
| 178 | + } |
| 179 | + |
| 180 | + return ret; |
| 181 | +} |
0 commit comments