/* GDK - The GIMP Drawing Kit
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/*
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
 */

#include "config.h"
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <ctype.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <sys/vt.h>
#include <sys/kd.h>
#include <errno.h>

#include <sys/socket.h>
#include <sys/un.h>

#include "gdk.h"

#include "gdkprivate-fb.h"
#include "gdkinternals.h"
#include "gdkfbmanager.h"

/* Private variable declarations
 */
static int gdk_initialized = 0;			    /* 1 if the library is initialized,
						     * 0 otherwise.
						     */

#ifdef G_ENABLE_DEBUG
static const GDebugKey gdk_debug_keys[] = {
  {"misc",	    GDK_DEBUG_MISC},
  {"events",	    GDK_DEBUG_EVENTS},
};

static const int gdk_ndebug_keys = sizeof(gdk_debug_keys)/sizeof(GDebugKey);

#endif /* G_ENABLE_DEBUG */

GdkArgDesc _gdk_windowing_args[] = {
  { NULL }
};

static const GScannerConfig     fb_modes_scanner_config =
{
  (
   " \t\n"
   )                    /* cset_skip_characters */,
  (
   G_CSET_a_2_z
   G_CSET_A_2_Z
   )                    /* cset_identifier_first */,
  (
   G_CSET_a_2_z
   "_-0123456789"
   G_CSET_A_2_Z
   )                    /* cset_identifier_nth */,
  ( "#\n" )             /* cpair_comment_single */,
  
  FALSE                  /* case_sensitive */,
  
  FALSE                 /* skip_comment_multi */,
  TRUE                  /* skip_comment_single */,
  FALSE                 /* scan_comment_multi */,
  TRUE                  /* scan_identifier */,
  TRUE                  /* scan_identifier_1char */,
  FALSE                 /* scan_identifier_NULL */,
  TRUE                  /* scan_symbols */,
  FALSE                 /* scan_binary */,
  FALSE                 /* scan_octal */,
  FALSE                  /* scan_float */,
  FALSE                  /* scan_hex */,
  FALSE                 /* scan_hex_dollar */,
  FALSE                 /* scan_string_sq */,
  TRUE                  /* scan_string_dq */,
  TRUE                  /* numbers_2_int */,
  FALSE                 /* int_2_float */,
  FALSE                 /* identifier_2_string */,
  TRUE                  /* char_2_token */,
  FALSE                 /* symbol_2_token */,
  FALSE                 /* scope_0_fallback */,
};

enum {
  FB_MODE,
  FB_ENDMODE,
  FB_GEOMETRY,
  FB_TIMINGS,
  FB_LACED,
  FB_HSYNC,
  FB_VSYNC,
  FB_CSYNC,
  FB_EXTSYNC,
  FB_DOUBLE
};

char *fb_modes_keywords[] =
{
  "mode",
  "endmode",
  "geometry",
  "timings",
  "laced",
  "hsync",
  "vsync",
  "csync",
  "extsync",
  "double"
};

