1 #if !defined(lint) && !defined(DOS)
2 static char rcsid
[] = "$Id: execview.c 942 2008-03-04 18:21:33Z hubert@u.washington.edu $";
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 * ========================================================================
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"
42 #include "../signal.h"
43 #include "../../pico/estruct.h"
44 #include "../../pico/pico.h"
45 #include "../mailview.h"
46 #include "termin.gen.h"
49 #include "../../pico/osdep/mswin.h"
52 /* Useful structures */
54 typedef struct _execview_event_data_s
{
56 ProcessSerialNumber pid
;
62 /* internal prototypes */
64 pascal OSStatus
osx_launch_app_callback(EventHandlerCallRef
,
66 int install_app_launch_cb(void *);
67 void osx_launch_special_handling(MCAP_CMD_S
*, char *);
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?
80 exec_mailcap_cmd(MCAP_CMD_S
*mc_cmd
, char *image_file
, int needsterminal
)
83 STARTUPINFO start_info
;
84 PROCESS_INFORMATION proc_info
;
88 LPTSTR image_file_lpt
= NULL
;
89 LPTSTR cmd_lpt
= NULL
;
91 /* no special handling yet, but could be used to replace '*' hack */
93 cmd
= mc_cmd
->command
;
97 dprint((9, "run_viewer: command=%s\n", cmd
? cmd
: "?")) ;
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 */
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;
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
;
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",
148 childProcess
= proc_info
.hProcess
;
153 fs_give((void **) &cmd_lpt
);
157 int rc
= (int) GetLastError();
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
));
170 fs_give((void **) &image_file_lpt
);
174 char *command
= NULL
,
183 if(mc_cmd
->special_handling
){
186 if(mime_os_specific_access())
187 osx_launch_special_handling(mc_cmd
, image_file
);
189 q_status_message(SM_ORDER
, 0, 4, "VIEWER command cancelled");
190 our_unlink(image_file
);
194 char *cmd
= mc_cmd
->command
;
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));
202 snprintf(p
, l
+1-(p
-command
), "%s ; rm -f %s", cmd
, image_file
);
205 snprintf(p
, l
+1-(p
-command
), "%s ; sleep 5 ; rm -f %s", cmd
, image_file
);
210 if(p
-command
+2 < l
+1){
223 dprint((9, "exec_mailcap_cmd: command=%s\n",
224 command
? command
: "?"));
227 if(needsterminal
== 1)
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);
242 display_output_file(result_file
, "VIEWER", " command launched", 1);
245 q_status_message1(SM_ORDER
, 3, 4, "Cannot spawn command : %s", cmd
);
247 fs_give((void **)&command
);
249 fs_give((void **)&result_file
);
252 char *command
= NULL
,
260 /* no os-specific command handling */
262 cmd
= mc_cmd
->command
;
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));
271 snprintf(p
, l
+1-(p
-command
), "%s ; rm -f %s", cmd
, image_file
);
274 snprintf(p
, l
+1-(p
-command
), "%s ; sleep 5 ; rm -f %s", cmd
, image_file
);
279 if(!needsterminal
&& (p
-command
)+5 < l
){
288 dprint((9, "exec_mailcap_cmd: command=%s\n",
289 command
? command
: "?"));
292 if(needsterminal
== 1)
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);
307 display_output_file(result_file
, "VIEWER", " command launched", 1);
310 q_status_message1(SM_ORDER
, 3, 4, "Cannot spawn command : %s", cmd
);
312 fs_give((void **)&command
);
315 fs_give((void **)&result_file
);
320 /* ----------------------------------------------------------------------
321 Execute the given mailcap test= cmd
323 Args: cmd -- command to execute
328 exec_mailcap_test_cmd(char *cmd
)
331 return((WinExec(cmd
, SW_SHOWMINNOACTIVE
) < 32) ? 1 : 0);
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);
343 url_os_specified_browser(char *url
)
346 return(mswin_reg_default_browser(url
));
348 if(mime_os_specific_access()){
349 return(cpystr("open"));
352 /* do nothing here */
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
363 execview_pretty_command(MCAP_CMD_S
*mc_cmd
, int *free_ret
)
369 *free_ret
= rv_to_free
;
374 str
= mc_cmd
->command
;
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");
383 str
= cpystr("via Windows-specific method");
388 if(mc_cmd
->special_handling
){
389 CFStringRef str_ref
= NULL
, kind_str_ref
= NULL
;
393 if((str_ref
= CFStringCreateWithCString(NULL
, mc_cmd
->command
,
394 kCFStringEncodingASCII
)) == NULL
)
397 if((url_ref
= CFURLCreateWithString(NULL
, str_ref
, NULL
)) == NULL
)
400 if(LSCopyDisplayNameForURL(url_ref
, &kind_str_ref
) != noErr
)
403 if(CFStringGetCString(kind_str_ref
, buf
, (CFIndex
)255,
404 kCFStringEncodingASCII
) == false)
411 CFRelease(kind_str_ref
);
418 *free_ret
= rv_to_free
;
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
)
438 if((url_ref
= CFURLCreateWithString(NULL
, str_ref
, NULL
)) == NULL
)
440 if(CFURLGetFSRef(url_ref
, &app_ref
) == false)
442 if(FSPathMakeRef((unsigned char *)image_file
,
443 &file_ref
, NULL
) != noErr
)
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.
473 EventTargetRef target
;
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();
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
){
496 else if(rne_rv
== eventLoopQuitErr
)
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
;
510 static int dont_do_anything_yet
= 0;
511 switch(GetEventClass(event
)){
512 case kEventClassKeyboard
:
515 case kEventClassApplication
:
516 switch(GetEventKind(event
)){
517 case kEventAppTerminated
:
518 GetEventParameter(event
,
519 kEventParamProcessID
,
520 typeProcessSerialNumber
, NULL
,
523 SameProcess(&ev_datap
->pid
, &pid
, &res
);
526 ev_datap
->set_pid
= 0;
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
,
537 ev_datap
->set_pid
= 1;
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
)
560 InstallApplicationEventHandler(cb_upp
, 2, event_types
,
562 already_installed
= 1;
566 #endif /* OSX_TARGET */