/* display.c -- display handling
   $Id: display.c,v 1.44 2002/04/09 03:36:11 jsh Exp $

   Copyright (C) 1999 John Harper <john@dcs.warwick.ac.uk>

   This file is part of sawmill.

   sawmill 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, or (at your option)
   any later version.

   sawmill 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 sawmill; see the file COPYING.   If not, write to
   the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */

#include "sawmill.h"
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
#include <X11/keysym.h>
#include <X11/extensions/shape.h>
#include <string.h>
#include <assert.h>
#include <stdarg.h>
#include <ctype.h>

#ifdef HAVE_UNIX
# ifdef HAVE_FCNTL_H
#  include <fcntl.h>
# endif
# ifdef HAVE_UNISTD_H
#  include <unistd.h>
# endif
# ifdef HAVE_SYS_UTSNAME_H
#  include <sys/utsname.h>
# endif
# include <netdb.h>
#endif

char *prog_name, *visual_name;
Display *dpy;
int screen_num, screen_width, screen_height;
Window root_window, no_focus_window;
int shape_event_base, shape_error_base;

Visual *preferred_visual;
int preferred_depth;

/* some atoms that may be useful.. */
Atom xa_wm_state, xa_wm_change_state, xa_wm_protocols, xa_wm_delete_window,
    xa_wm_colormap_windows, xa_wm_take_focus, xa_compound_text;

DEFSYM(display_name, "display-name");
DEFSYM(canonical_display_name, "canonical-display-name");


/* X error handlers */

static void
print_error (XErrorEvent *ev)
{
    char buf[256];
    XGetErrorText(dpy, ev->error_code, buf, sizeof (buf));
    fprintf(stderr, "X Error: %s\n", buf);
    fprintf(stderr, "  Request Major code: %d\n", ev->request_code);
    fprintf(stderr, "  Request Minor code: %d\n", ev->minor_code);
    fprintf(stderr, "  ResourceId 0x%x\n", (u_int)ev->resourceid);
}

/* General error handler. Probably due to lag between windows being
   killed and us receiving DestroyNotify events */
static int
error_handler (Display *dpy, XErrorEvent *ev)
{
    Lisp_Window *w;
#ifdef DEBUG
    print_error (ev);
#endif
    if (ev->resourceid == 0)		/* probably a deleted window */
	return 0;
    w = x_find_window_by_id (ev->resourceid);
    if (w != 0 && (ev->error_code == BadWindow
		   || ev->error_code == BadDrawable))
    {
	DB(("error_handler (%s)\n", rep_STR(w->name)));
	if (!WINDOW_IS_GONE_P (w))
	    remove_window (w, Qt, Qt);
	/* so we call emit_pending_destroys () at some point */
	rep_mark_input_pending (ConnectionNumber (dpy));
	return 0;			/* ?? */
    }
    else
    {
#if 0
	/* I'm fed up seeing these errors! :-) */
#ifndef DEBUG
	print_error (ev);
#endif
#endif
	return 0;
    }
}

/* Installed whilst trying to set the root window event mask */
static int
error_other_wm (Display *dpy, XErrorEvent *ev)
{
    fputs ("You may only run one window manager\n", stderr);
    exit (1);
}



static char *
canonical_host (char *host)
{
    static char buf[256];
    char *ptr;

    /* check that the name is fully qualified */
    if (!strchr (host, '.'))
    {
	struct hostent *h = gethostbyname (host);
	if (h != 0)
	{
	    if (!strchr (h->h_name, '.'))
	    {
		char **aliases = h->h_aliases;
		while (*aliases && !strchr (*aliases, '.'))
		    aliases++;
		host = *aliases ? *aliases : h->h_name;
	    }
	    else
		host = h->h_name;
	}
    }

    ptr = buf;
    while (*host != 0)
    {
	*ptr++ = tolower (*host);
	host++;
    }
    return buf;
}

static char *
canonical_display (char *name)
{
    static char buf[256];
    char *ptr = buf;
    if (strncmp ("unix:", name, 5) == 0)
	name += 4;
    if (*name == ':')
    {
	repv host = Fsystem_name ();
	if (host && rep_STRINGP(host))
	    strcpy (ptr, rep_STR(host));
	else
	    *ptr = 0;
	ptr += strlen (ptr);
    }
    else
    {
	char *fq;
	while (*name && *name != ':')
	    *ptr++ = *name++;
	*ptr = 0;
	fq = canonical_host (buf);
	if (fq != buf)
	{
	    strcpy (buf, fq);
	    ptr = buf + strlen (buf);
	}
    }
    *ptr++ = *name++;
    while (*name && *name != '.')
	*ptr++ = *name++;
    if (*name == 0)
	strcpy (ptr, ".0");
    else
	strcpy (ptr, name);
    return buf;
}