static int
fb_modes_parse_mode (GScanner *scanner,
		     struct fb_var_screeninfo *modeinfo,
		     char *specified_modename)
{
  guint token;
  int keyword;
  int i;
  char *modename;
  int geometry[5];
  int timings[7];
  int vsync=0, hsync=0, csync=0, extsync=0, doublescan=0, laced=0;
  int found_geometry = 0;
  int found_timings = 0;
    
  token = g_scanner_get_next_token (scanner);
  if (token != G_TOKEN_SYMBOL)
    return -1;
  
  keyword = GPOINTER_TO_INT (scanner->value.v_symbol);
  if (keyword != FB_MODE)
    return -1;

  token = g_scanner_get_next_token (scanner);
  if (token != G_TOKEN_STRING)
    return -1;

  modename = g_strdup (scanner->value.v_string);
  
  token = g_scanner_get_next_token (scanner);
  if (token != G_TOKEN_SYMBOL)
    {
      g_free (modename);
      return -1; /* Not a valid keyword */
    }
  keyword = GPOINTER_TO_INT (scanner->value.v_symbol);
  while ( keyword != FB_ENDMODE )
    {

      switch (GPOINTER_TO_INT (scanner->value.v_symbol))
	{
	case FB_GEOMETRY:
	  for (i = 0; i < 5;i++) {
	    token = g_scanner_get_next_token (scanner);
	    if (token != G_TOKEN_INT)
	      {
		g_free (modename);
		return -1; /* need a integer */
	      }
	    geometry[i] = scanner->value.v_int;
	  }
	  found_geometry = TRUE;
	  break;
	case FB_TIMINGS:
	  for (i = 0; i < 7; i++) {
	    token = g_scanner_get_next_token (scanner);
	    if (token != G_TOKEN_INT)
	      {
		g_free (modename);
		return -1; /* need a integer */
	      }
	    timings[i] = scanner->value.v_int;
	  }
	  found_timings = TRUE;
	  break;
	case FB_LACED:
	  token = g_scanner_get_next_token (scanner);
	  if (token != G_TOKEN_IDENTIFIER)
	      {
		g_free (modename);
		return -1;
	      }
	  if (g_strcasecmp (scanner->value.v_identifier, "true")==0)
	    laced = 1;
	  else if (g_strcasecmp (scanner->value.v_identifier, "false")==0)
	    laced = 0;
	  else
	    {
	      g_free (modename);
	      return -1;
	    }
	  break;
	case FB_EXTSYNC:
	  token = g_scanner_get_next_token (scanner);
	  if (token != G_TOKEN_IDENTIFIER)
	      {
		g_free (modename);
		return -1;
	      }
	  if (g_strcasecmp (scanner->value.v_identifier, "true")==0)
	    extsync = 1;
	  else if (g_strcasecmp (scanner->value.v_identifier, "false")==0)
	    extsync = 0;
	  else
	    {
	      g_free (modename);
	      return -1;
	    }
	  break;
	case FB_DOUBLE:
	  token = g_scanner_get_next_token (scanner);
	  if (token != G_TOKEN_IDENTIFIER)
	      {
		g_free (modename);
		return -1;
	      }
	  if (g_strcasecmp (scanner->value.v_identifier, "true")==0)
	    doublescan = 1;
	  else if (g_strcasecmp (scanner->value.v_identifier, "false")==0)
	    doublescan = 0;
	  else
	    {
	      g_free (modename);
	      return -1;
	    }
	  break;
	case FB_VSYNC:
	  token = g_scanner_get_next_token (scanner);
	  if (token != G_TOKEN_IDENTIFIER)
	      {
		g_free (modename);
		return -1;
	      }
	  if (g_strcasecmp (scanner->value.v_identifier, "high")==0)
	    vsync = 1;
	  else if (g_strcasecmp (scanner->value.v_identifier, "low")==0)
	    vsync = 0;
	  else
	    {
	      g_free (modename);
	      return -1;
	    }
	  break;
	case FB_HSYNC:
	  token = g_scanner_get_next_token (scanner);
	  if (token != G_TOKEN_IDENTIFIER)
	    {
	      g_free (modename);
	      return -1;
	    }
	  if (g_strcasecmp (scanner->value.v_identifier, "high")==0)
	    hsync = 1;
	  else if (g_strcasecmp (scanner->value.v_identifier, "low")==0)
	    hsync = 0;
	  else
	    {
	      g_free (modename);
	      return -1;
	    }
	  break;
	case FB_CSYNC:
	  token = g_scanner_get_next_token (scanner);
	  if (token != G_TOKEN_IDENTIFIER)
	    {
	      g_free (modename);
	      return -1;
	    }
	  if (g_strcasecmp (scanner->value.v_identifier, "high")==0)
	    csync = 1;
	  else if (g_strcasecmp (scanner->value.v_identifier, "low")==0)
	    csync = 0;
	  else
	    {
	      g_free (modename);
	      return -1;
	    }
	  break;
	}
      
      token = g_scanner_get_next_token (scanner);
      if (token != G_TOKEN_SYMBOL)
	{
	  g_free (modename);
	  return -1; /* Not a valid keyword */
	}
      keyword = GPOINTER_TO_INT (scanner->value.v_symbol);
    }

  if (strcmp (modename, specified_modename)== 0) {
    if (!found_geometry)
      g_warning ("Geometry not specified");

    if (found_geometry)
      {
	modeinfo->xres = geometry[0];
	modeinfo->yres = geometry[1];
	modeinfo->xres_virtual = geometry[2];
	modeinfo->yres_virtual = geometry[3];
	modeinfo->bits_per_pixel = geometry[4];
      }
    
    if (!found_timings)
      g_warning ("Timing not specified");
    
    if (found_timings)
      {
	modeinfo->pixclock = timings[0];
	modeinfo->left_margin = timings[1];
	modeinfo->right_margin = timings[2];
	modeinfo->upper_margin = timings[3];
	modeinfo->lower_margin = timings[4];
	modeinfo->hsync_len = timings[5];
	modeinfo->vsync_len = timings[6];
	
	modeinfo->vmode = 0;
	if (laced)
	  modeinfo->vmode |= FB_VMODE_INTERLACED;
	if (doublescan)
	  modeinfo->vmode |= FB_VMODE_DOUBLE;
	  
	modeinfo->sync = 0;
	if (hsync)
	  modeinfo->sync |= FB_SYNC_HOR_HIGH_ACT;
	if (vsync)
	  modeinfo->sync |= FB_SYNC_VERT_HIGH_ACT;
      }

    g_free (modename);
    return 1;
  }
  
  g_free (modename);

  return 0;
}

static int
gdk_fb_setup_mode_from_name (struct fb_var_screeninfo *modeinfo,
			     char *modename)
{
  GScanner *scanner;
  char *filename;
  gint result;
  int fd, i;
  int retval;

  retval = 0;
  
  filename = "/etc/fb.modes";
  
  fd = open (filename, O_RDONLY);
  if (fd < 0)
    {
      g_warning ("Cannot read %s", filename);
      return retval;
    }
  
  scanner = g_scanner_new ((GScannerConfig *) &fb_modes_scanner_config);
  scanner->input_name = filename;

  for (i = 0; i < sizeof(fb_modes_keywords)/sizeof(fb_modes_keywords[0]); i++)
    g_scanner_add_symbol (scanner, fb_modes_keywords[i], GINT_TO_POINTER (i));

  g_scanner_input_file (scanner, fd);
  
  while (1) {
    if (g_scanner_peek_next_token (scanner) == G_TOKEN_EOF) {
      break;
    } 
    result = fb_modes_parse_mode (scanner, modeinfo, modename);
      
    if (result < 0) {
      g_warning ("parse error in %s at line %d", filename, scanner->line);
      break;
    }
    if (result > 0)
      {
	retval = 1;
	break;
      }
  }
  
  g_scanner_destroy (scanner);
  
  close (fd);
  
  return retval;
}
  

