* Remove the ability to choose between the device and authorize methods
[alpine.git] / alpine / osdep / execview.c
blob56730667a2ab7624941d1479da79854a4a0f45d5
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: execview.c 942 2008-03-04 18:21:33Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2006-2008 University of Washington
8 * Copyright 2013-2020 Eduardo Chappa
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 #include <system.h>
20 #include <general.h>
22 #include "../../c-client/mail.h" /* for MAILSTREAM and friends */
23 #include "../../c-client/osdep.h"
24 #include "../../c-client/rfc822.h" /* for soutr_t and such */
25 #include "../../c-client/misc.h" /* for cpystr proto */
26 #include "../../c-client/utf8.h" /* for CHARSET and such*/
27 #include "../../c-client/imap4r1.h"
29 #include "../../pith/debug.h"
31 #include "../../pith/osdep/temp_nam.h"
32 #include "../../pith/osdep/color.h"
33 #include "../../pith/osdep/mimedisp.h"
35 #include "../../pith/charconv/utf8.h"
36 #include "../../pith/charconv/filesys.h"
38 #include "../pith/mailcap.h"
40 #include "../status.h"
41 #include "../radio.h"
42 #include "../signal.h"
43 #include "../../pico/estruct.h"
44 #include "../../pico/pico.h"
45 #include "../mailview.h"
46 #include "termin.gen.h"
48 #ifdef _WINDOWS
49 #include "../../pico/osdep/mswin.h"
50 #endif
52 /* Useful structures */
53 #if OSX_TARGET
54 typedef struct _execview_event_data_s {
55 int done;
56 ProcessSerialNumber pid;
57 int set_pid;
58 } EXEC_EVENT_DATA_S;
59 #endif
62 /* internal prototypes */
63 #if OSX_TARGET
64 pascal OSStatus osx_launch_app_callback(EventHandlerCallRef,
65 EventRef, void *);
66 int install_app_launch_cb(void *);
67 void osx_launch_special_handling(MCAP_CMD_S *, char *);
68 #endif
72 /* ----------------------------------------------------------------------
73 Execute the given mailcap command
75 Args: cmd -- the command to execute
76 image_file -- the file the data is in
77 needsterminal -- does this command want to take over the terminal?
78 ----*/
79 void
80 exec_mailcap_cmd(MCAP_CMD_S *mc_cmd, char *image_file, int needsterminal)
82 #ifdef _WINDOWS
83 STARTUPINFO start_info;
84 PROCESS_INFORMATION proc_info;
85 WINHAND childProcess;
86 int success = 0;
87 char *cmd;
88 LPTSTR image_file_lpt = NULL;
89 LPTSTR cmd_lpt = NULL;
91 /* no special handling yet, but could be used to replace '*' hack */
92 if(mc_cmd)
93 cmd = mc_cmd->command;
94 else
95 return;
97 dprint((9, "run_viewer: command=%s\n", cmd ? cmd : "?")) ;
99 if(image_file)
100 image_file_lpt = utf8_to_lptstr(image_file);
102 /* Set to READONLY so the viewer can't try to edit it and keep it around */
103 if(image_file_lpt)
104 SetFileAttributes(image_file_lpt, FILE_ATTRIBUTE_READONLY);
106 if(*cmd == '*' || (*cmd == '\"' && *(cmd+1) == '*')){
108 * It has been asked that there be the ability to do an
109 * "Open With..." on attachments like you can from the
110 * Windows file browser. After looking into this, it
111 * seems that the only way to do this would be through
112 * an undocumented hack. Here, we would pass "openas" as
113 * the verb to mswin_shell_exec (also some changes in
114 * mswin_shell_exec). Since this is the delicate world
115 * of attachment handling, it seems right not to rely on
116 * a hack. The interface wouldn't be too clean anyways,
117 * as we would have to download the attachment only to
118 * display the "Open With..." dialog. Go figure, some
119 * things Microsoft just wants to keep to themselves.
123 * 2/1/2007. No idea when the above comment was written, but it is
124 * documented now at least. The below two urls describe the "openas" verb:
126 * http://blogs.msdn.com/oldnewthing/archive/2004/11/26/270710.aspx
127 * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/
128 * shellcc/platform/shell/programmersguide/shell_basics/
129 * shell_basics_extending/context.asp
131 success = mswin_shell_exec(cmd, &childProcess) == 0;
133 else{
134 memset(&proc_info, 0, sizeof(proc_info));
135 memset(&start_info, 0, sizeof(start_info));
136 start_info.dwFlags = STARTF_FORCEONFEEDBACK;
137 start_info.wShowWindow = SW_SHOWNORMAL;
139 if(cmd)
140 cmd_lpt = utf8_to_lptstr(cmd);
142 if(CreateProcess(NULL, cmd_lpt, NULL, NULL, FALSE,
143 CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP,
144 NULL, NULL, &start_info, &proc_info) == TRUE){
145 q_status_message(SM_ORDER, 0, 4, "VIEWER command completed");
146 dprint ((3, "CreatProcess(%s) Success.\n",
147 cmd ? cmd : "?"));
148 childProcess = proc_info.hProcess;
149 success = 1;
152 if(cmd_lpt)
153 fs_give((void **) &cmd_lpt);
156 if(!success){
157 int rc = (int) GetLastError();
158 if(image_file_lpt)
159 SetFileAttributes(image_file_lpt, FILE_ATTRIBUTE_NORMAL);
161 our_unlink(image_file);
162 q_status_message2(SM_ORDER, 3, 4, "\007Can't start viewer. %s%s.",
163 (rc == 2 || rc == 3) ? "Viewer not found: " :
164 (rc == 8) ? "Not enough memory" : "Windows error ",
165 (rc == 2 || rc == 3) ? cmd :
166 (rc == 8) ? "" : int2string(rc));
169 if(image_file_lpt)
170 fs_give((void **) &image_file_lpt);
172 #elif OSX_TARGET
174 char *command = NULL,
175 *result_file = NULL,
177 char **r_file_h;
178 PIPE_S *syspipe;
179 int mode;
181 if(!mc_cmd)
182 return;
183 if(mc_cmd->special_handling){
184 char *rhost;
186 if(mime_os_specific_access())
187 osx_launch_special_handling(mc_cmd, image_file);
188 else{
189 q_status_message(SM_ORDER, 0, 4, "VIEWER command cancelled");
190 our_unlink(image_file);
193 else {
194 char *cmd = mc_cmd->command;
195 size_t l;
197 /* 32 is enough to contain "( ; sleep 5 ; rm -f ) &\n\0" */
198 l = 32 + strlen(cmd) + strlen(image_file);
199 p = command = (char *) fs_get((l+1) * sizeof(char));
201 if(needsterminal)
202 snprintf(p, l+1-(p-command), "%s ; rm -f %s", cmd, image_file);
203 else{
204 *p++ = '(';
205 snprintf(p, l+1-(p-command), "%s ; sleep 5 ; rm -f %s", cmd, image_file);
208 p += strlen(p);
209 if(!needsterminal){
210 if(p-command+2 < l+1){
211 *p++ = ')';
212 *p++ = ' ';
213 *p++ = '&';
217 if(p-command < l+1)
218 *p++ = '\n';
220 if(p-command < l+1)
221 *p = '\0';
223 dprint((9, "exec_mailcap_cmd: command=%s\n",
224 command ? command : "?"));
226 mode = PIPE_RESET;
227 if(needsterminal == 1)
228 r_file_h = NULL;
229 else{
230 mode |= PIPE_WRITE | PIPE_STDERR;
231 result_file = temp_nam(NULL, "pine_cmd");
232 r_file_h = &result_file;
235 if((syspipe = open_system_pipe(command, r_file_h, NULL, mode, 0, pipe_callback, NULL)) != NULL){
236 close_system_pipe(&syspipe, NULL, pipe_callback);
237 if(needsterminal == 1)
238 q_status_message(SM_ORDER, 0, 4, "VIEWER command completed");
239 else if(needsterminal == 2)
240 display_output_file(result_file, "VIEWER", " command result", 1);
241 else
242 display_output_file(result_file, "VIEWER", " command launched", 1);
244 else
245 q_status_message1(SM_ORDER, 3, 4, "Cannot spawn command : %s", cmd);
247 fs_give((void **)&command);
248 if(result_file)
249 fs_give((void **)&result_file);
251 #else
252 char *command = NULL,
253 *result_file = NULL,
254 *p, *cmd;
255 char **r_file_h;
256 PIPE_S *syspipe;
257 int mode;
258 size_t l;
260 /* no os-specific command handling */
261 if(mc_cmd)
262 cmd = mc_cmd->command;
263 else
264 return;
266 /* 32 is enough for "( ; sleep 5; rm -f ) &\n\0" */
267 l = 32 + strlen(cmd) + strlen(image_file);
268 p = command = (char *)fs_get((l+1) * sizeof(char));
270 if(needsterminal)
271 snprintf(p, l+1-(p-command), "%s ; rm -f %s", cmd, image_file);
272 else {
273 *p++ = '(';
274 snprintf(p, l+1-(p-command), "%s ; sleep 5 ; rm -f %s", cmd, image_file);
277 command[l] = '\0';
278 p += strlen(p);
279 if(!needsterminal && (p-command)+5 < l){
280 *p++ = ')';
281 *p++ = ' ';
282 *p++ = '&';
285 *p++ = '\n';
286 *p = '\0';
288 dprint((9, "exec_mailcap_cmd: command=%s\n",
289 command ? command : "?"));
291 mode = PIPE_RESET;
292 if(needsterminal == 1)
293 r_file_h = NULL;
294 else{
295 mode |= PIPE_WRITE | PIPE_STDERR;
296 result_file = temp_nam(NULL, "pine_cmd");
297 r_file_h = &result_file;
300 if((syspipe = open_system_pipe(command, r_file_h, NULL, mode, 0, pipe_callback, NULL)) != NULL){
301 close_system_pipe(&syspipe, NULL, pipe_callback);
302 if(needsterminal == 1)
303 q_status_message(SM_ORDER, 0, 4, "VIEWER command completed");
304 else if(needsterminal == 2)
305 display_output_file(result_file, "VIEWER", " command result", 1);
306 else
307 display_output_file(result_file, "VIEWER", " command launched", 1);
309 else
310 q_status_message1(SM_ORDER, 3, 4, "Cannot spawn command : %s", cmd);
312 fs_give((void **)&command);
314 if(result_file)
315 fs_give((void **)&result_file);
316 #endif
320 /* ----------------------------------------------------------------------
321 Execute the given mailcap test= cmd
323 Args: cmd -- command to execute
324 Returns exit status
326 ----*/
328 exec_mailcap_test_cmd(char *cmd)
330 #ifdef _WINDOWS
331 return((WinExec(cmd, SW_SHOWMINNOACTIVE) < 32) ? 1 : 0);
332 #else
333 PIPE_S *syspipe;
335 return((syspipe = open_system_pipe(cmd, NULL, NULL, PIPE_SILENT, 0,
336 pipe_callback, NULL))
337 ? close_system_pipe(&syspipe, NULL, pipe_callback) : -1);
338 #endif
342 char *
343 url_os_specified_browser(char *url)
345 #ifdef _WINDOWS
346 return(mswin_reg_default_browser(url));
347 #elif OSX_TARGET
348 if(mime_os_specific_access()){
349 return(cpystr("open"));
351 #endif
352 /* do nothing here */
353 return(NULL);
357 * Return a pretty command, on some OS's we might do something
358 * different than just display the command.
360 * free_ret - whether or not to free the return value
362 char *
363 execview_pretty_command(MCAP_CMD_S *mc_cmd, int *free_ret)
365 char *str;
366 int rv_to_free = 0;
368 if(free_ret)
369 *free_ret = rv_to_free;
371 if(!mc_cmd)
372 return NULL;
374 str = mc_cmd->command;
376 #ifdef _WINDOWS
377 if(*str == '*' || (*str == '\"' && str[1] == '*')){
378 if(!strncmp(str + ((*str == '\"') ? 2 : 1), "DDE*", 4))
379 str = cpystr("via app already running");
380 else if(!strncmp(str + ((*str == '\"') ? 2 : 1),"ShellEx*",8))
381 str = cpystr("via Explorer defined app");
382 else
383 str = cpystr("via Windows-specific method");
385 rv_to_free = 1;
387 #elif OSX_TARGET
388 if(mc_cmd->special_handling){
389 CFStringRef str_ref = NULL, kind_str_ref = NULL;
390 CFURLRef url_ref;
391 char buf[256];
393 if((str_ref = CFStringCreateWithCString(NULL, mc_cmd->command,
394 kCFStringEncodingASCII)) == NULL)
395 return "";
397 if((url_ref = CFURLCreateWithString(NULL, str_ref, NULL)) == NULL)
398 return "";
400 if(LSCopyDisplayNameForURL(url_ref, &kind_str_ref) != noErr)
401 return "";
403 if(CFStringGetCString(kind_str_ref, buf, (CFIndex)255,
404 kCFStringEncodingASCII) == false)
405 return "";
407 buf[255] = '\0';
408 str = cpystr(buf);
409 rv_to_free = 1;
410 if(kind_str_ref)
411 CFRelease(kind_str_ref);
413 #else
414 /* always pretty */
415 #endif
417 if(free_ret)
418 *free_ret = rv_to_free;
420 return(str);
424 #if OSX_TARGET
425 void
426 osx_launch_special_handling(MCAP_CMD_S *mc_cmd, char *image_file)
428 CFStringRef str_ref = NULL;
429 CFURLRef url_ref = NULL;
430 LSLaunchFSRefSpec launch_spec;
431 FSRef app_ref, file_ref;
432 static EXEC_EVENT_DATA_S event_data;
434 install_app_launch_cb((void *)&event_data);
435 if((str_ref = CFStringCreateWithCString(NULL, mc_cmd->command,
436 kCFStringEncodingASCII)) == NULL)
437 return;
438 if((url_ref = CFURLCreateWithString(NULL, str_ref, NULL)) == NULL)
439 return;
440 if(CFURLGetFSRef(url_ref, &app_ref) == false)
441 return;
442 if(FSPathMakeRef((unsigned char *)image_file,
443 &file_ref, NULL) != noErr)
444 return;
445 launch_spec.appRef = &app_ref;
446 launch_spec.numDocs = 1;
447 launch_spec.itemRefs = &file_ref;
448 launch_spec.passThruParams = NULL;
449 launch_spec.launchFlags = kLSLaunchDontAddToRecents | kLSLaunchNoParams
450 | kLSLaunchAsync | kLSLaunchNewInstance;
451 /* would want to use this if we ever did true event handling */
452 launch_spec.asyncRefCon = 0;
454 if(LSOpenFromRefSpec( &launch_spec, NULL) == noErr){
456 * Here's the strategy: we want to be able to just launch
457 * the app and then just delete the temp file, but that
458 * doesn't work because the called app needs the temp file
459 * at least until it's finished loading. Being that there's
460 * no way to tell when the app has finished loading, we wait
461 * until the program has exited, which is the safest thing to
462 * do and is what we do for windows. Since we haven't totally
463 * embraced event handling at this point, we must do the waiting
464 * synchronously. We allow for a keystroke to stop waiting, and
465 * just delete the temp file.
466 * Ideally, we would launch the app, and keep running, checking
467 * the events until the process terminates, and then delete the
468 * temp file. In this method, we would delete the temp file
469 * at close time if the app was still running.
471 int ch;
472 OSStatus rne_rv;
473 EventTargetRef target;
474 EventRef out_event;
475 EventTypeSpec event_types[2] = {
476 {kEventClassApplication, kEventAppTerminated},
477 {kEventClassApplication, kEventAppLaunchNotification}};
479 q_status_message(SM_ORDER, 0, 4,
480 "Waiting for program to finish, or press a key to stop waiting...");
481 flush_status_messages(1);
482 target = GetEventDispatcherTarget();
483 event_data.done = 0;
484 event_data.set_pid = 0;
485 while(!event_data.done){
486 if((rne_rv = ReceiveNextEvent(2, event_types, 1,
487 true, &out_event)) == noErr){
488 SendEventToEventTarget(out_event, target);
489 ReleaseEvent(out_event);
491 else if(rne_rv == eventLoopTimedOutErr){
492 ch = read_char(1);
493 if(ch)
494 event_data.done = 1;
496 else if(rne_rv == eventLoopQuitErr)
497 event_data.done = 1;
499 our_unlink(image_file);
501 q_status_message(SM_ORDER, 0, 4, "VIEWER command completed");
504 pascal OSStatus osx_launch_app_callback(EventHandlerCallRef next_h, EventRef event,void *user_data)
506 EXEC_EVENT_DATA_S *ev_datap = (EXEC_EVENT_DATA_S *)user_data;
507 ProcessSerialNumber pid;
508 Boolean res = 0;
510 static int dont_do_anything_yet = 0;
511 switch(GetEventClass(event)){
512 case kEventClassKeyboard:
513 ev_datap->done = 1;
514 break;
515 case kEventClassApplication:
516 switch(GetEventKind(event)){
517 case kEventAppTerminated:
518 GetEventParameter(event,
519 kEventParamProcessID,
520 typeProcessSerialNumber, NULL,
521 sizeof(pid), NULL,
522 &pid);
523 SameProcess(&ev_datap->pid, &pid, &res);
524 if(res){
525 ev_datap->done = 1;
526 ev_datap->set_pid = 0;
528 break;
529 case kEventAppLaunchNotification:
530 /* could check asyncRef too */
531 if(!ev_datap->set_pid){ /* should always be true */
532 GetEventParameter(event,
533 kEventParamProcessID,
534 typeProcessSerialNumber, NULL,
535 sizeof(ev_datap->pid), NULL,
536 &(ev_datap->pid));
537 ev_datap->set_pid = 1;
539 break;
541 break;
543 return(noErr);
548 install_app_launch_cb(void *user_data)
550 static int already_installed = 0;
552 if(!already_installed){
553 EventHandlerUPP cb_upp;
554 EventTypeSpec event_types[2] = {
555 {kEventClassApplication, kEventAppTerminated},
556 {kEventClassApplication, kEventAppLaunchNotification}};
558 if((cb_upp = NewEventHandlerUPP(osx_launch_app_callback)) == NULL)
559 return 1;
560 InstallApplicationEventHandler(cb_upp, 2, event_types,
561 user_data, NULL);
562 already_installed = 1;
564 return 0;
566 #endif /* OSX_TARGET */