source: trunk/appswitch/appswitch/main.c@ 92

Last change on this file since 92 was 92, checked in by Nicholas Riley, 20 years ago

moved to standard directory structure

File size: 14.4 KB
Line 
1/*
2 appswitch - a command-line application switcher
3 Nicholas Riley <appswitch@sabi.net>
4
5 Copyright (c) 2003, Nicholas Riley
6 All rights reserved.
7
8 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
9
10 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
11 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
12 * Neither the name of this software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
13
14 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
16*/
17
18#define DEBUG 0
19
20#include <unistd.h>
21#include <sys/ioctl.h>
22#include "CPS.h"
23
24const char *APP_NAME;
25
26#define VERSION "1.0b1"
27
28struct {
29 OSType creator;
30 CFStringRef bundleID;
31 char *name;
32 pid_t pid;
33 char *path;
34 enum {
35 MATCH_UNKNOWN, MATCH_FRONT, MATCH_CREATOR, MATCH_BUNDLE_ID, MATCH_NAME, MATCH_PID, MATCH_PATH, MATCH_ALL
36 } matchType;
37 enum {
38 APP_NONE, APP_SWITCH, APP_SHOW, APP_HIDE, APP_KILL, APP_LIST, APP_PRINT_PID
39 } appAction;
40 enum {
41 ACTION_NONE, ACTION_SHOW_ALL, ACTION_HIDE_OTHERS
42 } action;
43 enum {
44 FINAL_NONE, FINAL_SWITCH
45 } finalAction;
46} OPTS =
47{
48 kLSUnknownCreator, NULL, NULL, -1, NULL, MATCH_UNKNOWN, APP_NONE, ACTION_NONE, FINAL_NONE
49};
50
51typedef struct {
52 OSStatus status;
53 const char *desc;
54} errRec, errList[];
55
56static errList ERRS = {
57 // Process Manager errors
58 { appIsDaemon, "application is background-only\n", },
59 { procNotFound, "unable to connect to system service.\nAre you logged in?" },
60 // CoreGraphics errors
61 { kCGErrorIllegalArgument, "window server error.\nAre you logged in?" },
62 { fnfErr, "file not found" },
63 { 0, NULL }
64};
65
66void usage() {
67 fprintf(stderr, "usage: %s [-sShHkFlP] [-c creator] [-i bundleID] [-a name] [-p pid] [path]\n"
68 " -s show application, bring windows to front (do not switch)\n"
69 " -S show all applications\n"
70 " -h hide application\n"
71 " -H hide other applications\n"
72 " -k kill application\n"
73 " -l list applications\n"
74 " -P print application process ID\n"
75 " -F bring current application's windows to front\n"
76 " -c creator match application by four-character creator code ('ToyS')\n"
77 " -i bundle ID match application by bundle identifier (com.apple.scripteditor)\n"
78 " -p pid match application by process identifier [slower]\n"
79 " -a name match application by name\n"
80 , APP_NAME);
81 fprintf(stderr, "appswitch "VERSION" (c) 2003 Nicholas Riley <http://web.sabi.net/nriley/software/>.\n"
82 "Please send bugs, suggestions, etc. to <appswitch@sabi.net>.\n");
83
84 exit(1);
85}
86
87char *osstatusstr(OSStatus err) {
88 errRec *rec;
89 const char *errDesc = "unknown error";
90 char * const failedStr = "(unable to retrieve error message)";
91 static char *str = NULL;
92 size_t len;
93 if (str != NULL && str != failedStr) free(str);
94 for (rec = &(ERRS[0]) ; rec->status != 0 ; rec++)
95 if (rec->status == err) {
96 errDesc = rec->desc;
97 break;
98 }
99 len = strlen(errDesc) + 10 * sizeof(char);
100 str = (char *)malloc(len);
101 if (str != NULL)
102 snprintf(str, len, "%s (%ld)", errDesc, err);
103 else
104 str = failedStr;
105 return str;
106}
107
108void osstatusexit(OSStatus err, const char *fmt, ...) {
109 va_list ap;
110 const char *errDesc = osstatusstr(err);
111 va_start(ap, fmt);
112 fprintf(stderr, "%s: ", APP_NAME);
113 vfprintf(stderr, fmt, ap);
114 fprintf(stderr, ": %s\n", errDesc);
115 exit(1);
116}
117
118void errexit(const char *fmt, ...) {
119 va_list ap;
120 va_start(ap, fmt);
121 fprintf(stderr, "%s: ", APP_NAME);
122 vfprintf(stderr, fmt, ap);
123 fprintf(stderr, "\n");
124 exit(1);
125}
126
127void getargs(int argc, char * const argv[]) {
128 extern char *optarg;
129 extern int optind;
130 int ch;
131
132 if (argc == 1) usage();
133
134 const char *opts = "c:i:p:a:sShHklPF";
135
136 while ( (ch = getopt(argc, argv, opts)) != -1) {
137 switch (ch) {
138 case 'p':
139 if (OPTS.matchType != MATCH_UNKNOWN) errexit("choose only one of -c, -i, -p, -a options");
140 if (sscanf(optarg, "%d", &OPTS.pid) != 1 || OPTS.pid < 0)
141 errexit("invalid process identifier (argument of -p)");
142 OPTS.matchType = MATCH_PID;
143 break;
144 case 'c':
145 if (OPTS.matchType != MATCH_UNKNOWN) errexit("choose only one of -c, -i, -p, -a options");
146 if (strlen(optarg) != 4) errexit("creator (argument of -c) must be four characters long");
147 OPTS.creator = *(OSTypePtr)optarg;
148 OPTS.matchType = MATCH_CREATOR;
149 break;
150 case 'i':
151 if (OPTS.matchType != MATCH_UNKNOWN) errexit("choose only one of -c, -i, -p, -a options");
152 OPTS.bundleID = CFStringCreateWithCString(NULL, optarg, CFStringGetSystemEncoding());
153 OPTS.matchType = MATCH_BUNDLE_ID;
154 break;
155 case 'a':
156 if (OPTS.matchType != MATCH_UNKNOWN) errexit("choose only one of -c, -i, -p, -a options");
157 OPTS.name = strdup(optarg);
158 OPTS.matchType = MATCH_NAME;
159 break;
160 case 's':
161 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -k, -l, -P options");
162 OPTS.appAction = APP_SHOW;
163 break;
164 case 'h':
165 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -k, -l, -P options");
166 OPTS.appAction = APP_HIDE;
167 break;
168 case 'k':
169 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -k, -l, -P options");
170 OPTS.appAction = APP_KILL;
171 break;
172 case 'l':
173 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -k, -l, -P options");
174 OPTS.appAction = APP_LIST;
175 break;
176 case 'P':
177 if (OPTS.appAction != APP_NONE) errexit("choose only one of -s, -h, -k, -l, -P options");
178 OPTS.appAction = APP_PRINT_PID;
179 break;
180 case 'S':
181 if (OPTS.action != ACTION_NONE) errexit("choose -S, -H or neither option");
182 OPTS.action = ACTION_SHOW_ALL;
183 break;
184 case 'H':
185 if (OPTS.action != ACTION_NONE) errexit("choose -S, -H or neither option");
186 OPTS.action = ACTION_HIDE_OTHERS;
187 break;
188 case 'F':
189 if (OPTS.finalAction != FINAL_NONE) errexit("choose only one -F option");
190 OPTS.finalAction = FINAL_SWITCH;
191 break;
192 default: usage();
193 }
194 }
195
196 argc -= optind;
197 argv += optind;
198
199 if (OPTS.matchType != MATCH_UNKNOWN && argc != 0) usage();
200
201 if (OPTS.matchType == MATCH_UNKNOWN) {
202 if (argc == 0) {
203 if (OPTS.appAction == APP_LIST) {
204 OPTS.matchType = MATCH_ALL;
205 } else if (OPTS.action != ACTION_NONE || OPTS.finalAction != FINAL_NONE) {
206 OPTS.matchType = MATCH_FRONT;
207 } else usage();
208 } else if (argc == 1) {
209 OPTS.path = argv[0];
210 OPTS.matchType = MATCH_PATH;
211 } else usage();
212 }
213
214 if (OPTS.matchType != MATCH_FRONT && OPTS.appAction == APP_NONE)
215 OPTS.appAction = APP_SWITCH;
216
217}
218
219CPSProcessSerNum frontApplication() {
220 CPSProcessSerNum psn;
221 OSStatus err = CPSGetFrontProcess(&psn);
222 if (err != noErr) osstatusexit(err, "can't get frontmost process");
223#if DEBUG
224 fprintf(stderr, "front application PSN %ld.%ld\n", psn.hi, psn.lo);
225#endif
226 return psn;
227}
228
229CPSProcessSerNum matchApplication(CPSProcessInfoRec *info) {
230 long pathMaxLength = pathconf("/", _PC_PATH_MAX);
231 long nameMaxLength = pathconf("/", _PC_NAME_MAX);
232
233 char *path = (char *)malloc(pathMaxLength);
234 char *name = (char *)malloc(nameMaxLength);;
235
236 if (path == NULL || name == NULL) errexit("can't allocate memory for path or filename buffer");
237
238 if (OPTS.matchType == MATCH_FRONT) return frontApplication();
239
240 OSStatus err;
241 CPSProcessSerNum psn = {
242 kNoProcess, kNoProcess
243 };
244 int len;
245 char *format = NULL;
246 if (OPTS.appAction == APP_LIST) {
247 int termwidth = 80;
248 struct winsize ws;
249 char *banner = " PSN PID TYPE CREA NAME ";
250 // 12345678.0 12345 1234 1234 12345678901234567890
251 printf("%s PATH\n", banner);
252 if ((ioctl(STDOUT_FILENO, TIOCGWINSZ, (char *)&ws) != -1 ||
253 ioctl(STDERR_FILENO, TIOCGWINSZ, (char *)&ws) != -1 ||
254 ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&ws) != -1) ||
255 ws.ws_col != 0) termwidth = ws.ws_col;
256 int pathlen = termwidth - strlen(banner) - 1;
257 asprintf(&format, "%%8ld.%%ld %%5ld %%c%%c%%c%%c %%c%%c%%c%%c %%-20.20s %%-%d.%ds\n", pathlen, pathlen);
258 }
259
260 while ( (err = CPSGetNextProcess(&psn)) == noErr) {
261 err = CPSGetProcessInfo(&psn, info, path, pathMaxLength, &len, name, nameMaxLength);
262 if (err != noErr) osstatusexit(err, "can't get information for process PSN %ld.%ld", psn.hi, psn.lo);
263
264#if DEBUG
265 fprintf(stderr, "%ld.%ld: %s : %s\n", psn.hi, psn.lo, name, path);
266#endif
267
268 switch (OPTS.matchType) {
269 case MATCH_ALL:
270 break;
271 case MATCH_CREATOR: if (OPTS.creator != info->ExecFileCreator) continue;
272 break;
273 case MATCH_NAME: if (strcmp(name, OPTS.name) != 0) continue;
274 break;
275 case MATCH_PID: if (OPTS.pid != info->UnixPID) continue;
276 break;
277 case MATCH_PATH: if (strcmp(path, OPTS.path) != 0) continue;
278 break;
279 case MATCH_BUNDLE_ID:
280 {
281 CFURLRef url = CFURLCreateFromFileSystemRepresentation(NULL, path, strlen(path), false);
282 if (url == NULL) errexit("can't get bundle location for process '%s' (PSN %ld.%ld, pid %ld)", name, psn.hi, psn.lo, info->UnixPID);
283 CFBundleRef bundle = CFBundleCreate(NULL, url);
284 if (bundle != NULL) {
285 CFStringRef bundleID = CFBundleGetIdentifier(bundle);
286#if DEBUG
287 CFShow(bundleID);
288#endif
289 if (bundleID != NULL && CFStringCompare(OPTS.bundleID, bundleID, kCFCompareCaseInsensitive) == kCFCompareEqualTo)
290 break;
291 }
292 CFRelease(url);
293 continue;
294 }
295 default:
296 errexit("internal error: invalid match type");
297 }
298 if (OPTS.appAction == APP_LIST) {
299 char *type = (char *)&(info->ExecFileType), *crea = (char *)&(info->ExecFileCreator);
300 printf(format, psn.hi, psn.lo, info->UnixPID,
301 type[0], type[1], type[2], type[3],
302 crea[0], crea[1], crea[2], crea[3],
303 name, path);
304 continue;
305 }
306 return psn;
307 }
308 if (err != procNotFound) osstatusexit(err, "can't get next process");
309
310 if (OPTS.appAction == APP_LIST) return frontApplication();
311
312 errexit("can't find matching process");
313 return psn;
314}
315
316int main (int argc, char * const argv[]) {
317 OSStatus err = noErr;
318
319 APP_NAME = argv[0];
320 getargs(argc, argv);
321
322 // need to establish connection with window server
323 InitCursor();
324
325 CPSProcessInfoRec info;
326 CPSProcessSerNum psn = matchApplication(&info);
327
328 const char *verb;
329 switch (OPTS.appAction) {
330 case APP_NONE: break;
331 case APP_LIST: break; // already handled in matchApplication
332 case APP_SWITCH: err = CPSSetFrontProcess(&psn); verb = "set front"; break;
333 case APP_SHOW: err = CPSPostShowReq(&psn); verb = "show"; break;
334 case APP_HIDE: err = CPSPostHideReq(&psn); verb = "hide"; break;
335 case APP_KILL: err = CPSPostKillRequest(&psn, kNilOptions); verb = "kill"; break;
336 case APP_PRINT_PID:
337 if (info.UnixPID <= 0) errexit("can't get process ID");
338 printf("%lu\n", info.UnixPID); // pid_t is signed, but this field isn't
339 break;
340 default:
341 errexit("internal error: invalid application action");
342 }
343 if (err != noErr) osstatusexit(err, "can't %s process", verb);
344
345 switch (OPTS.action) {
346 case ACTION_NONE: break;
347 case ACTION_SHOW_ALL: err = CPSPostShowAllReq(&psn); verb = "show all"; break;
348 case ACTION_HIDE_OTHERS: err = CPSPostHideMostReq(&psn); verb = "hide other"; break;
349 default:
350 errexit("internal error: invalid action");
351 }
352 if (err != noErr) osstatusexit(err, "can't %s processes", verb);
353
354 switch (OPTS.finalAction) {
355 case FINAL_NONE: break;
356 case FINAL_SWITCH:
357 psn = frontApplication();
358#if DEBUG
359 fprintf(stderr, "posting show request for %ld.%ld\n", psn.hi, psn.lo);
360#endif
361 if (OPTS.action != ACTION_NONE) usleep(750000); // XXX
362 err = CPSPostShowReq(&psn) || CPSSetFrontProcess(&psn);
363 verb = "bring current application's windows to the front";
364 break;
365 default:
366 errexit("internal error: invalid final action");
367 }
368 if (err != noErr) osstatusexit(err, "can't %s", verb);
369
370 exit(0);
371}
Note: See TracBrowser for help on using the repository browser.