/* src/display.c - X display management
 
  Copyright (C) 1999, 2000 Martin Grabmueller <mgrabmue@cs.tu-berlin.de>
 
  This 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.
  
  This software 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 package; see the file COPYING.  If not, write to the
  Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
  MA 02111-1307, USA.  */

#if HAVE_CONFIG_H
# include <config.h>
#endif 

#include <assert.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>

#include "alloc.h"
#include "client.h"
#include "display.h"
#include "wimpwm.h"

char * display_string;
Display * display;
XFontStruct * font;
int default_screen;
Window root_window;

GC invert_gc;
GC active_gc;
GC inactive_gc;
GC box_gc;
GC inactive_box_gc;
GC border_gc;
GC menu_gc;
GC highlight_gc;
GC shadow_gc;

XFontStruct * font;
int titlebar_height;
XColor title_active_color; 
XColor title_inactive_color;
XColor background_color;
XColor background_hl_color;
XColor background_sh_color;
XColor border_color;

Cursor move_curs;
Cursor resize_curs;

char title_font_name[128] = "lucidasans-bold-12";
char title_active_color_name[128] = "#D0E0F0";
char title_inactive_color_name[128] = "#90A0B0";
char background_color_name[128] = "#607080";
char background_color_hl_name[128] = "#8090A0";
char background_color_sh_name[128] = "#203040";
char border_color_name[128] = "black";
int border_width = 1;
int tab_scroll_speed = 0;

Atom _XA_WM_STATE;
Atom _XA_WM_CHANGE_STATE;
Atom _XA_WM_PROTOS;
Atom _XA_WM_DELETE;
Atom _XA_WM_CMAPWINS;

#ifdef SHAPE
Bool shape;
int shape_event;
#endif

zzz_scm_t cycle_candidate_p;
zzz_scm_t sticky_candidate_p;

/*
 * Callback routine for handling X errors.  Xlib calls this
 * if any recoverable error occurs.
 *
 * Display * display	Display on which the error occured.
 * XErrorEvent * e	Description of the error.
 *
 * Returns a non-zero value on error.
 */
static int
handle_xerror (Display *display, XErrorEvent *e)
{
  client_t c = find_client (e->resourceid, 0);

  /*
   * This error occurs when another window manager is already running.
   */
  if (e->error_code == BadAccess && e->resourceid == root_window)
    {
      error ("cannot access root window - maybe another window manager is "
	   "running\n");
      exit (1);
    }
#define DEBUG
#ifdef DEBUG
  else
    {
      char msg[80];
      XGetErrorText (display, e->error_code, msg, sizeof (msg));
      error ("X error (%#x, code %d): %s\n",
	     e->resourceid, e->request_code, msg);
    }
#endif

  if (c)
    remove_client (c);
  
  return 0;
}

/* Define user variables which can be set in configuration files.  */
void
define_display_variables (void)
{
  zzz_bind_string_variable ("title-font", title_font_name,
			    sizeof (title_font_name), 0);
  zzz_bind_string_variable ("title-active-color", title_active_color_name,
			    sizeof (title_active_color_name), 0);
  zzz_bind_string_variable ("title-inactive-color", title_inactive_color_name,
			    sizeof (title_inactive_color_name), 0);
  zzz_bind_string_variable ("background-color", background_color_name,
			    sizeof (background_color_name), 0);
  zzz_bind_string_variable ("background-color-highlight",
			    background_color_hl_name,
			    sizeof (background_color_hl_name), 0);
  zzz_bind_string_variable ("background-color-shadow",
			    background_color_sh_name,
			    sizeof (background_color_sh_name), 0);
  zzz_bind_string_variable ("border-color", border_color_name,
			    sizeof (border_color_name), 0);
  zzz_bind_int_variable ("border-width", &border_width, 0);
  zzz_bind_int_variable ("tab-scroll-speed", &tab_scroll_speed, 0);

  zzz_bind_scm_variable ("cycle-candidate?", &cycle_candidate_p, 0);
  zzz_bind_scm_variable ("sticky-candidate?", &sticky_candidate_p, 0);
}

