mirror of
https://github.com/seriocomedy/ShiftOS-C-.git
synced 2025-01-24 09:47:34 +00:00
503 lines
11 KiB
C#
503 lines
11 KiB
C#
|
// 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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|