static int
gdk_fb_set_mode (GdkFBDisplay *display)
{
  char *env, *end;
  int depth, height, width;
  gboolean changed;
  
  if (ioctl (display->fb_fd, FBIOGET_VSCREENINFO, &display->modeinfo) < 0)
    return -1;

  display->orig_modeinfo = display->modeinfo;

  changed = FALSE;
  
  env = getenv ("GDK_DISPLAY_MODE");
  if (env)
    {
      if (gdk_fb_setup_mode_from_name (&display->modeinfo, env))
	changed = TRUE;
      else
	g_warning ("Couldn't find mode named '%s'", env);
    }

  env = getenv ("GDK_DISPLAY_DEPTH");
  if (env)
    {
      depth = strtol (env, &end, 10);
      if (env != end)
	{
	  changed = TRUE;
	  display->modeinfo.bits_per_pixel = depth;
	}
    }
  
  env = getenv ("GDK_DISPLAY_WIDTH");
  if (env)
    {
      width = strtol (env, &end, 10);
      if (env != end)
	{
	  changed = TRUE;
	  display->modeinfo.xres = width;
	  display->modeinfo.xres_virtual = width;
	}
    }
    
  env = getenv ("GDK_DISPLAY_HEIGHT");
  if (env)
    {
      height = strtol (env, &end, 10);
      if (env != end)
	{
	  changed = TRUE;
	  display->modeinfo.yres = height;
	  display->modeinfo.yres_virtual = height;
	}
    }

  if (changed &&
      (ioctl (display->fb_fd, FBIOPUT_VSCREENINFO, &display->modeinfo) < 0))
    {
      g_warning ("Couldn't set specified mode");
      return -1;
    }
  
  if (ioctl (display->fb_fd, FBIOGET_FSCREENINFO, &display->sinfo) < 0)
    {
      g_warning ("Error getting fixed screen info");
      return -1;
    }
  return 0;
}

#ifdef ENABLE_FB_MANAGER
static void
gdk_fb_switch_from (void)
{
  g_print ("Switch from\n");
  gdk_shadow_fb_stop_updates ();
  gdk_fb_mouse_close ();
  gdk_fb_keyboard_close ();
}

static void
gdk_fb_switch_to (void)
{
  g_print ("switch_to\n");
  gdk_shadow_fb_update (0, 0, 
			gdk_display->fb_width, 
			gdk_display->fb_height);

  if (!gdk_fb_keyboard_open ())
    g_warning ("Failed to re-initialize keyboard");
  
  if (!gdk_fb_mouse_open ())
    g_warning ("Failed to re-initialize mouse");

}


static gboolean
gdk_fb_manager_callback (GIOChannel *gioc,
			 GIOCondition cond,
			 gpointer data)
{
  struct FBManagerMessage msg;
  GdkFBDisplay *display;
  int res;

  display = data;

  res = recv (display->manager_fd, &msg, sizeof (msg), 0);

  if (res==0)
    {
      g_source_remove (gdk_display->manager_tag);
      /*g_io_channel_unref (kb->io);*/
      close (gdk_display->manager_fd);

    }

  if (res != sizeof (msg))
    {
      g_warning ("Got wrong size message");
      return TRUE;
    }
  
  switch (msg.msg_type)
    {
    case FB_MANAGER_SWITCH_FROM:
      g_print ("Got switch from message\n");
      display->manager_blocked = TRUE;
      gdk_fb_switch_from ();
      msg.msg_type = FB_MANAGER_ACK;
      send (display->manager_fd, &msg, sizeof (msg), 0);
      break;
    case FB_MANAGER_SWITCH_TO:
      g_print ("Got switch to message\n");
      display->manager_blocked = FALSE;
      gdk_fb_switch_to ();
      break;
    default:
      g_warning ("Got unknown message");
    }
  return TRUE;
}

#endif /* ENABLE_FB_MANAGER */

static void
gdk_fb_manager_connect (GdkFBDisplay *display)
{
#ifdef ENABLE_FB_MANAGER
  int fd;
  struct sockaddr_un addr;
  struct msghdr msg = {0};
  struct cmsghdr *cmsg;
  struct ucred credentials;
  struct FBManagerMessage init_msg;
  struct iovec iov;
  char buf[CMSG_SPACE (sizeof (credentials))];  /* ancillary data buffer */
  int *fdptr;
  int res;

  display->manager_blocked = FALSE;
  display->manager_fd = -1;

  fd = socket (PF_UNIX, SOCK_STREAM, 0);
  
  g_print ("socket: %d\n", fd);

  if (fd < 0)
    return;

  addr.sun_family = AF_UNIX;
  strcpy (addr.sun_path, "/tmp/.fb.manager");

  if (connect(fd, (struct sockaddr *)&addr, sizeof (addr)) < 0) 
    {
      g_print ("connect failed\n");
      close (fd);
      return;
    }
  
  credentials.pid = getpid ();
  credentials.uid = geteuid ();
  credentials.gid = getegid ();

  init_msg.msg_type = FB_MANAGER_NEW_CLIENT;
  iov.iov_base = &init_msg;
  iov.iov_len = sizeof (init_msg);

  msg.msg_name = NULL;
  msg.msg_namelen = 0;
  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;
  msg.msg_control = buf;
  msg.msg_controllen = sizeof buf;
  cmsg = CMSG_FIRSTHDR(&msg);
  cmsg->cmsg_level = SOL_SOCKET;
  cmsg->cmsg_type = SCM_CREDENTIALS;
  cmsg->cmsg_len = CMSG_LEN (sizeof (credentials));
  /* Initialize the payload: */
  fdptr = (int *)CMSG_DATA (cmsg);
  memcpy (fdptr, &credentials, sizeof (credentials));
  /* Sum of the length of all control messages in the buffer: */
  msg.msg_controllen = cmsg->cmsg_len;

  res = sendmsg (fd, &msg, 0);

  display->manager_fd = fd;
  display->manager_blocked = TRUE;

  display->manager_tag = g_io_add_watch (g_io_channel_unix_new (fd),
					 G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
					 gdk_fb_manager_callback,
					 display);

  init_msg.msg_type = FB_MANAGER_REQUEST_SWITCH_TO_PID;
  init_msg.data = getpid ();

  /* Request a switch-to */
  send (fd, &init_msg, sizeof (init_msg), 0);
#endif
}

