Re: [AD] Patch to prototype _xwin_handle_input

[ Thread Index | Date Index | More lists.liballeg.org/allegro-developers Archives ]


More about the problem of nested calls to Xlib functions with possible
solution.

It is dangerous to interrupt call to Xlib function and call another
Xlib function on the same display connection.  Program may crash,
sometimes together with X-server, and because X-server may have root
privileges, it may damage system sometimes (early development versions
of XwinAllegro destroyed parts of file system on my computer).  It is
necessary to protect calls to Xlib functions with some kind of locks,
or split Xlib functions on two classes each of which can use different
display connection.

Currently X version of Allegro uses semaphores and disables interrupts
before calling any Xlib function.  This method is not very convenient
for AllegroGL, which needs to disable interrupts for every OpenGL
call (IMHO, it is not practical to make wrappers for all OpenGL
functions, so all calls to disable/enable interrupts should be added
to the program).

However, it is possible to create another display connection and ask
X-server to send input from particular window, even though it was
created on different display connection.  This method allows to send
graphics requests on the first display connection, which owns window,
and to receive input from window on the second display connection (DC),
and it is possible to interrupt Xlib calls on one DC with calls on
another DC.  Using this scheme, we can use only second DC inside
interrupts and get rid of Xlib protection.  There is one problem
though.  User might call something inside interrupt callback function,
which can call Xlib function on the first DC.  Then there are Expose
events, which should be processed inside interrupts.  These problems
can be solved by using the same semaphores.

The problem described in previous paragraph can be solved by using
implicit double buffering or dirty rectangles, when Allegro functions
don't call Xlib functions directly, but instead they modify bitmap
data in system memory and add dirty rectangles to the list, and parts
of screen are updated in timer interrupt.  But for AllegroGL it would
be better to have two DCs anyway (we have no control over OpenGL
functions).

Sample program which uses two DCs is attached below.  It uses one DC
for graphics output and another DC for getting input (it just counts
the number of events or prints information to console).  It seems to
work fine, though it crashed once with XIO error (when printing to
console was enabled and perhaps timeout was enabled too).

Unfortunately, I have some deadlines to meet, and will not have
enough time to play with X version of Allegro until march.

/* xio.c --- attempt to untie input and output in X
 *
 * This file is gift-ware.  This file is given to you freely
 * as a gift.  You may use, modify, redistribute, and generally hack
 * it about in any way you like, and you do not have to give anyone
 * anything in return.
 *
 * I do not accept any responsibility for any effects, adverse or
 * otherwise, that this code may have on just about anything that
 * you can think of.  Use it at your own risk.
 *
 * Copyright (C) 2000  Michael Bukin
 */

/*
 * Compile with: gcc -O2 -g -Wall -o xio xio.c -lX11 -lm
 */

#include <X11/Xlib.h>

#include <signal.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static Display *idsp;
static Display *odsp;
static Window win;
static int done = 0;
static void (*old_handler) (int num) = 0;

static struct
{
  int kp, kr;
  int bp, br;
  int mn;
} events;

static void start_timer (void);
static void stop_timer (void);

/* #define REPORT 1 */
/* #define SLOWDOWN 1 */

static void
new_handler (int num)
{
  XEvent event;

  /* Flush buffers.  */
  XSync (idsp, False);

  /* Got any events.  */
  while (XEventsQueued (idsp, QueuedAlready) > 0)
    {
      /* Get next event from window.  */
      XNextEvent (idsp, &event);
      switch (event.type)
	{
	case KeyPress:
	  events.kp++;
#ifdef REPORT
	  printf ("kp: %d\n", event.xkey.keycode);
#endif
	  break;
	case KeyRelease:
	  events.kr++;
#ifdef REPORT
	  printf ("kr: %d\n", event.xkey.keycode);
#endif
	  break;
	case ButtonPress:
	  events.bp++;
	  if (event.xbutton.button == Button1)
	    done = 1;
#ifdef REPORT
	  printf ("bp: %d\n", event.xbutton.button);
#endif
	  break;
	case ButtonRelease:
	  events.br++;
#ifdef REPORT
	  printf ("br: %d\n", event.xbutton.button);
#endif
	  break;
	case MotionNotify:
	  events.mn++;
#ifdef REPORT
	  printf ("mn: %d %d\n", event.xmotion.x, event.xmotion.y);
#endif
	  break;
	}
    }

  /* Restart timer.  */
  start_timer ();
}