static void
redisplay (void)
{
    commit_queued_reshapes ();

    /* round-trip requests swallow any pending events.. */
    if (XPending (dpy) > 0)
	rep_mark_input_pending (ConnectionNumber (dpy));
}

static void
beep(void)
{
    XBell(dpy, 0);
}

static void
choose_visual (void)
{
    int id = 0;
    if (visual_name != 0)
    {
	if (!strcasecmp ("StaticGray", visual_name))
	    id = StaticGray;
	else if (!strcasecmp ("StaticColor", visual_name))
	    id = StaticColor;
	else if (!strcasecmp ("TrueColor", visual_name))
	    id = TrueColor;
	else if (!strcasecmp ("GrayScale", visual_name)
		 || !strcasecmp ("GreyScale", visual_name))
	    id = GrayScale;
	else if (!strcasecmp ("PseudoColor", visual_name))
	    id = PseudoColor;
	else if (!strcasecmp ("DirectColor", visual_name))
	    id = DirectColor;
    }
    if (id != 0 || preferred_depth != 0)
    {
	XVisualInfo in, *out;
	int mask = VisualScreenMask, n_out;
	in.screen = screen_num;
	if (id != 0)
	{
	    in.class = id;
	    mask |= VisualClassMask;
	}
	out = XGetVisualInfo (dpy, mask, &in, &n_out);
	if (out != 0)
	{
	    int i, best = -1;
	    if (preferred_depth > 0)
	    {
		/* Look for a visual with the preferred depth. */
		for (i = 0; i < n_out; i++)
		{
		    if (out[i].depth == preferred_depth
			&& (best < 0
			    || out[i].colormap_size > out[best].colormap_size))
		    {
			best = i;
		    }
		}
	    }
	    if (best < 0)
	    {
		/* Else find the deepest visual of this type. */
		for (i = 0, best = 0; i < n_out; i++)
		{
		    if (out[i].depth > out[best].depth
			|| (out[i].depth == out[best].depth
			    && out[i].colormap_size > out[best].colormap_size))
		    {
			best = i;
		    }
		}
	    }
	    if (best >= 0 && best < n_out)
	    {
		preferred_visual = out[best].visual;
		preferred_depth = out[best].depth;
	    }
	    XFree (out);
	}
    }
    if (preferred_visual == 0)
    {
	if (visual_name != 0)
	    fprintf (stderr, "warning: using default visual\n");
	preferred_visual = DefaultVisual (dpy, screen_num);
	preferred_depth = DefaultDepth (dpy, screen_num);
    }
}