static GdkFBDisplay *
gdk_fb_display_new ()
{
  GdkFBDisplay *display;
  gchar *fb_filename;
  struct vt_stat vs;
  int vt, n;
  gchar *s, *send;
  char buf[32];

  display = g_new0 (GdkFBDisplay, 1);

  display->console_fd = open ("/dev/console", O_RDWR);
  if (display->console_fd < 0)
    {
      g_warning ("Can't open /dev/console: %s", strerror (errno));
      g_free (display);
      return NULL;
    }
  
  ioctl (display->console_fd, VT_GETSTATE, &vs);
  display->start_vt = vs.v_active;

  vt = display->start_vt;
  s = getenv("GDK_VT");
  if (s)
    {
      if (g_strcasecmp ("new", s)==0)
	{
	  n = ioctl (display->console_fd, VT_OPENQRY, &vt);
	  if (n < 0 || vt == -1)
	    g_error("Cannot allocate new VT");
	}
      else
	{
	  vt = strtol (s, &send, 10);
	  if (s==send)
	    {
	      g_warning ("Cannot parse GDK_VT");
	      vt = display->start_vt;
	    }
	}
      
    }

  display->vt = vt;
  
  /* Switch to the new VT */
  if (vt != display->start_vt)
    {
      ioctl (display->console_fd, VT_ACTIVATE, vt);
      ioctl (display->console_fd, VT_WAITACTIVE, vt);
    }
  
  /* Open the tty */
  g_snprintf (buf, sizeof(buf), "/dev/tty%d", vt);
  display->tty_fd = open (buf, O_RDWR|O_NONBLOCK);
  if (display->tty_fd < 0)
    {
      g_warning ("Can't open %s: %s", buf, strerror (errno));
      close (display->console_fd);
      g_free (display);
      return NULL;
    }

  /* Set controlling tty */
  ioctl (0, TIOCNOTTY, 0);
  ioctl (display->tty_fd, TIOCSCTTY, 0);
  
  fb_filename = gdk_get_display ();
  display->fb_fd = open (fb_filename, O_RDWR);
  if (display->fb_fd < 0)
    {
      g_warning ("Can't open %s: %s", fb_filename, strerror (errno));
      g_free (fb_filename);
      close (display->tty_fd);
      close (display->console_fd);
      g_free (display);
      return NULL;
    }
  g_free (fb_filename);

  if (gdk_fb_set_mode (display) < 0)
    {
      close (display->fb_fd);
      close (display->tty_fd);
      close (display->console_fd);
      g_free (display);
      return NULL;
    }

  /* Disable normal text on the console */
  ioctl (display->fb_fd, KDSETMODE, KD_GRAPHICS);

  ioctl (display->fb_fd, FBIOBLANK, 0);

  /* We used to use sinfo.smem_len, but that seemed to be broken in many cases */
  display->fb_mmap = mmap (NULL,
			   display->modeinfo.yres * display->sinfo.line_length,
			   PROT_READ|PROT_WRITE,
			   MAP_SHARED,
			   display->fb_fd,
			   0);
  g_assert (display->fb_mmap != MAP_FAILED);

  if (display->sinfo.visual == FB_VISUAL_TRUECOLOR)
    {
      display->red_byte = display->modeinfo.red.offset >> 3;
      display->green_byte = display->modeinfo.green.offset >> 3;
      display->blue_byte = display->modeinfo.blue.offset >> 3;
    }

#ifdef ENABLE_SHADOW_FB
  if (_gdk_fb_screen_angle % 2 == 0)
    {
      display->fb_width = display->modeinfo.xres;
      display->fb_height = display->modeinfo.yres;
    } 
  else
    {
      display->fb_width = display->modeinfo.yres;
      display->fb_height = display->modeinfo.xres;
    }
  display->fb_stride =
    display->fb_width * (display->modeinfo.bits_per_pixel / 8);
  display->fb_mem = g_malloc(display->fb_height * display->fb_stride);
#else
  display->fb_mem = display->fb_mmap;
  display->fb_width = display->modeinfo.xres;
  display->fb_height = display->modeinfo.yres;
  display->fb_stride = display->sinfo.line_length;
#endif

  return display;
}

static void
gdk_fb_display_destroy (GdkFBDisplay *display)
{
  /* Restore old framebuffer mode */
  ioctl (display->fb_fd, FBIOPUT_VSCREENINFO, &display->orig_modeinfo);
  
  /* Enable normal text on the console */
  ioctl (display->fb_fd, KDSETMODE, KD_TEXT);
  
  munmap (display->fb_mmap, display->modeinfo.yres * display->sinfo.line_length);
  close (display->fb_fd);

  ioctl (display->console_fd, VT_ACTIVATE, display->start_vt);
  ioctl (display->console_fd, VT_WAITACTIVE, display->start_vt);
  if (display->vt != display->start_vt)
    ioctl (display->console_fd, VT_DISALLOCATE, display->vt);
  
  close (display->tty_fd);
  close (display->console_fd);
  g_free (display);
}

