Opentk/Source/OpenTK/Platform/X11/X11GLNative.cs
thefiddler 88f7cd68f5 [X11] Fixed border size on Gnome 3
The _NET_FRAME_EXTENTS atom is implemented differently by
different window managers, when window decorations are hidden
with Motif. Unity returns a 0 size, while Gnome 3 returns the
previous size.

This patch removes that ambiguity: when decorations are hidden,
border size becomes zero. This should work everywhere, unless
some window manager decides to troll us by decorating the window
when we explicitly request no decorations. Sigh...
2013-12-27 13:31:51 +02:00

1717 lines
61 KiB
C#

#region License
//
// The Open Toolkit Library License
//
// Copyright (c) 2006 - 2009 the Open Toolkit library.
//
// 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.
//
#endregion
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
#if !MINIMAL
using System.Drawing;
#endif
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using OpenTK.Graphics;
using OpenTK.Input;
namespace OpenTK.Platform.X11
{
/// \internal
/// <summary>
/// Drives GameWindow on X11.
/// This class supports OpenTK, and is not intended for use by OpenTK programs.
/// </summary>
internal sealed class X11GLNative : INativeWindow, IDisposable
{
// TODO: Disable screensaver.
// TODO: What happens if we can't disable decorations through motif?
// TODO: Mouse/keyboard grabbing/wrapping.
#region Fields
const int _min_width = 30, _min_height = 30;
X11WindowInfo window = new X11WindowInfo();
// Legacy input support
X11Input driver;
KeyboardDevice keyboard;
MouseDevice mouse;
// Window manager hints for fullscreen windows.
// Not used right now (the code is written, but is not 64bit-correct), but could be useful for older WMs which
// are not ICCM compliant, but may support MOTIF hints.
const string MOTIF_WM_ATOM = "_MOTIF_WM_HINTS";
const string KDE_WM_ATOM = "KWM_WIN_DECORATION";
const string KDE_NET_WM_ATOM = "_KDE_NET_WM_WINDOW_TYPE";
const string ICCM_WM_ATOM = "_NET_WM_WINDOW_TYPE";
const string ICON_NET_ATOM = "_NET_WM_ICON";
// The Atom class from Mono might be useful to avoid calling XInternAtom by hand (somewhat error prone).
IntPtr _atom_wm_destroy;
IntPtr _atom_net_wm_state;
IntPtr _atom_net_wm_state_minimized;
IntPtr _atom_net_wm_state_fullscreen;
IntPtr _atom_net_wm_state_maximized_horizontal;
IntPtr _atom_net_wm_state_maximized_vertical;
IntPtr _atom_net_wm_allowed_actions;
IntPtr _atom_net_wm_action_resize;
IntPtr _atom_net_wm_action_maximize_horizontally;
IntPtr _atom_net_wm_action_maximize_vertically;
IntPtr _atom_net_wm_icon;
IntPtr _atom_net_frame_extents;
readonly IntPtr _atom_xa_cardinal = new IntPtr(6);
//IntPtr _atom_motif_wm_hints;
//IntPtr _atom_kde_wm_hints;
//IntPtr _atom_kde_net_wm_hints;
static readonly IntPtr _atom_remove = (IntPtr)0;
static readonly IntPtr _atom_add = (IntPtr)1;
static readonly IntPtr _atom_toggle = (IntPtr)2;
Rectangle bounds, client_rectangle;
int border_left, border_right, border_top, border_bottom;
Icon icon;
bool has_focus;
bool visible;
// Used for event loop.
XEvent e = new XEvent();
bool disposed;
bool exists;
bool isExiting;
bool _decorations_hidden = false;
bool cursor_visible = true;
int mouse_rel_x, mouse_rel_y;
// Keyboard input
readonly byte[] ascii = new byte[16];
readonly char[] chars = new char[16];
readonly KeyPressEventArgs KPEventArgs = new KeyPressEventArgs('\0');
readonly KeyboardKeyEventArgs KeyDownEventArgs = new KeyboardKeyEventArgs();
readonly KeyboardKeyEventArgs KeyUpEventArgs = new KeyboardKeyEventArgs();
readonly IntPtr EmptyCursor;
public static bool MouseWarpActive = false;
#endregion
#region Constructors
public X11GLNative(int x, int y, int width, int height, string title,
GraphicsMode mode,GameWindowFlags options, DisplayDevice device)
: this()
{
if (width <= 0)
throw new ArgumentOutOfRangeException("width", "Must be higher than zero.");
if (height <= 0)
throw new ArgumentOutOfRangeException("height", "Must be higher than zero.");
XVisualInfo info = new XVisualInfo();
Debug.Indent();
using (new XLock(window.Display))
{
if (!mode.Index.HasValue)
{
mode = new X11GraphicsMode().SelectGraphicsMode(
mode.ColorFormat, mode.Depth, mode.Stencil, mode.Samples,
mode.AccumulatorFormat, mode.Buffers, mode.Stereo);
}
info.VisualID = mode.Index.Value;
int dummy;
window.VisualInfo = (XVisualInfo)Marshal.PtrToStructure(
Functions.XGetVisualInfo(window.Display, XVisualInfoMask.ID, ref info, out dummy), typeof(XVisualInfo));
// Create a window on this display using the visual above
Debug.Write("Opening render window... ");
XSetWindowAttributes attributes = new XSetWindowAttributes();
attributes.background_pixel = IntPtr.Zero;
attributes.border_pixel = IntPtr.Zero;
attributes.colormap = Functions.XCreateColormap(window.Display, window.RootWindow, window.VisualInfo.Visual, 0/*AllocNone*/);
window.EventMask = EventMask.StructureNotifyMask /*| EventMask.SubstructureNotifyMask*/ | EventMask.ExposureMask |
EventMask.KeyReleaseMask | EventMask.KeyPressMask | EventMask.KeymapStateMask |
EventMask.PointerMotionMask | EventMask.FocusChangeMask |
EventMask.ButtonPressMask | EventMask.ButtonReleaseMask |
EventMask.EnterWindowMask | EventMask.LeaveWindowMask |
EventMask.PropertyChangeMask;
attributes.event_mask = (IntPtr)window.EventMask;
uint mask = (uint)SetWindowValuemask.ColorMap | (uint)SetWindowValuemask.EventMask |
(uint)SetWindowValuemask.BackPixel | (uint)SetWindowValuemask.BorderPixel;
window.Handle = Functions.XCreateWindow(window.Display, window.RootWindow,
x, y, width, height, 0, window.VisualInfo.Depth/*(int)CreateWindowArgs.CopyFromParent*/,
(int)CreateWindowArgs.InputOutput, window.VisualInfo.Visual, (UIntPtr)mask, ref attributes);
if (window.Handle == IntPtr.Zero)
throw new ApplicationException("XCreateWindow call failed (returned 0).");
if (title != null)
Functions.XStoreName(window.Display, window.Handle, title);
}
// Set the window hints
SetWindowMinMax(_min_width, _min_height, -1, -1);
XSizeHints hints = new XSizeHints();
hints.base_width = width;
hints.base_height = height;
hints.flags = (IntPtr)(XSizeHintsFlags.PSize | XSizeHintsFlags.PPosition);
using (new XLock(window.Display))
{
Functions.XSetWMNormalHints(window.Display, window.Handle, ref hints);
// Register for window destroy notification
Functions.XSetWMProtocols(window.Display, window.Handle, new IntPtr[] { _atom_wm_destroy }, 1);
}
// Set the initial window size to ensure X, Y, Width, Height and the rest
// return the correct values inside the constructor and the Load event.
XEvent e = new XEvent();
e.ConfigureEvent.x = x;
e.ConfigureEvent.y = y;
e.ConfigureEvent.width = width;
e.ConfigureEvent.height = height;
RefreshWindowBounds(ref e);
driver = new X11Input(window);
keyboard = driver.Keyboard[0];
mouse = driver.Mouse[0];
EmptyCursor = CreateEmptyCursor(window);
Debug.WriteLine(String.Format("X11GLNative window created successfully (id: {0}).", Handle));
Debug.Unindent();
exists = true;
}
/// <summary>
/// Constructs and initializes a new X11GLNative window.
/// Call CreateWindow to create the actual render window.
/// </summary>
public X11GLNative()
{
try
{
Debug.Print("Creating X11GLNative window.");
Debug.Indent();
// Open a display connection to the X server, and obtain the screen and root window.
window.Display = Functions.XOpenDisplay(IntPtr.Zero);
//window.Display = API.DefaultDisplay;
if (window.Display == IntPtr.Zero)
throw new Exception("Could not open connection to X");
using (new XLock(window.Display))
{
window.Screen = Functions.XDefaultScreen(window.Display); //API.DefaultScreen;
window.RootWindow = Functions.XRootWindow(window.Display, window.Screen); // API.RootWindow;
}
Debug.Print("Display: {0}, Screen {1}, Root window: {2}", window.Display, window.Screen,
window.RootWindow);
RegisterAtoms(window);
}
finally
{
Debug.Unindent();
}
}
#endregion
#region Private Members
#region private void RegisterAtoms()
/// <summary>
/// Not used yet.
/// Registers the necessary atoms for GameWindow.
/// </summary>
private void RegisterAtoms(X11WindowInfo window)
{
using (new XLock(window.Display))
{
Debug.WriteLine("Registering atoms.");
_atom_wm_destroy = Functions.XInternAtom(window.Display, "WM_DELETE_WINDOW", true);
_atom_net_wm_state = Functions.XInternAtom(window.Display, "_NET_WM_STATE", false);
_atom_net_wm_state_minimized = Functions.XInternAtom(window.Display, "_NET_WM_STATE_MINIMIZED", false);
_atom_net_wm_state_fullscreen = Functions.XInternAtom(window.Display, "_NET_WM_STATE_FULLSCREEN", false);
_atom_net_wm_state_maximized_horizontal =
Functions.XInternAtom(window.Display, "_NET_WM_STATE_MAXIMIZED_HORZ", false);
_atom_net_wm_state_maximized_vertical =
Functions.XInternAtom(window.Display, "_NET_WM_STATE_MAXIMIZED_VERT", false);
_atom_net_wm_allowed_actions =
Functions.XInternAtom(window.Display, "_NET_WM_ALLOWED_ACTIONS", false);
_atom_net_wm_action_resize =
Functions.XInternAtom(window.Display, "_NET_WM_ACTION_RESIZE", false);
_atom_net_wm_action_maximize_horizontally =
Functions.XInternAtom(window.Display, "_NET_WM_ACTION_MAXIMIZE_HORZ", false);
_atom_net_wm_action_maximize_vertically =
Functions.XInternAtom(window.Display, "_NET_WM_ACTION_MAXIMIZE_VERT", false);
_atom_net_wm_icon =
Functions.XInternAtom(window.Display, "_NEW_WM_ICON", false);
_atom_net_frame_extents =
Functions.XInternAtom(window.Display, "_NET_FRAME_EXTENTS", false);
// string[] atom_names = new string[]
// {
// //"WM_TITLE",
// //"UTF8_STRING"
// };
// IntPtr[] atoms = new IntPtr[atom_names.Length];
// //Functions.XInternAtoms(window.Display, atom_names, atom_names.Length, false, atoms);
//
// int offset = 0;
// //WMTitle = atoms[offset++];
// //UTF8String = atoms[offset++];
}
}
#endregion
#region SetWindowMinMax
void SetWindowMinMax(int min_width, int min_height, int max_width, int max_height)
{
SetWindowMinMax((short)min_width, (short)min_height, (short)max_width, (short)max_height);
}
void SetWindowMinMax(short min_width, short min_height, short max_width, short max_height)
{
IntPtr dummy;
XSizeHints hints = new XSizeHints();
using (new XLock(window.Display))
{
Functions.XGetWMNormalHints(window.Display, window.Handle, ref hints, out dummy);
}
if (min_width > 0 || min_height > 0)
{
hints.flags = (IntPtr)((int)hints.flags | (int)XSizeHintsFlags.PMinSize);
hints.min_width = min_width;
hints.min_height = min_height;
}
else
hints.flags = (IntPtr)((int)hints.flags & ~(int)XSizeHintsFlags.PMinSize);
if (max_width > 0 || max_height > 0)
{
hints.flags = (IntPtr)((int)hints.flags | (int)XSizeHintsFlags.PMaxSize);
hints.max_width = max_width;
hints.max_height = max_height;
}
else
hints.flags = (IntPtr)((int)hints.flags & ~(int)XSizeHintsFlags.PMaxSize);
if (hints.flags != IntPtr.Zero)
{
// The Metacity team has decided that they won't care about this when clicking the maximize
// icon, will maximize the window to fill the screen/parent no matter what.
// http://bugzilla.ximian.com/show_bug.cgi?id=80021
using (new XLock(window.Display))
{
Functions.XSetWMNormalHints(window.Display, window.Handle, ref hints);
}
}
}
#endregion
#region IsWindowBorderResizable
bool IsWindowBorderResizable
{
get
{
IntPtr actual_atom;
int actual_format;
IntPtr nitems;
IntPtr bytes_after;
IntPtr prop = IntPtr.Zero;
IntPtr atom;
//XWindowAttributes attributes;
using (new XLock(window.Display))
{
Functions.XGetWindowProperty(window.Display, window.Handle,
_atom_net_wm_allowed_actions, IntPtr.Zero, new IntPtr(256), false,
IntPtr.Zero, out actual_atom, out actual_format, out nitems,
out bytes_after, ref prop);
if ((long)nitems > 0 && prop != IntPtr.Zero)
{
for (int i = 0; i < (long)nitems; i++)
{
atom = (IntPtr)Marshal.ReadIntPtr(prop, i * IntPtr.Size);
if (atom == _atom_net_wm_action_resize)
return true;
}
Functions.XFree(prop);
}
}
return false;
}
}
#endregion
#region bool IsWindowBorderHidden
bool IsWindowBorderHidden
{
get
{
//IntPtr actual_atom;
//int actual_format;
//IntPtr nitems;
//IntPtr bytes_after;
IntPtr prop = IntPtr.Zero;
//IntPtr atom;
//XWindowAttributes attributes;
using (new XLock(window.Display))
{
// Test if decorations have been disabled through Motif.
IntPtr motif_hints_atom = Functions.XInternAtom(this.window.Display, MOTIF_WM_ATOM, true);
if (motif_hints_atom != IntPtr.Zero)
{
// TODO: How to check if MotifWMHints decorations have been really disabled?
if (_decorations_hidden)
return true;
}
// Some WMs remove decorations when the transient_for hint is set. Most new ones do not (but those
// should obey the Motif hint). Anyway, if this hint is set, we say the decorations have been remove
// although there is a slight chance this is not the case.
IntPtr transient_for_parent;
Functions.XGetTransientForHint(window.Display, window.Handle, out transient_for_parent);
if (transient_for_parent != IntPtr.Zero)
return true;
return false;
}
}
}
#endregion
#region void DisableWindowDecorations()
void DisableWindowDecorations()
{
if (DisableMotifDecorations())
{
Debug.Print("Removed decorations through motif.");
_decorations_hidden = true;
}
using (new XLock(window.Display))
{
// Functions.XSetTransientForHint(this.window.Display, this.Handle, this.window.RootWindow);
// Some WMs remove decorations when this hint is set. Doesn't hurt to try.
Functions.XSetTransientForHint(this.window.Display, this.Handle, this.window.RootWindow);
if (_decorations_hidden)
{
Functions.XUnmapWindow(this.window.Display, this.Handle);
Functions.XMapWindow(this.window.Display, this.Handle);
}
}
}
#region bool DisableMotifDecorations()
bool DisableMotifDecorations()
{
using (new XLock(window.Display))
{
IntPtr atom = Functions.XInternAtom(this.window.Display, MOTIF_WM_ATOM, true);
if (atom != IntPtr.Zero)
{
//Functions.XGetWindowProperty(window.Display, window.Handle, atom, IntPtr.Zero, IntPtr.Zero, false,
MotifWmHints hints = new MotifWmHints();
hints.flags = (IntPtr)MotifFlags.Decorations;
Functions.XChangeProperty(this.window.Display, this.Handle, atom, atom, 32, PropertyMode.Replace,
ref hints, Marshal.SizeOf(hints) / IntPtr.Size);
return true;
}
return false;
}
}
#endregion
#region bool DisableGnomeDecorations()
bool DisableGnomeDecorations()
{
using (new XLock(window.Display))
{
IntPtr atom = Functions.XInternAtom(this.window.Display, Constants.XA_WIN_HINTS, true);
if (atom != IntPtr.Zero)
{
IntPtr hints = IntPtr.Zero;
Functions.XChangeProperty(this.window.Display, this.Handle, atom, atom, 32, PropertyMode.Replace,
ref hints, Marshal.SizeOf(hints) / IntPtr.Size);
return true;
}
return false;
}
}
#endregion
#endregion
#region void EnableWindowDecorations()
void EnableWindowDecorations()
{
if (EnableMotifDecorations())
{
Debug.Print("Activated decorations through motif.");
_decorations_hidden = false;
}
//if (EnableGnomeDecorations()) { Debug.Print("Activated decorations through gnome."); activated = true; }
using (new XLock(window.Display))
{
Functions.XSetTransientForHint(this.window.Display, this.Handle, IntPtr.Zero);
if (!_decorations_hidden)
{
Functions.XUnmapWindow(this.window.Display, this.Handle);
Functions.XMapWindow(this.window.Display, this.Handle);
}
}
}
#region bool EnableMotifDecorations()
bool EnableMotifDecorations()
{
using (new XLock(window.Display))
{
IntPtr atom = Functions.XInternAtom(this.window.Display, MOTIF_WM_ATOM, true);
if (atom != IntPtr.Zero)
{
//Functions.XDeleteProperty(this.window.Display, this.Handle, atom);
MotifWmHints hints = new MotifWmHints();
hints.flags = (IntPtr)MotifFlags.Decorations;
hints.decorations = (IntPtr)MotifDecorations.All;
Functions.XChangeProperty(this.window.Display, this.Handle, atom, atom, 32, PropertyMode.Replace,
ref hints, Marshal.SizeOf(hints) / IntPtr.Size);
return true;
}
return false;
}
}
#endregion
#region bool EnableGnomeDecorations()
bool EnableGnomeDecorations()
{
using (new XLock(window.Display))
{
// Restore window layer.
//XEvent xev = new XEvent();
//xev.ClientMessageEvent.window = this.window.Handle;
//xev.ClientMessageEvent.type = XEventName.ClientMessage;
//xev.ClientMessageEvent.message_type = Functions.XInternAtom(this.window.Display, Constants.XA_WIN_LAYER, false);
//xev.ClientMessageEvent.format = 32;
//xev.ClientMessageEvent.ptr1 = (IntPtr)WindowLayer.AboveDock;
//Functions.XSendEvent(this.window.Display, this.window.RootWindow, false, (IntPtr)EventMask.SubstructureNotifyMask, ref xev);
IntPtr atom = Functions.XInternAtom(this.window.Display, Constants.XA_WIN_HINTS, true);
if (atom != IntPtr.Zero)
{
Functions.XDeleteProperty(this.window.Display, this.Handle, atom);
return true;
}
return false;
}
}
#endregion
#endregion
#region DeleteIconPixmaps
static void DeleteIconPixmaps(IntPtr display, IntPtr window)
{
using (new XLock(display))
{
IntPtr wmHints_ptr = Functions.XGetWMHints(display, window);
if (wmHints_ptr != IntPtr.Zero)
{
XWMHints wmHints = (XWMHints)Marshal.PtrToStructure(wmHints_ptr, typeof(XWMHints));
XWMHintsFlags flags = (XWMHintsFlags)wmHints.flags.ToInt32();
if ((flags & XWMHintsFlags.IconPixmapHint) != 0)
{
wmHints.flags = new IntPtr((int)(flags & ~XWMHintsFlags.IconPixmapHint));
Functions.XFreePixmap(display, wmHints.icon_pixmap);
}
if ((flags & XWMHintsFlags.IconMaskHint) != 0)
{
wmHints.flags = new IntPtr((int)(flags & ~XWMHintsFlags.IconMaskHint));
Functions.XFreePixmap(display, wmHints.icon_mask);
}
Functions.XSetWMHints(display, window, ref wmHints);
Functions.XFree(wmHints_ptr);
}
}
}
#endregion
bool RefreshWindowBorders()
{
bool borders_changed = false;
if (IsWindowBorderHidden)
{
borders_changed =
border_left != 0 ||
border_right != 0 ||
border_top != 0 ||
border_bottom != 0;
border_left = 0;
border_right = 0;
border_top = 0;
border_bottom = 0;
}
else
{
IntPtr atom, nitems, bytes_after, prop = IntPtr.Zero;
int format;
using (new XLock(window.Display))
{
Functions.XGetWindowProperty(window.Display, window.Handle,
_atom_net_frame_extents, IntPtr.Zero, new IntPtr(16), false,
(IntPtr)Atom.XA_CARDINAL, out atom, out format, out nitems, out bytes_after, ref prop);
}
if ((prop != IntPtr.Zero))
{
if ((long)nitems == 4)
{
int new_border_left = Marshal.ReadIntPtr(prop, 0).ToInt32();
int new_border_right = Marshal.ReadIntPtr(prop, IntPtr.Size).ToInt32();
int new_border_top = Marshal.ReadIntPtr(prop, IntPtr.Size * 2).ToInt32();
int new_border_bottom = Marshal.ReadIntPtr(prop, IntPtr.Size * 3).ToInt32();
borders_changed =
new_border_left != border_left ||
new_border_right != border_right ||
new_border_top != border_top ||
new_border_bottom != border_bottom;
border_left = new_border_left;
border_right = new_border_right;
border_top = new_border_top;
border_bottom = new_border_bottom;
//Debug.WriteLine(border_left);
//Debug.WriteLine(border_right);
//Debug.WriteLine(border_top);
//Debug.WriteLine(border_bottom);
}
using (new XLock(window.Display))
{
Functions.XFree(prop);
}
}
}
return borders_changed;
}
void RefreshWindowBounds(ref XEvent e)
{
RefreshWindowBorders();
// For whatever reason, the x/y coordinates
// of a configure event are global to the
// root window when it is a send_event but
// local when it is a regular event.
// I don't know who designed this, but this is
// utter nonsense.
int x, y;
IntPtr unused;
if (!e.ConfigureEvent.send_event)
{
Functions.XTranslateCoordinates(window.Display,
window.Handle, window.RootWindow,
0, 0, out x, out y, out unused);
}
else
{
x = e.ConfigureEvent.x;
y = e.ConfigureEvent.y;
}
Point new_location = new Point(
x - border_left,
y - border_top);
if (Location != new_location)
{
bounds.Location = new_location;
Move(this, EventArgs.Empty);
}
// Note: width and height denote the internal (client) size.
// To get the external (window) size, we need to add the border size.
Size new_size = new Size(
e.ConfigureEvent.width + border_left + border_right,
e.ConfigureEvent.height + border_top + border_bottom);
if (Bounds.Size != new_size)
{
bounds.Size = new_size;
client_rectangle.Size = new Size(e.ConfigureEvent.width, e.ConfigureEvent.height);
Resize(this, EventArgs.Empty);
}
//Debug.Print("[X11] Window bounds changed: {0}", bounds);
}
static IntPtr CreateEmptyCursor(X11WindowInfo window)
{
IntPtr cursor = IntPtr.Zero;
using (new XLock(window.Display))
{
XColor black, dummy;
IntPtr cmap = Functions.XDefaultColormap(window.Display, window.Screen);
Functions.XAllocNamedColor(window.Display, cmap, "black", out black, out dummy);
IntPtr bmp_empty = Functions.XCreateBitmapFromData(window.Display,
window.Handle, new byte[,] { { 0 } });
cursor = Functions.XCreatePixmapCursor(window.Display,
bmp_empty, bmp_empty, ref black, ref black, 0, 0);
}
return cursor;
}
static void SetMouseClamped(MouseDevice mouse, int x, int y,
int left, int top, int width, int height)
{
// Clamp mouse to the specified rectangle.
x = Math.Max(x, left);
x = Math.Min(x, width);
y = Math.Max(y, top);
y = Math.Min(y, height);
mouse.Position = new Point(x, y);
}
#endregion
#region INativeWindow Members
#region ProcessEvents
public void ProcessEvents()
{
// Process all pending events
while (Exists && window != null)
{
using (new XLock(window.Display))
{
if (!Functions.XCheckWindowEvent(window.Display, window.Handle, window.EventMask, ref e) &&
!Functions.XCheckTypedWindowEvent(window.Display, window.Handle, XEventName.ClientMessage, ref e))
break;
}
// Respond to the event e
switch (e.type)
{
case XEventName.MapNotify:
{
bool previous_visible = visible;
visible = true;
if (visible != previous_visible)
VisibleChanged(this, EventArgs.Empty);
}
return;
case XEventName.UnmapNotify:
{
bool previous_visible = visible;
visible = false;
if (visible != previous_visible)
VisibleChanged(this, EventArgs.Empty);
}
break;
case XEventName.CreateNotify:
// A child was was created - nothing to do
break;
case XEventName.ClientMessage:
if (!isExiting && e.ClientMessageEvent.ptr1 == _atom_wm_destroy)
{
Debug.WriteLine("Exit message received.");
CancelEventArgs ce = new CancelEventArgs();
Closing(this, ce);
if (!ce.Cancel)
{
isExiting = true;
Debug.WriteLine("Destroying window.");
using (new XLock(window.Display))
{
Functions.XDestroyWindow(window.Display, window.Handle);
}
break;
}
}
break;
case XEventName.DestroyNotify:
Debug.WriteLine("Window destroyed");
exists = false;
Closed(this, EventArgs.Empty);
return;
case XEventName.ConfigureNotify:
RefreshWindowBounds(ref e);
break;
case XEventName.KeyPress:
case XEventName.KeyRelease:
bool pressed = e.type == XEventName.KeyPress;
Key key;
if (driver.TranslateKey(ref e.KeyEvent, out key))
{
if (pressed)
{
// Raise KeyDown event
KeyDownEventArgs.Key = key;
KeyDownEventArgs.ScanCode = (uint)e.KeyEvent.keycode;
KeyDown(this, KeyDownEventArgs);
}
else
{
// Raise KeyUp event
KeyUpEventArgs.Key = key;
KeyUpEventArgs.ScanCode = (uint)e.KeyEvent.keycode;
KeyUp(this, KeyDownEventArgs);
}
// Update legacy GameWindow.Keyboard API:
keyboard.SetKey(key, (uint)e.KeyEvent.keycode, pressed);
if (pressed)
{
// Translate XKeyPress to characters and
// raise KeyPress events
int status = 0;
status = Functions.XLookupString(
ref e.KeyEvent, ascii, ascii.Length, null, IntPtr.Zero);
Encoding.Default.GetChars(ascii, 0, status, chars, 0);
for (int i = 0; i < status; i++)
{
if (!Char.IsControl(chars[i]))
{
KPEventArgs.KeyChar = chars[i];
KeyPress(this, KPEventArgs);
}
}
}
}
break;
case XEventName.MotionNotify:
{
// Try to detect and ignore events from XWarpPointer, below.
// This heuristic will fail if the user actually moves the pointer
// to the dead center of the window. Fortunately, this situation
// is very very uncommon. Todo: Can this be remedied?
int x = e.MotionEvent.x;
int y =e.MotionEvent.y;
int middle_x = (Bounds.Left + Bounds.Right) / 2;
int middle_y = (Bounds.Top + Bounds.Bottom) / 2;
Point screen_xy = PointToScreen(new Point(x, y));
if (!CursorVisible && MouseWarpActive &&
screen_xy.X == middle_x && screen_xy.Y == middle_y)
{
MouseWarpActive = false;
mouse_rel_x = x;
mouse_rel_y = y;
}
else if (!CursorVisible)
{
SetMouseClamped(mouse,
mouse.X + x - mouse_rel_x,
mouse.Y + y - mouse_rel_y,
0, 0, Width, Height);
mouse_rel_x = x;
mouse_rel_y = y;
// Warp cursor to center of window.
MouseWarpActive = true;
Mouse.SetPosition(middle_x, middle_y);
}
else
{
SetMouseClamped(mouse, x, y, 0, 0, Width, Height);
mouse_rel_x = x;
mouse_rel_y = y;
}
break;
}
case XEventName.ButtonPress:
case XEventName.ButtonRelease:
driver.ProcessEvent(ref e);
break;
case XEventName.FocusIn:
{
bool previous_focus = has_focus;
has_focus = true;
if (has_focus != previous_focus)
FocusedChanged(this, EventArgs.Empty);
}
break;
case XEventName.FocusOut:
{
bool previous_focus = has_focus;
has_focus = false;
if (has_focus != previous_focus)
FocusedChanged(this, EventArgs.Empty);
}
break;
case XEventName.LeaveNotify:
if (CursorVisible)
{
MouseLeave(this, EventArgs.Empty);
}
break;
case XEventName.EnterNotify:
MouseEnter(this, EventArgs.Empty);
break;
case XEventName.MappingNotify:
// 0 == MappingModifier, 1 == MappingKeyboard
if (e.MappingEvent.request == 0 || e.MappingEvent.request == 1)
{
Debug.Print("keybard mapping refreshed");
Functions.XRefreshKeyboardMapping(ref e.MappingEvent);
}
break;
case XEventName.PropertyNotify:
if (e.PropertyEvent.atom == _atom_net_wm_state)
{
WindowStateChanged(this, EventArgs.Empty);
}
//if (e.PropertyEvent.atom == _atom_net_frame_extents)
//{
// RefreshWindowBorders();
//}
break;
default:
//Debug.WriteLine(String.Format("{0} event was not handled", e.type));
break;
}
}
}
#endregion
#region Bounds
public Rectangle Bounds
{
get
{
return bounds;
}
set
{
bool is_location_changed = bounds.Location != value.Location;
bool is_size_changed = bounds.Size != value.Size;
int x = value.X;
int y = value.Y;
int width = value.Width - border_left - border_right;
int height = value.Height - border_top - border_bottom;
if (WindowBorder != WindowBorder.Resizable)
{
SetWindowMinMax(width, height, width, height);
}
using (new XLock(window.Display))
{
if (is_location_changed && is_size_changed)
{
Functions.XMoveResizeWindow(window.Display, window.Handle,
x, y, width, height);
}
else if (is_location_changed)
{
Functions.XMoveWindow(window.Display, window.Handle,
x, y);
}
else if (is_size_changed)
{
Functions.XResizeWindow(window.Display, window.Handle,
width, height);
}
}
ProcessEvents();
}
}
#endregion
#region Location
public Point Location
{
get { return Bounds.Location; }
set
{
Bounds = new Rectangle(value, Bounds.Size);
}
}
#endregion
#region Size
public Size Size
{
get { return Bounds.Size; }
set
{
Bounds = new Rectangle(Bounds.Location, value);
}
}
#endregion
#region ClientRectangle
public Rectangle ClientRectangle
{
get
{
if (client_rectangle.Width == 0)
client_rectangle.Width = 1;
if (client_rectangle.Height == 0)
client_rectangle.Height = 1;
return client_rectangle;
}
set
{
using (new XLock(window.Display))
{
Functions.XMoveWindow(window.Display, window.Handle,
value.X, value.Y);
Functions.XResizeWindow(window.Display, window.Handle,
value.Width, value.Height);
}
ProcessEvents();
}
}
#endregion
#region ClientSize
public Size ClientSize
{
get
{
return ClientRectangle.Size;
}
set
{
ClientRectangle = new Rectangle(Point.Empty, value);
}
}
#endregion
#region Width
public int Width
{
get { return ClientSize.Width; }
set { ClientSize = new Size(value, Height); }
}
#endregion
#region Height
public int Height
{
get { return ClientSize.Height; }
set { ClientSize = new Size(Width, value); }
}
#endregion
#region X
public int X
{
get { return Location.X; }
set { Location = new Point(value, Y); }
}
#endregion
#region Y
public int Y
{
get { return Location.Y; }
set { Location = new Point(X, value); }
}
#endregion
#region Icon
public Icon Icon
{
get
{
return icon;
}
set
{
if (value == icon)
return;
// Note: it seems that Gnome/Metacity does not respect the _NET_WM_ICON hint.
// For this reason, we'll also set the icon using XSetWMHints.
if (value == null)
{
using (new XLock(window.Display))
{
Functions.XDeleteProperty(window.Display, window.Handle, _atom_net_wm_icon);
DeleteIconPixmaps(window.Display, window.Handle);
}
}
else
{
// Set _NET_WM_ICON
Bitmap bitmap = value.ToBitmap();
int size = bitmap.Width * bitmap.Height + 2;
IntPtr[] data = new IntPtr[size];
int index = 0;
data[index++] = (IntPtr)bitmap.Width;
data[index++] = (IntPtr)bitmap.Height;
for (int y = 0; y < bitmap.Height; y++)
for (int x = 0; x < bitmap.Width; x++)
data[index++] = (IntPtr)bitmap.GetPixel(x, y).ToArgb();
using (new XLock(window.Display))
{
Functions.XChangeProperty(window.Display, window.Handle,
_atom_net_wm_icon, _atom_xa_cardinal, 32,
PropertyMode.Replace, data, size);
}
// Set XWMHints
DeleteIconPixmaps(window.Display, window.Handle);
using (new XLock(window.Display))
{
IntPtr wmHints_ptr = Functions.XGetWMHints(window.Display, window.Handle);
if (wmHints_ptr == IntPtr.Zero)
wmHints_ptr = Functions.XAllocWMHints();
XWMHints wmHints = (XWMHints)Marshal.PtrToStructure(wmHints_ptr, typeof(XWMHints));
wmHints.flags = new IntPtr(wmHints.flags.ToInt32() | (int)(XWMHintsFlags.IconPixmapHint | XWMHintsFlags.IconMaskHint));
wmHints.icon_pixmap = Functions.CreatePixmapFromImage(window.Display, bitmap);
wmHints.icon_mask = Functions.CreateMaskFromImage(window.Display, bitmap);
Functions.XSetWMHints(window.Display, window.Handle, ref wmHints);
Functions.XFree (wmHints_ptr);
Functions.XSync(window.Display, false);
}
}
icon = value;
IconChanged(this, EventArgs.Empty);
}
}
#endregion
#region Focused
public bool Focused
{
get
{
return has_focus;
}
}
#endregion
#region WindowState
public OpenTK.WindowState WindowState
{
get
{
IntPtr actual_atom;
int actual_format;
IntPtr nitems;
IntPtr bytes_after;
IntPtr prop = IntPtr.Zero;
IntPtr atom;
//XWindowAttributes attributes;
bool fullscreen = false;
int maximized = 0;
bool minimized = false;
using (new XLock(window.Display))
{
Functions.XGetWindowProperty(window.Display, window.Handle,
_atom_net_wm_state, IntPtr.Zero, new IntPtr(256), false,
new IntPtr(4) /*XA_ATOM*/, out actual_atom, out actual_format,
out nitems, out bytes_after, ref prop);
}
if ((long)nitems > 0 && prop != IntPtr.Zero)
{
for (int i = 0; i < (long)nitems; i++)
{
atom = (IntPtr)Marshal.ReadIntPtr(prop, i * IntPtr.Size);
if (atom == _atom_net_wm_state_maximized_horizontal ||
atom == _atom_net_wm_state_maximized_vertical)
maximized++;
else if (atom == _atom_net_wm_state_minimized)
minimized = true;
else if (atom == _atom_net_wm_state_fullscreen)
fullscreen = true;
}
using (new XLock(window.Display))
{
Functions.XFree(prop);
}
}
if (minimized)
return OpenTK.WindowState.Minimized;
else if (maximized == 2)
return OpenTK.WindowState.Maximized;
else if (fullscreen)
return OpenTK.WindowState.Fullscreen;
/*
attributes = new XWindowAttributes();
Functions.XGetWindowAttributes(window.Display, window.Handle, ref attributes);
if (attributes.map_state == MapState.IsUnmapped)
return (OpenTK.WindowState)(-1);
*/
return OpenTK.WindowState.Normal;
}
set
{
OpenTK.WindowState current_state = this.WindowState;
if (current_state == value)
return;
Debug.Print("GameWindow {0} changing WindowState from {1} to {2}.", window.Handle.ToString(),
current_state.ToString(), value.ToString());
using (new XLock(window.Display))
{
// Reset the current window state
if (current_state == OpenTK.WindowState.Minimized)
Functions.XMapWindow(window.Display, window.Handle);
else if (current_state == OpenTK.WindowState.Fullscreen)
Functions.SendNetWMMessage(window, _atom_net_wm_state, _atom_remove,
_atom_net_wm_state_fullscreen,
IntPtr.Zero);
else if (current_state == OpenTK.WindowState.Maximized)
Functions.SendNetWMMessage(window, _atom_net_wm_state, _atom_toggle,
_atom_net_wm_state_maximized_horizontal,
_atom_net_wm_state_maximized_vertical);
Functions.XSync(window.Display, false);
}
// We can't resize the window if its border is fixed, so make it resizable first.
bool temporary_resizable = false;
WindowBorder previous_state = WindowBorder;
if (WindowBorder != WindowBorder.Resizable)
{
temporary_resizable = true;
WindowBorder = WindowBorder.Resizable;
}
using (new XLock(window.Display))
{
switch (value)
{
case OpenTK.WindowState.Normal:
Functions.XRaiseWindow(window.Display, window.Handle);
break;
case OpenTK.WindowState.Maximized:
Functions.SendNetWMMessage(window, _atom_net_wm_state, _atom_add,
_atom_net_wm_state_maximized_horizontal,
_atom_net_wm_state_maximized_vertical);
Functions.XRaiseWindow(window.Display, window.Handle);
break;
case OpenTK.WindowState.Minimized:
// Todo: multiscreen support
Functions.XIconifyWindow(window.Display, window.Handle, window.Screen);
break;
case OpenTK.WindowState.Fullscreen:
//_previous_window_border = this.WindowBorder;
//this.WindowBorder = WindowBorder.Hidden;
Functions.SendNetWMMessage(window, _atom_net_wm_state, _atom_add,
_atom_net_wm_state_fullscreen, IntPtr.Zero);
Functions.XRaiseWindow(window.Display, window.Handle);
break;
}
}
if (temporary_resizable)
WindowBorder = previous_state;
ProcessEvents();
}
}
#endregion
#region WindowBorder
public OpenTK.WindowBorder WindowBorder
{
get
{
if (IsWindowBorderHidden)
return WindowBorder.Hidden;
if (IsWindowBorderResizable)
return WindowBorder.Resizable;
else
return WindowBorder.Fixed;
}
set
{
if (WindowBorder == value)
return;
if (WindowBorder == WindowBorder.Hidden)
EnableWindowDecorations();
switch (value)
{
case WindowBorder.Fixed:
Debug.Print("Making WindowBorder fixed.");
SetWindowMinMax((short)Width, (short)Height, (short)Width, (short)Height);
break;
case WindowBorder.Resizable:
Debug.Print("Making WindowBorder resizable.");
SetWindowMinMax(_min_width, _min_height, -1, -1);
break;
case WindowBorder.Hidden:
Debug.Print("Making WindowBorder hidden.");
DisableWindowDecorations();
break;
}
WindowBorderChanged(this, EventArgs.Empty);
}
}
#endregion
#region Events
public event EventHandler<EventArgs> Move = delegate { };
public event EventHandler<EventArgs> Resize = delegate { };
public event EventHandler<System.ComponentModel.CancelEventArgs> Closing = delegate { };
public event EventHandler<EventArgs> Closed = delegate { };
public event EventHandler<EventArgs> Disposed = delegate { };
public event EventHandler<EventArgs> IconChanged = delegate { };
public event EventHandler<EventArgs> TitleChanged = delegate { };
public event EventHandler<EventArgs> VisibleChanged = delegate { };
public event EventHandler<EventArgs> FocusedChanged = delegate { };
public event EventHandler<EventArgs> WindowBorderChanged = delegate { };
public event EventHandler<EventArgs> WindowStateChanged = delegate { };
public event EventHandler<KeyboardKeyEventArgs> KeyDown = delegate { };
public event EventHandler<KeyPressEventArgs> KeyPress = delegate { };
public event EventHandler<KeyboardKeyEventArgs> KeyUp = delegate { };
public event EventHandler<EventArgs> MouseEnter = delegate { };
public event EventHandler<EventArgs> MouseLeave = delegate { };
#endregion
public bool CursorVisible
{
get { return cursor_visible; }
set
{
if (value)
{
using (new XLock(window.Display))
{
Functions.XUndefineCursor(window.Display, window.Handle);
cursor_visible = true;
}
}
else
{
using (new XLock(window.Display))
{
Functions.XDefineCursor(window.Display, window.Handle, EmptyCursor);
cursor_visible = false;
}
}
}
}
#endregion
#region --- INativeGLWindow Members ---
#region public IInputDriver InputDriver
public IInputDriver InputDriver
{
get
{
return driver;
}
}
#endregion
#region public bool Exists
/// <summary>
/// Returns true if a render window/context exists.
/// </summary>
public bool Exists
{
get { return exists; }
}
#endregion
#region public bool IsIdle
public bool IsIdle
{
get { throw new Exception("The method or operation is not implemented."); }
}
#endregion
#region public IntPtr Handle
/// <summary>
/// Gets the current window handle.
/// </summary>
public IntPtr Handle
{
get { return this.window.Handle; }
}
#endregion
#region public string Title
/// <summary>
/// TODO: Use atoms for this property.
/// Gets or sets the GameWindow title.
/// </summary>
public string Title
{
get
{
IntPtr name = IntPtr.Zero;
using (new XLock(window.Display))
{
Functions.XFetchName(window.Display, window.Handle, ref name);
}
if (name != IntPtr.Zero)
return Marshal.PtrToStringAnsi(name);
return String.Empty;
}
set
{
if (value != null && value != Title)
{
using (new XLock(window.Display))
{
Functions.XStoreName(window.Display, window.Handle, value);
}
}
TitleChanged(this, EventArgs.Empty);
}
}
#endregion
#region public bool Visible
public bool Visible
{
get
{
return visible;
}
set
{
if (value && !visible)
{
using (new XLock(window.Display))
{
Functions.XMapWindow(window.Display, window.Handle);
}
}
else if (!value && visible)
{
using (new XLock(window.Display))
{
Functions.XUnmapWindow(window.Display, window.Handle);
}
}
}
}
#endregion
#region public IWindowInfo WindowInfo
public IWindowInfo WindowInfo
{
get { return window; }
}
#endregion
public void Close() { Exit(); }
#region public void Exit()
public void Exit()
{
XEvent ev = new XEvent();
ev.type = XEventName.ClientMessage;
ev.ClientMessageEvent.format = 32;
ev.ClientMessageEvent.display = window.Display;
ev.ClientMessageEvent.window = window.Handle;
ev.ClientMessageEvent.ptr1 = _atom_wm_destroy;
using (new XLock(window.Display))
{
Functions.XSendEvent(window.Display, window.Handle, false,
EventMask.NoEventMask, ref ev);
Functions.XFlush(window.Display);
}
}
#endregion
#region public void DestroyWindow()
public void DestroyWindow()
{
Debug.WriteLine("X11GLNative shutdown sequence initiated.");
using (new XLock(window.Display))
{
Functions.XDestroyWindow(window.Display, window.Handle);
}
}
#endregion
#region PointToClient
public Point PointToClient(Point point)
{
int ox, oy;
IntPtr child;
using (new XLock(window.Display))
{
Functions.XTranslateCoordinates(window.Display, window.RootWindow, window.Handle, point.X, point.Y, out ox, out oy, out child);
}
point.X = ox;
point.Y = oy;
return point;
}
#endregion
#region PointToScreen
public Point PointToScreen(Point point)
{
int ox, oy;
IntPtr child;
using (new XLock(window.Display))
{
Functions.XTranslateCoordinates(window.Display, window.Handle, window.RootWindow, point.X, point.Y, out ox, out oy, out child);
}
point.X = ox;
point.Y = oy;
return point;
}
#endregion
#endregion
#region IDisposable Members
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool manuallyCalled)
{
if (!disposed)
{
if (manuallyCalled)
{
if (window != null && window.Handle != IntPtr.Zero)
{
if (Exists)
{
using (new XLock(window.Display))
{
Functions.XFreeCursor(window.Display, EmptyCursor);
Functions.XDestroyWindow(window.Display, window.Handle);
}
while (Exists)
ProcessEvents();
}
window.Dispose();
window = null;
}
}
else
{
Debug.Print("[Warning] {0} leaked.", this.GetType().Name);
}
disposed = true;
}
}
~X11GLNative()
{
this.Dispose(false);
}
#endregion
}
}