/* Cache and returns a font cursor. */
Cursor
get_font_cursor (unsigned int shape)
{
  static Cursor cursor_cache[XC_num_glyphs];
  assert (shape < XC_num_glyphs);
  if (!cursor_cache[shape])
    cursor_cache[shape] = XCreateFontCursor (display, shape);
  return cursor_cache[shape];
}

/*
 * Query which windows are currently on the server and adopt them, so we
 * can manage them properly.
 */
void
adopt_windows (void)
{
  unsigned int i, nwins;
  Window dw1, dw2, *wins;
  XWindowAttributes attr;

  XQueryTree (display, root_window, &dw1, &dw2, &wins, &nwins);
  for (i = 0; i < nwins; i++)
    {
      /*
       * Only adopt windows which allow us to do so.
       */
      XGetWindowAttributes (display, wins[i], &attr);
      if (!attr.override_redirect && attr.map_state == IsViewable)
	create_client_window (wins[i], &attr);
    }
  XFree (wins);
}

static result_t
setup_gcs (void)
{
  XGCValues gv;

  /*
   * Create a GC for drawing the title bar foreground.
   */
  gv.function = GXcopy;
  gv.foreground = title_active_color.pixel;
  gv.font = font->fid;
  gv.line_width = 1;
  active_gc = XCreateGC (display, root_window,
			 GCFunction | GCForeground | GCFont | GCLineWidth,
			 &gv);
  /*
   * Create a GC for drawing the box of active windows.
   */
  gv.function = GXcopy;
  gv.foreground = title_active_color.pixel;
  gv.line_width = 2;
  box_gc = XCreateGC (display, root_window,
			 GCFunction | GCForeground | GCLineWidth,
			 &gv);
  /*
   * Create a GC for drawing hightlighting on window titles.
   */
  gv.function = GXcopy;
  gv.foreground = background_hl_color.pixel;
  gv.line_width = 1;
  highlight_gc = XCreateGC (display, root_window,
			    GCFunction | GCForeground | GCLineWidth,
			    &gv);
  /*
   * Create a GC for drawing shadows on window titles.
   */
  gv.function = GXcopy;
  gv.foreground = background_sh_color.pixel;
  gv.line_width = 1;
  shadow_gc = XCreateGC (display, root_window,
			 GCFunction | GCForeground | GCLineWidth,
			 &gv);
  /* 
   * Create a GC for drawing the foreground of inactive windows.
   */
  gv.function = GXcopy;
  gv.foreground = title_inactive_color.pixel;
  gv.font = font->fid;
  gv.line_width = 1;
  inactive_gc = XCreateGC (display, root_window,
			   GCFunction | GCForeground | GCFont | GCLineWidth,
			   &gv);

  /* 
   * Create a GC for drawing the box of inactive windows.
   */
  gv.function = GXcopy;
  gv.foreground = title_inactive_color.pixel;
  gv.line_width = 2;
  inactive_box_gc = XCreateGC (display, root_window,
			       GCFunction | GCForeground | GCLineWidth,
			       &gv);

  /*
   * Create a GC for drawing window borders.
   */
  gv.foreground = border_color.pixel;
  gv.line_width = 1;
  border_gc = XCreateGC (display, root_window,
			 GCForeground | GCLineWidth, &gv);

  /*
   * Create a GC which is used to draw the outline of windows during
   * moving and resizing.
   */
  gv.function = GXinvert;
  gv.subwindow_mode = IncludeInferiors;
  gv.line_width = 2;
  invert_gc = XCreateGC (display, root_window,
			 GCFunction | GCSubwindowMode | GCLineWidth | GCFont,
			 &gv);

  /*
   * Create a GC for drawing the popup menus.
   */
/*    gv.foreground = BlackPixel (display, default_screen); */
  gv.foreground = background_color.pixel;
  gv.background = WhitePixel (display, default_screen);
  gv.font = font->fid;
  menu_gc = XCreateGC (display, root_window,
		       GCForeground | GCBackground | GCFont, &gv);

  return RESULT_SUCCESS;
}

/*
 * Initialize all X stuff.  Open the display DISPLAY_STRING, set up
 * error and signal handlers, allocate X resources etc.
 *
 * char * display_string	Name of the display to connect to.
 *
 * Returns RESULT_SUCCESS on error, and an error code otherwise.
 */