gboolean
_gdk_windowing_init_check (int argc, char **argv)
{
  if (gdk_initialized)
    return TRUE;

  /* Create new session and become session leader */
  setsid();

  gdk_display = gdk_fb_display_new ();

  if (!gdk_display)
    return FALSE;

  gdk_shadow_fb_init ();
  
  gdk_fb_manager_connect (gdk_display);

  if (!gdk_fb_keyboard_init (!gdk_display->manager_blocked))
    {
      g_warning ("Failed to initialize keyboard");
      gdk_fb_display_destroy (gdk_display);
      gdk_display = NULL;
      return FALSE;
    }
  
  if (!gdk_fb_mouse_init (!gdk_display->manager_blocked))
    {
      g_warning ("Failed to initialize mouse");
      gdk_fb_keyboard_close ();
      gdk_fb_display_destroy (gdk_display);
      gdk_display = NULL;
      return FALSE;
    }

  gdk_initialized = TRUE;

  _gdk_selection_property = gdk_atom_intern ("GDK_SELECTION", FALSE);

  
  return TRUE;
}

/*
 *--------------------------------------------------------------
 * gdk_pointer_grab
 *
 *   Grabs the pointer to a specific window
 *
 * Arguments:
 *   "window" is the window which will receive the grab
 *   "owner_events" specifies whether events will be reported as is,
 *     or relative to "window"
 *   "event_mask" masks only interesting events
 *   "confine_to" limits the cursor movement to the specified window
 *   "cursor" changes the cursor for the duration of the grab
 *   "time" specifies the time
 *
 * Results:
 *
 * Side effects:
 *   requires a corresponding call to gdk_pointer_ungrab
 *
 *--------------------------------------------------------------
 */

GdkGrabStatus
gdk_pointer_grab (GdkWindow *	  window,
		  gint		  owner_events,
		  GdkEventMask	  event_mask,
		  GdkWindow *	  confine_to,
		  GdkCursor *	  cursor,
		  guint32	  time)
{
  return gdk_fb_pointer_grab (window,
			      owner_events,
			      event_mask,
			      confine_to,
			      cursor,
			      time, FALSE);
}

static gboolean _gdk_fb_pointer_implicit_grab = FALSE;

GdkGrabStatus
gdk_fb_pointer_grab (GdkWindow *	  window,
		     gint		  owner_events,
		     GdkEventMask	  event_mask,
		     GdkWindow *	  confine_to,
		     GdkCursor *	  cursor,
		     guint32	          time,
		     gboolean             implicit_grab)
{
  g_return_val_if_fail (window != NULL, 0);
  g_return_val_if_fail (GDK_IS_WINDOW (window), 0);
  g_return_val_if_fail (confine_to == NULL || GDK_IS_WINDOW (confine_to), 0);

  if (_gdk_fb_pointer_grab_window)
    {
      if (implicit_grab && !_gdk_fb_pointer_implicit_grab)
	return GDK_GRAB_ALREADY_GRABBED;

      gdk_pointer_ungrab (time);
    }

  gdk_fb_window_send_crossing_events (NULL,
				      window,
				      GDK_CROSSING_GRAB);  

  if (event_mask & GDK_BUTTON_MOTION_MASK)
      event_mask |=
	GDK_BUTTON1_MOTION_MASK |
	GDK_BUTTON2_MOTION_MASK |
	GDK_BUTTON3_MOTION_MASK;
  
  _gdk_fb_pointer_implicit_grab = implicit_grab;

  _gdk_fb_pointer_grab_window = gdk_window_ref (window);
  _gdk_fb_pointer_grab_owner_events = owner_events;

  _gdk_fb_pointer_grab_confine = confine_to ? gdk_window_ref (confine_to) : NULL;
  _gdk_fb_pointer_grab_events = event_mask;
  _gdk_fb_pointer_grab_cursor = cursor ? gdk_cursor_ref (cursor) : NULL;


  
  if (cursor)
    gdk_fb_cursor_reset ();

  return GDK_GRAB_SUCCESS;
}

/*
 *--------------------------------------------------------------
 * gdk_pointer_ungrab
 *
 *   Releases any pointer grab
 *
 * Arguments:
 *
 * Results:
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

void
gdk_pointer_ungrab (guint32 time)
{
  gdk_fb_pointer_ungrab (time, FALSE);
}

void
gdk_fb_pointer_ungrab (guint32 time, gboolean implicit_grab)
{
  gboolean have_grab_cursor = _gdk_fb_pointer_grab_cursor && 1;
  GdkWindow *mousewin;
  GdkWindow *old_grab_window;
  
  if (!_gdk_fb_pointer_grab_window)
    return;

  if (implicit_grab && !_gdk_fb_pointer_implicit_grab)
    return;

  if (_gdk_fb_pointer_grab_confine)
    gdk_window_unref (_gdk_fb_pointer_grab_confine);
  _gdk_fb_pointer_grab_confine = NULL;

  if (_gdk_fb_pointer_grab_cursor)
    gdk_cursor_unref (_gdk_fb_pointer_grab_cursor);
  _gdk_fb_pointer_grab_cursor = NULL;

  if (have_grab_cursor)
    gdk_fb_cursor_reset ();

  old_grab_window = _gdk_fb_pointer_grab_window;
  
  _gdk_fb_pointer_grab_window = NULL;

  _gdk_fb_pointer_implicit_grab = FALSE;

  mousewin = gdk_window_at_pointer (NULL, NULL);
  gdk_fb_window_send_crossing_events (old_grab_window,
				      mousewin,
				      GDK_CROSSING_UNGRAB);
  
  if (old_grab_window)
    gdk_window_unref (old_grab_window);
}

/*
 *--------------------------------------------------------------
 * gdk_pointer_is_grabbed
 *
 *   Tell wether there is an active x pointer grab in effect
 *
 * Arguments:
 *
 * Results:
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

gint
gdk_pointer_is_grabbed (void)
{
  return _gdk_fb_pointer_grab_window != NULL;
}

/*
 *--------------------------------------------------------------
 * gdk_keyboard_grab
 *
 *   Grabs the keyboard to a specific window
 *
 * Arguments:
 *   "window" is the window which will receive the grab
 *   "owner_events" specifies whether events will be reported as is,
 *     or relative to "window"
 *   "time" specifies the time
 *
 * Results:
 *
 * Side effects:
 *   requires a corresponding call to gdk_keyboard_ungrab
 *
 *--------------------------------------------------------------
 */

