summaryrefslogtreecommitdiff
path: root/alpine/osdep/execview.c
diff options
context:
space:
mode:
Diffstat (limited to 'alpine/osdep/execview.c')
-rw-r--r--alpine/osdep/execview.c555
1 files changed, 555 insertions, 0 deletions
diff --git a/alpine/osdep/execview.c b/alpine/osdep/execview.c
new file mode 100644
index 00000000..da7310df
--- /dev/null
+++ b/alpine/osdep/execview.c
@@ -0,0 +1,555 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: execview.c 942 2008-03-04 18:21:33Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+#include <general.h>
+
+#include "../../c-client/mail.h" /* for MAILSTREAM and friends */
+#include "../../c-client/osdep.h"
+#include "../../c-client/rfc822.h" /* for soutr_t and such */
+#include "../../c-client/misc.h" /* for cpystr proto */
+#include "../../c-client/utf8.h" /* for CHARSET and such*/
+#include "../../c-client/imap4r1.h"
+
+#include "../../pith/debug.h"
+
+#include "../../pith/osdep/temp_nam.h"
+#include "../../pith/osdep/color.h"
+#include "../../pith/osdep/mimedisp.h"
+
+#include "../../pith/charconv/utf8.h"
+#include "../../pith/charconv/filesys.h"
+
+#include "../pith/mailcap.h"
+
+#include "../status.h"
+#include "../radio.h"
+#include "../signal.h"
+#include "../../pico/estruct.h"
+#include "../../pico/pico.h"
+#include "../mailview.h"
+#include "termin.gen.h"
+
+#ifdef _WINDOWS
+#include "../../pico/osdep/mswin.h"
+#endif
+
+/* Useful structures */
+#if OSX_TARGET
+typedef struct _execview_event_data_s {
+ int done;
+ ProcessSerialNumber pid;
+ int set_pid;
+} EXEC_EVENT_DATA_S;
+#endif
+
+
+/* internal prototypes */
+#if OSX_TARGET
+pascal OSStatus osx_launch_app_callback(EventHandlerCallRef,
+ EventRef, void *);
+int install_app_launch_cb(void *);
+void osx_launch_special_handling(MCAP_CMD_S *, char *);
+#endif
+
+
+
+/* ----------------------------------------------------------------------
+ Execute the given mailcap command
+
+ Args: cmd -- the command to execute
+ image_file -- the file the data is in
+ needsterminal -- does this command want to take over the terminal?
+ ----*/
+void
+exec_mailcap_cmd(MCAP_CMD_S *mc_cmd, char *image_file, int needsterminal)
+{
+#ifdef _WINDOWS
+ STARTUPINFO start_info;
+ PROCESS_INFORMATION proc_info;
+ WINHAND childProcess;
+ int success = 0;
+ char *cmd;
+ LPTSTR image_file_lpt = NULL;
+ LPTSTR cmd_lpt = NULL;
+
+ /* no special handling yet, but could be used to replace '*' hack */
+ if(mc_cmd)
+ cmd = mc_cmd->command;
+ else
+ return;
+
+ dprint((9, "run_viewer: command=%s\n", cmd ? cmd : "?")) ;
+
+ if(image_file)
+ image_file_lpt = utf8_to_lptstr(image_file);
+
+ /* Set to READONLY so the viewer can't try to edit it and keep it around */
+ if(image_file_lpt)
+ SetFileAttributes(image_file_lpt, FILE_ATTRIBUTE_READONLY);
+
+ if(*cmd == '*' || (*cmd == '\"' && *(cmd+1) == '*')){
+ /*
+ * It has been asked that there be the ability to do an
+ * "Open With..." on attachments like you can from the
+ * Windows file browser. After looking into this, it
+ * seems that the only way to do this would be through
+ * an undocumented hack. Here, we would pass "openas" as
+ * the verb to mswin_shell_exec (also some changes in
+ * mswin_shell_exec). Since this is the delicate world
+ * of attachment handling, it seems right not to rely on
+ * a hack. The interface wouldn't be too clean anyways,
+ * as we would have to download the attachment only to
+ * display the "Open With..." dialog. Go figure, some
+ * things Microsoft just wants to keep to themselves.
+ */
+
+ /*
+ * 2/1/2007. No idea when the above comment was written, but it is
+ * documented now at least. The below two urls describe the "openas" verb:
+ *
+ * http://blogs.msdn.com/oldnewthing/archive/2004/11/26/270710.aspx
+ * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/
+ * shellcc/platform/shell/programmersguide/shell_basics/
+ * shell_basics_extending/context.asp
+ */
+ success = mswin_shell_exec(cmd, &childProcess) == 0;
+ }
+ else{
+ memset(&proc_info, 0, sizeof(proc_info));
+ memset(&start_info, 0, sizeof(start_info));
+ start_info.dwFlags = STARTF_FORCEONFEEDBACK;
+ start_info.wShowWindow = SW_SHOWNORMAL;
+
+ if(cmd)
+ cmd_lpt = utf8_to_lptstr(cmd);
+
+ if(CreateProcess(NULL, cmd_lpt, NULL, NULL, FALSE,
+ CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP,
+ NULL, NULL, &start_info, &proc_info) == TRUE){
+ q_status_message(SM_ORDER, 0, 4, "VIEWER command completed");
+ dprint ((3, "CreatProcess(%s) Success.\n",
+ cmd ? cmd : "?"));
+ childProcess = proc_info.hProcess;
+ success = 1;
+ }
+
+ if(cmd_lpt)
+ fs_give((void **) &cmd_lpt);
+ }
+
+ if(!success){
+ int rc = (int) GetLastError();
+ if(image_file_lpt)
+ SetFileAttributes(image_file_lpt, FILE_ATTRIBUTE_NORMAL);
+
+ our_unlink(image_file);
+ q_status_message2(SM_ORDER, 3, 4, "\007Can't start viewer. %s%s.",
+ (rc == 2 || rc == 3) ? "Viewer not found: " :
+ (rc == 8) ? "Not enough memory" : "Windows error ",
+ (rc == 2 || rc == 3) ? cmd :
+ (rc == 8) ? "" : int2string(rc));
+ }
+
+ if(image_file_lpt)
+ fs_give((void **) &image_file_lpt);
+
+#elif OSX_TARGET
+
+ char *command = NULL,
+ *result_file = NULL,
+ *p;
+ char **r_file_h;
+ PIPE_S *syspipe;
+ int mode;
+
+ if(!mc_cmd)
+ return;
+ if(mc_cmd->special_handling){
+ char *rhost;
+
+ if(mime_os_specific_access())
+ osx_launch_special_handling(mc_cmd, image_file);
+ else{
+ q_status_message(SM_ORDER, 0, 4, "VIEWER command cancelled");
+ our_unlink(image_file);
+ }
+ }
+ else {
+ char *cmd = mc_cmd->command;
+ size_t l;
+
+ l = 32 + strlen(cmd) + (2*strlen(image_file));
+ p = command = (char *) fs_get((l+1) * sizeof(char));
+ if(!needsterminal) /* put in background if it doesn't need terminal */
+ *p++ = '(';
+
+ snprintf(p, l+1-(p-command), "%s", cmd);
+ p += strlen(p);
+ if(!needsterminal){
+ if(p-command+2 < l+1){
+ *p++ = ')';
+ *p++ = ' ';
+ *p++ = '&';
+ }
+ }
+
+ if(p-command < l+1)
+ *p++ = '\n';
+
+ if(p-command < l+1)
+ *p = '\0';
+
+ dprint((9, "exec_mailcap_cmd: command=%s\n",
+ command ? command : "?"));
+
+ mode = PIPE_RESET;
+ if(needsterminal == 1)
+ r_file_h = NULL;
+ else{
+ mode |= PIPE_WRITE | PIPE_STDERR;
+ result_file = temp_nam(NULL, "pine_cmd");
+ r_file_h = &result_file;
+ }
+
+ if(syspipe = open_system_pipe(command, r_file_h, NULL, mode, 0, pipe_callback, NULL)){
+ close_system_pipe(&syspipe, NULL, pipe_callback);
+ if(needsterminal == 1)
+ q_status_message(SM_ORDER, 0, 4, "VIEWER command completed");
+ else if(needsterminal == 2)
+ display_output_file(result_file, "VIEWER", " command result", 1);
+ else
+ display_output_file(result_file, "VIEWER", " command launched", 1);
+ }
+ else
+ q_status_message1(SM_ORDER, 3, 4, "Cannot spawn command : %s", cmd);
+
+ fs_give((void **)&command);
+ if(result_file)
+ fs_give((void **)&result_file);
+ }
+#else
+ char *command = NULL,
+ *result_file = NULL,
+ *p, *cmd;
+ char **r_file_h;
+ PIPE_S *syspipe;
+ int mode;
+ size_t l;
+
+ /* no os-specific command handling */
+ if(mc_cmd)
+ cmd = mc_cmd->command;
+ else
+ return;
+ l = 32 + strlen(cmd) + 2*strlen(image_file);
+ p = command = (char *)fs_get((l+1) * sizeof(char));
+ if(!needsterminal) /* put in background if it doesn't need terminal */
+ *p++ = '(';
+ snprintf(p, l+1-(p-command), "%s ; rm -f %s", cmd, image_file);
+ command[l] = '\0';
+ p += strlen(p);
+ if(!needsterminal && (p-command)+5 < l){
+ *p++ = ')';
+ *p++ = ' ';
+ *p++ = '&';
+ }
+
+ *p++ = '\n';
+ *p = '\0';
+
+ dprint((9, "exec_mailcap_cmd: command=%s\n",
+ command ? command : "?"));
+
+ mode = PIPE_RESET;
+ if(needsterminal == 1)
+ r_file_h = NULL;
+ else{
+ mode |= PIPE_WRITE | PIPE_STDERR;
+ result_file = temp_nam(NULL, "pine_cmd");
+ r_file_h = &result_file;
+ }
+
+ if((syspipe = open_system_pipe(command, r_file_h, NULL, mode, 0, pipe_callback, NULL)) != NULL){
+ close_system_pipe(&syspipe, NULL, pipe_callback);
+ if(needsterminal == 1)
+ q_status_message(SM_ORDER, 0, 4, "VIEWER command completed");
+ else if(needsterminal == 2)
+ display_output_file(result_file, "VIEWER", " command result", 1);
+ else
+ display_output_file(result_file, "VIEWER", " command launched", 1);
+ }
+ else
+ q_status_message1(SM_ORDER, 3, 4, "Cannot spawn command : %s", cmd);
+
+ fs_give((void **)&command);
+
+ if(result_file)
+ fs_give((void **)&result_file);
+#endif
+}
+
+
+/* ----------------------------------------------------------------------
+ Execute the given mailcap test= cmd
+
+ Args: cmd -- command to execute
+ Returns exit status
+
+ ----*/
+int
+exec_mailcap_test_cmd(char *cmd)
+{
+#ifdef _WINDOWS
+ return((WinExec(cmd, SW_SHOWMINNOACTIVE) < 32) ? 1 : 0);
+#else
+ PIPE_S *syspipe;
+
+ return((syspipe = open_system_pipe(cmd, NULL, NULL, PIPE_SILENT, 0,
+ pipe_callback, NULL))
+ ? close_system_pipe(&syspipe, NULL, pipe_callback) : -1);
+#endif
+}
+
+
+char *
+url_os_specified_browser(char *url)
+{
+#ifdef _WINDOWS
+ return(mswin_reg_default_browser(url));
+#elif OSX_TARGET
+ if(mime_os_specific_access()){
+ return(cpystr("open"));
+ }
+#else
+ /* do nothing here */
+ return(NULL);
+#endif
+}
+
+/*
+ * Return a pretty command, on some OS's we might do something
+ * different than just display the command.
+ *
+ * free_ret - whether or not to free the return value
+ */
+char *
+execview_pretty_command(MCAP_CMD_S *mc_cmd, int *free_ret)
+{
+ char *str;
+ int rv_to_free = 0;
+
+ if(free_ret)
+ *free_ret = rv_to_free;
+
+ if(!mc_cmd)
+ return NULL;
+
+ str = mc_cmd->command;
+
+#ifdef _WINDOWS
+ if(*str == '*' || (*str == '\"' && str[1] == '*')){
+ if(!strncmp(str + ((*str == '\"') ? 2 : 1), "DDE*", 4))
+ str = cpystr("via app already running");
+ else if(!strncmp(str + ((*str == '\"') ? 2 : 1),"ShellEx*",8))
+ str = cpystr("via Explorer defined app");
+ else
+ str = cpystr("via Windows-specific method");
+
+ rv_to_free = 1;
+ }
+#elif OSX_TARGET
+ if(mc_cmd->special_handling){
+ CFStringRef str_ref = NULL, kind_str_ref = NULL;
+ CFURLRef url_ref;
+ char buf[256];
+
+ if((str_ref = CFStringCreateWithCString(NULL, mc_cmd->command,
+ kCFStringEncodingASCII)) == NULL)
+ return "";
+
+ if((url_ref = CFURLCreateWithString(NULL, str_ref, NULL)) == NULL)
+ return "";
+
+ if(LSCopyDisplayNameForURL(url_ref, &kind_str_ref) != noErr)
+ return "";
+
+ if(CFStringGetCString(kind_str_ref, buf, (CFIndex)255,
+ kCFStringEncodingASCII) == false)
+ return "";
+
+ buf[255] = '\0';
+ str = cpystr(buf);
+ rv_to_free = 1;
+ if(kind_str_ref)
+ CFRelease(kind_str_ref);
+ }
+#else
+ /* always pretty */
+#endif
+
+ if(free_ret)
+ *free_ret = rv_to_free;
+
+ return(str);
+}
+
+
+#if OSX_TARGET
+void
+osx_launch_special_handling(MCAP_CMD_S *mc_cmd, char *image_file)
+{
+ CFStringRef str_ref = NULL;
+ CFURLRef url_ref = NULL;
+ LSLaunchFSRefSpec launch_spec;
+ FSRef app_ref, file_ref;
+ static EXEC_EVENT_DATA_S event_data;
+
+ install_app_launch_cb((void *)&event_data);
+ if((str_ref = CFStringCreateWithCString(NULL, mc_cmd->command,
+ kCFStringEncodingASCII)) == NULL)
+ return;
+ if((url_ref = CFURLCreateWithString(NULL, str_ref, NULL)) == NULL)
+ return;
+ if(CFURLGetFSRef(url_ref, &app_ref) == false)
+ return;
+ if(FSPathMakeRef((unsigned char *)image_file,
+ &file_ref, NULL) != noErr)
+ return;
+ launch_spec.appRef = &app_ref;
+ launch_spec.numDocs = 1;
+ launch_spec.itemRefs = &file_ref;
+ launch_spec.passThruParams = NULL;
+ launch_spec.launchFlags = kLSLaunchDontAddToRecents | kLSLaunchNoParams
+ | kLSLaunchAsync | kLSLaunchNewInstance;
+ /* would want to use this if we ever did true event handling */
+ launch_spec.asyncRefCon = 0;
+
+ if(LSOpenFromRefSpec( &launch_spec, NULL) == noErr){
+ /*
+ * Here's the strategy: we want to be able to just launch
+ * the app and then just delete the temp file, but that
+ * doesn't work because the called app needs the temp file
+ * at least until it's finished loading. Being that there's
+ * no way to tell when the app has finished loading, we wait
+ * until the program has exited, which is the safest thing to
+ * do and is what we do for windows. Since we haven't totally
+ * embraced event handling at this point, we must do the waiting
+ * synchronously. We allow for a keystroke to stop waiting, and
+ * just delete the temp file.
+ * Ideally, we would launch the app, and keep running, checking
+ * the events until the process terminates, and then delete the
+ * temp file. In this method, we would delete the temp file
+ * at close time if the app was still running.
+ */
+ int ch;
+ OSStatus rne_rv;
+ EventTargetRef target;
+ EventRef out_event;
+ EventTypeSpec event_types[2] = {
+ {kEventClassApplication, kEventAppTerminated},
+ {kEventClassApplication, kEventAppLaunchNotification}};
+
+ q_status_message(SM_ORDER, 0, 4,
+ "Waiting for program to finish, or press a key to stop waiting...");
+ flush_status_messages(1);
+ target = GetEventDispatcherTarget();
+ event_data.done = 0;
+ event_data.set_pid = 0;
+ while(!event_data.done){
+ if((rne_rv = ReceiveNextEvent(2, event_types, 1,
+ true, &out_event)) == noErr){
+ SendEventToEventTarget(out_event, target);
+ ReleaseEvent(out_event);
+ }
+ else if(rne_rv == eventLoopTimedOutErr){
+ ch = read_char(1);
+ if(ch)
+ event_data.done = 1;
+ }
+ else if(rne_rv == eventLoopQuitErr)
+ event_data.done = 1;
+ }
+ our_unlink(image_file);
+ }
+ q_status_message(SM_ORDER, 0, 4, "VIEWER command completed");
+}
+
+pascal OSStatus osx_launch_app_callback(EventHandlerCallRef next_h, EventRef event,void *user_data)
+{
+ EXEC_EVENT_DATA_S *ev_datap = (EXEC_EVENT_DATA_S *)user_data;
+ ProcessSerialNumber pid;
+ Boolean res = 0;
+
+ static int dont_do_anything_yet = 0;
+ switch(GetEventClass(event)){
+ case kEventClassKeyboard:
+ ev_datap->done = 1;
+ break;
+ case kEventClassApplication:
+ switch(GetEventKind(event)){
+ case kEventAppTerminated:
+ GetEventParameter(event,
+ kEventParamProcessID,
+ typeProcessSerialNumber, NULL,
+ sizeof(pid), NULL,
+ &pid);
+ SameProcess(&ev_datap->pid, &pid, &res);
+ if(res){
+ ev_datap->done = 1;
+ ev_datap->set_pid = 0;
+ }
+ break;
+ case kEventAppLaunchNotification:
+ /* could check asyncRef too */
+ if(!ev_datap->set_pid){ /* should always be true */
+ GetEventParameter(event,
+ kEventParamProcessID,
+ typeProcessSerialNumber, NULL,
+ sizeof(ev_datap->pid), NULL,
+ &(ev_datap->pid));
+ ev_datap->set_pid = 1;
+ }
+ break;
+ }
+ break;
+ }
+ return(noErr);
+}
+
+
+int
+install_app_launch_cb(void *user_data)
+{
+ static int already_installed = 0;
+
+ if(!already_installed){
+ EventHandlerUPP cb_upp;
+ EventTypeSpec event_types[2] = {
+ {kEventClassApplication, kEventAppTerminated},
+ {kEventClassApplication, kEventAppLaunchNotification}};
+
+ if((cb_upp = NewEventHandlerUPP(osx_launch_app_callback)) == NULL)
+ return 1;
+ InstallApplicationEventHandler(cb_upp, 2, event_types,
+ user_data, NULL);
+ already_installed = 1;
+ }
+ return 0;
+}
+#endif /* OSX_TARGET */