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 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 l
= 32 + strlen(cmd
) + (2*strlen(image_file
));
198 p
= command
= (char *) fs_get((l
+1) * sizeof(char));
199 if(!needsterminal
) /* put in background if it doesn't need terminal */
202 snprintf(p
, l
+1-(p
-command
), "%s", cmd
);
205 if(p
-command
+2 < l
+1){
218 dprint((9, "exec_mailcap_cmd: command=%s\n",
219 command
? command
: "?"));
222 if(needsterminal
== 1)
225 mode
|= PIPE_WRITE
| PIPE_STDERR
;
226 result_file
= temp_nam(NULL
, "pine_cmd");
227 r_file_h
= &result_file
;
230 if(syspipe
= open_system_pipe(command
, r_file_h
, NULL
, mode
, 0, pipe_callback
, NULL
)){
231 close_system_pipe(&syspipe
, NULL
, pipe_callback
);
232 if(needsterminal
== 1)
233 q_status_message(SM_ORDER
, 0, 4, "VIEWER command completed");
234 else if(needsterminal
== 2)
235 display_output_file(result_file
, "VIEWER", " command result", 1);
237 display_output_file(result_file
, "VIEWER", " command launched", 1);
240 q_status_message1(SM_ORDER
, 3, 4, "Cannot spawn command : %s", cmd
);
242 fs_give((void **)&command
);
244 fs_give((void **)&result_file
);
247 char *command
= NULL
,
255 /* no os-specific command handling */
257 cmd
= mc_cmd
->command
;
260 l
= 32 + strlen(cmd
) + 2*strlen(image_file
);
261 p
= command
= (char *)fs_get((l
+1) * sizeof(char));
262 if(!needsterminal
) /* put in background if it doesn't need terminal */
264 snprintf(p
, l
+1-(p
-command
), "%s ; sleep %d ; rm -f %s", cmd
, ps_global
->sleep
, image_file
);
267 if(!needsterminal
&& (p
-command
)+5 < l
){
276 dprint((9, "exec_mailcap_cmd: command=%s\n",
277 command
? command
: "?"));
280 if(needsterminal
== 1)
283 mode
|= PIPE_WRITE
| PIPE_STDERR
;
284 result_file
= temp_nam(NULL
, "pine_cmd");
285 r_file_h
= &result_file
;
288 if((syspipe
= open_system_pipe(command
, r_file_h
, NULL
, mode
, 0, pipe_callback
, NULL
)) != NULL
){
289 close_system_pipe(&syspipe
, NULL
, pipe_callback
);
290 if(needsterminal
== 1)
291 q_status_message(SM_ORDER
, 0, 4, "VIEWER command completed");
292 else if(needsterminal
== 2)
293 display_output_file(result_file
, "VIEWER", " command result", 1);
295 display_output_file(result_file
, "VIEWER", " command launched", 1);
298 q_status_message1(SM_ORDER
, 3, 4, "Cannot spawn command : %s", cmd
);
300 fs_give((void **)&command
);
303 fs_give((void **)&result_file
);
308 /* ----------------------------------------------------------------------
309 Execute the given mailcap test= cmd
311 Args: cmd -- command to execute
316 exec_mailcap_test_cmd(char *cmd
)
319 return((WinExec(cmd
, SW_SHOWMINNOACTIVE
) < 32) ? 1 : 0);
323 return((syspipe
= open_system_pipe(cmd
, NULL
, NULL
, PIPE_SILENT
, 0,
324 pipe_callback
, NULL
))
325 ? close_system_pipe(&syspipe
, NULL
, pipe_callback
) : -1);
331 url_os_specified_browser(char *url
)
334 return(mswin_reg_default_browser(url
));
336 if(mime_os_specific_access()){
337 return(cpystr("open"));
340 /* do nothing here */
346 * Return a pretty command, on some OS's we might do something
347 * different than just display the command.
349 * free_ret - whether or not to free the return value
352 execview_pretty_command(MCAP_CMD_S
*mc_cmd
, int *free_ret
)
358 *free_ret
= rv_to_free
;
363 str
= mc_cmd
->command
;
366 if(*str
== '*' || (*str
== '\"' && str
[1] == '*')){
367 if(!strncmp(str
+ ((*str
== '\"') ? 2 : 1), "DDE*", 4))
368 str
= cpystr("via app already running");
369 else if(!strncmp(str
+ ((*str
== '\"') ? 2 : 1),"ShellEx*",8))
370 str
= cpystr("via Explorer defined app");
372 str
= cpystr("via Windows-specific method");
377 if(mc_cmd
->special_handling
){
378 CFStringRef str_ref
= NULL
, kind_str_ref
= NULL
;
382 if((str_ref
= CFStringCreateWithCString(NULL
, mc_cmd
->command
,
383 kCFStringEncodingASCII
)) == NULL
)
386 if((url_ref
= CFURLCreateWithString(NULL
, str_ref
, NULL
)) == NULL
)
389 if(LSCopyDisplayNameForURL(url_ref
, &kind_str_ref
) != noErr
)
392 if(CFStringGetCString(kind_str_ref
, buf
, (CFIndex
)255,
393 kCFStringEncodingASCII
) == false)
400 CFRelease(kind_str_ref
);
407 *free_ret
= rv_to_free
;
415 osx_launch_special_handling(MCAP_CMD_S
*mc_cmd
, char *image_file
)
417 CFStringRef str_ref
= NULL
;
418 CFURLRef url_ref
= NULL
;
419 LSLaunchFSRefSpec launch_spec
;
420 FSRef app_ref
, file_ref
;
421 static EXEC_EVENT_DATA_S event_data
;
423 install_app_launch_cb((void *)&event_data
);
424 if((str_ref
= CFStringCreateWithCString(NULL
, mc_cmd
->command
,
425 kCFStringEncodingASCII
)) == NULL
)
427 if((url_ref
= CFURLCreateWithString(NULL
, str_ref
, NULL
)) == NULL
)
429 if(CFURLGetFSRef(url_ref
, &app_ref
) == false)
431 if(FSPathMakeRef((unsigned char *)image_file
,
432 &file_ref
, NULL
) != noErr
)
434 launch_spec
.appRef
= &app_ref
;
435 launch_spec
.numDocs
= 1;
436 launch_spec
.itemRefs
= &file_ref
;
437 launch_spec
.passThruParams
= NULL
;
438 launch_spec
.launchFlags
= kLSLaunchDontAddToRecents
| kLSLaunchNoParams
439 | kLSLaunchAsync
| kLSLaunchNewInstance
;
440 /* would want to use this if we ever did true event handling */
441 launch_spec
.asyncRefCon
= 0;
443 if(LSOpenFromRefSpec( &launch_spec
, NULL
) == noErr
){
445 * Here's the strategy: we want to be able to just launch
446 * the app and then just delete the temp file, but that
447 * doesn't work because the called app needs the temp file
448 * at least until it's finished loading. Being that there's
449 * no way to tell when the app has finished loading, we wait
450 * until the program has exited, which is the safest thing to
451 * do and is what we do for windows. Since we haven't totally
452 * embraced event handling at this point, we must do the waiting
453 * synchronously. We allow for a keystroke to stop waiting, and
454 * just delete the temp file.
455 * Ideally, we would launch the app, and keep running, checking
456 * the events until the process terminates, and then delete the
457 * temp file. In this method, we would delete the temp file
458 * at close time if the app was still running.
462 EventTargetRef target
;
464 EventTypeSpec event_types
[2] = {
465 {kEventClassApplication
, kEventAppTerminated
},
466 {kEventClassApplication
, kEventAppLaunchNotification
}};
468 q_status_message(SM_ORDER
, 0, 4,
469 "Waiting for program to finish, or press a key to stop waiting...");
470 flush_status_messages(1);
471 target
= GetEventDispatcherTarget();
473 event_data
.set_pid
= 0;
474 while(!event_data
.done
){
475 if((rne_rv
= ReceiveNextEvent(2, event_types
, 1,
476 true, &out_event
)) == noErr
){
477 SendEventToEventTarget(out_event
, target
);
478 ReleaseEvent(out_event
);
480 else if(rne_rv
== eventLoopTimedOutErr
){
485 else if(rne_rv
== eventLoopQuitErr
)
488 our_unlink(image_file
);
490 q_status_message(SM_ORDER
, 0, 4, "VIEWER command completed");
493 pascal OSStatus
osx_launch_app_callback(EventHandlerCallRef next_h
, EventRef event
,void *user_data
)
495 EXEC_EVENT_DATA_S
*ev_datap
= (EXEC_EVENT_DATA_S
*)user_data
;
496 ProcessSerialNumber pid
;
499 static int dont_do_anything_yet
= 0;
500 switch(GetEventClass(event
)){
501 case kEventClassKeyboard
:
504 case kEventClassApplication
:
505 switch(GetEventKind(event
)){
506 case kEventAppTerminated
:
507 GetEventParameter(event
,
508 kEventParamProcessID
,
509 typeProcessSerialNumber
, NULL
,
512 SameProcess(&ev_datap
->pid
, &pid
, &res
);
515 ev_datap
->set_pid
= 0;
518 case kEventAppLaunchNotification
:
519 /* could check asyncRef too */
520 if(!ev_datap
->set_pid
){ /* should always be true */
521 GetEventParameter(event
,
522 kEventParamProcessID
,
523 typeProcessSerialNumber
, NULL
,
524 sizeof(ev_datap
->pid
), NULL
,
526 ev_datap
->set_pid
= 1;
537 install_app_launch_cb(void *user_data
)
539 static int already_installed
= 0;
541 if(!already_installed
){
542 EventHandlerUPP cb_upp
;
543 EventTypeSpec event_types
[2] = {
544 {kEventClassApplication
, kEventAppTerminated
},
545 {kEventClassApplication
, kEventAppLaunchNotification
}};
547 if((cb_upp
= NewEventHandlerUPP(osx_launch_app_callback
)) == NULL
)
549 InstallApplicationEventHandler(cb_upp
, 2, event_types
,
551 already_installed
= 1;
555 #endif /* OSX_TARGET */