GdkGrabStatus
gdk_keyboard_grab (GdkWindow *	   window,
		   gint		   owner_events,
		   guint32	   time)
{
  g_return_val_if_fail (window != NULL, 0);
  g_return_val_if_fail (GDK_IS_WINDOW (window), 0);

  if (_gdk_fb_pointer_grab_window)
    gdk_keyboard_ungrab (time);

  _gdk_fb_keyboard_grab_window = gdk_window_ref (window);
  _gdk_fb_keyboard_grab_owner_events = owner_events;
  
  return GDK_GRAB_SUCCESS;
}

/*
 *--------------------------------------------------------------
 * gdk_keyboard_ungrab
 *
 *   Releases any keyboard grab
 *
 * Arguments:
 *
 * Results:
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

void
gdk_keyboard_ungrab (guint32 time)
{
  if (_gdk_fb_keyboard_grab_window)
    gdk_window_unref (_gdk_fb_keyboard_grab_window);
  _gdk_fb_keyboard_grab_window = NULL;
}

gboolean
gdk_pointer_grab_info_libgtk_only (GdkWindow **grab_window,
				   gboolean   *owner_events)
{
  if (_gdk_fb_pointer_grab_window)
    {
      if (grab_window)
        *grab_window = (GdkWindow *)_gdk_fb_pointer_grab_window;
      if (owner_events)
        *owner_events = _gdk_fb_pointer_grab_owner_events;
      
      return TRUE;
    }
  else
    return FALSE;
}

gboolean
gdk_keyboard_grab_info_libgtk_only (GdkWindow **grab_window,
                                   gboolean   *owner_events)
{
  if (_gdk_fb_keyboard_grab_window)
    {
      if (grab_window)
        *grab_window = (GdkWindow *)_gdk_fb_keyboard_grab_window;
      if (owner_events)
        *owner_events = _gdk_fb_keyboard_grab_owner_events;

      return TRUE;
    }
  else
    return FALSE;
}


/*
 *--------------------------------------------------------------
 * gdk_screen_width
 *
 *   Return the width of the screen.
 *
 * Arguments:
 *
 * Results:
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

gint
gdk_screen_width (void)
{
  return gdk_display->fb_width;
}

/*
 *--------------------------------------------------------------
 * gdk_screen_height
 *
 *   Return the height of the screen.
 *
 * Arguments:
 *
 * Results:
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

gint
gdk_screen_height (void)
{
  return gdk_display->fb_height;
}

/*
 *--------------------------------------------------------------
 * gdk_screen_width_mm
 *
 *   Return the width of the screen in millimeters.
 *
 * Arguments:
 *
 * Results:
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

gint
gdk_screen_width_mm (void)
{
  return 0.5 + gdk_screen_width () * (25.4 / 72.);
}

/*
 *--------------------------------------------------------------
 * gdk_screen_height
 *
 *   Return the height of the screen in millimeters.
 *
 * Arguments:
 *
 * Results:
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

gint
gdk_screen_height_mm (void)
{
  return 0.5 + gdk_screen_height () * (25.4 / 72.);
}

/*
 *--------------------------------------------------------------
 * gdk_set_sm_client_id
 *
 *   Set the SM_CLIENT_ID property on the WM_CLIENT_LEADER window
 *   so that the window manager can save our state using the
 *   X11R6 ICCCM session management protocol. A NULL value should 
 *   be set following disconnection from the session manager to
 *   remove the SM_CLIENT_ID property.
 *
 * Arguments:
 * 
 *   "sm_client_id" specifies the client id assigned to us by the
 *   session manager or NULL to remove the property.
 *
 * Results:
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

void
gdk_set_sm_client_id (const gchar* sm_client_id)
{
}


extern void keyboard_shutdown(void);

void
_gdk_windowing_exit (void)
{

  gdk_fb_mouse_close ();
  /*leak  g_free (gdk_fb_mouse);*/
  
  gdk_fb_keyboard_close ();
  /*leak g_free (gdk_fb_keyboard);*/
  
  gdk_fb_display_destroy (gdk_display);
  
  gdk_display = NULL;
}

gchar*
gdk_keyval_name (guint	      keyval)
{
  return NULL;
}

guint
gdk_keyval_from_name (const gchar *keyval_name)
{
  return 0;
}

gchar *
gdk_get_display(void)
{
  gchar *s;

  s = getenv ("GDK_DISPLAY");
  if (s==0)
    s = "/dev/fb0";
  
  return g_strdup (s);
}


void
gdk_beep (void)
{
  static int pitch = 600, duration = 100;
  gulong arg;

  /* Thank you XFree86 */
  arg = ((1193190 / pitch) & 0xffff) |
    (((unsigned long)duration) << 16);

  ioctl (gdk_display->tty_fd, KDMKTONE, arg);
}

