Ignore:
Timestamp:
10/28/06 13:47:45 (16 years ago)
Author:
Nicholas Riley
Message:

VERSION: Updated for 1.1d1.

main.c: Mostly switch to Process Manager. Remove obsolete comments.

README: Updated for 1.1d1.

appswitch.xcodeproj: Upgraded Xcode project.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/appswitch/appswitch/main.c

    r163 r306  
    33 Nicholas Riley <appswitch@sabi.net>
    44
    5  Copyright (c) 2003-04, Nicholas Riley
     5 Copyright (c) 2003-06, Nicholas Riley
    66 All rights reserved.
    77
     
    1919
    2020#include <unistd.h>
     21#include <signal.h>
    2122#include <sys/ioctl.h>
     23#include <ApplicationServices/ApplicationServices.h>
    2224#include "CPS.h"
    2325
    2426const char *APP_NAME;
    2527
    26 #define VERSION "1.0.1"
     28#define VERSION "1.1d1"
    2729
    2830struct {
    29     OSType creator;
     31    CFStringRef creator;
    3032    CFStringRef bundleID;
    31     char *name;
     33    CFStringRef name;
    3234    pid_t pid;
    33     char *path;
     35    CFStringRef path;
    3436    enum {
    3537        MATCH_UNKNOWN, MATCH_FRONT, MATCH_CREATOR, MATCH_BUNDLE_ID, MATCH_NAME, MATCH_PID, MATCH_PATH, MATCH_ALL
    3638    } matchType;
    3739    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
    3941    } appAction;
    4042    Boolean longList;
     
    5759static errList ERRS = {
    5860    // 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", },
    6164    // CoreGraphics errors
    6265    { kCGErrorIllegalArgument, "window server error.\nAre you logged in?" },
    6366    { fnfErr, "file not found" },
     67    // (abused) errors
     68    { permErr, "no permission" },
    6469    { 0, NULL }
    6570};
    6671
    6772void usage() {
    68     fprintf(stderr, "usage: %s [-sShHqkFlLP] [-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"
    6974            "  -s            show application, bring windows to front (do not switch)\n"
    7075            "  -S            show all applications\n"
     
    7277            "  -H            hide other applications\n"
    7378            "  -q            quit application\n"
    74             "  -k            kill application (SIGINT)\n"
     79            "  -k            kill application (SIGTERM)\n"
    7580            "  -K            kill application hard (SIGKILL)\n"
    7681            "  -l            list applications\n"
    7782            "  -L            list applications including full paths and bundle identifiers\n"
    7883            "  -P            print application process ID\n"
     84            "  -f            bring application's frontmost window to front\n"
    7985            "  -F            bring current application's windows to front\n"
    8086            "  -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"
    8389            "  -a name       match application by name\n"
    8490            , APP_NAME);
    85     fprintf(stderr, "appswitch "VERSION" (c) 2003-04 Nicholas 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"
    8692            "Please send bugs, suggestions, etc. to <appswitch@sabi.net>.\n");
    8793
     
    101107            break;
    102108        }
    103             len = strlen(errDesc) + 10 * sizeof(char);
     109    len = strlen(errDesc) + 10 * sizeof(char);
    104110    str = (char *)malloc(len);
    105111    if (str != NULL)
     
    136142    if (argc == 1) usage();
    137143
    138     const char *opts = "c:i:p:a:sShHqkKlLPF";
     144    const char *opts = "c:i:p:a:sShHqkKlLPfF";
    139145
    140146    while ( (ch = getopt(argc, argv, opts)) != -1) {
     
    148154            case 'c':
    149155                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");
    152159                OPTS.matchType = MATCH_CREATOR;
    153160                break;
    154161            case 'i':
    155162                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?)");
    157165                OPTS.matchType = MATCH_BUNDLE_ID;
    158166                break;
    159167            case 'a':
    160168                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?)");
    162171                OPTS.matchType = MATCH_NAME;
    163172                break;
    164173            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");
    166175                OPTS.appAction = APP_SHOW;
    167176                break;
    168177            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");
    170179                OPTS.appAction = APP_HIDE;
    171180                break;
    172181            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");
    174183                OPTS.appAction = APP_QUIT;
    175184                break;
    176185            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");
    178187                OPTS.appAction = APP_KILL;
    179188                break;
    180189            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");
    182191                OPTS.appAction = APP_KILL_HARD;
    183192                break;
    184193            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");
    186195                OPTS.appAction = APP_LIST;
    187196                break;
    188197            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");
    190199                OPTS.appAction = APP_LIST;
    191200                OPTS.longList = true;
    192201                break;
    193202            case 'P':
    194                 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -q, -k, -K, -l, -P options");
     203                if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -q, -k, -K, -l, -L, -P, -f options");
    195204                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;
    196209                break;
    197210            case 'S':
     
    224237            } else usage();
    225238        } 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?)");
    227241            OPTS.matchType = MATCH_PATH;
    228242        } else usage();
     
    234248}
    235249
    236 CPSProcessSerNum frontApplication() {
    237     CPSProcessSerNum psn;
    238     OSStatus err = CPSGetFrontProcess(&psn);
     250ProcessSerialNumber frontApplication() {
     251    ProcessSerialNumber psn;
     252    OSStatus err = GetFrontProcess(&psn);
    239253    if (err != noErr) osstatusexit(err, "can't get frontmost process");
    240254#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);
    242256#endif
    243257    return psn;
    244258}
    245259
    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) {
     260OSStatus quitApplication(ProcessSerialNumber *psn) {
    267261    AppleEvent event;
    268262    AEAddressDesc appDesc;
     
    273267    if (err != noErr) return err;
    274268
    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>
    277269    err = AECreateAppleEvent(kCoreEventClass, kAEQuitApplication, &appDesc, kAutoGenerateReturnID, kAnyTransactionID, &event);
    278270    if (err != noErr) return err;
     
    288280}
    289281
    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 
     282pid_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
     289bool 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
     297char *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
     320ProcessSerialNumber matchApplication(void) {
    299321    if (OPTS.matchType == MATCH_FRONT) return frontApplication();
    300322
    301323    OSStatus err;
    302     CPSProcessSerNum psn = {
     324    ProcessSerialNumber psn = {
    303325        kNoProcess, kNoProcess
    304326    };
    305     int len;
     327    pid_t pid;
    306328    char *format = NULL;
    307329    if (OPTS.appAction == APP_LIST) {
     
    314336             ioctl(STDIN_FILENO,  TIOCGWINSZ, (char *)&ws) != -1) ||
    315337            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";
    317339        int pathlen = termwidth - strlen(banner) - 1;
    318340        // XXX don't ever free 'format', should fix if we get called repeatedly
     
    328350    }
    329351   
    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);
    337358
    338359        switch (OPTS.matchType) {
    339360            case MATCH_ALL:
    340361                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;
    362372            default:
    363373                errexit("internal error: invalid match type");
    364374        }
    365375        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")));
    372381            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);
    392385            }
    393386            putchar('\n');
     
    410403    getargs(argc, argv);
    411404
    412     // need to establish connection with window server
    413     InitCursor();
    414 
    415     CPSProcessInfoRec info;
    416     CPSProcessSerNum psn = matchApplication(&info);
     405    ProcessSerialNumber psn = matchApplication();
    417406
    418407    const char *verb = NULL;
     
    420409        case APP_NONE: break;
    421410        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;
    425414        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";
    431421            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;
    432426        default:
    433427            errexit("internal error: invalid application action");
     
    449443            psn = frontApplication();
    450444#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);
    452446#endif
    453447            if (OPTS.action != ACTION_NONE) usleep(750000); // XXX
    454             err = CPSPostShowReq(&psn) || CPSSetFrontProcess(&psn);
     448            err = ShowHideProcess(&psn, true) || SetFrontProcess(&psn);
    455449            verb = "bring current application's windows to the front";
    456450            break;
Note: See TracChangeset for help on using the changeset viewer.