static void
start_timer (void)
{
  struct itimerval timer;

  old_handler = signal (SIGALRM, new_handler);

  timer.it_interval.tv_sec = 0;
  timer.it_interval.tv_usec = 0;
  timer.it_value.tv_sec = 0;
  timer.it_value.tv_usec = 100000; /* 100 ms */
  setitimer (ITIMER_REAL, &timer, 0);
}

static void
stop_timer (void)
{
  struct itimerval timer;

  timer.it_interval.tv_sec = 0;
  timer.it_interval.tv_usec = 0;
  timer.it_value.tv_sec = 0;
  timer.it_value.tv_usec = 0;
  setitimer (ITIMER_REAL, &timer, 0);

  signal (SIGALRM, old_handler);
}

int
main (int argc, char *argv[])
{
  int x = 0;
  int y = 0;
  XSetWindowAttributes attr;
  XGCValues gcval;
  GC fgc, bgc;

  /* Open output display.  */
  odsp = XOpenDisplay (0);
  if (odsp == 0)
    {
      fprintf (stderr, "%s: XOpenDisplay failed for output thread\n", argv[0]);
      exit (EXIT_FAILURE);
    }

  /* Create window.  */
  attr.event_mask = ExposureMask;
  attr.background_pixel = WhitePixel (odsp, XDefaultScreen (odsp));
  win = XCreateWindow (odsp, XDefaultRootWindow (odsp),
		       0, 0, 320, 200, 0, CopyFromParent, InputOutput, CopyFromParent,
		       CWEventMask | CWBackPixel, &attr);
  XMapWindow (odsp, win);

  /* Create graphics contexts.  */
  gcval.foreground = XBlackPixel (odsp, XDefaultScreen (odsp));
  gcval.background = XWhitePixel (odsp, XDefaultScreen (odsp));
  gcval.function = GXcopy;
  fgc = XCreateGC (odsp, win, GCFunction | GCForeground | GCBackground, &gcval);

  gcval.foreground = XWhitePixel (odsp, XDefaultScreen (odsp));
  gcval.background = XBlackPixel (odsp, XDefaultScreen (odsp));
  gcval.function = GXcopy;
  bgc = XCreateGC (odsp, win, GCFunction | GCForeground | GCBackground, &gcval);

  /* Wait for the first exposure event.  */
  while (1)
    {
      XEvent event;
      XNextEvent (odsp, &event);
      if ((event.type == Expose) && (event.xexpose.count == 0))
	break;
    }

  /* Open input display.  */
  idsp = XOpenDisplay (0);
  if (idsp == 0)
    {
      fprintf (stderr, "%s: XOpenDisplay failed for input thread\n", argv[0]);
      exit (EXIT_FAILURE);
    }

  /* Select input from window.  */
  XSelectInput (idsp, win, KeyPressMask | KeyReleaseMask | ButtonPressMask | PointerMotionMask);

  /* Create input thread.  */
  start_timer ();

  /* Wait for input thread.  */
  while (!done)
    {
#ifdef SLOWDOWN
      struct timeval timeout;
#endif

      x = (x + 1) % 320;
      y = (y + 1) % 200;

      XFillRectangle (odsp, win, bgc, 0, 0, 320, 200);
      XDrawLine (odsp, win, fgc, 0, y, 319, y);
      XDrawLine (odsp, win, fgc, x, 0, x, 199);
      XSync (odsp,False);

#ifdef SLOWDOWN
      timeout.tv_sec = 0;
      timeout.tv_usec = 1000;
      select (0, 0, 0, 0, &timeout);
#endif
    }

  stop_timer ();

  /* Keep your country tidy.  */
  XCloseDisplay (idsp);

  XFreeGC (odsp, bgc);
  XFreeGC (odsp, fgc);

  XUnmapWindow (odsp, win);
  XDestroyWindow (odsp, win);
  XCloseDisplay (odsp);

  printf ("Received:\n"
	  "  %d KeyPress events\n"
	  "  %d KeyRelease events\n"
	  "  %d ButtonPress events\n"
	  "  %d ButtonRelease events\n"
	  "  %d MotionNotify events\n",
	  events.kp, events.kr,
	  events.bp, events.br,
	  events.mn);

  return 0;
}

/*
 * xio.c ends here
 */


-- 
Michael Bukin



Mail converted by MHonArc 2.6.19+ http://listengine.tuxfamily.org/