result_t
initialize_display (void)
{
  XSetWindowAttributes attr;
  XColor dummy;
#ifdef SHAPE
  int dummy2;
#endif

  XSetErrorHandler (handle_xerror);

  /*
   * Open the X display and get some basic information about the
   * environment. (DISPLAY_STRING gets set in yakazao:main()).
   */
  display = XOpenDisplay (display_string);

  if (!display)
    { 
      if (display_string)
	error ("cannot open display: %s", display_string);
      else
	error ("cannot open display");
      return RESULT_ACCESS;
    }

  default_screen = DefaultScreen (display);
  root_window = RootWindow (display, default_screen);

  /*
   * Intern some important X atoms.
   */
  _XA_WM_STATE = XInternAtom (display, "WM_STATE", False);
  _XA_WM_CHANGE_STATE = XInternAtom (display, "WM_CHANGE_STATE", False);
  _XA_WM_PROTOS = XInternAtom (display, "WM_PROTOCOLS", False);
  _XA_WM_DELETE = XInternAtom (display, "WM_DELETE_WINDOW", False);
  _XA_WM_CMAPWINS = XInternAtom (display, "WM_COLORMAP_WINDOWS", False);

  /*
   * Allocate colors for drawing the frame windows.
   */
  if (!XAllocNamedColor (display, DefaultColormap (display, default_screen),
			 title_active_color_name, &title_active_color,
			 &dummy))
    error ("cannot allocate color `%s'\n", title_active_color_name);
  if (!XAllocNamedColor (display, DefaultColormap (display, default_screen),
			 title_inactive_color_name, &title_inactive_color,
			 &dummy))
    error ("cannot allocate color `%s'\n", title_inactive_color_name);

  if (!XAllocNamedColor (display, DefaultColormap(display, default_screen),
			 background_color_name, &background_color, &dummy))
    error ("cannot allocate color `%s'\n", background_color_name);

  if (!XAllocNamedColor (display, DefaultColormap(display, default_screen),
			 background_color_hl_name, &background_hl_color,
			 &dummy))
    error ("cannot allocate color `%s'\n", background_color_hl_name);

  if (!XAllocNamedColor (display, DefaultColormap(display, default_screen),
			 background_color_sh_name, &background_sh_color,
			 &dummy))
    error ("cannot allocate color `%s'\n", background_color_sh_name);

  if (!XAllocNamedColor (display, DefaultColormap (display, default_screen),
			 border_color_name, &border_color, &dummy))
    error ("cannot allocate color `%s'\n", border_color_name);

  /*
   * Allocate font for title bars and menus.
   */
  font = XLoadQueryFont (display, title_font_name);
  if (!font) 
    {
      error ("cannot load font `%s', using `fixed' instead\n");
      font = XLoadQueryFont(display, "fixed");
    }
  titlebar_height = font->ascent + font->descent + 2 * SPACE;

#ifdef SHAPE
  /*
   * Test whether the shape extension is available.
   */
  shape = XShapeQueryExtension (display, &shape_event, &dummy2);
#endif

  /*
   * Allocate cursors for moving and resizing of windows.
   */
  move_curs = XCreateFontCursor(display, XC_fleur);
  resize_curs = XCreateFontCursor(display, XC_plus);

  /*
   * Create GCs for drawing window frames etc.
   */
  setup_gcs ();

  /*
   * Attempt to set window attributes on the root window.  This will fail
   * if another window manager is running already, in which case our
   * X error function gets called and aborts with an error message.
   */
  attr.event_mask = ChildMask | ColormapChangeMask | ButtonMask
    | PropertyChangeMask | KeyPressMask;
  XChangeWindowAttributes(display, root_window, CWEventMask, &attr);

  /*
   * Make sure that the connection to the X server does not get inherited
   * by any child process we might fork & exec.
   */
  if ((fcntl (ConnectionNumber (display), F_SETFD, 1)) == -1)
    {
      error ("Cannot disinherit file descriptor");
      return RESULT_INVALID;
    }

  return RESULT_SUCCESS;
}