/* Called from main(). */
bool
sys_init(char *program_name)
{
    char *display_name = 0;
    repv opt;

#ifdef HAVE_UNIX
    if (!batch_mode_p ())
	setpgid (0, 0);
#endif

    prog_name = program_name;
    if (rep_get_option ("--name", &opt))
	prog_name = strdup (rep_STR(opt));

    rep_INTERN_SPECIAL(display_name);
    rep_INTERN_SPECIAL(canonical_display_name);
    Fset (Qdisplay_name, rep_null_string ());
    Fset (Qcanonical_display_name, rep_null_string ());

    if(!batch_mode_p ())
    {
	if (rep_get_option ("--display", &opt))
	    display_name = strdup (rep_STR(opt));
	if (rep_get_option ("--visual", &opt))
	    visual_name = strdup (rep_STR(opt));
	if (rep_get_option ("--depth", &opt))
	    preferred_depth = atoi (rep_STR (opt));

	if (display_name == 0)
	    display_name = getenv("DISPLAY");

	dpy = XOpenDisplay(display_name);
	if(dpy != 0)
	{
	    Fset (Qdisplay_name, rep_string_dup (display_name));
	    Fset (Qcanonical_display_name,
		  rep_string_dup (canonical_display (display_name)));
	    rep_register_input_fd (ConnectionNumber(dpy), handle_sync_input);
	    screen_num = DefaultScreen(dpy);
	    root_window = RootWindow(dpy, screen_num);
	    screen_width = DisplayWidth(dpy, screen_num);
	    screen_height = DisplayHeight(dpy, screen_num);
	    choose_visual ();

	    xa_wm_state = XInternAtom (dpy, "WM_STATE", False);
	    xa_wm_change_state = XInternAtom (dpy, "WM_CHANGE_STATE", False);
	    xa_wm_protocols = XInternAtom (dpy, "WM_PROTOCOLS", False);
	    xa_wm_delete_window = XInternAtom (dpy, "WM_DELETE_WINDOW", False);
	    xa_wm_colormap_windows = XInternAtom (dpy, "WM_COLORMAP_WINDOWS", False);
	    xa_wm_take_focus = XInternAtom (dpy, "WM_TAKE_FOCUS", False);
	    xa_compound_text = XInternAtom (dpy, "COMPOUND_TEXT", False);

	    if (!XShapeQueryExtension (dpy, &shape_event_base,
				       &shape_error_base))
	    {
		fprintf (stderr, "sawfish: your X server doesn't suppot the SHAPE extension; aborting\n");
		return FALSE;
	    }

	    XSetErrorHandler (error_other_wm);
	    XSelectInput (dpy, root_window, ROOT_EVENTS);
	    XSync (dpy, False);
	    XSetErrorHandler (error_handler);

	    {
		/* Create the mapped-but-invisible window that is given
		   the focus when no other window has it. */
		XSetWindowAttributes attr;
		/* this value is assumed in events.c:get_server_timestamp */
		attr.event_mask = KeyPressMask;
		attr.override_redirect = True;
		no_focus_window = XCreateWindow (dpy, root_window,
						 -10, -10, 10, 10, 0, 0,
						 InputOnly, CopyFromParent,
						 CWEventMask
						 | CWOverrideRedirect,
						 &attr);
		XMapWindow (dpy, no_focus_window);
	    }

	    /* This should _never_ be used in Real Life; only for
	       debugging. Sawmill tries to work out when the error
	       handle might be called (i.e. after any XGet, XQuery, XFetch
	       type function) and then call emit_pending_destroys ()
	       as soon as possible, so that there's as small as possible
	       delay between the window being destroyed and the hook
	       being called.. */
	    if (rep_get_option ("--sync", 0))
		XSynchronize (dpy, True);

	    /* If I don't do this all the events that are created by
	       the window initialiation are ignored until the next
	       new event arrives (because of the XSync calls above) */
	    rep_mark_input_pending (ConnectionNumber(dpy));

	    rep_redisplay_fun = redisplay;
	    rep_beep_fun = beep;

	    return TRUE;
	}
	else
	{
	    fprintf(stderr, "sawfish: Can't open display: %s\n",
		    display_name ? display_name : "");
	    return FALSE;
	}
    }
    else
	return TRUE;
}

void
sys_kill (void)
{
    if(!batch_mode_p ())
    {
	XSetInputFocus (dpy, PointerRoot, 0, last_event_time);
	XDestroyWindow (dpy, no_focus_window);
	XCloseDisplay (dpy);
    }
}


/* utilities */

repv
x_atom_symbol (Atom atom)
{
    char *name = XGetAtomName (dpy, atom);
    if (name != 0)
    {
	repv sym = Fintern (rep_string_dup (name), rep_obarray);
	XFree (name);
	return sym;
    }
    else
	return Qnil;
}

Window
x_win_from_arg (repv arg)
{
    if (arg == Qroot)
	return root_window;
    else if (WINDOWP(arg))
	return VWIN(arg)->id;
    else if (rep_INTEGERP(arg))
	return rep_get_long_uint (arg);
    else
	return 0;
}

/***************************************************************************
 *
 * ICCCM Client Messages - Section 4.2.8 of the ICCCM dictates that all
 * client messages will have the following form:
 *
 *     event type       ClientMessage
 *     message type     _XA_WM_PROTOCOLS
 *     window           tmp->w
 *     format           32
 *     data[0]          message atom
 *     data[1]          time stamp
 *
 ****************************************************************************/
void
send_client_message (Window w, Atom a, Time time)
{
  XClientMessageEvent ev;
  
  ev.type = ClientMessage;
  ev.window = w;
  ev.message_type = xa_wm_protocols;
  ev.format = 32;
  ev.data.l[0] = a;
  ev.data.l[1] = time;
  XSendEvent (dpy, w, False, 0L, (XEvent *) &ev);
}

#if XlibSpecificationRelease < 6
Status
XGetAtomNames (Display *dpy, Atom *atoms, int count, char **names_ret)
{
    int i;
    for (i = 0; i < count; i++)
    {
	names_ret[i] = XGetAtomName (dpy, atoms[i]);
	if (names_ret[i] == 0)
	    break;
    }
    if (i == count)
	return 1;
    for (i--; i >= 0; i--)
	XFree (names_ret[i]);
    return 0;
}
#endif

void
db_printf(char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    rep_db_vprintf(rep_common_db, fmt, args);
    va_end(args);
}


syntax highlighted by Code2HTML, v. 0.9.1