mutter/src/uislave.c

533 lines
13 KiB
C
Raw Normal View History

2001-06-02 04:14:18 +00:00
/* Metacity UI Slave */
/*
* Copyright (C) 2001 Havoc Pennington
*
* 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.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#include "uislave.h"
2001-06-03 01:33:27 +00:00
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
typedef enum
{
READ_FAILED = 0, /* FALSE */
READ_OK,
READ_EOF
} ReadResult;
static void respawn_child (MetaUISlave *uislave);
static gboolean output_callback (GIOChannel *source,
GIOCondition condition,
gpointer data);
static gboolean error_callback (GIOChannel *source,
GIOCondition condition,
gpointer data);
static void kill_child (MetaUISlave *uislave);
static void reset_vals (MetaUISlave *uislave);
static ReadResult read_data (GString *str,
gint fd);
/* Message queue main loop source */
static gboolean mq_prepare (GSource *source,
gint *timeout);
static gboolean mq_check (GSource *source);
static gboolean mq_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data);
static void mq_destroy (GSource *source);
static GSourceFuncs mq_funcs = {
mq_prepare,
mq_check,
mq_dispatch,
mq_destroy
};
2001-06-02 04:14:18 +00:00
MetaUISlave*
2001-06-03 01:33:27 +00:00
meta_ui_slave_new (const char *display_name,
MetaUISlaveFunc func,
gpointer data)
{
MetaUISlave *uislave;
GSource *source;
source = g_source_new (&mq_funcs, sizeof (MetaUISlave));
uislave = (MetaUISlave*) source;
uislave->display_name = g_strdup (display_name);
uislave->queue = g_queue_new ();
uislave->buf = g_string_new ("");
uislave->current_message = g_string_new ("");
reset_vals (uislave);
/* This may fail; all UISlave functions become no-ops
* if uislave->child_pids == 0, and metacity just runs
* with no UI features other than window borders.
*/
respawn_child (uislave);
g_source_set_priority (source, G_PRIORITY_DEFAULT);
g_source_set_can_recurse (source, TRUE);
g_source_set_callback (source, (GSourceFunc) func, data, NULL);
g_source_attach (source, NULL);
return uislave;
}
void
meta_ui_slave_free (MetaUISlave *uislave)
{
GSource *source;
source = (GSource*) uislave;
g_source_destroy (source);
}
void
meta_ui_slave_disable (MetaUISlave *uislave)
{
/* Change UI slave into "black hole" mode,
* we found out it's hosed for some reason.
*/
kill_child (uislave);
uislave->no_respawn = TRUE;
}
static void
respawn_child (MetaUISlave *uislave)
{
GError *error;
const char *uislavedir;
char *argv[] = { "./metacity-uislave", NULL };
char *envp[2] = { NULL, NULL };
int child_pid, inpipe, outpipe, errpipe;
if (uislave->no_respawn)
return;
uislavedir = g_getenv ("METACITY_UISLAVE_DIR");
if (uislavedir == NULL)
uislavedir = METACITY_LIBEXECDIR;
envp[0] = g_strconcat ("DISPLAY=", uislave->display_name, NULL);
error = NULL;
if (g_spawn_async_with_pipes (uislavedir,
argv,
envp,
/* flags */
0,
/* setup func, data */
NULL, NULL,
&child_pid,
&inpipe, &outpipe, &errpipe,
&error))
{
uislave->child_pid = child_pid;
uislave->in_pipe = inpipe;
uislave->err_pipe = errpipe;
uislave->out_poll.fd = outpipe;
uislave->out_poll.events = G_IO_IN;
uislave->err_channel = g_io_channel_unix_new (errpipe);
uislave->errwatch = g_io_add_watch (uislave->err_channel,
G_IO_IN,
error_callback,
uislave);
meta_verbose ("Spawned UI slave with PID %d\n", uislave->child_pid);
}
else
{
meta_warning ("Failed to create user interface process: %s\n",
error->message);
g_error_free (error);
}
g_free (envp[0]);
}
static void
append_pending (MetaUISlave *uislave)
{
int needed;
needed = uislave->current_required_len - uislave->current_message->len;
g_assert (needed >= 0);
needed = MIN (needed, uislave->buf->len);
/* Move data from buf to current_message */
g_string_append_len (uislave->current_message,
uislave->buf->str,
needed);
g_string_erase (uislave->buf,
0, needed);
}
static gboolean
output_callback (GIOChannel *source,
GIOCondition condition,
gpointer data)
{
/* Read messages from slave */
MetaUISlave *uislave;
ReadResult res;
uislave = data;
res = read_data (uislave->buf, uislave->out_pipe);
switch (res)
{
case READ_OK:
meta_verbose ("Read data from slave, %d bytes in buffer\n",
uislave->buf->len);
break;
case READ_EOF:
meta_verbose ("EOF reading stdout from slave process\n");
break;
case READ_FAILED:
/* read_data printed the error */
break;
}
return TRUE;
}
static gboolean
error_callback (GIOChannel *source,
GIOCondition condition,
gpointer data)
{
/* Relay slave errors to WM stderr */
#define BUFSIZE 1024
MetaUISlave *uislave;
char buf[1024];
int n;
uislave = data;
/* Classic loop from Stevens */
n = read (uislave->err_pipe, buf, BUFSIZE);
if (n > 0)
{
if (write (2, buf, n) != n)
; /* error, but printing a message to stderr will hardly help. */
}
else if (n < 0)
meta_warning (_("Error reading errors from UI slave: %s\n"),
g_strerror (errno));
return TRUE;
#undef BUFSIZE
}
static void
mq_queue_messages (MetaUISlave *uislave)
{
if (uislave->buf->len == 0)
return;
if (uislave->current_message->len > 0)
{
/* We had a pending message. */
append_pending (uislave);
}
else if (uislave->buf->len > META_MESSAGE_ESCAPE_LEN)
{
/* See if we can start a current message */
const char *p;
int esc_pos;
const char *esc;
MetaMessageHeader header;
/* note that the string from the UI slave includes the nul byte */
esc = META_MESSAGE_ESCAPE;
esc_pos = 0;
p = uislave->buf->str;
while (p != (uislave->buf->str + uislave->buf->len) &&
esc_pos < META_MESSAGE_ESCAPE_LEN)
{
if (*p != esc[esc_pos])
esc_pos = 0;
else
++esc_pos;
++p;
}
if (esc_pos == META_MESSAGE_ESCAPE_LEN)
{
/* We found an entire escape sequence; can safely toss
* out the entire buffer before it
*/
int ignored;
ignored = p - uislave->buf->str;
ignored -= META_MESSAGE_ESCAPE_LEN;
g_assert (ignored >= 0);
if (ignored > 0)
{
meta_verbose ("Ignoring %d bytes from UI slave\n",
ignored);
g_string_erase (uislave->buf, 0, ignored);
}
}
else if (esc_pos == 0)
{
/* End of buffer doesn't begin an escape sequence;
* toss out entire buffer.
*/
meta_verbose ("Ignoring %d bytes from UI slave\n",
uislave->buf->len);
g_string_truncate (uislave->buf, 0);
}
if (uislave->buf->len < (META_MESSAGE_ESCAPE_LEN + sizeof (MetaMessageHeader)))
return; /* Not enough data yet. */
memcpy (&header, uislave->buf->str + META_MESSAGE_ESCAPE_LEN, sizeof (MetaMessageHeader));
/* Length includes the header even though it's in the header. */
meta_verbose ("Read header code: %d length: %d from UI slave\n",
header.message_code, header.length);
uislave->current_required_len = header.length;
g_string_erase (uislave->buf, 0, META_MESSAGE_ESCAPE_LEN);
append_pending (uislave);
}
g_assert (uislave->current_message->len <= uislave->current_required_len);
if (uislave->current_required_len > 0 &&
uislave->current_message->len == uislave->current_required_len)
{
MetaMessage *message;
message = g_new (MetaMessage, 1);
memcpy (message,
uislave->current_message->str, uislave->current_message->len);
g_queue_push_tail (uislave->queue, message);
meta_verbose ("Added %d-byte message to queue\n",
uislave->current_message->len);
uislave->current_required_len = 0;
g_string_truncate (uislave->current_message, 0);
}
}
static gboolean
mq_messages_pending (MetaUISlave *uislave)
{
return uislave->queue->length > 0 || uislave->buf->len > 0;
}
static gboolean
mq_prepare (GSource *source, gint *timeout)
{
MetaUISlave *uislave;
uislave = (MetaUISlave*) source;
*timeout = -1;
return mq_messages_pending (uislave);
}
static gboolean
mq_check (GSource *source)
{
MetaUISlave *uislave;
uislave = (MetaUISlave*) source;
return mq_messages_pending (uislave);
}
static gboolean
mq_dispatch (GSource *source, GSourceFunc callback, gpointer user_data)
{
MetaUISlave *uislave;
uislave = (MetaUISlave*) source;
mq_queue_messages (uislave);
if (uislave->queue->length > 0)
{
MetaUISlaveFunc func;
MetaMessage *msg;
static int count = 0;
++count;
msg = g_queue_pop_head (uislave->queue);
func = (MetaUISlaveFunc) callback;
(* func) (uislave, msg, user_data);
meta_verbose ("%d messages dispatched\n", count);
g_free (msg);
}
return TRUE;
}
static void
kill_child (MetaUISlave *uislave)
{
if (uislave->outwatch != 0)
g_source_remove (uislave->outwatch);
if (uislave->errwatch != 0)
g_source_remove (uislave->errwatch);
if (uislave->out_channel)
g_io_channel_unref (uislave->out_channel);
if (uislave->err_channel)
g_io_channel_unref (uislave->err_channel);
if (uislave->out_pipe >= 0)
close (uislave->out_pipe);
if (uislave->in_pipe >= 0)
close (uislave->in_pipe);
if (uislave->err_pipe >= 0)
close (uislave->err_pipe);
while (uislave->queue->length > 0)
{
MetaMessage *msg;
msg = g_queue_pop_head (uislave->queue);
g_free (msg);
}
if (uislave->buf->len > 0)
g_string_truncate (uislave->buf, 0);
if (uislave->current_message->len > 0)
g_string_truncate (uislave->current_message, 0);
if (uislave->child_pid > 0)
{
/* don't care if this fails except in verbose mode */
if (kill (uislave->child_pid, SIGTERM) != 0)
{
meta_verbose ("Kill of UI slave process %d failed: %s\n",
uislave->child_pid, g_strerror (errno));
}
uislave->child_pid = 0;
}
reset_vals (uislave);
}
static void
reset_vals (MetaUISlave *uislave)
{
uislave->child_pid = 0;
uislave->in_pipe = -1;
uislave->out_pipe = -1;
uislave->err_pipe = -1;
uislave->no_respawn = FALSE;
uislave->out_channel = NULL;
uislave->err_channel = NULL;
uislave->outwatch = 0;
uislave->errwatch = 0;
uislave->current_required_len = 0;
}
static void
mq_destroy (GSource *source)
{
MetaUISlave *uislave;
uislave = (MetaUISlave*) source;
meta_verbose ("Deleting UI slave for display '%s'\n",
uislave->display_name);
kill_child (uislave);
g_string_free (uislave->buf, TRUE);
g_string_free (uislave->current_message, TRUE);
g_queue_free (uislave->queue);
g_free (uislave->display_name);
/* source itself is freed by glib */
}
static ReadResult
read_data (GString *str,
gint fd)
2001-06-02 04:14:18 +00:00
{
2001-06-03 01:33:27 +00:00
#define BUFSIZE 16
gint bytes;
gchar buf[BUFSIZE];
2001-06-02 04:14:18 +00:00
2001-06-03 01:33:27 +00:00
again:
bytes = read (fd, &buf, BUFSIZE);
2001-06-02 04:14:18 +00:00
2001-06-03 01:33:27 +00:00
if (bytes == 0)
return READ_EOF;
else if (bytes > 0)
{
g_string_append_len (str, buf, bytes);
return READ_OK;
}
else if (bytes < 0 && errno == EINTR)
goto again;
else if (bytes < 0)
{
meta_warning (_("Failed to read data from UI slave: %s\n"),
g_strerror (errno));
return READ_FAILED;
}
else
return READ_OK;
2001-06-02 04:14:18 +00:00
}