medit/moo/mooterm/mootermpt-win32.c

829 lines
21 KiB
C

/*
* mooterm/mootermvt-win32.c
*
* Copyright (C) 2004-2005 by Yevgen Muntyan <muntyan@math.tamu.edu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* See COPYING file that comes with this distribution.
*/
#define MOOTERM_COMPILATION
#include "mooterm/mootermpt-private.h"
#include "mooterm/mooterm-private.h"
#include "mooterm/mootermhelper.h"
#include "mooterm/mooterm-private.h"
#include "mooutils/moomarshals.h"
#include "mooutils/moocompat.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <io.h>
#include <fcntl.h>
#include <errno.h>
#define TRY_NUM 10
#define SLEEP_TIME 10
#define TERM_EMULATION "xterm"
#define READ_CHUNK_SIZE 1024
#define WRITE_CHUNK_SIZE 4096
#define POLL_TIME 5
#define POLL_NUM 1
static char *HELPER_DIR = NULL;
#define HELPER_BINARY "termhelper.exe"
void moo_term_set_helper_directory (const char *dir)
{
g_free (HELPER_DIR);
HELPER_DIR = g_strdup (dir);
}
#define TERM_WIDTH(pt__) (MOO_TERM_PT(pt__)->priv->term->priv->width)
#define TERM_HEIGHT(pt__) (MOO_TERM_PT(pt__)->priv->term->priv->height)
#define MOO_TERM_PT_WIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MOO_TYPE_TERM_PT_WIN, MooTermPtWin))
#define MOO_TERM_PT_WIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MOO_TYPE_TERM_PT_WIN, MooTermPtWinClass))
#define MOO_IS_TERM_PT_WIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MOO_TYPE_TERM_PT_WIN))
#define MOO_IS_TERM_PT_WIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MOO_TYPE_TERM_PT_WIN))
#define MOO_TERM_PT_WIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MOO_TYPE_TERM_PT_WIN, MooTermPtWinClass))
typedef struct _MooTermPtWin MooTermPtWin;
typedef struct _MooTermPtWinClass MooTermPtWinClass;
struct _MooTermPtWin {
MooTermPt parent;
GPid pid;
gulong process_id;
int in;
int out;
GIOChannel *in_io;
GIOChannel *out_io;
guint watch_id;
guint out_watch_id;
};
struct _MooTermPtWinClass {
MooTermPtClass parent_class;
};
static void moo_term_pt_win_finalize (GObject *object);
static gboolean read_helper_out (GIOChannel *source,
GIOCondition condition,
MooTermPtWin *self);
static void set_size (MooTermPt *pt,
guint width,
guint height);
static gboolean fork_command (MooTermPt *pt,
const MooTermCommand *cmd,
const char *working_dir,
char **envp,
GError **error);
static void pt_write (MooTermPt *pt,
const char *string,
gssize len);
static void kill_child (MooTermPt *pt);
static gboolean run_in_helper (const char *cmd,
const char *working_dir,
char **env,
guint width, guint height,
int *hin, int *hout,
GPid *hpid, gulong *hproc_id,
GError **error);
/* MOO_TYPE_TERM_PT_WIN */
G_DEFINE_TYPE (MooTermPtWin, moo_term_pt_win, MOO_TYPE_TERM_PT)
static void
moo_term_pt_win_class_init (MooTermPtWinClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
MooTermPtClass *pt_class = MOO_TERM_PT_CLASS (klass);
gobject_class->finalize = moo_term_pt_win_finalize;
pt_class->set_size = set_size;
pt_class->fork_command = fork_command;
pt_class->write = pt_write;
pt_class->kill_child = kill_child;
}
static void
moo_term_pt_win_init (MooTermPtWin *pt)
{
MOO_TERM_PT(pt)->priv->child_alive = FALSE;
pt->pid = (GPid) -1;
pt->process_id = 0;
pt->watch_id = 0;
pt->in = -1;
pt->in_io = NULL;
pt->out = -1;
pt->out_io = NULL;
pt->out_watch_id = 0;
}
static void
moo_term_pt_win_finalize (GObject *object)
{
MooTermPtWin *pt = MOO_TERM_PT_WIN (object);
kill_child (MOO_TERM_PT (pt));
G_OBJECT_CLASS (moo_term_pt_win_parent_class)->finalize (object);
}
MooTermCommand *
_moo_term_get_default_shell (void)
{
g_return_val_if_reached (NULL);
}
static gboolean
fork_command (MooTermPt *pt_gen,
const MooTermCommand *cmd,
const char *working_dir,
char **envp,
GError **error)
{
MooTermPtWin *pt;
gboolean result;
g_return_val_if_fail (!pt_gen->priv->child_alive, FALSE);
g_return_val_if_fail (cmd->cmd_line != NULL, FALSE);
pt = MOO_TERM_PT_WIN (pt_gen);
result = run_in_helper (cmd->cmd_line, working_dir, envp,
TERM_WIDTH (pt), TERM_HEIGHT (pt),
&pt->in, &pt->out, &pt->pid,
&pt->process_id, error);
if (!result)
return FALSE;
else
g_message ("%s: started helper pid %d", G_STRLOC,
(int) pt->pid);
pt->in_io = g_io_channel_win32_new_fd (pt->in);
g_io_channel_set_encoding (pt->in_io, NULL, NULL);
// pt->priv->in_watch_id = g_io_add_watch (pt->priv->in_io,
// (GIOCondition)(G_IO_ERR | G_IO_HUP),
// (GIOFunc)helper_in_hup,
// pt);
pt->out_io = g_io_channel_win32_new_fd (pt->out);
g_io_channel_set_encoding (pt->out_io, NULL, NULL);
g_io_channel_set_buffered (pt->in_io, FALSE);
pt->out_watch_id = g_io_add_watch (pt->out_io,
(GIOCondition)(G_IO_IN | G_IO_PRI | G_IO_HUP),
(GIOFunc) read_helper_out,
pt);
// GSource *src = g_main_context_find_source_by_id (NULL, helper.out_watch_id);
// if (src) g_source_set_priority (src, READ_HELPER_OUT_PRIORITY);
// else g_warning ("%s: could not find helper_io_watch source", G_STRLOC);
pt_gen->priv->child_alive = TRUE;
return TRUE;
}
static gboolean
read_helper_out (GIOChannel *source,
GIOCondition condition,
MooTermPtWin *pt)
{
GError *err = NULL;
gboolean error_occured = FALSE;
g_assert (pt->out_io == source);
if (condition & G_IO_HUP)
{
g_message ("%s: G_IO_HUP", G_STRLOC);
error_occured = TRUE;
}
else if (condition & G_IO_ERR)
{
g_message ("%s: G_IO_ERR", G_STRLOC);
error_occured = TRUE;
}
else if (condition & (G_IO_IN | G_IO_PRI))
{
char buf[READ_CHUNK_SIZE];
int count = 0;
gsize read;
int again = TRY_NUM;
g_io_channel_read_chars (source, buf, 1, &read, &err);
if (read == 1) ++count;
while (again && !err && !error_occured && count < READ_CHUNK_SIZE)
{
if (g_io_channel_get_buffer_condition (source) & G_IO_IN)
{
g_io_channel_read_chars (source, buf + count, 1, &read, &err);
if (read == 1)
{
++count;
}
else
{
if (--again)
g_usleep (SLEEP_TIME);
}
}
else
{
if (--again)
g_usleep (SLEEP_TIME);
}
}
if (count > 0)
moo_term_feed (MOO_TERM_PT(pt)->priv->term, buf, count);
error_occured = (err != NULL);
}
else
{
g_critical ("%s: unknown source condition", G_STRLOC);
}
if (error_occured)
{
if (err)
{
g_message ("%s: %s", G_STRLOC, err->message);
g_error_free (err);
}
g_io_channel_shutdown (pt->in_io, TRUE, NULL);
g_io_channel_unref (pt->in_io);
pt->in_io = NULL;
pt->in = -1;
kill_child (MOO_TERM_PT (pt));
return FALSE;
}
return TRUE;
}
static void
kill_child (MooTermPt *pt_gen)
{
MooTermPtWin *pt = MOO_TERM_PT_WIN (pt_gen);
if (pt->in_io)
{
char cmd[2] = {HELPER_CMD_CHAR, HELPER_GOODBYE};
pt_write (MOO_TERM_PT (pt), cmd, 2);
g_io_channel_shutdown (pt->in_io, TRUE, NULL);
g_io_channel_unref (pt->in_io);
pt->in_io = NULL;
}
if (pt->out_io)
{
g_io_channel_shutdown (pt->out_io, TRUE, NULL);
g_io_channel_unref (pt->out_io);
pt->out_io = NULL;
}
if (pt->watch_id)
{
g_source_remove (pt->watch_id);
pt->watch_id = 0;
}
if (pt->out_watch_id)
{
g_source_remove (pt->out_watch_id);
pt->out_watch_id = 0;
}
if (pt->pid != (GPid) -1)
{
g_spawn_close_pid (pt->pid);
pt->pid = (GPid) -1;
}
pt->in = -1;
pt->out = -1;
pt->process_id = 0;
if (pt_gen->priv->child_alive)
{
pt_gen->priv->child_alive = FALSE;
g_signal_emit_by_name (pt, "child-died");
}
}
static void
set_size (MooTermPt *pt,
guint width,
guint height)
{
if (pt->priv->child_alive)
pt_write (MOO_TERM_PT (pt), set_size_cmd (width, height), SIZE_CMD_LEN);
}
static void
append (MooTermPt *pt,
const char *data,
guint len)
{
GByteArray *ar = g_byte_array_sized_new (len);
g_byte_array_append (ar, (const guint8*)data, len);
g_queue_push_tail (pt->priv->pending_write, ar);
}
/* writes given data to file, returns TRUE on successful write,
FALSE when could not write al teh data, puts start of leftover
to string, length of it to len, and fills err in case of error */
static gboolean
do_write (MooTermPt *pt_gen,
const char **string,
guint *plen,
GError **err)
{
GIOStatus status;
guint written;
MooTermPtWin *pt = MOO_TERM_PT_WIN (pt_gen);
g_return_val_if_fail (pt->in_io != NULL, FALSE);
status = g_io_channel_write_chars (pt->in_io, *string,
*plen > WRITE_CHUNK_SIZE ? WRITE_CHUNK_SIZE : *plen,
&written, err);
if (status == G_IO_STATUS_ERROR)
return FALSE;
*string += written;
*plen -= written;
if (!*plen)
*string = NULL;
return TRUE;
}
static gboolean
write_cb (MooTermPt *pt)
{
pt_write (pt, NULL, 0);
return TRUE;
}
static void
start_writer (MooTermPt *pt)
{
if (!pt->priv->pending_write_id)
pt->priv->pending_write_id =
g_idle_add_full (PT_WRITER_PRIORITY,
(GSourceFunc) write_cb,
pt, NULL);
}
static void
stop_writer (MooTermPt *pt)
{
if (pt->priv->pending_write_id)
{
g_source_remove (pt->priv->pending_write_id);
pt->priv->pending_write_id = 0;
}
}
static void
pt_write (MooTermPt *pt,
const char *data,
gssize data_len)
{
g_return_if_fail (data == NULL || data_len != 0);
g_return_if_fail (pt->priv->child_alive);
while (data || !g_queue_is_empty (pt->priv->pending_write))
{
GError *err = NULL;
const char *string;
guint len;
GByteArray *freeme = NULL;
if (!g_queue_is_empty (pt->priv->pending_write))
{
if (data)
{
append (pt, data, data_len > 0 ? (guint)data_len : strlen (data));
data = NULL;
}
freeme = g_queue_peek_head (pt->priv->pending_write);
string = (const char *) freeme->data;
len = freeme->len;
}
else
{
string = data;
len = data_len > 0 ? (guint)data_len : strlen (data);
data = NULL;
}
if (do_write (pt, &string, &len, &err))
{
if (len)
{
if (freeme)
{
memmove (freeme->data, freeme->data + (freeme->len - len), len);
g_byte_array_set_size (freeme, len);
}
else
{
append (pt, string, len);
}
break;
}
else if (freeme)
{
g_byte_array_free (freeme, TRUE);
g_queue_pop_head (pt->priv->pending_write);
}
}
else
{
g_message ("%s: stopping writing to child", G_STRLOC);
kill_child (pt);
}
if (err)
{
g_message ("%s: %s", G_STRLOC, err->message);
g_error_free (err);
}
}
if (!g_queue_is_empty (pt->priv->pending_write))
start_writer (pt);
else
stop_writer (pt);
}
enum {
READ_END = 0,
WRITE_END = 1
};
static gboolean
run_in_helper (const char *cmd,
const char *working_dir,
char **env,
guint width, guint height,
int *hin, int *hout,
GPid *hpid, gulong *hproc_id,
GError **error)
{
/* char *cwidth = NULL, *cheight = NULL; */
int my_stdin = _dup(0);
int my_stdout = _dup(1);
GPid helper = (GPid)INVALID_HANDLE_VALUE;
int helper_in[2] = {-1, -1};
int helper_out[2] = {-1, -1};
char *cmd_line = NULL;
STARTUPINFO sinfo;
PROCESS_INFORMATION pinfo;
GString *helper_binary = NULL;
GPtrArray *saved_env = NULL;
const char **s;
g_return_val_if_fail (cmd != NULL && hin != NULL && hout != NULL &&
hpid != NULL, FALSE);
if(_pipe (helper_in, 512, O_NOINHERIT | O_BINARY) == -1)
{
g_set_error (error, MOO_TERM_ERROR, errno,
"_pipe: %s", g_strerror (errno));
goto error;
}
if(_pipe (helper_out, 512, O_NOINHERIT | O_BINARY) == -1)
{
g_set_error (error, MOO_TERM_ERROR, errno,
"_pipe: %s", g_strerror (errno));
goto error;
}
if (_dup2 (helper_in[READ_END], 0))
{
g_set_error (error, MOO_TERM_ERROR, errno,
"_dup2: %s", g_strerror (errno));
goto error;
}
if (_dup2 (helper_out[WRITE_END], 1))
{
g_set_error (error, MOO_TERM_ERROR, errno,
"_dup2: %s", g_strerror (errno));
goto error;
}
close (helper_in[READ_END]);
close (helper_out[WRITE_END]);
#if 0
// cwidth = g_strdup_printf ("%d", width);
// cheight = g_strdup_printf ("%d", height);
// helper = (GPid) _spawnl(P_NOWAIT,
// HELPER_BINARY,
// HELPER_BINARY,
// cwidth,
// cheight,
// cmd,
// NULL);
#endif
/************************************************************************
* We need to use CreateProcess here in order to be able to create new,
* hidden console
*/
if (!HELPER_DIR || !HELPER_DIR[0])
{
g_warning ("%s: helper directory is not set", G_STRLOC);
g_free (HELPER_DIR);
HELPER_DIR = NULL;
}
if (HELPER_DIR)
{
helper_binary = g_string_new (HELPER_DIR);
g_string_append (helper_binary, "\\" HELPER_BINARY);
}
else
{
helper_binary = g_string_new (HELPER_BINARY);
}
g_message ("%s: helper '%s'", G_STRLOC, helper_binary->str);
cmd_line = g_strdup_printf ("%s %d %d %s", HELPER_BINARY,
width, height, cmd);
saved_env = g_ptr_array_new ();
if (env)
{
for (s = (const char**)env; *s != NULL; ++s)
{
char **pair = g_strsplit (*s, "=", 2);
if (pair && pair[0])
{
const char *val = g_getenv (pair[0]);
g_ptr_array_add (saved_env, g_strdup (pair[0]));
g_ptr_array_add (saved_env, g_strdup (val));
if (pair[1])
g_setenv (pair[0], pair[1], TRUE);
else
g_unsetenv (pair[0]);
}
g_strfreev (pair);
}
}
if (HELPER_DIR)
{
const char *var = "PATH";
const char *val = g_getenv (var);
char *newval = 0;
g_message ("%s: pushing '%s' to %s", G_STRLOC, HELPER_DIR, var);
g_ptr_array_add (saved_env, g_strdup (var));
g_ptr_array_add (saved_env, g_strdup (val));
if (val)
newval = g_strdup_printf ("%s;%s", HELPER_DIR, val);
else
newval = g_strdup (HELPER_DIR);
g_setenv (var, newval, TRUE);
g_free (newval);
}
if (working_dir)
{
const char *val = g_getenv (MOO_TERM_HELPER_ENV);
g_ptr_array_add (saved_env, g_strdup (MOO_TERM_HELPER_ENV));
g_ptr_array_add (saved_env, g_strdup (val));
g_setenv (MOO_TERM_HELPER_ENV, working_dir, TRUE);
}
g_ptr_array_add (saved_env, NULL);
g_message ("%s: command line '%s'", G_STRLOC, cmd_line);
memset (&sinfo, 0, sizeof (sinfo));
sinfo.cb = sizeof (STARTUPINFO);
sinfo.dwFlags = STARTF_USESTDHANDLES;
sinfo.hStdInput = (HANDLE)_get_osfhandle (0);
sinfo.hStdOutput = (HANDLE)_get_osfhandle (1);
sinfo.hStdError = (HANDLE)_get_osfhandle (2);
sinfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
sinfo.wShowWindow = SW_HIDE;
if (! CreateProcess (helper_binary->str, cmd_line, NULL, NULL, TRUE,
CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP,
NULL,
HELPER_DIR,
&sinfo,
&pinfo))
{
g_set_error (error, MOO_TERM_ERROR, 0,
"CreateProcess: %s",
g_win32_error_message (GetLastError ()));
goto error;
}
g_free (cmd_line);
cmd_line = NULL;
for (s = (const char**)saved_env->pdata; *s != NULL; )
{
const char *var = *s++;
const char *val = *s++;
if (val)
g_setenv (var, val, TRUE);
else
g_unsetenv (var);
}
CloseHandle (pinfo.hThread);
/***********************************************************************/
#if 0
// g_free (cwidth); cwidth = NULL;
// g_free (cheight); cheight = NULL;
// if (helper == (GPid)-1) {
// g_critical ("%s: error in spawn()", G_STRLOC);
// goto error;
// }
#endif
_dup2 (my_stdin, 0);
_dup2 (my_stdout, 1);
close (my_stdin); my_stdin = -1;
close (my_stdout); my_stdout = -1;
/* g_message ("%s: created process pid %d", G_STRLOC, (int)pinfo.dwProcessId); */
*hin = helper_in[WRITE_END];
*hout = helper_out[READ_END];
*hpid = pinfo.hProcess;
*hproc_id = pinfo.dwProcessId;
if (helper_binary)
g_string_free (helper_binary, TRUE);
if (saved_env)
{
g_strfreev ((char**)saved_env->pdata);
g_ptr_array_free (saved_env, FALSE);
}
return TRUE;
/* cleanup on error */
g_assert_not_reached();
error:
_dup2(my_stdin, 0);
_dup2(my_stdout, 1);
close (helper_in[0]);
close (helper_in[1]);
close (helper_out[0]);
close (helper_out[1]);
#if 0
// if (cwidth) g_free (cwidth);
// if (cheight) g_free (cheight);
#endif
if (cmd_line)
g_free (cmd_line);
CloseHandle ((HANDLE)helper);
if (helper_binary)
g_string_free (helper_binary, TRUE);
if (saved_env)
{
for (s = (const char**)saved_env->pdata; *s != NULL; )
{
const char *var = *s++;
const char *val = *s++;
if (val)
g_setenv (var, val, TRUE);
else
g_unsetenv (var);
}
g_strfreev ((char**)saved_env->pdata);
g_ptr_array_free (saved_env, FALSE);
}
return FALSE;
}
char
_moo_term_pt_get_erase_char (G_GNUC_UNUSED MooTermPt *pt)
{
return 127;
}
void
_moo_term_pt_send_intr (MooTermPt *pt)
{
g_return_if_fail (pt->priv->child_alive);
pt_flush_pending_write (pt);
pt_write (pt, "\3", 1);
}
gboolean
_moo_term_check_cmd (MooTermCommand *cmd,
G_GNUC_UNUSED GError **error)
{
g_return_val_if_fail (cmd != NULL, FALSE);
g_return_val_if_fail (cmd->cmd_line != NULL || cmd->argv != NULL, FALSE);
if (cmd->cmd_line)
{
g_strfreev (cmd->argv);
cmd->argv = NULL;
return TRUE;
}
else
{
GString *cmd_line = NULL;
char **p;
g_return_val_if_fail (cmd->argv[0] != NULL, FALSE);
cmd_line = g_string_new ("");
for (p = cmd->argv; *p != NULL; ++p)
{
if (strchr (*p, ' '))
g_string_append_printf (cmd_line, "\"%s\" ", *p);
else
g_string_append_printf (cmd_line, "%s ", *p);
}
cmd->cmd_line = g_string_free (cmd_line, FALSE);
return TRUE;
}
}