Changeset 306 for trunk/appswitch/appswitch/main.c
- Timestamp:
- 10/28/06 13:47:45 (18 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/appswitch/appswitch/main.c
r163 r306 3 3 Nicholas Riley <appswitch@sabi.net> 4 4 5 Copyright (c) 2003-0 4, Nicholas Riley5 Copyright (c) 2003-06, Nicholas Riley 6 6 All rights reserved. 7 7 … … 19 19 20 20 #include <unistd.h> 21 #include <signal.h> 21 22 #include <sys/ioctl.h> 23 #include <ApplicationServices/ApplicationServices.h> 22 24 #include "CPS.h" 23 25 24 26 const char *APP_NAME; 25 27 26 #define VERSION "1. 0.1"28 #define VERSION "1.1d1" 27 29 28 30 struct { 29 OSTypecreator;31 CFStringRef creator; 30 32 CFStringRef bundleID; 31 char *name;33 CFStringRef name; 32 34 pid_t pid; 33 char *path;35 CFStringRef path; 34 36 enum { 35 37 MATCH_UNKNOWN, MATCH_FRONT, MATCH_CREATOR, MATCH_BUNDLE_ID, MATCH_NAME, MATCH_PID, MATCH_PATH, MATCH_ALL 36 38 } matchType; 37 39 enum { 38 APP_NONE, APP_SWITCH, APP_SHOW, APP_HIDE, APP_QUIT, APP_KILL, APP_KILL_HARD, APP_LIST, APP_PRINT_PID 40 APP_NONE, APP_SWITCH, APP_SHOW, APP_HIDE, APP_QUIT, APP_KILL, APP_KILL_HARD, APP_LIST, APP_PRINT_PID, APP_FRONTMOST 39 41 } appAction; 40 42 Boolean longList; … … 57 59 static errList ERRS = { 58 60 // Process Manager errors 59 { appIsDaemon, "application is background-only\n", }, 60 { procNotFound, "unable to connect to system service.\nAre you logged in?" }, 61 { appIsDaemon, "application is background-only", }, 62 { procNotFound, "application not found" }, 63 { connectionInvalid, "application is not background-only", }, 61 64 // CoreGraphics errors 62 65 { kCGErrorIllegalArgument, "window server error.\nAre you logged in?" }, 63 66 { fnfErr, "file not found" }, 67 // (abused) errors 68 { permErr, "no permission" }, 64 69 { 0, NULL } 65 70 }; 66 71 67 72 void usage() { 68 fprintf(stderr, "usage: %s [-sShHqk FlLP] [-c creator] [-i bundleID] [-a name] [-p pid] [path]\n"73 fprintf(stderr, "usage: %s [-sShHqklLPfF] [-c creator] [-i bundleID] [-a name] [-p pid] [path]\n" 69 74 " -s show application, bring windows to front (do not switch)\n" 70 75 " -S show all applications\n" … … 72 77 " -H hide other applications\n" 73 78 " -q quit application\n" 74 " -k kill application (SIG INT)\n"79 " -k kill application (SIGTERM)\n" 75 80 " -K kill application hard (SIGKILL)\n" 76 81 " -l list applications\n" 77 82 " -L list applications including full paths and bundle identifiers\n" 78 83 " -P print application process ID\n" 84 " -f bring application's frontmost window to front\n" 79 85 " -F bring current application's windows to front\n" 80 86 " -c creator match application by four-character creator code ('ToyS')\n" 81 " -i bundle ID match application by bundle identifier (com.apple. scripteditor)\n"82 " -p pid match application by process identifier [slower]\n"87 " -i bundle ID match application by bundle identifier (com.apple.ScriptEditor2)\n" 88 " -p pid match application by process identifier\n" 83 89 " -a name match application by name\n" 84 90 , APP_NAME); 85 fprintf(stderr, "appswitch "VERSION" (c) 2003-0 4Nicholas Riley <http://web.sabi.net/nriley/software/>.\n"91 fprintf(stderr, "appswitch "VERSION" (c) 2003-06 Nicholas Riley <http://web.sabi.net/nriley/software/>.\n" 86 92 "Please send bugs, suggestions, etc. to <appswitch@sabi.net>.\n"); 87 93 … … 101 107 break; 102 108 } 103 109 len = strlen(errDesc) + 10 * sizeof(char); 104 110 str = (char *)malloc(len); 105 111 if (str != NULL) … … 136 142 if (argc == 1) usage(); 137 143 138 const char *opts = "c:i:p:a:sShHqkKlLP F";144 const char *opts = "c:i:p:a:sShHqkKlLPfF"; 139 145 140 146 while ( (ch = getopt(argc, argv, opts)) != -1) { … … 148 154 case 'c': 149 155 if (OPTS.matchType != MATCH_UNKNOWN) errexit("choose only one of -c, -i, -p, -a options"); 150 if (strlen(optarg) != 4) errexit("creator (argument of -c) must be four characters long"); 151 OPTS.creator = *(OSTypePtr)optarg; 156 OPTS.creator = CFStringCreateWithFileSystemRepresentation(NULL, optarg); 157 if (OPTS.creator == NULL) errexit("invalid creator (wrong text encoding?)"); 158 if (CFStringGetLength(OPTS.creator) != 4) errexit("creator (argument of -c) must be four characters long"); 152 159 OPTS.matchType = MATCH_CREATOR; 153 160 break; 154 161 case 'i': 155 162 if (OPTS.matchType != MATCH_UNKNOWN) errexit("choose only one of -c, -i, -p, -a options"); 156 OPTS.bundleID = CFStringCreateWithCString(NULL, optarg, CFStringGetSystemEncoding()); 163 OPTS.bundleID = CFStringCreateWithFileSystemRepresentation(NULL, optarg); 164 if (OPTS.bundleID == NULL) errexit("invalid bundle ID (wrong text encoding?)"); 157 165 OPTS.matchType = MATCH_BUNDLE_ID; 158 166 break; 159 167 case 'a': 160 168 if (OPTS.matchType != MATCH_UNKNOWN) errexit("choose only one of -c, -i, -p, -a options"); 161 OPTS.name = strdup(optarg); 169 OPTS.name = CFStringCreateWithFileSystemRepresentation(NULL, optarg); 170 if (OPTS.name == NULL) errexit("invalid application name (wrong text encoding?)"); 162 171 OPTS.matchType = MATCH_NAME; 163 172 break; 164 173 case 's': 165 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -q, -k, -K, -l, -L, -P options");174 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -q, -k, -K, -l, -L, -P, -f options"); 166 175 OPTS.appAction = APP_SHOW; 167 176 break; 168 177 case 'h': 169 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -q, -k, -K, -l, -L, -P options");178 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -q, -k, -K, -l, -L, -P, -f options"); 170 179 OPTS.appAction = APP_HIDE; 171 180 break; 172 181 case 'q': 173 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -q, -k, -K, -l, -L, -P options");182 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -q, -k, -K, -l, -L, -P, -f options"); 174 183 OPTS.appAction = APP_QUIT; 175 184 break; 176 185 case 'k': 177 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -q, -k, -K, -l, -L, -P options");186 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -q, -k, -K, -l, -L, -P, -f options"); 178 187 OPTS.appAction = APP_KILL; 179 188 break; 180 189 case 'K': 181 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -q, -k, -K, -l, -L, -P options");190 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -q, -k, -K, -l, -L, -P, -f options"); 182 191 OPTS.appAction = APP_KILL_HARD; 183 192 break; 184 193 case 'l': 185 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -q, -k, -K, -l, -L, -P options");194 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -q, -k, -K, -l, -L, -P, -f options"); 186 195 OPTS.appAction = APP_LIST; 187 196 break; 188 197 case 'L': 189 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -q, -k, -K, -l, -L, -P options");198 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -q, -k, -K, -l, -L, -P, -f options"); 190 199 OPTS.appAction = APP_LIST; 191 200 OPTS.longList = true; 192 201 break; 193 202 case 'P': 194 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -q, -k, -K, -l, - Poptions");203 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -q, -k, -K, -l, -L, -P, -f options"); 195 204 OPTS.appAction = APP_PRINT_PID; 205 break; 206 case 'f': 207 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -q, -k, -K, -l, -L, -P, -f options"); 208 OPTS.appAction = APP_FRONTMOST; 196 209 break; 197 210 case 'S': … … 224 237 } else usage(); 225 238 } else if (argc == 1) { 226 OPTS.path = argv[0]; 239 OPTS.path = CFStringCreateWithFileSystemRepresentation(NULL, argv[0]); 240 if (OPTS.path == NULL) errexit("invalid path (wrong text encoding?)"); 227 241 OPTS.matchType = MATCH_PATH; 228 242 } else usage(); … … 234 248 } 235 249 236 CPSProcessSerNumfrontApplication() {237 CPSProcessSerNumpsn;238 OSStatus err = CPSGetFrontProcess(&psn);250 ProcessSerialNumber frontApplication() { 251 ProcessSerialNumber psn; 252 OSStatus err = GetFrontProcess(&psn); 239 253 if (err != noErr) osstatusexit(err, "can't get frontmost process"); 240 254 #if DEBUG 241 fprintf(stderr, "front application PSN %ld.%ld\n", psn. hi, psn.lo);255 fprintf(stderr, "front application PSN %ld.%ld\n", psn.lowLongOfPSN, psn.highLongOfPSN); 242 256 #endif 243 257 return psn; 244 258 } 245 259 246 Boolean bundleIdentifierForApplication(CFStringRef *bundleID, char *path) { 247 CFURLRef url = CFURLCreateFromFileSystemRepresentation(NULL, path, strlen(path), false); 248 if (url == NULL) return false; 249 CFBundleRef bundle = CFBundleCreate(NULL, url); 250 if (bundle != NULL) { 251 *bundleID = CFBundleGetIdentifier(bundle); 252 if (*bundleID != NULL) { 253 CFRetain(*bundleID); 254 #if DEBUG 255 CFShow(*bundleID); 256 #endif 257 } 258 CFRelease(bundle); 259 } else { 260 *bundleID = NULL; 261 } 262 CFRelease(url); 263 return true; 264 } 265 266 OSStatus quitApplication(CPSProcessSerNum *psn) { 260 OSStatus quitApplication(ProcessSerialNumber *psn) { 267 261 AppleEvent event; 268 262 AEAddressDesc appDesc; … … 273 267 if (err != noErr) return err; 274 268 275 // XXX AECreateAppleEvent is very slow in Mac OS X 10.2.4 and earlier.276 // XXX This is Apple's bug: <http://lists.apple.com/archives/applescript-implementors/2003/Feb/19/aecreateappleeventfromco.txt>277 269 err = AECreateAppleEvent(kCoreEventClass, kAEQuitApplication, &appDesc, kAutoGenerateReturnID, kAnyTransactionID, &event); 278 270 if (err != noErr) return err; … … 288 280 } 289 281 290 CPSProcessSerNum matchApplication(CPSProcessInfoRec *info) { 291 long pathMaxLength = pathconf("/", _PC_PATH_MAX); 292 long nameMaxLength = pathconf("/", _PC_NAME_MAX); 293 294 char *path = (char *)malloc(pathMaxLength); 295 char *name = (char *)malloc(nameMaxLength);; 296 297 if (path == NULL || name == NULL) errexit("can't allocate memory for path or filename buffer"); 298 282 pid_t getPID(const ProcessSerialNumber *psn) { 283 pid_t pid; 284 OSStatus err = GetProcessPID(psn, &pid); 285 if (err != noErr) osstatusexit(err, "can't get process ID"); 286 return pid; 287 } 288 289 bool infoStringMatches(CFDictionaryRef info, CFStringRef key, CFStringRef matchStr) { 290 CFStringRef str = CFDictionaryGetValue(info, key); 291 if (str == NULL) 292 return false; 293 /* note: this means we might match names/paths that are wrong, but works better in the common case */ 294 return CFStringCompare(str, matchStr, kCFCompareCaseInsensitive) == kCFCompareEqualTo; 295 } 296 297 char *getInfoCString(CFDictionaryRef info, CFStringRef key) { 298 CFStringRef str = CFDictionaryGetValue(info, key); 299 if (str == NULL) 300 return ""; 301 static char *cStr = NULL; 302 static bool wasDynamic = false; 303 if (wasDynamic) 304 free(cStr); 305 cStr = (char *)CFStringGetCStringPtr(str, CFStringGetSystemEncoding()); 306 if (cStr != NULL) { 307 wasDynamic = false; 308 } else { 309 CFIndex cStrLength = CFStringGetMaximumSizeOfFileSystemRepresentation(str); 310 cStr = (char *)malloc(cStrLength * sizeof(char)); 311 if (!CFStringGetFileSystemRepresentation(str, cStr, cStrLength)) { 312 CFShow(cStr); 313 errexit("internal error: string encoding conversion failed"); 314 } 315 wasDynamic = true; 316 } 317 return cStr; 318 } 319 320 ProcessSerialNumber matchApplication(void) { 299 321 if (OPTS.matchType == MATCH_FRONT) return frontApplication(); 300 322 301 323 OSStatus err; 302 CPSProcessSerNumpsn = {324 ProcessSerialNumber psn = { 303 325 kNoProcess, kNoProcess 304 326 }; 305 int len;327 pid_t pid; 306 328 char *format = NULL; 307 329 if (OPTS.appAction == APP_LIST) { … … 314 336 ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&ws) != -1) || 315 337 ws.ws_col != 0) termwidth = ws.ws_col; 316 char *formatButPath = "%9ld.%ld %5ld % c%c%c%c %c%c%c%c%-19.19s";338 char *formatButPath = "%9ld.%ld %5ld %4s %4s %-19.19s"; 317 339 int pathlen = termwidth - strlen(banner) - 1; 318 340 // XXX don't ever free 'format', should fix if we get called repeatedly … … 328 350 } 329 351 330 while ( (err = CPSGetNextProcess(&psn)) == noErr) { 331 err = CPSGetProcessInfo(&psn, info, path, pathMaxLength, &len, name, nameMaxLength); 332 if (err != noErr) osstatusexit(err, "can't get information for process PSN %ld.%ld", psn.hi, psn.lo); 333 334 #if DEBUG 335 fprintf(stderr, "%ld.%ld: %s : %s\n", psn.hi, psn.lo, name, path); 336 #endif 352 CFDictionaryRef info = NULL; 353 while ( (err = GetNextProcess(&psn)) == noErr) { 354 if (info != NULL) CFRelease(info); 355 info = ProcessInformationCopyDictionary(&psn, kProcessDictionaryIncludeAllInformationMask); 356 if (info == NULL) errexit("can't get information for process with PSN %ld.%ld", 357 psn.lowLongOfPSN, psn.highLongOfPSN); 337 358 338 359 switch (OPTS.matchType) { 339 360 case MATCH_ALL: 340 361 break; 341 case MATCH_CREATOR: if (OPTS.creator != info->ExecFileCreator) continue; 342 break; 343 case MATCH_NAME: if (strcmp(name, OPTS.name) != 0) continue; 344 break; 345 case MATCH_PID: if (OPTS.pid != info->UnixPID) continue; 346 break; 347 case MATCH_PATH: if (strcmp(path, OPTS.path) != 0) continue; 348 break; 349 case MATCH_BUNDLE_ID: 350 { 351 CFStringRef bundleID; 352 if (!bundleIdentifierForApplication(&bundleID, path)) 353 errexit("can't get bundle location for process '%s' (PSN %ld.%ld, pid %ld)", name, psn.hi, psn.lo, info->UnixPID); 354 if (bundleID != NULL) { 355 CFComparisonResult result = CFStringCompare(OPTS.bundleID, bundleID, kCFCompareCaseInsensitive); 356 if (result == kCFCompareEqualTo) 357 break; 358 CFRelease(bundleID); 359 } 360 continue; 361 } 362 case MATCH_CREATOR: if (!infoStringMatches(info, CFSTR("FileCreator"), OPTS.creator)) continue; 363 break; 364 case MATCH_NAME: if (!infoStringMatches(info, CFSTR("CFBundleName"), OPTS.name)) continue; 365 break; 366 case MATCH_PID: err = GetProcessPID(&psn, &pid); if (err != noErr || OPTS.pid != pid) continue; 367 break; 368 case MATCH_PATH: if (!infoStringMatches(info, CFSTR("BundlePath"), OPTS.path)) continue; 369 break; 370 case MATCH_BUNDLE_ID: if (!infoStringMatches(info, CFSTR("CFBundleIdentifier"), OPTS.bundleID)) continue; 371 break; 362 372 default: 363 373 errexit("internal error: invalid match type"); 364 374 } 365 375 if (OPTS.appAction == APP_LIST) { 366 char *type = (char *)&(info->ExecFileType), *crea = (char *)&(info->ExecFileCreator); 367 #define CXX(c) ( (c) < ' ' ? ' ' : (c) ) 368 #define OSTYPE_CHAR_ARGS(t) CXX(t[0]), CXX(t[1]), CXX(t[2]), CXX(t[3]) 369 printf(format, psn.hi, psn.lo, info->UnixPID, 370 OSTYPE_CHAR_ARGS(type), OSTYPE_CHAR_ARGS(crea), 371 name, path); 376 if (GetProcessPID(&psn, &pid) != noErr) 377 pid = -1; 378 printf(format, psn.lowLongOfPSN, psn.highLongOfPSN, pid, 379 getInfoCString(info, CFSTR("FileType")), getInfoCString(info, CFSTR("FileCreator")), 380 getInfoCString(info, CFSTR("CFBundleName")), getInfoCString(info, CFSTR("BundlePath"))); 372 381 if (OPTS.longList) { 373 CFStringRef bundleID = NULL; 374 if (!bundleIdentifierForApplication(&bundleID, path)) 375 errexit("can't get bundle location for process '%s' (PSN %ld.%ld, pid %ld)", name, psn.hi, psn.lo, info->UnixPID); 376 if (bundleID != NULL) { 377 char *bundleIDStr = (char *)CFStringGetCStringPtr(bundleID, CFStringGetSystemEncoding()); 378 if (bundleIDStr == NULL) { 379 CFIndex bundleIDLength = CFStringGetLength(bundleID) + 1; 380 bundleIDStr = (char *)malloc(bundleIDLength * sizeof(char)); 381 if (!CFStringGetCString(bundleID, bundleIDStr, bundleIDLength, CFStringGetSystemEncoding())) { 382 CFShow(bundleIDStr); 383 errexit("internal error: string encoding conversion failed for bundle identifier"); 384 } 385 printf(" (%s)", bundleIDStr); 386 free(bundleIDStr); 387 } else { 388 printf(" (%s)", bundleIDStr); 389 } 390 CFRelease(bundleID); 391 } 382 char *bundleID = getInfoCString(info, CFSTR("CFBundleIdentifier")); 383 if (bundleID[0] != '\0') 384 printf(" (%s)", bundleID); 392 385 } 393 386 putchar('\n'); … … 410 403 getargs(argc, argv); 411 404 412 // need to establish connection with window server 413 InitCursor(); 414 415 CPSProcessInfoRec info; 416 CPSProcessSerNum psn = matchApplication(&info); 405 ProcessSerialNumber psn = matchApplication(); 417 406 418 407 const char *verb = NULL; … … 420 409 case APP_NONE: break; 421 410 case APP_LIST: break; // already handled in matchApplication 422 case APP_SWITCH: err = CPSSetFrontProcess(&psn); verb = "set front"; break;423 case APP_SHOW: err = CPSPostShowReq(&psn); verb = "show"; break;424 case APP_HIDE: err = CPSPostHideReq(&psn); verb = "hide"; break;411 case APP_SWITCH: err = SetFrontProcess(&psn); verb = "set front"; break; 412 case APP_SHOW: err = ShowHideProcess(&psn, true); verb = "show"; break; 413 case APP_HIDE: err = ShowHideProcess(&psn, false); verb = "hide"; break; 425 414 case APP_QUIT: err = quitApplication(&psn); verb = "quit"; break; 426 case APP_KILL: err = CPSPostKillRequest(&psn, kNilOptions); verb = "kill"; break; 427 case APP_KILL_HARD: err = CPSPostKillRequest(&psn, bfCPSKillHard); verb = "kill"; break; 428 case APP_PRINT_PID: 429 if (info.UnixPID <= 0) errexit("can't get process ID"); 430 printf("%lu\n", info.UnixPID); // pid_t is signed, but this field isn't 415 case APP_KILL: err = KillProcess(&psn); verb = "send SIGTERM to"; break; 416 case APP_KILL_HARD: 417 { 418 if (kill(getPID(&psn), SIGKILL) == -1) 419 err = (errno == ESRCH) ? procNotFound : (errno == EPERM ? permErr : paramErr); 420 verb = "send SIGKILL to"; 431 421 break; 422 } 423 case APP_PRINT_PID: printf("%d\n", getPID(&psn)); break; 424 case APP_FRONTMOST: err = SetFrontProcessWithOptions(&psn, kSetFrontProcessFrontWindowOnly); 425 verb = "bring frontmost window to front"; break; 432 426 default: 433 427 errexit("internal error: invalid application action"); … … 449 443 psn = frontApplication(); 450 444 #if DEBUG 451 fprintf(stderr, "posting show request for %ld.%ld\n", psn. hi, psn.lo);445 fprintf(stderr, "posting show request for %ld.%ld\n", psn.lowLongOfPSN, psn.highLongOfPSN); 452 446 #endif 453 447 if (OPTS.action != ACTION_NONE) usleep(750000); // XXX 454 err = CPSPostShowReq(&psn) || CPSSetFrontProcess(&psn);448 err = ShowHideProcess(&psn, true) || SetFrontProcess(&psn); 455 449 verb = "bring current application's windows to the front"; 456 450 break;
Note:
See TracChangeset
for help on using the changeset viewer.