ShiftOS-C-/source/ShiftUI/Internal/X11ThreadQueue.cs

503 lines
11 KiB
C#
Raw Normal View History

// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Copyright (c) 2004-2006 Novell, Inc.
//
// Authors:
// Jackson Harper (jackson@ximian.com)
// Peter Dennis Bartok (pbartok@novell.com)
// Chris Toshok (toshok@ximian.com)
//
using System;
using System.Threading;
using System.Collections;
namespace ShiftUI.X11Internal {
internal class X11ThreadQueue {
XEventQueue xqueue;
PaintQueue paint_queue;
ConfigureQueue configure_queue;
ArrayList timer_list;
Thread thread;
bool quit_posted;
bool dispatch_idle;
bool need_dispatch_idle = true;
object lockobj = new object ();
static readonly int InitialXEventQueueSize = 128;
static readonly int InitialHwndQueueSize = 50;
public X11ThreadQueue (Thread thread)
{
xqueue = new XEventQueue (InitialXEventQueueSize);
paint_queue = new PaintQueue (InitialHwndQueueSize);
configure_queue = new ConfigureQueue (InitialHwndQueueSize);
timer_list = new ArrayList ();
this.thread = thread;
this.quit_posted = false;
this.dispatch_idle = true;
}
public int CountUnlocked {
get { return xqueue.Count + paint_queue.Count; }
}
public Thread Thread {
get { return thread; }
}
public void EnqueueUnlocked (XEvent xevent)
{
switch (xevent.type) {
case XEventName.KeyPress:
case XEventName.KeyRelease:
case XEventName.ButtonPress:
case XEventName.ButtonRelease:
NeedDispatchIdle = true;
break;
case XEventName.MotionNotify:
if (xqueue.Count > 0) {
XEvent peek = xqueue.Peek ();
if (peek.AnyEvent.type == XEventName.MotionNotify)
return; // we've already got a pending motion notify.
}
// otherwise fall through and enqueue
// the event.
break;
}
xqueue.Enqueue (xevent);
// wake up any thread blocking in DequeueUnlocked
Monitor.PulseAll (lockobj);
}
public void Enqueue (XEvent xevent)
{
lock (lockobj) {
EnqueueUnlocked (xevent);
}
}
public bool Dequeue (out XEvent xevent)
{
StartOver:
bool got_xevent = false;
lock (lockobj) {
if (xqueue.Count > 0) {
got_xevent = true;
xevent = xqueue.Dequeue ();
}
else
xevent = new XEvent (); /* not strictly needed, but mcs complains */
}
if (got_xevent) {
if (xevent.AnyEvent.type == XEventName.Expose) {
#if spew
Console.Write ("E");
Console.Out.Flush ();
#endif
X11Hwnd hwnd = (X11Hwnd)Hwnd.GetObjectFromWindow (xevent.AnyEvent.window);
hwnd.AddExpose (xevent.AnyEvent.window == hwnd.ClientWindow,
xevent.ExposeEvent.x, xevent.ExposeEvent.y,
xevent.ExposeEvent.width, xevent.ExposeEvent.height);
goto StartOver;
}
else if (xevent.AnyEvent.type == XEventName.ConfigureNotify) {
#if spew
Console.Write ("C");
Console.Out.Flush ();
#endif
X11Hwnd hwnd = (X11Hwnd)Hwnd.GetObjectFromWindow (xevent.AnyEvent.window);
hwnd.AddConfigureNotify (xevent);
goto StartOver;
}
else {
#if spew
Console.Write ("X");
Console.Out.Flush ();
#endif
/* it was an event we can deal with directly, return it */
return true;
}
}
else {
if (paint_queue.Count > 0) {
xevent = paint_queue.Dequeue ();
#if spew
Console.Write ("e");
Console.Out.Flush ();
#endif
return true;
}
else if (configure_queue.Count > 0) {
xevent = configure_queue.Dequeue ();
#if spew
Console.Write ("c");
Console.Out.Flush ();
#endif
return true;
}
}
if (dispatch_idle && need_dispatch_idle) {
OnIdle (EventArgs.Empty);
need_dispatch_idle = false;
}
lock (lockobj) {
if (CountUnlocked > 0)
goto StartOver;
if (Monitor.Wait (lockobj, NextTimeout (), true)) {
// the lock was reaquired before the
// timeout. meaning an event was
// enqueued by X11Display.XEventThread.
goto StartOver;
}
else {
CheckTimers ();
return false;
}
}
}
public void RemovePaint (Hwnd hwnd)
{
paint_queue.Remove (hwnd);
}
public void AddPaint (Hwnd hwnd)
{
paint_queue.Enqueue (hwnd);
}
public void AddConfigure (Hwnd hwnd)
{
configure_queue.Enqueue (hwnd);
}
public ConfigureQueue Configure {
get { return configure_queue; }
}
public PaintQueue Paint {
get { return paint_queue; }
}
public void Lock ()
{
Monitor.Enter (lockobj);
}
public void Unlock ()
{
Monitor.Exit (lockobj);
}
private int NextTimeout ()
{
int timeout = Int32.MaxValue;
DateTime now = DateTime.UtcNow;
foreach (Timer timer in timer_list) {
int next = (int) (timer.Expires - now).TotalMilliseconds;
if (next < 0)
return 0; // Have a timer that has already expired
if (next < timeout)
timeout = next;
}
if (timeout < Timer.Minimum) {
timeout = Timer.Minimum;
}
if (timeout == Int32.MaxValue)
timeout = Timeout.Infinite;
return timeout;
}
public void CheckTimers ()
{
int count;
DateTime now = DateTime.UtcNow;
count = timer_list.Count;
if (count == 0)
return;
for (int i = 0; i < timer_list.Count; i++) {
Timer timer;
timer = (Timer) timer_list [i];
if (timer.Enabled && timer.Expires <= now) {
timer.Update (now);
timer.FireTick ();
}
}
}
public void SetTimer (Timer timer)
{
lock (lockobj) {
timer_list.Add (timer);
// we need to wake up any thread waiting in DequeueUnlocked,
// since it might need to wait for a different amount of time.
Monitor.PulseAll (lockobj);
}
}
public void KillTimer (Timer timer)
{
lock (lockobj) {
timer_list.Remove (timer);
// we need to wake up any thread waiting in DequeueUnlocked,
// since it might need to wait for a different amount of time.
Monitor.PulseAll (lockobj);
}
}
public event EventHandler Idle;
public void OnIdle (EventArgs e)
{
if (Idle != null)
Idle (thread, e);
}
public bool NeedDispatchIdle {
get { return need_dispatch_idle; }
set { need_dispatch_idle = value; }
}
public bool DispatchIdle {
get { return dispatch_idle; }
set { dispatch_idle = value; }
}
public bool PostQuitState {
get { return quit_posted; }
set { quit_posted = value; }
}
public abstract class HwndEventQueue {
protected ArrayList hwnds;
#if DebugHwndEventQueue
protected ArrayList stacks;
#endif
public HwndEventQueue (int size)
{
hwnds = new ArrayList (size);
#if DebugHwndEventQueue
stacks = new ArrayList (size);
#endif
}
public int Count {
get { return hwnds.Count; }
}
public void Enqueue (Hwnd hwnd)
{
if (hwnds.Contains (hwnd)) {
#if DebugHwndEventQueue
Console.WriteLine ("hwnds can only appear in the queue once.");
Console.WriteLine (Environment.StackTrace);
Console.WriteLine ("originally added here:");
Console.WriteLine (stacks[hwnds.IndexOf (hwnd)]);
#endif
return;
}
hwnds.Add(hwnd);
#if DebugHwndEventQueue
stacks.Add(Environment.StackTrace);
#endif
}
public void Remove(Hwnd hwnd)
{
#if DebugHwndEventQueue
int index = hwnds.IndexOf(hwnd);
if (index != -1)
stacks.RemoveAt(index);
#endif
hwnds.Remove(hwnd);
}
protected abstract XEvent Peek ();
public virtual XEvent Dequeue ()
{
if (hwnds.Count == 0)
throw new Exception ("Attempt to dequeue empty queue.");
return Peek ();
}
}
public class ConfigureQueue : HwndEventQueue
{
public ConfigureQueue (int size) : base (size)
{
}
protected override XEvent Peek ()
{
X11Hwnd hwnd = (X11Hwnd)hwnds[0];
XEvent xevent = new XEvent ();
xevent.AnyEvent.type = XEventName.ConfigureNotify;
xevent.ConfigureEvent.window = hwnd.ClientWindow;
xevent.ConfigureEvent.x = hwnd.X;
xevent.ConfigureEvent.y = hwnd.Y;
xevent.ConfigureEvent.width = hwnd.Width;
xevent.ConfigureEvent.height = hwnd.Height;
return xevent;
}
public override XEvent Dequeue ()
{
XEvent xev = base.Dequeue ();
hwnds.RemoveAt(0);
#if DebugHwndEventQueue
stacks.RemoveAt(0);
#endif
return xev;
}
}
public class PaintQueue : HwndEventQueue
{
public PaintQueue (int size) : base (size)
{
}
protected override XEvent Peek ()
{
X11Hwnd hwnd = (X11Hwnd)hwnds[0];
XEvent xevent = new XEvent ();
xevent.AnyEvent.type = XEventName.Expose;
if (hwnd.PendingExpose) {
xevent.ExposeEvent.window = hwnd.ClientWindow;
} else {
xevent.ExposeEvent.window = hwnd.WholeWindow;
xevent.ExposeEvent.x = hwnd.nc_invalid.X;
xevent.ExposeEvent.y = hwnd.nc_invalid.Y;
xevent.ExposeEvent.width = hwnd.nc_invalid.Width;
xevent.ExposeEvent.height = hwnd.nc_invalid.Height;
}
return xevent;
}
// don't override Dequeue like ConfigureQueue does.
}
/* a circular queue for holding X events for processing by GetMessage */
private class XEventQueue {
XEvent[] xevents;
int head;
int tail;
int size;
public XEventQueue (int initial_size)
{
if (initial_size % 2 != 0)
throw new Exception ("XEventQueue must be a power of 2 size");
xevents = new XEvent [initial_size];
}
public int Count {
get { return size; }
}
public void Enqueue (XEvent xevent)
{
if (size == xevents.Length)
Grow ();
xevents [tail] = xevent;
tail = (tail + 1) & (xevents.Length - 1);
size++;
}
public XEvent Dequeue ()
{
if (size < 1)
throw new Exception ("Attempt to dequeue empty queue.");
XEvent res = xevents [head];
head = (head + 1) & (xevents.Length - 1);
size--;
return res;
}
public XEvent Peek()
{
if (size < 1)
throw new Exception ("Attempt to peek at empty queue.");
return xevents[head];
}
private void Grow ()
{
int newcap = (xevents.Length * 2);
XEvent [] na = new XEvent [newcap];
if (head + size > xevents.Length) {
Array.Copy (xevents, head, na, 0, xevents.Length - head);
Array.Copy (xevents, 0, na, xevents.Length - head, head + size - xevents.Length);
}
else {
Array.Copy (xevents, head, na, 0, size);
}
xevents = na;
head = 0;
tail = head + size;
}
}
}
}