/* utils */
static const guint type_masks[] = {
  GDK_SUBSTRUCTURE_MASK, /* GDK_DELETE		= 0, */
  GDK_STRUCTURE_MASK, /* GDK_DESTROY		= 1, */
  GDK_EXPOSURE_MASK, /* GDK_EXPOSE		= 2, */
  GDK_POINTER_MOTION_MASK, /* GDK_MOTION_NOTIFY	= 3, */
  GDK_BUTTON_PRESS_MASK, /* GDK_BUTTON_PRESS	= 4, */
  GDK_BUTTON_PRESS_MASK, /* GDK_2BUTTON_PRESS	= 5, */
  GDK_BUTTON_PRESS_MASK, /* GDK_3BUTTON_PRESS	= 6, */
  GDK_BUTTON_RELEASE_MASK, /* GDK_BUTTON_RELEASE	= 7, */
  GDK_KEY_PRESS_MASK, /* GDK_KEY_PRESS	= 8, */
  GDK_KEY_RELEASE_MASK, /* GDK_KEY_RELEASE	= 9, */
  GDK_ENTER_NOTIFY_MASK, /* GDK_ENTER_NOTIFY	= 10, */
  GDK_LEAVE_NOTIFY_MASK, /* GDK_LEAVE_NOTIFY	= 11, */
  GDK_FOCUS_CHANGE_MASK, /* GDK_FOCUS_CHANGE	= 12, */
  GDK_STRUCTURE_MASK, /* GDK_CONFIGURE		= 13, */
  GDK_VISIBILITY_NOTIFY_MASK, /* GDK_MAP		= 14, */
  GDK_VISIBILITY_NOTIFY_MASK, /* GDK_UNMAP		= 15, */
  GDK_PROPERTY_CHANGE_MASK, /* GDK_PROPERTY_NOTIFY	= 16, */
  GDK_PROPERTY_CHANGE_MASK, /* GDK_SELECTION_CLEAR	= 17, */
  GDK_PROPERTY_CHANGE_MASK, /* GDK_SELECTION_REQUEST = 18, */
  GDK_PROPERTY_CHANGE_MASK, /* GDK_SELECTION_NOTIFY	= 19, */
  GDK_PROXIMITY_IN_MASK, /* GDK_PROXIMITY_IN	= 20, */
  GDK_PROXIMITY_OUT_MASK, /* GDK_PROXIMITY_OUT	= 21, */
  GDK_ALL_EVENTS_MASK, /* GDK_DRAG_ENTER        = 22, */
  GDK_ALL_EVENTS_MASK, /* GDK_DRAG_LEAVE        = 23, */
  GDK_ALL_EVENTS_MASK, /* GDK_DRAG_MOTION       = 24, */
  GDK_ALL_EVENTS_MASK, /* GDK_DRAG_STATUS       = 25, */
  GDK_ALL_EVENTS_MASK, /* GDK_DROP_START        = 26, */
  GDK_ALL_EVENTS_MASK, /* GDK_DROP_FINISHED     = 27, */
  GDK_ALL_EVENTS_MASK, /* GDK_CLIENT_EVENT	= 28, */
  GDK_VISIBILITY_NOTIFY_MASK, /* GDK_VISIBILITY_NOTIFY = 29, */
  GDK_EXPOSURE_MASK, /* GDK_NO_EXPOSE		= 30, */
  GDK_SCROLL_MASK /* GDK_SCROLL            = 31 */
};

GdkWindow *
gdk_fb_other_event_window (GdkWindow *window,
			   GdkEventType type)
{
  guint32 evmask;
  GdkWindow *w;

  w = window;
  while (w != _gdk_parent_root)
    {
      /* Huge hack, so that we don't propagate events to GtkWindow->frame */
      if ((w != window) &&
	  (GDK_WINDOW_P (w)->window_type != GDK_WINDOW_CHILD) &&
	  (g_object_get_data (G_OBJECT (w), "gdk-window-child-handler")))
	  break;
	  
      evmask = GDK_WINDOW_IMPL_FBDATA(window)->event_mask;

      if (evmask & type_masks[type])
	return w;
      
      w = gdk_window_get_parent (w);
    }
  
  return NULL;
}

GdkWindow *
gdk_fb_pointer_event_window (GdkWindow *window,
			     GdkEventType type)
{
  guint evmask;
  GdkModifierType mask;
  GdkWindow *w;
	  
  gdk_fb_mouse_get_info (NULL, NULL, &mask);
  
  if (_gdk_fb_pointer_grab_window &&
      !_gdk_fb_pointer_grab_owner_events)
    {
      evmask = _gdk_fb_pointer_grab_events;

      if (evmask & (GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK))
	{
	  if (((mask & GDK_BUTTON1_MASK) && (evmask & GDK_BUTTON1_MOTION_MASK)) ||
	      ((mask & GDK_BUTTON2_MASK) && (evmask & GDK_BUTTON2_MOTION_MASK)) ||
	      ((mask & GDK_BUTTON3_MASK) && (evmask & GDK_BUTTON3_MOTION_MASK)))
	    evmask |= GDK_POINTER_MOTION_MASK;
	}

      if (evmask & type_masks[type])
	return _gdk_fb_pointer_grab_window;
      else
	return NULL;
    }

  w = window;
  while (w != _gdk_parent_root)
    {
      /* Huge hack, so that we don't propagate events to GtkWindow->frame */
      if ((w != window) &&
	  (GDK_WINDOW_P (w)->window_type != GDK_WINDOW_CHILD) &&
	  (g_object_get_data (G_OBJECT (w), "gdk-window-child-handler")))
	  break;
      
      evmask = GDK_WINDOW_IMPL_FBDATA(window)->event_mask;

      if (evmask & (GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK))
	{
	  if (((mask & GDK_BUTTON1_MASK) && (evmask & GDK_BUTTON1_MOTION_MASK)) ||
	      ((mask & GDK_BUTTON2_MASK) && (evmask & GDK_BUTTON2_MOTION_MASK)) ||
	      ((mask & GDK_BUTTON3_MASK) && (evmask & GDK_BUTTON3_MOTION_MASK)))
	    evmask |= GDK_POINTER_MOTION_MASK;
	}

      if (evmask & type_masks[type])
	return w;
      
      w = gdk_window_get_parent (w);
    }

  if (_gdk_fb_pointer_grab_window &&
      _gdk_fb_pointer_grab_owner_events)
    {
      evmask = _gdk_fb_pointer_grab_events;
      
      if (evmask & (GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK))
	{
	  if (((mask & GDK_BUTTON1_MASK) && (evmask & GDK_BUTTON1_MOTION_MASK)) ||
	      ((mask & GDK_BUTTON2_MASK) && (evmask & GDK_BUTTON2_MOTION_MASK)) ||
	      ((mask & GDK_BUTTON3_MASK) && (evmask & GDK_BUTTON3_MOTION_MASK)))
	    evmask |= GDK_POINTER_MOTION_MASK;
	}
      
      if (evmask & type_masks[type])
	return _gdk_fb_pointer_grab_window;
    }
  
  return NULL;
}

GdkWindow *
gdk_fb_keyboard_event_window (GdkWindow *window,
			      GdkEventType type)
{
  guint32 evmask;
  GdkWindow *w;
  
  if (_gdk_fb_keyboard_grab_window &&
      !_gdk_fb_keyboard_grab_owner_events)
    {
      return _gdk_fb_keyboard_grab_window;
    }
  
  w = window;
  while (w != _gdk_parent_root)
    {
      /* Huge hack, so that we don't propagate events to GtkWindow->frame */
      if ((w != window) &&
	  (GDK_WINDOW_P (w)->window_type != GDK_WINDOW_CHILD) &&
	  (g_object_get_data (G_OBJECT (w), "gdk-window-child-handler")))
	  break;
	  
      evmask = GDK_WINDOW_IMPL_FBDATA(window)->event_mask;

      if (evmask & type_masks[type])
	return w;
      
      w = gdk_window_get_parent (w);
    }
  
  if (_gdk_fb_keyboard_grab_window &&
      _gdk_fb_keyboard_grab_owner_events)
    {
      return _gdk_fb_keyboard_grab_window;
    }

  return NULL;
}

GdkEvent *
gdk_event_make (GdkWindow *window,
		GdkEventType type,
		gboolean append_to_queue)
{
  GdkEvent *event = _gdk_event_new ();
  guint32 the_time;
  
  the_time = gdk_fb_get_time ();
  
  event->any.type = type;
  event->any.window = gdk_window_ref (window);
  event->any.send_event = FALSE;
  switch (type)
    {
    case GDK_MOTION_NOTIFY:
      event->motion.time = the_time;
      event->motion.axes = NULL;
      break;
    case GDK_BUTTON_PRESS:
    case GDK_2BUTTON_PRESS:
    case GDK_3BUTTON_PRESS:
    case GDK_BUTTON_RELEASE:
      event->button.time = the_time;
      event->button.axes = NULL;
      break;
    case GDK_KEY_PRESS:
    case GDK_KEY_RELEASE:
      event->key.time = the_time;
      break;
    case GDK_ENTER_NOTIFY:
    case GDK_LEAVE_NOTIFY:
      event->crossing.time = the_time;
      break;
      
    case GDK_PROPERTY_NOTIFY:
      event->property.time = the_time;
      break;
      
    case GDK_SELECTION_CLEAR:
    case GDK_SELECTION_REQUEST:
    case GDK_SELECTION_NOTIFY:
      event->selection.time = the_time;
      break;
    case GDK_PROXIMITY_IN:
    case GDK_PROXIMITY_OUT:
      event->proximity.time = the_time;
      break;
    case GDK_DRAG_ENTER:
    case GDK_DRAG_LEAVE:
    case GDK_DRAG_MOTION:
    case GDK_DRAG_STATUS:
    case GDK_DROP_START:
    case GDK_DROP_FINISHED:
      event->dnd.time = the_time;
      break;
      
    case GDK_FOCUS_CHANGE:
    case GDK_CONFIGURE:
    case GDK_MAP:
    case GDK_UNMAP:
    case GDK_CLIENT_EVENT:
    case GDK_VISIBILITY_NOTIFY:
    case GDK_NO_EXPOSE:
    case GDK_SCROLL:
    case GDK_DELETE:
    case GDK_DESTROY:
    case GDK_EXPOSE:
    default:
      break;
    }
  
  if (append_to_queue)
    _gdk_event_queue_append (event);
  
  return event;
}

void
gdk_fb_set_rotation (GdkFBAngle angle)
{
  if (angle == _gdk_fb_screen_angle)
    return;
  
#ifdef ENABLE_SHADOW_FB
  if (gdk_display)
    {
      gdk_shadow_fb_stop_updates ();

      gdk_fb_cursor_hide ();
      
      _gdk_fb_screen_angle = angle;

      if (angle % 2 == 0)
	{
	  gdk_display->fb_width = gdk_display->modeinfo.xres;
	  gdk_display->fb_height = gdk_display->modeinfo.yres;
	} 
      else
	{
	  gdk_display->fb_width = gdk_display->modeinfo.yres;
	  gdk_display->fb_height = gdk_display->modeinfo.xres;
	}
      gdk_display->fb_stride =
	gdk_display->fb_width * (gdk_display->modeinfo.bits_per_pixel / 8);
      
      gdk_fb_recompute_all();
      gdk_fb_redraw_all ();
      
      gdk_fb_cursor_unhide ();
    }
  else
    _gdk_fb_screen_angle = angle;
#else
  g_warning ("Screen rotation without shadow fb not supported.");
#endif
}

void
gdk_error_trap_push (void)
{
}

gint
gdk_error_trap_pop (void)
{
  return 0;
}
