diff options
| author | MichaelTheShifter <[email protected]> | 2016-07-20 09:40:36 -0400 |
|---|---|---|
| committer | MichaelTheShifter <[email protected]> | 2016-07-20 09:40:36 -0400 |
| commit | d40fed5ce2bc806a91245adb18039634eac13ed0 (patch) | |
| tree | f1d7168aee6db109ac2c738ad18c9db667a6ba69 /source/ShiftUI/Internal/X11Display.cs | |
| parent | f1856e8ed30ed882229fd3fa2a4038122a5fb441 (diff) | |
| download | shiftos-c--d40fed5ce2bc806a91245adb18039634eac13ed0.tar.gz shiftos-c--d40fed5ce2bc806a91245adb18039634eac13ed0.tar.bz2 shiftos-c--d40fed5ce2bc806a91245adb18039634eac13ed0.zip | |
Move ShiftUI source code to ShiftOS
This'll be a lot easier to work on.
Diffstat (limited to 'source/ShiftUI/Internal/X11Display.cs')
| -rw-r--r-- | source/ShiftUI/Internal/X11Display.cs | 2693 |
1 files changed, 2693 insertions, 0 deletions
diff --git a/source/ShiftUI/Internal/X11Display.cs b/source/ShiftUI/Internal/X11Display.cs new file mode 100644 index 0000000..221e5f1 --- /dev/null +++ b/source/ShiftUI/Internal/X11Display.cs @@ -0,0 +1,2693 @@ +// 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) 2006 Novell, Inc. (http://www.novell.com) +// +// +using System; +using System.Collections; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using ShiftUI; +// Only do the poll when building with mono for now +#if __MonoCS__ +//using Mono.Unix.Native; +#endif + +namespace ShiftUI.X11Internal { + + internal class X11Display { + + IntPtr display; /* our X handle */ + + // XXX internal because X11Hwnd needs them + internal IntPtr CustomVisual; // Visual for window creation + internal IntPtr CustomColormap; // Colormap for window creation + + X11Keyboard Keyboard; + internal X11Dnd Dnd; // XXX X11Hwnd needs it to enable Dnd + bool detectable_key_auto_repeat; + + X11Atoms atoms; + X11RootHwnd root_hwnd; + X11Hwnd foster_hwnd; + + // Clipboard + IntPtr ClipMagic; + + // Focus tracking + internal X11Hwnd ActiveWindow; + X11Hwnd FocusWindow; + + // Modality support + Stack ModalWindows; // Stack of our modal windows + + // Caret + CaretStruct Caret; + + // mouse hover message generation + // XXX internal because X11Atoms needs to access it.. + internal HoverStruct HoverState; + + // double click message generation + ClickStruct ClickPending; + int DoubleClickInterval; // msec; max interval between clicks to count as double click + + // Support for mouse grab + GrabStruct Grab; + + // Cursors + IntPtr LastCursorWindow; // The last window we set the cursor on + IntPtr LastCursorHandle; // The handle that was last set on LastCursorWindow + IntPtr OverrideCursorHandle; // The cursor that is set to override any other cursors + + // State + Point MousePosition; // Last position of mouse, in screen coords + MouseButtons MouseState; // Last state of mouse buttons + + XErrorHandler ErrorHandler; // Error handler delegate + bool ErrorExceptions; // Throw exceptions on X errors + + Thread event_thread; // the background thread that just watches our X socket + +#if __MonoCS__ + Pollfd[] pollfds; +#endif + + public X11Display (IntPtr display) + { + if (display == IntPtr.Zero) { + throw new ArgumentNullException("Display", + "Could not open display (X-Server required. Check your DISPLAY environment variable)"); + } + + this.display = display; + + // Debugging support + if (Environment.GetEnvironmentVariable ("MONO_XSYNC") != null) { + Xlib.XSynchronize (display, true); + } + + if (Environment.GetEnvironmentVariable ("MONO_XEXCEPTIONS") != null) { + ErrorExceptions = true; + } + + atoms = new X11Atoms (this); + + DoubleClickInterval = 500; + + HoverState.Interval = 500; + HoverState.Timer = new Timer(); + HoverState.Timer.Enabled = false; + HoverState.Timer.Interval = HoverState.Interval; + HoverState.Timer.Tick += new EventHandler(MouseHover); + HoverState.Size = new Size(4, 4); + HoverState.X = -1; + HoverState.Y = -1; + + ActiveWindow = null; + FocusWindow = null; + ModalWindows = new Stack(3); + + MouseState = MouseButtons.None; + MousePosition = new Point(0, 0); + + Caret.Timer = new Timer(); + Caret.Timer.Interval = 500; // FIXME - where should this number come from? + Caret.Timer.Tick += new EventHandler(CaretCallback); + + // XXX multiscreen work here + root_hwnd = new X11RootHwnd (this, Xlib.XRootWindow (display, DefaultScreen)); + + // XXX do we need a per-screen foster parent? + // Create the foster parent + foster_hwnd = new X11Hwnd (this, + Xlib.XCreateSimpleWindow (display, root_hwnd.WholeWindow, + 0, 0, 1, 1, 4, UIntPtr.Zero, UIntPtr.Zero)); + +#if __MonoCS__ + pollfds = new Pollfd [1]; + pollfds [0] = new Pollfd (); + pollfds [0].fd = Xlib.XConnectionNumber (display); + pollfds [0].events = PollEvents.POLLIN; +#endif + + Keyboard = new X11Keyboard(display, foster_hwnd.Handle); + Dnd = new X11Dnd (display, Keyboard); + + ErrorExceptions = false; + + // Handle any upcoming errors + ErrorHandler = new XErrorHandler (HandleError); + Xlib.XSetErrorHandler (ErrorHandler); + + X11DesktopColors.Initialize(); // XXX we need to figure out how to make this display specific? + + // Disable keyboard autorepeat + try { + Xlib.XkbSetDetectableAutoRepeat (display, true, IntPtr.Zero); + detectable_key_auto_repeat = true; + } catch { + Console.Error.WriteLine ("Could not disable keyboard auto repeat, will attempt to disable manually."); + detectable_key_auto_repeat = false; + } + + // we re-set our error handler here, X11DesktopColor stuff might have stolen it (gtk does) + Xlib.XSetErrorHandler (ErrorHandler); + + // create our event thread (just sits on the X socket waiting for events) + event_thread = new Thread (new ThreadStart (XEventThread)); + event_thread.IsBackground = true; + event_thread.Start (); + } + + #region Callbacks + private void MouseHover(object sender, EventArgs e) + { + HoverState.Timer.Enabled = false; + + if (HoverState.Window != IntPtr.Zero) { + X11Hwnd hwnd = (X11Hwnd)Hwnd.GetObjectFromWindow (HoverState.Window); + if (hwnd != null) { + XEvent xevent = new XEvent (); + + xevent.type = XEventName.ClientMessage; + xevent.ClientMessageEvent.display = display; + xevent.ClientMessageEvent.window = HoverState.Window; + xevent.ClientMessageEvent.message_type = HoverState.Atom; + xevent.ClientMessageEvent.format = 32; + xevent.ClientMessageEvent.ptr1 = (IntPtr) (HoverState.Y << 16 | HoverState.X); + + hwnd.Queue.Enqueue (xevent); + } + } + } + + private void CaretCallback (object sender, EventArgs e) + { + if (Caret.Paused) { + return; + } + Caret.On = !Caret.On; + + Xlib.XDrawLine (display, Caret.Hwnd, Caret.gc, Caret.X, Caret.Y, Caret.X, Caret.Y + Caret.Height); + } + + internal string WhereString () + { + StackTrace stack; + StackFrame frame; + string newline; + string unknown; + StringBuilder sb; + MethodBase method; + + newline = String.Format("{0}\t {1} ", Environment.NewLine, "at"); + unknown = "<unknown method>"; + sb = new StringBuilder(); + stack = new StackTrace(true); + + for (int i = 0; i < stack.FrameCount; i++) { + frame = stack.GetFrame (i); + sb.Append(newline); + + method = frame.GetMethod(); + if (method != null) { + if (frame.GetFileLineNumber() != 0) + sb.AppendFormat ("{0}.{1} () [{2}:{3}]", + method.DeclaringType.FullName, method.Name, + Path.GetFileName(frame.GetFileName()), frame.GetFileLineNumber()); + else + sb.AppendFormat ("{0}.{1} ()", method.DeclaringType.FullName, method.Name); + } else { + sb.Append(unknown); + } + } + return sb.ToString(); + } + + private int HandleError (IntPtr display, ref XErrorEvent error_event) + { + if (ErrorExceptions) + throw new X11Exception (error_event.display, error_event.resourceid, + error_event.serial, error_event.error_code, + error_event.request_code, error_event.minor_code); + else + Console.WriteLine ("X11 Error encountered: {0}{1}\n", + X11Exception.GetMessage(error_event.display, error_event.resourceid, + error_event.serial, error_event.error_code, + error_event.request_code, error_event.minor_code), + WhereString()); + return 0; + } + #endregion // Callbacks + + private void ShowCaret() + { + if ((Caret.gc == IntPtr.Zero) || Caret.On) { + return; + } + Caret.On = true; + + Xlib.XDrawLine (display, Caret.Window, Caret.gc, Caret.X, Caret.Y, Caret.X, Caret.Y + Caret.Height); + } + + private void HideCaret() + { + if ((Caret.gc == IntPtr.Zero) || !Caret.On) { + return; + } + Caret.On = false; + + Xlib.XDrawLine (display, Caret.Window, Caret.gc, Caret.X, Caret.Y, Caret.X, Caret.Y + Caret.Height); + } + + public void CaretVisible (IntPtr handle, bool visible) + { + if (Caret.Hwnd == handle) { + if (visible) { + if (!Caret.Visible) { + Caret.Visible = true; + ShowCaret(); + Caret.Timer.Start(); + } + } else { + Caret.Visible = false; + Caret.Timer.Stop(); + HideCaret(); + } + } + } + + public void AudibleAlert () + { + Xlib.XBell (display, 0); + } + + public void Flush () + { + Xlib.XFlush (display); + } + + public void Close () + { + // XXX shut down the event_thread + Xlib.XCloseDisplay (display); + } + + public IntPtr XGetParent(IntPtr handle) + { + IntPtr Root; + IntPtr Parent; + IntPtr Children; + int ChildCount; + + Xlib.XQueryTree (display, handle, out Root, out Parent, out Children, out ChildCount); + + if (Children!=IntPtr.Zero) { + Xlib.XFree(Children); + } + + return Parent; + } + + public bool SystrayAdd(IntPtr handle, string tip, Icon icon, out ToolTip tt) + { + IntPtr SystrayMgrWindow; + + Xlib.XGrabServer (display); + SystrayMgrWindow = Xlib.XGetSelectionOwner (display, Atoms._NET_SYSTEM_TRAY_S); + Xlib.XUngrabServer (display); + + if (SystrayMgrWindow != IntPtr.Zero) { + XSizeHints size_hints; + X11Hwnd hwnd; + + hwnd = (X11Hwnd)Hwnd.ObjectFromHandle(handle); +#if DriverDebug + Console.WriteLine("Adding Systray Whole:{0:X}, Client:{1:X}", + hwnd.WholeWindow.ToInt32(), hwnd.ClientWindow.ToInt32()); +#endif + + // Oh boy. + if (hwnd.ClientWindow != hwnd.WholeWindow) { + Xlib.XDestroyWindow (display, hwnd.ClientWindow); + hwnd.ClientWindow = hwnd.WholeWindow; + + try { + hwnd.Queue.Lock (); + + /* by virtue of the way the tests are ordered when determining if it's PAINT + or NCPAINT, ClientWindow == WholeWindow will always be PAINT. So, if we're + waiting on an nc_expose, drop it and remove the hwnd from the list (unless + there's a pending expose). */ + hwnd.PendingNCExpose = false; + } + finally { + hwnd.Queue.Unlock (); + } + } + + size_hints = new XSizeHints(); + + size_hints.flags = (IntPtr)(XSizeHintsFlags.PMinSize | XSizeHintsFlags.PMaxSize | XSizeHintsFlags.PBaseSize); + + size_hints.min_width = 24; + size_hints.min_height = 24; + size_hints.max_width = 24; + size_hints.max_height = 24; + size_hints.base_width = 24; + size_hints.base_height = 24; + + Xlib.XSetWMNormalHints (display, hwnd.WholeWindow, ref size_hints); + + int[] atoms = new int[2]; + atoms [0] = 1; // Version 1 + atoms [1] = 1; // we want to be mapped + + // This line cost me 3 days... + Xlib.XChangeProperty (display, + hwnd.WholeWindow, Atoms._XEMBED_INFO, Atoms._XEMBED_INFO, 32, + PropertyMode.Replace, atoms, 2); + + // Need to pick some reasonable defaults + tt = new ToolTip(); + tt.AutomaticDelay = 100; + tt.InitialDelay = 250; + tt.ReshowDelay = 250; + tt.ShowAlways = true; + + if ((tip != null) && (tip != string.Empty)) { + tt.SetToolTip(Widget.FromHandle(handle), tip); + tt.Active = true; + } else { + tt.Active = false; + } + + SendNetClientMessage (SystrayMgrWindow, + Atoms._NET_SYSTEM_TRAY_OPCODE, + IntPtr.Zero, + (IntPtr)SystrayRequest.SYSTEM_TRAY_REQUEST_DOCK, + hwnd.WholeWindow); + + return true; + } + + tt = null; + return false; + } + + public bool SystrayChange (IntPtr handle, string tip, Icon icon, ref ToolTip tt) + { + Widget Widget; + + Widget = Widget.FromHandle(handle); + if (Widget != null && tt != null) { + tt.SetToolTip(Widget, tip); + tt.Active = true; + return true; + } else { + return false; + } + } + + public void SystrayRemove(IntPtr handle, ref ToolTip tt) + { +#if GTKSOCKET_SUPPORTS_REPARENTING + X11Hwnd hwnd; + + hwnd = (X11Hwnd)Hwnd.ObjectFromHandle(handle); + + /* in the XEMBED spec, it mentions 3 ways for a client window to break the protocol with the embedder. + * 1. The embedder can unmap the window and reparent to the root window (we should probably handle this...) + * 2. The client can reparent its window out of the embedder window. + * 3. The client can destroy its window. + * + * this call to SetParent is case 2, but in + * the spec it also mentions that gtk doesn't + * support this at present. Looking at HEAD + * gtksocket-x11.c jives with this statement. + * + * so we can't reparent. we have to destroy. + */ + SetParent(hwnd.WholeWindow, FosterParent); +#else + Widget Widget = Widget.FromHandle(handle); + if (Widget is NotifyIcon.NotifyIconWindow) + ((NotifyIcon.NotifyIconWindow)Widget).InternalRecreateHandle (); +#endif + + // The caller can now re-dock it later... + if (tt != null) { + tt.Dispose(); + tt = null; + } + } + + public void ResetMouseHover (X11Hwnd hovering) + { + HoverState.Timer.Enabled = hovering != null; + HoverState.X = MousePosition.X; + HoverState.Y = MousePosition.Y; + HoverState.Window = hovering == null ? IntPtr.Zero : hovering.Handle; + } + + public void ShowCursor (bool show) + { + ; // FIXME - X11 doesn't 'hide' the cursor. we could create an empty cursor + } + + public void SetModal (X11Hwnd hwnd, bool Modal) + { + if (Modal) { + ModalWindows.Push(hwnd); + } else { + // XXX do we need to pop until the + // hwnd is off the stack? or just the + // most recently pushed hwnd? + if (ModalWindows.Contains(hwnd)) { + ModalWindows.Pop(); + } + + if (ModalWindows.Count > 0) { + X11Hwnd top_hwnd = (X11Hwnd)ModalWindows.Peek(); + top_hwnd.Activate(); + } + } + } + + public TransparencySupport SupportsTransparency () + { + // compiz adds _NET_WM_WINDOW_OPACITY to _NET_SUPPORTED on the root window, check for that + return ((IList)root_hwnd._NET_SUPPORTED).Contains (Atoms._NET_WM_WINDOW_OPACITY) ? TransparencySupport.GetSet : TransparencySupport.None; + } + + public void SendAsyncMethod (AsyncMethodData method) + { + X11Hwnd hwnd = (X11Hwnd)Hwnd.ObjectFromHandle(method.Handle); + XEvent xevent = new XEvent (); + + xevent.type = XEventName.ClientMessage; + xevent.ClientMessageEvent.display = display; + xevent.ClientMessageEvent.window = method.Handle; + xevent.ClientMessageEvent.message_type = Atoms.AsyncAtom; + xevent.ClientMessageEvent.format = 32; + xevent.ClientMessageEvent.ptr1 = (IntPtr) GCHandle.Alloc (method); + + hwnd.Queue.Enqueue (xevent); + } + + delegate IntPtr WndProcDelegate (IntPtr hwnd, Msg message, IntPtr wParam, IntPtr lParam); + + public IntPtr SendMessage (IntPtr handle, Msg message, IntPtr wParam, IntPtr lParam) + { + X11Hwnd hwnd = (X11Hwnd)Hwnd.ObjectFromHandle(handle); + if (hwnd == null) + return IntPtr.Zero; + + if (hwnd.Queue.Thread != Thread.CurrentThread) { + AsyncMethodResult result; + AsyncMethodData data; + + result = new AsyncMethodResult (); + data = new AsyncMethodData (); + + data.Handle = hwnd.Handle; + data.Method = new WndProcDelegate (NativeWindow.WndProc); + data.Args = new object[] { hwnd.Handle, message, wParam, lParam }; + data.Result = result; + + SendAsyncMethod (data); +#if DriverDebug || DriverDebugThreads + Console.WriteLine ("Sending {0} message across.", message); +#endif + + return IntPtr.Zero; + } + else { + return NativeWindow.WndProc (hwnd.Handle, message, wParam, lParam); + } + } + + public int SendInput (IntPtr handle, Queue keys) { + if (handle == IntPtr.Zero) + return 0; + + int count = keys.Count; + Hwnd hwnd = Hwnd.ObjectFromHandle(handle); + + while (keys.Count > 0) { + + MSG msg = (MSG)keys.Dequeue(); + + XEvent xevent = new XEvent (); + + xevent.type = (msg.message == Msg.WM_KEYUP ? XEventName.KeyRelease : XEventName.KeyPress); + xevent.KeyEvent.display = display; + + if (hwnd != null) { + xevent.KeyEvent.window = hwnd.whole_window; + } else { + xevent.KeyEvent.window = IntPtr.Zero; + } + + xevent.KeyEvent.keycode = Keyboard.ToKeycode((int)msg.wParam); + + hwnd.Queue.EnqueueLocked (xevent); + } + return count; + } + + // FIXME - I think this should just enqueue directly + public bool PostMessage (IntPtr handle, Msg message, IntPtr wparam, IntPtr lparam) + { + XEvent xevent = new XEvent (); + X11Hwnd hwnd = (X11Hwnd)Hwnd.ObjectFromHandle(handle); + + xevent.type = XEventName.ClientMessage; + xevent.ClientMessageEvent.display = display; + + if (hwnd != null) { + xevent.ClientMessageEvent.window = hwnd.WholeWindow; + } else { + xevent.ClientMessageEvent.window = IntPtr.Zero; + } + + xevent.ClientMessageEvent.message_type = Atoms.PostAtom; + xevent.ClientMessageEvent.format = 32; + xevent.ClientMessageEvent.ptr1 = handle; + xevent.ClientMessageEvent.ptr2 = (IntPtr) message; + xevent.ClientMessageEvent.ptr3 = wparam; + xevent.ClientMessageEvent.ptr4 = lparam; + + hwnd.Queue.Enqueue (xevent); + + return true; + } + + public void SendNetWMMessage (IntPtr window, IntPtr message_type, IntPtr l0, IntPtr l1, IntPtr l2) + { + XEvent xev; + + xev = new XEvent(); + xev.ClientMessageEvent.type = XEventName.ClientMessage; + xev.ClientMessageEvent.send_event = true; + xev.ClientMessageEvent.window = window; + xev.ClientMessageEvent.message_type = message_type; + xev.ClientMessageEvent.format = 32; + xev.ClientMessageEvent.ptr1 = l0; + xev.ClientMessageEvent.ptr2 = l1; + xev.ClientMessageEvent.ptr3 = l2; + + Xlib.XSendEvent (display, root_hwnd.Handle, false, + new IntPtr ((int) (EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask)), ref xev); + } + + public void SendNetClientMessage (IntPtr window, IntPtr message_type, IntPtr l0, IntPtr l1, IntPtr l2) + { + XEvent xev; + + xev = new XEvent(); + xev.ClientMessageEvent.type = XEventName.ClientMessage; + xev.ClientMessageEvent.send_event = true; + xev.ClientMessageEvent.window = window; + xev.ClientMessageEvent.message_type = message_type; + xev.ClientMessageEvent.format = 32; + xev.ClientMessageEvent.ptr1 = l0; + xev.ClientMessageEvent.ptr2 = l1; + xev.ClientMessageEvent.ptr3 = l2; + + Xlib.XSendEvent (display, window, false, new IntPtr ((int)EventMask.NoEventMask), ref xev); + } + + public bool TranslateMessage (ref MSG msg) + { + return Keyboard.TranslateMessage (ref msg); + } + + public IntPtr DispatchMessage (ref MSG msg) + { + return NativeWindow.WndProc(msg.hwnd, msg.message, msg.wParam, msg.lParam); + } + + private void QueryPointer (IntPtr w, out IntPtr root, out IntPtr child, + out int root_x, out int root_y, out int child_x, out int child_y, + out int mask) + { + /* this code was written with the help of + glance at gdk. I never would have realized we + needed a loop in order to traverse down in the + hierarchy. I would have assumed you'd get the + most deeply nested child and have to do + XQueryTree to move back up the hierarchy.. + stupid me, of course. */ + IntPtr c; + + // Xlib.XGrabServer (display); + + Xlib.XQueryPointer (display, w, out root, out c, + out root_x, out root_y, out child_x, out child_y, + out mask); + + if (root != w) + c = root; + + IntPtr child_last = IntPtr.Zero; + while (c != IntPtr.Zero) { + child_last = c; + Xlib.XQueryPointer (display, c, out root, out c, + out root_x, out root_y, out child_x, out child_y, + out mask); + } + + // Xlib.XUngrabServer (display); + + child = child_last; + } + + public void SetCursorPos (int x, int y) + { + IntPtr root, child; + int root_x, root_y, child_x, child_y, mask; + + /* we need to do a + * QueryPointer before warping + * because if the warp is on + * the RootWindow, the x/y are + * relative to the current + * mouse position + */ + QueryPointer (RootWindow.Handle, + out root, + out child, + out root_x, out root_y, + out child_x, out child_y, + out mask); + + Xlib.XWarpPointer (display, IntPtr.Zero, IntPtr.Zero, 0, 0, 0, 0, x - root_x, y - root_y); + + Xlib.XFlush (display); + + /* then we need to a + * QueryPointer after warping + * to manually generate a + * motion event for the window + * we move into. + */ + QueryPointer (RootWindow.Handle, + out root, + out child, + out root_x, out root_y, + out child_x, out child_y, + out mask); + + X11Hwnd child_hwnd = (X11Hwnd)Hwnd.ObjectFromHandle(child); + if (child_hwnd == null) + return; + + XEvent xevent = new XEvent (); + + xevent.type = XEventName.MotionNotify; + xevent.MotionEvent.display = display; + xevent.MotionEvent.window = child_hwnd.Handle; + xevent.MotionEvent.root = RootWindow.Handle; + xevent.MotionEvent.x = child_x; + xevent.MotionEvent.y = child_y; + xevent.MotionEvent.x_root = root_x; + xevent.MotionEvent.y_root = root_y; + xevent.MotionEvent.state = mask; + + child_hwnd.Queue.Enqueue (xevent); + } + + public void SetFocus (X11Hwnd new_focus) + { + if (new_focus == FocusWindow) + return; + + X11Hwnd prev_focus = FocusWindow; + FocusWindow = new_focus; + + if (prev_focus != null) + SendMessage (prev_focus.Handle, Msg.WM_KILLFOCUS, + FocusWindow == null ? IntPtr.Zero : FocusWindow.Handle, IntPtr.Zero); + if (FocusWindow != null) + SendMessage (FocusWindow.Handle, Msg.WM_SETFOCUS, + prev_focus == null ? IntPtr.Zero : prev_focus.Handle, IntPtr.Zero); + + //XSetInputFocus(DisplayHandle, Hwnd.ObjectFromHandle(handle).ClientWindow, RevertTo.None, IntPtr.Zero); + } + + public IntPtr DefineCursor (Bitmap bitmap, Bitmap mask, Color cursor_pixel, Color mask_pixel, int xHotSpot, int yHotSpot) + { + IntPtr cursor; + Bitmap cursor_bitmap; + Bitmap cursor_mask; + Byte[] cursor_bits; + Byte[] mask_bits; + Color c_pixel; + Color m_pixel; + int width; + int height; + IntPtr cursor_pixmap; + IntPtr mask_pixmap; + XColor fg; + XColor bg; + bool and; + bool xor; + + if (Xlib.XQueryBestCursor (display, RootWindow.Handle, bitmap.Width, bitmap.Height, out width, out height) == 0) { + return IntPtr.Zero; + } + + // Win32 only allows creation cursors of a certain size + if ((bitmap.Width != width) || (bitmap.Width != height)) { + cursor_bitmap = new Bitmap(bitmap, new Size(width, height)); + cursor_mask = new Bitmap(mask, new Size(width, height)); + } else { + cursor_bitmap = bitmap; + cursor_mask = mask; + } + + width = cursor_bitmap.Width; + height = cursor_bitmap.Height; + + cursor_bits = new Byte[(width / 8) * height]; + mask_bits = new Byte[(width / 8) * height]; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + c_pixel = cursor_bitmap.GetPixel(x, y); + m_pixel = cursor_mask.GetPixel(x, y); + + and = c_pixel == cursor_pixel; + xor = m_pixel == mask_pixel; + + if (!and && !xor) { + // Black + // cursor_bits[y * width / 8 + x / 8] &= (byte)~((1 << (x % 8))); // The bit already is 0 + mask_bits[y * width / 8 + x / 8] |= (byte)(1 << (x % 8)); + } else if (and && !xor) { + // White + cursor_bits[y * width / 8 + x / 8] |= (byte)(1 << (x % 8)); + mask_bits[y * width / 8 + x / 8] |= (byte)(1 << (x % 8)); +#if notneeded + } else if (and && !xor) { + // Screen + } else if (and && xor) { + // Inverse Screen + + // X11 doesn't know the 'reverse screen' concept, so we'll treat them the same + // we want both to be 0 so nothing to be done + //cursor_bits[y * width / 8 + x / 8] &= (byte)~((1 << (x % 8))); + //mask_bits[y * width / 8 + x / 8] |= (byte)(01 << (x % 8)); +#endif + } + } + } + + cursor_pixmap = Xlib.XCreatePixmapFromBitmapData (display, RootWindow.Handle, + cursor_bits, width, height, (IntPtr)1, (IntPtr)0, 1); + mask_pixmap = Xlib.XCreatePixmapFromBitmapData (display, RootWindow.Handle, + mask_bits, width, height, (IntPtr)1, (IntPtr)0, 1); + fg = new XColor(); + bg = new XColor(); + + fg.pixel = Xlib.XWhitePixel (display, DefaultScreen); + fg.red = (ushort)65535; + fg.green = (ushort)65535; + fg.blue = (ushort)65535; + + bg.pixel = Xlib.XBlackPixel (display, DefaultScreen); + + cursor = Xlib.XCreatePixmapCursor (display, cursor_pixmap, mask_pixmap, ref fg, ref bg, xHotSpot, yHotSpot); + + Xlib.XFreePixmap (display, cursor_pixmap); + Xlib.XFreePixmap (display, mask_pixmap); + + return cursor; + } + + public Bitmap DefineStdCursorBitmap (StdCursor id) + { + CursorFontShape shape; + string name; + IntPtr theme; + int size; + Bitmap bmp = null; + + try { + shape = XplatUIX11.StdCursorToFontShape (id); + name = shape.ToString ().Replace ("XC_", string.Empty); + size = XplatUIX11.XcursorGetDefaultSize (Handle); + theme = XplatUIX11.XcursorGetTheme (Handle); + IntPtr images_ptr = XplatUIX11.XcursorLibraryLoadImages (name, theme, size); +#if debug + Console.WriteLine ("DefineStdCursorBitmap, id={0}, #id={1}, name{2}, size={3}, theme: {4}, images_ptr={5}", id, (int) id, name, size, Marshal.PtrToStringAnsi (theme), images_ptr); +#endif + + if (images_ptr == IntPtr.Zero) { + return null; + } + + XcursorImages images = (XcursorImages)Marshal.PtrToStructure (images_ptr, typeof (XcursorImages)); +#if debug + Console.WriteLine ("DefineStdCursorBitmap, cursor has {0} images", images.nimage); +#endif + + if (images.nimage > 0) { + // We only care about the first image. + XcursorImage image = (XcursorImage)Marshal.PtrToStructure (Marshal.ReadIntPtr (images.images), typeof (XcursorImage)); + +#if debug + Console.WriteLine ("DefineStdCursorBitmap, loaded image <size={0}, height={1}, width={2}, xhot={3}, yhot={4}, pixels={5}", image.size, image.height, image.width, image.xhot, image.yhot, image.pixels); +#endif + // A sanity check + if (image.width <= short.MaxValue && image.height <= short.MaxValue) { + int [] pixels = new int [image.width * image.height]; + Marshal.Copy (image.pixels, pixels, 0, pixels.Length); + bmp = new Bitmap (image.width, image.height); + for (int w = 0; w < image.width; w++) { + for (int h = 0; h < image.height; h++) { + bmp.SetPixel (w, h, Color.FromArgb (pixels [h * image.width + w])); + } + } + } + } + + XplatUIX11.XcursorImagesDestroy (images_ptr); + + } catch (DllNotFoundException ex) { + Console.WriteLine ("Could not load libXcursor: " + ex.Message + " (" + ex.GetType ().Name + ")"); + return null; + } + + return bmp; + } + + public IntPtr DefineStdCursor (StdCursor id) + { + CursorFontShape shape; + + // FIXME - define missing shapes + + switch (id) { + case StdCursor.AppStarting: + shape = CursorFontShape.XC_watch; + break; + + case StdCursor.Arrow: + shape = CursorFontShape.XC_top_left_arrow; + break; + + case StdCursor.Cross: + shape = CursorFontShape.XC_crosshair; + break; + + case StdCursor.Default: + shape = CursorFontShape.XC_top_left_arrow; + break; + + case StdCursor.Hand: + shape = CursorFontShape.XC_hand1; + break; + + case StdCursor.Help: + shape = CursorFontShape.XC_question_arrow; + break; + + case StdCursor.HSplit: + shape = CursorFontShape.XC_sb_v_double_arrow; + break; + + case StdCursor.IBeam: + shape = CursorFontShape.XC_xterm; + break; + + case StdCursor.No: + shape = CursorFontShape.XC_circle; + break; + + case StdCursor.NoMove2D: + shape = CursorFontShape.XC_fleur; + break; + + case StdCursor.NoMoveHoriz: + shape = CursorFontShape.XC_fleur; + break; + + case StdCursor.NoMoveVert: + shape = CursorFontShape.XC_fleur; + break; + + case StdCursor.PanEast: + shape = CursorFontShape.XC_fleur; + break; + + case StdCursor.PanNE: + shape = CursorFontShape.XC_fleur; + break; + + case StdCursor.PanNorth: + shape = CursorFontShape.XC_fleur; + break; + + case StdCursor.PanNW: + shape = CursorFontShape.XC_fleur; + break; + + case StdCursor.PanSE: + shape = CursorFontShape.XC_fleur; + break; + + case StdCursor.PanSouth: + shape = CursorFontShape.XC_fleur; + break; + + case StdCursor.PanSW: + shape = CursorFontShape.XC_fleur; + break; + + case StdCursor.PanWest: + shape = CursorFontShape.XC_sizing; + break; + + case StdCursor.SizeAll: + shape = CursorFontShape.XC_fleur; + break; + + case StdCursor.SizeNESW: + shape = CursorFontShape.XC_top_right_corner; + break; + + case StdCursor.SizeNS: + shape = CursorFontShape.XC_sb_v_double_arrow; + break; + + case StdCursor.SizeNWSE: + shape = CursorFontShape.XC_top_left_corner; + break; + + case StdCursor.SizeWE: + shape = CursorFontShape.XC_sb_h_double_arrow; + break; + + case StdCursor.UpArrow: + shape = CursorFontShape.XC_center_ptr; + break; + + case StdCursor.VSplit: + shape = CursorFontShape.XC_sb_h_double_arrow; + break; + + case StdCursor.WaitCursor: + shape = CursorFontShape.XC_watch; + break; + + default: + return IntPtr.Zero; + } + + return Xlib.XCreateFontCursor (display, shape); + } + + // XXX this should take an X11Hwnd. + public void CreateCaret (IntPtr handle, int width, int height) + { + XGCValues gc_values; + X11Hwnd hwnd; + + hwnd = (X11Hwnd)Hwnd.ObjectFromHandle(handle); + + if (Caret.Hwnd != IntPtr.Zero) + DestroyCaret(Caret.Hwnd); + + Caret.Hwnd = handle; + Caret.Window = hwnd.ClientWindow; + Caret.Width = width; + Caret.Height = height; + Caret.Visible = false; + Caret.On = false; + + gc_values = new XGCValues(); + gc_values.line_width = width; + + Caret.gc = Xlib.XCreateGC (display, Caret.Window, new IntPtr ((int)GCFunction.GCLineWidth), ref gc_values); + if (Caret.gc == IntPtr.Zero) { + Caret.Hwnd = IntPtr.Zero; + return; + } + + Xlib.XSetFunction (display, Caret.gc, GXFunction.GXinvert); + } + + + // XXX this should take an X11Hwnd. + public void DestroyCaret (IntPtr handle) + { + if (Caret.Hwnd == handle) { + if (Caret.Visible == true) { + Caret.Timer.Stop (); + } + if (Caret.gc != IntPtr.Zero) { + Xlib.XFreeGC (display, Caret.gc); + Caret.gc = IntPtr.Zero; + } + Caret.Hwnd = IntPtr.Zero; + Caret.Visible = false; + Caret.On = false; + } + } + + public void SetCaretPos (IntPtr handle, int x, int y) + { + if (Caret.Hwnd == handle) { + Caret.Timer.Stop(); + HideCaret(); + + Caret.X = x; + Caret.Y = y; + + if (Caret.Visible == true) { + ShowCaret(); + Caret.Timer.Start(); + } + } + } + + public void DestroyCursor (IntPtr cursor) + { + Xlib.XFreeCursor (display, cursor); + } + + private void AccumulateDestroyedHandles (Widget c, ArrayList list) + { + if (c != null) { + Widget[] Widgets = c.Widgets.GetAllWidgets (); + + if (c.IsHandleCreated && !c.IsDisposed) { + X11Hwnd hwnd = (X11Hwnd)Hwnd.ObjectFromHandle(c.Handle); + +#if DriverDebug || DriverDebugDestroy + Console.WriteLine (" + adding {0} to the list of zombie windows", XplatUI.Window (hwnd.Handle)); + Console.WriteLine (" + parent X window is {0:X}", XGetParent (hwnd.WholeWindow).ToInt32()); +#endif + + list.Add (hwnd); + CleanupCachedWindows (hwnd); + hwnd.zombie = true; + } + + for (int i = 0; i < Widgets.Length; i ++) { + AccumulateDestroyedHandles (Widgets[i], list); + } + } + + } + + void CleanupCachedWindows (X11Hwnd hwnd) + { + if (ActiveWindow == hwnd) { + SendMessage (hwnd.ClientWindow, Msg.WM_ACTIVATE, (IntPtr)WindowActiveFlags.WA_INACTIVE, IntPtr.Zero); + ActiveWindow = null; + } + + if (FocusWindow == hwnd) { + SendMessage (hwnd.ClientWindow, Msg.WM_KILLFOCUS, IntPtr.Zero, IntPtr.Zero); + FocusWindow = null; + } + + if (Grab.Hwnd == hwnd.Handle) { + Grab.Hwnd = IntPtr.Zero; + Grab.Confined = false; + } + + DestroyCaret (hwnd.Handle); + } + + + public void DestroyWindow (X11Hwnd hwnd) + { + CleanupCachedWindows (hwnd); + + hwnd.SendParentNotify (Msg.WM_DESTROY, int.MaxValue, int.MaxValue); + + ArrayList windows = new ArrayList (); + + AccumulateDestroyedHandles (Widget.WidgetNativeWindow.WidgetFromHandle(hwnd.Handle), windows); + + hwnd.DestroyWindow (); + + foreach (X11Hwnd h in windows) { + SendMessage (h.Handle, Msg.WM_DESTROY, IntPtr.Zero, IntPtr.Zero); + } + } + + public X11Hwnd GetActiveWindow () + { + IntPtr actual_atom; + int actual_format; + IntPtr nitems; + IntPtr bytes_after; + IntPtr prop = IntPtr.Zero; + IntPtr active = IntPtr.Zero; + + Xlib.XGetWindowProperty (display, RootWindow.Handle, + Atoms._NET_ACTIVE_WINDOW, IntPtr.Zero, new IntPtr (1), false, + Atoms.XA_WINDOW, out actual_atom, out actual_format, out nitems, out bytes_after, ref prop); + + if (((long)nitems > 0) && (prop != IntPtr.Zero)) { + active = (IntPtr)Marshal.ReadInt32(prop); + Xlib.XFree(prop); + } + + return (X11Hwnd)Hwnd.GetObjectFromWindow(active); + } + + public void SetActiveWindow (X11Hwnd new_active_window) + { + if (new_active_window != ActiveWindow) { + if (ActiveWindow != null) + PostMessage (ActiveWindow.Handle, Msg.WM_ACTIVATE, + (IntPtr)WindowActiveFlags.WA_INACTIVE, IntPtr.Zero); + + ActiveWindow = new_active_window; + + if (ActiveWindow != null) + PostMessage (ActiveWindow.Handle, Msg.WM_ACTIVATE, + (IntPtr)WindowActiveFlags.WA_ACTIVE, IntPtr.Zero); + } + + if (ModalWindows.Count > 0) { + // Modality handling, if we are modal and the new active window is one + // of ours but not the modal one, switch back to the modal window + + if (ActiveWindow != null && + NativeWindow.FromHandle (ActiveWindow.Handle) != null) { + if (ActiveWindow != (X11Hwnd)ModalWindows.Peek()) + ((X11Hwnd)ModalWindows.Peek()).Activate (); + } + } + } + + public void GetDisplaySize (out Size size) + { + XWindowAttributes attributes = new XWindowAttributes(); + + // FIXME - use _NET_WM messages instead? + Xlib.XGetWindowAttributes (display, RootWindow.Handle, ref attributes); + + size = new Size(attributes.width, attributes.height); + } + + // XXX this method doesn't really fit well anywhere in the backend + public SizeF GetAutoScaleSize (Font font) + { + Graphics g; + float width; + string magic_string = "The quick brown fox jumped over the lazy dog."; + double magic_number = 44.549996948242189; // XXX my god, where did this number come from? + + g = Graphics.FromHwnd (FosterParent.Handle); + + width = (float) (g.MeasureString (magic_string, font).Width / magic_number); + return new SizeF(width, font.Height); + } + + public void GetCursorPos (X11Hwnd hwnd, out int x, out int y) + { + IntPtr use_handle; + IntPtr root; + IntPtr child; + int root_x; + int root_y; + int win_x; + int win_y; + int keys_buttons; + + if (hwnd != null) + use_handle = hwnd.Handle; + else + use_handle = RootWindow.Handle; + + QueryPointer (use_handle, out root, out child, out root_x, out root_y, out win_x, out win_y, out keys_buttons); + + if (hwnd != null) { + x = win_x; + y = win_y; + } else { + x = root_x; + y = root_y; + } + } + + public IntPtr GetFocus () + { + return FocusWindow.Handle; + } + + public IntPtr GetMousewParam (int Delta) + { + int result = 0; + + if ((MouseState & MouseButtons.Left) != 0) { + result |= (int)MsgButtons.MK_LBUTTON; + } + + if ((MouseState & MouseButtons.Middle) != 0) { + result |= (int)MsgButtons.MK_MBUTTON; + } + + if ((MouseState & MouseButtons.Right) != 0) { + result |= (int)MsgButtons.MK_RBUTTON; + } + + Keys mods = ModifierKeys; + if ((mods & Keys.Widget) != 0) { + result |= (int)MsgButtons.MK_CONTROL; + } + + if ((mods & Keys.Shift) != 0) { + result |= (int)MsgButtons.MK_SHIFT; + } + + result |= Delta << 16; + + return (IntPtr)result; + } + + public void GrabInfo (out IntPtr handle, out bool GrabConfined, out Rectangle GrabArea) + { + handle = Grab.Hwnd; + GrabConfined = Grab.Confined; + GrabArea = Grab.Area; + } + + public void GrabWindow (X11Hwnd hwnd, X11Hwnd confine_to) + { + IntPtr confine_to_window; + + confine_to_window = IntPtr.Zero; + + if (confine_to != null) { + Console.WriteLine (Environment.StackTrace); + + XWindowAttributes attributes = new XWindowAttributes(); + + Xlib.XGetWindowAttributes (display, confine_to.ClientWindow, ref attributes); + + Grab.Area.X = attributes.x; + Grab.Area.Y = attributes.y; + Grab.Area.Width = attributes.width; + Grab.Area.Height = attributes.height; + Grab.Confined = true; + confine_to_window = confine_to.ClientWindow; + } + + Grab.Hwnd = hwnd.ClientWindow; + + Xlib.XGrabPointer (display, hwnd.ClientWindow, false, + EventMask.ButtonPressMask | EventMask.ButtonMotionMask | + EventMask.ButtonReleaseMask | EventMask.PointerMotionMask, + GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine_to_window, IntPtr.Zero, IntPtr.Zero); + } + + public void UngrabWindow (X11Hwnd hwnd) + { + Xlib.XUngrabPointer (display, IntPtr.Zero); + Xlib.XFlush (display); + + // XXX make sure hwnd is what should have the grab and throw if not + Grab.Hwnd = IntPtr.Zero; + Grab.Confined = false; + } + +#if notyet + private void TranslatePropertyToClipboard (IntPtr property) + { + IntPtr actual_atom; + int actual_format; + IntPtr nitems; + IntPtr bytes_after; + IntPtr prop = IntPtr.Zero; + + Clipboard.Item = null; + + Xlib.XGetWindowProperty (display, FosterParent.Handle, + property, IntPtr.Zero, new IntPtr (0x7fffffff), true, + Atoms.AnyPropertyType, out actual_atom, out actual_format, out nitems, out bytes_after, ref prop); + + if ((long)nitems > 0) { + if (property == Atoms.XA_STRING) { + Clipboard.Item = Marshal.PtrToStringAnsi(prop); + } else if (property == Atoms.XA_BITMAP) { + // FIXME - convert bitmap to image + } else if (property == Atoms.XA_PIXMAP) { + // FIXME - convert pixmap to image + } else if (property == Atoms.OEMTEXT) { + Clipboard.Item = Marshal.PtrToStringAnsi(prop); + } else if (property == Atoms.UNICODETEXT) { + Clipboard.Item = Marshal.PtrToStringAnsi(prop); + } + + Xlib.XFree(prop); + } + } +#endif + + // XXX should we be using @handle instead of Atoms.CLIPBOARD here? + public int[] ClipboardAvailableFormats (IntPtr handle) + { + // XXX deal with the updatemessagequeue stuff +#if true + return new int[0]; +#else + DataFormats.Format f; + int[] result; + + f = DataFormats.Format.List; + + if (Xlib.XGetSelectionOwner (display, Atoms.CLIPBOARD) == IntPtr.Zero) { + return null; + } + + Clipboard.Formats = new ArrayList(); + + while (f != null) { + Xlib.XConvertSelection (display, Atoms.CLIPBOARD, (IntPtr)f.Id, (IntPtr)f.Id, FosterParent.Handle, IntPtr.Zero); + + Clipboard.Enumerating = true; + while (Clipboard.Enumerating) { + UpdateMessageQueue(null); + } + f = f.Next; + } + + result = new int[Clipboard.Formats.Count]; + + for (int i = 0; i < Clipboard.Formats.Count; i++) { + result[i] = ((IntPtr)Clipboard.Formats[i]).ToInt32 (); + } + + Clipboard.Formats = null; + return result; +#endif + } + + public void ClipboardClose (IntPtr handle) + { + if (handle != ClipMagic) { + throw new ArgumentException("handle is not a valid clipboard handle"); + } + return; + } + + public int ClipboardGetID (IntPtr handle, string format) + { + if (handle != ClipMagic) { + throw new ArgumentException("handle is not a valid clipboard handle"); + } + + if (format == "Text" ) return Atoms.XA_STRING.ToInt32(); + else if (format == "Bitmap" ) return Atoms.XA_BITMAP.ToInt32(); + //else if (format == "MetaFilePict" ) return 3; + //else if (format == "SymbolicLink" ) return 4; + //else if (format == "DataInterchangeFormat" ) return 5; + //else if (format == "Tiff" ) return 6; + else if (format == "OEMText" ) return Atoms.OEMTEXT.ToInt32(); + else if (format == "DeviceIndependentBitmap" ) return Atoms.XA_PIXMAP.ToInt32(); + else if (format == "Palette" ) return Atoms.XA_COLORMAP.ToInt32(); // Useless + //else if (format == "PenData" ) return 10; + //else if (format == "RiffAudio" ) return 11; + //else if (format == "WaveAudio" ) return 12; + else if (format == "UnicodeText" ) return Atoms.UNICODETEXT.ToInt32(); + //else if (format == "EnhancedMetafile" ) return 14; + //else if (format == "FileDrop" ) return 15; + //else if (format == "Locale" ) return 16; + + return Xlib.XInternAtom (display, format, false).ToInt32(); + } + + public IntPtr ClipboardOpen (bool primary_selection) + { + if (!primary_selection) + ClipMagic = Atoms.CLIPBOARD; + else + ClipMagic = Atoms.PRIMARY; + + return ClipMagic; + } + + // XXX @converter? + public object ClipboardRetrieve (IntPtr handle, int type, XplatUI.ClipboardToObject converter) + { + // XXX deal with the UpdateMessageQueue stuff +#if true + return null; +#else + Xlib.XConvertSelection (display, handle, (IntPtr)type, (IntPtr)type, FosterParent, IntPtr.Zero); + + Clipboard.Retrieving = true; + while (Clipboard.Retrieving) { + UpdateMessageQueue(null); + } + + return Clipboard.Item; +#endif + } + + public PaintEventArgs PaintEventStart (ref Message m, IntPtr handle, bool client) + { + X11Hwnd hwnd = (X11Hwnd)Hwnd.ObjectFromHandle(handle); + + if (Caret.Visible == true) { + Caret.Paused = true; + HideCaret(); + } + + return hwnd.PaintEventStart (ref m, client); + } + + public void PaintEventEnd (ref Message m, IntPtr handle, bool client) + { + X11Hwnd hwnd = (X11Hwnd)Hwnd.ObjectFromHandle(handle); + + hwnd.PaintEventEnd (ref m, client); + + if (Caret.Visible == true) { + ShowCaret(); + Caret.Paused = false; + } + } + + public void SetCursor (IntPtr handle, IntPtr cursor) + { + Hwnd hwnd; + + if (OverrideCursorHandle == IntPtr.Zero) { + if ((LastCursorWindow == handle) && (LastCursorHandle == cursor)) + return; + + LastCursorHandle = cursor; + LastCursorWindow = handle; + + hwnd = Hwnd.ObjectFromHandle(handle); + if (cursor != IntPtr.Zero) + Xlib.XDefineCursor (display, hwnd.whole_window, cursor); + else + Xlib.XUndefineCursor (display, hwnd.whole_window); + Xlib.XFlush (display); + } + else { + hwnd = Hwnd.ObjectFromHandle(handle); + Xlib.XDefineCursor (display, hwnd.whole_window, OverrideCursorHandle); + } + } + + public DragDropEffects StartDrag (IntPtr handle, object data, + DragDropEffects allowed_effects) + { + X11Hwnd hwnd = (X11Hwnd)Hwnd.ObjectFromHandle (handle); + + if (hwnd == null) + throw new ArgumentException ("Attempt to begin drag from invalid window handle (" + handle.ToInt32 () + ")."); + + return Dnd.StartDrag (hwnd.ClientWindow, data, allowed_effects); + } + + public X11Atoms Atoms { + get { return atoms; } + } + + public int CurrentTimestamp { + get { + TimeSpan t = (DateTime.UtcNow - new DateTime(1970, 1, 1)); + + return (int) t.TotalSeconds; + } + } + + public Size CursorSize { + get { + int x; + int y; + + if (Xlib.XQueryBestCursor (display, RootWindow.Handle, 32, 32, out x, out y) != 0) { + return new Size (x, y); + } else { + return new Size (16, 16); + } + } + } + + public IntPtr Handle { + get { return display; } + } + + public Size IconSize { + get { + IntPtr list; + XIconSize size; + int count; + + if (Xlib.XGetIconSizes (display, RootWindow.Handle, out list, out count) != 0) { + long current; + int largest; + + current = (long)list; + largest = 0; + + size = new XIconSize(); + + for (int i = 0; i < count; i++) { + size = (XIconSize)Marshal.PtrToStructure((IntPtr)current, size.GetType()); + current += Marshal.SizeOf(size); + + // Look for our preferred size + if (size.min_width == 32) { + Xlib.XFree(list); + return new Size(32, 32); + } + + if (size.max_width == 32) { + Xlib.XFree(list); + return new Size(32, 32); + } + + if (size.min_width < 32 && size.max_width > 32) { + int x; + + // check if we can fit one + x = size.min_width; + while (x < size.max_width) { + x += size.width_inc; + if (x == 32) { + Xlib.XFree(list); + return new Size(32, 32); + } + } + } + + if (largest < size.max_width) { + largest = size.max_width; + } + } + + // We didn't find a match or we wouldn't be here + return new Size(largest, largest); + + } else { + return new Size(32, 32); + } + } + } + + public int KeyboardSpeed { + get { + // + // A lot harder: need to do: + // XkbQueryExtension(0x08051008, 0xbfffdf4c, 0xbfffdf50, 0xbfffdf54, 0xbfffdf58) = 1 + // XkbAllocKeyboard(0x08051008, 0xbfffdf4c, 0xbfffdf50, 0xbfffdf54, 0xbfffdf58) = 0x080517a8 + // XkbGetWidgets(0x08051008, 1, 0x080517a8, 0xbfffdf54, 0xbfffdf58) = 0 + // + // And from that we can tell the repetition rate + // + // Notice, the values must map to: + // [0, 31] which maps to 2.5 to 30 repetitions per second. + // + return 0; + } + } + + public int KeyboardDelay { + get { + // + // Return values must range from 0 to 4, 0 meaning 250ms, + // and 4 meaning 1000 ms. + // + return 1; // ie, 500 ms + } + } + + public int DefaultScreen { + get { return Xlib.XDefaultScreen (display); } + } + + public IntPtr DefaultColormap { + // XXX multiscreen + get { return Xlib.XDefaultColormap (display, DefaultScreen); } + } + + public Keys ModifierKeys { + get { return Keyboard.ModifierKeys; } + } + + public IntPtr OverrideCursor { + get { return OverrideCursorHandle; } + set { + if (Grab.Hwnd != IntPtr.Zero) { + Xlib.XChangeActivePointerGrab (display, + EventMask.ButtonMotionMask | + EventMask.PointerMotionMask | + EventMask.ButtonPressMask | + EventMask.ButtonReleaseMask, + value, IntPtr.Zero); + return; + } + + OverrideCursorHandle = value; + } + } + + public X11RootHwnd RootWindow { + get { return root_hwnd; } + } + + public Size SmallIconSize { + get { + IntPtr list; + XIconSize size; + int count; + + if (Xlib.XGetIconSizes (display, RootWindow.Handle, out list, out count) != 0) { + long current; + int smallest; + + current = (long)list; + smallest = 0; + + size = new XIconSize(); + + for (int i = 0; i < count; i++) { + size = (XIconSize)Marshal.PtrToStructure((IntPtr)current, size.GetType()); + current += Marshal.SizeOf(size); + + // Look for our preferred size + if (size.min_width == 16) { + Xlib.XFree(list); + return new Size(16, 16); + } + + if (size.max_width == 16) { + Xlib.XFree(list); + return new Size(16, 16); + } + + if (size.min_width < 16 && size.max_width > 16) { + int x; + + // check if we can fit one + x = size.min_width; + while (x < size.max_width) { + x += size.width_inc; + if (x == 16) { + Xlib.XFree(list); + return new Size(16, 16); + } + } + } + + if (smallest == 0 || smallest > size.min_width) { + smallest = size.min_width; + } + } + + // We didn't find a match or we wouldn't be here + return new Size(smallest, smallest); + + } else { + return new Size(16, 16); + } + } + } + + public X11Hwnd FosterParent { + get { return foster_hwnd; } + } + + public int MouseHoverTime { + get { return HoverState.Interval; } + } + + public Rectangle VirtualScreen { + get { + IntPtr actual_atom; + int actual_format; + IntPtr nitems; + IntPtr bytes_after; + IntPtr prop = IntPtr.Zero; + int width; + int height; + + Xlib.XGetWindowProperty (display, RootWindow.Handle, + Atoms._NET_DESKTOP_GEOMETRY, IntPtr.Zero, new IntPtr (256), false, Atoms.XA_CARDINAL, + out actual_atom, out actual_format, out nitems, out bytes_after, ref prop); + + if ((long)nitems < 2) + goto failsafe; + + width = Marshal.ReadIntPtr(prop, 0).ToInt32(); + height = Marshal.ReadIntPtr(prop, IntPtr.Size).ToInt32(); + Xlib.XFree(prop); + + return new Rectangle(0, 0, width, height); + + failsafe: + XWindowAttributes attributes = new XWindowAttributes(); + + Xlib.XGetWindowAttributes (display, RootWindow.Handle, ref attributes); + + return new Rectangle(0, 0, attributes.width, attributes.height); + } + } + + public Rectangle WorkingArea { + get { + IntPtr actual_atom; + int actual_format; + IntPtr nitems; + IntPtr bytes_after; + IntPtr prop = IntPtr.Zero; + int width; + int height; + int current_desktop; + int x; + int y; + + Xlib.XGetWindowProperty (display, RootWindow.Handle, + Atoms._NET_CURRENT_DESKTOP, IntPtr.Zero, new IntPtr(1), false, Atoms.XA_CARDINAL, + out actual_atom, out actual_format, out nitems, out bytes_after, ref prop); + + if ((long)nitems < 1) { + goto failsafe; + } + + current_desktop = Marshal.ReadIntPtr(prop, 0).ToInt32(); + Xlib.XFree(prop); + + Xlib.XGetWindowProperty (display, RootWindow.Handle, + Atoms._NET_WORKAREA, IntPtr.Zero, new IntPtr (256), false, Atoms.XA_CARDINAL, + out actual_atom, out actual_format, out nitems, out bytes_after, ref prop); + + if ((long)nitems < 4 * current_desktop) { + goto failsafe; + } + + x = Marshal.ReadIntPtr(prop, IntPtr.Size * 4 * current_desktop).ToInt32(); + y = Marshal.ReadIntPtr(prop, IntPtr.Size * 4 * current_desktop + IntPtr.Size).ToInt32(); + width = Marshal.ReadIntPtr(prop, IntPtr.Size * 4 * current_desktop + IntPtr.Size * 2).ToInt32(); + height = Marshal.ReadIntPtr(prop, IntPtr.Size * 4 * current_desktop + IntPtr.Size * 3).ToInt32(); + Xlib.XFree(prop); + + return new Rectangle(x, y, width, height); + + failsafe: + XWindowAttributes attributes = new XWindowAttributes(); + + Xlib.XGetWindowAttributes (display, RootWindow.Handle, ref attributes); + + return new Rectangle(0, 0, attributes.width, attributes.height); + } + } + + private void XEventThread () + { + while (true) { +#if __MonoCS__ + Syscall.poll (pollfds, 1U, -1); + + while (Xlib.XPending (display) > 0) { +#endif + XEvent xevent = new XEvent (); + Xlib.XNextEvent (display, ref xevent); + + // this is kind of a gross place to put this, but we don't know about the + // key repeat state in X11ThreadQueue, nor to we want the queue code calling + // XPeekEvent. + if (!detectable_key_auto_repeat && + xevent.type == XEventName.KeyRelease && + Xlib.XPending (display) > 0) { + + XEvent nextevent = new XEvent (); + Xlib.XPeekEvent (display, ref nextevent); + + if (nextevent.type == XEventName.KeyPress && + nextevent.KeyEvent.keycode == xevent.KeyEvent.keycode && + nextevent.KeyEvent.time == xevent.KeyEvent.time) { + continue; + } + } + + X11Hwnd hwnd = (X11Hwnd)Hwnd.GetObjectFromWindow(xevent.AnyEvent.window); + if (hwnd != null) + hwnd.Queue.Enqueue (xevent); +#if __MonoCS__ + } +#endif + } + } + + private void RedirectMsgToEnabledAncestor (X11Hwnd hwnd, MSG msg, IntPtr window, + ref int event_x, ref int event_y) + { + int x, y; + + IntPtr dummy; + msg.hwnd = hwnd.EnabledHwnd; + Xlib.XTranslateCoordinates (display, window, + Hwnd.ObjectFromHandle(msg.hwnd).ClientWindow, + event_x, event_y, + out x, out y, out dummy); + event_x = x; + event_y = y; + msg.lParam = (IntPtr)(MousePosition.Y << 16 | MousePosition.X); + } + + + // This is called from the thread owning the corresponding X11ThreadQueue + [MonoTODO("Implement filtering")] + public bool GetMessage (object queue_id, ref MSG msg, IntPtr handle, int wFilterMin, int wFilterMax) + { + X11ThreadQueue queue = (X11ThreadQueue)queue_id; + XEvent xevent; + bool client; + bool got_xevent = false; + + X11Hwnd hwnd; + + ProcessNextMessage: + do { + got_xevent = queue.Dequeue (out xevent); + + if (!got_xevent) { +#if spew + Console.WriteLine (">"); + Console.Out.Flush (); +#endif + break; + } + +#if spew + Console.Write ("-"); + Console.Out.Flush (); +#endif + + hwnd = (X11Hwnd)Hwnd.GetObjectFromWindow (xevent.AnyEvent.window); + + // Handle messages for windows that are already or are about to be destroyed. + + // we need a special block for this because unless we remove the hwnd from the paint + // queue it will always stay there (since we don't handle the expose), and we'll + // effectively loop infinitely trying to repaint a non-existant window. + if (hwnd != null && hwnd.zombie && xevent.type == XEventName.Expose) { + hwnd.PendingExpose = hwnd.PendingNCExpose = false; + goto ProcessNextMessage; + } + + // We need to make sure we only allow DestroyNotify events through for zombie + // hwnds, since much of the event handling code makes requests using the hwnd's + // ClientWindow, and that'll result in BadWindow errors if there's some lag + // between the XDestroyWindow call and the DestroyNotify event. + if (hwnd == null || hwnd.zombie) { +#if DriverDebug || DriverDebugDestroy + Console.WriteLine("GetMessage(): Got message {0} for non-existent or already destroyed window {1:X}", + xevent.type, xevent.AnyEvent.window.ToInt32()); +#endif + goto ProcessNextMessage; + } + + client = hwnd.ClientWindow == xevent.AnyEvent.window; + + msg.hwnd = hwnd.Handle; + + switch (xevent.type) { + case XEventName.KeyPress: + Keyboard.KeyEvent (FocusWindow.Handle, xevent, ref msg); + return true; + + case XEventName.KeyRelease: + Keyboard.KeyEvent (FocusWindow.Handle, xevent, ref msg); + return true; + + case XEventName.ButtonPress: { + switch(xevent.ButtonEvent.button) { + case 1: + MouseState |= MouseButtons.Left; + if (client) { + msg.message = Msg.WM_LBUTTONDOWN; + } else { + msg.message = Msg.WM_NCLBUTTONDOWN; + hwnd.MenuToScreen (ref xevent.ButtonEvent.x, ref xevent.ButtonEvent.y); + } + // TODO: For WM_NCLBUTTONDOWN wParam specifies a hit-test value not the virtual keys down + msg.wParam=GetMousewParam(0); + break; + + case 2: + MouseState |= MouseButtons.Middle; + if (client) { + msg.message = Msg.WM_MBUTTONDOWN; + } else { + msg.message = Msg.WM_NCMBUTTONDOWN; + hwnd.MenuToScreen (ref xevent.ButtonEvent.x, ref xevent.ButtonEvent.y); + } + msg.wParam=GetMousewParam(0); + break; + + case 3: + MouseState |= MouseButtons.Right; + if (client) { + msg.message = Msg.WM_RBUTTONDOWN; + } else { + msg.message = Msg.WM_NCRBUTTONDOWN; + hwnd.MenuToScreen (ref xevent.ButtonEvent.x, ref xevent.ButtonEvent.y); + } + msg.wParam=GetMousewParam(0); + break; + + case 4: + msg.hwnd = FocusWindow.Handle; + msg.message=Msg.WM_MOUSEWHEEL; + msg.wParam=GetMousewParam(120); + break; + + case 5: + msg.hwnd = FocusWindow.Handle; + msg.message=Msg.WM_MOUSEWHEEL; + msg.wParam=GetMousewParam(-120); + break; + } + + msg.lParam=(IntPtr) (xevent.ButtonEvent.y << 16 | xevent.ButtonEvent.x); + MousePosition.X = xevent.ButtonEvent.x; + MousePosition.Y = xevent.ButtonEvent.y; + + if (!hwnd.Enabled) { + RedirectMsgToEnabledAncestor (hwnd, msg, xevent.AnyEvent.window, + ref xevent.ButtonEvent.x, ref xevent.ButtonEvent.y); + } + + if (Grab.Hwnd != IntPtr.Zero) + msg.hwnd = Grab.Hwnd; + + if (ClickPending.Pending && + ((((long)xevent.ButtonEvent.time - ClickPending.Time) < DoubleClickInterval) && + (msg.wParam == ClickPending.wParam) && + (msg.lParam == ClickPending.lParam) && + (msg.message == ClickPending.Message))) { + // Looks like a genuine double click, clicked twice on the same spot with the same keys + switch(xevent.ButtonEvent.button) { + case 1: + msg.message = client ? Msg.WM_LBUTTONDBLCLK : Msg.WM_NCLBUTTONDBLCLK; + break; + + case 2: + msg.message = client ? Msg.WM_MBUTTONDBLCLK : Msg.WM_NCMBUTTONDBLCLK; + break; + + case 3: + msg.message = client ? Msg.WM_RBUTTONDBLCLK : Msg.WM_NCRBUTTONDBLCLK; + break; + } + + ClickPending.Pending = false; + + } + else { + ClickPending.Pending = true; + ClickPending.Hwnd = msg.hwnd; + ClickPending.Message = msg.message; + ClickPending.wParam = msg.wParam; + ClickPending.lParam = msg.lParam; + ClickPending.Time = (long)xevent.ButtonEvent.time; + } + + if (msg.message == Msg.WM_LBUTTONDOWN || msg.message == Msg.WM_MBUTTONDOWN || msg.message == Msg.WM_RBUTTONDOWN) { + hwnd.SendParentNotify (msg.message, MousePosition.X, MousePosition.Y); + + // Win32 splurts MouseMove events all over the place, regardless of whether the mouse is actually moving or + // not, especially after mousedown and mouseup. To support apps relying on mousemove events between and after + // mouse clicks to repaint or whatever, we generate a mousemove event here. *sigh* + XEvent motionEvent = new XEvent (); + motionEvent.type = XEventName.MotionNotify; + motionEvent.MotionEvent.display = display; + motionEvent.MotionEvent.window = xevent.ButtonEvent.window; + motionEvent.MotionEvent.x = xevent.ButtonEvent.x; + motionEvent.MotionEvent.y = xevent.ButtonEvent.y; + hwnd.Queue.Enqueue (motionEvent); + } + + return true; + } + + case XEventName.ButtonRelease: + switch(xevent.ButtonEvent.button) { + case 1: + if (client) { + msg.message = Msg.WM_LBUTTONUP; + } else { + msg.message = Msg.WM_NCLBUTTONUP; + hwnd.MenuToScreen (ref xevent.ButtonEvent.x, ref xevent.ButtonEvent.y); + } + MouseState &= ~MouseButtons.Left; + msg.wParam=GetMousewParam(0); + break; + + case 2: + if (client) { + msg.message = Msg.WM_MBUTTONUP; + } else { + msg.message = Msg.WM_NCMBUTTONUP; + hwnd.MenuToScreen (ref xevent.ButtonEvent.x, ref xevent.ButtonEvent.y); + } + MouseState &= ~MouseButtons.Middle; + msg.wParam=GetMousewParam(0); + break; + + case 3: + if (client) { + msg.message = Msg.WM_RBUTTONUP; + } else { + msg.message = Msg.WM_NCRBUTTONUP; + hwnd.MenuToScreen (ref xevent.ButtonEvent.x, ref xevent.ButtonEvent.y); + } + MouseState &= ~MouseButtons.Right; + msg.wParam=GetMousewParam(0); + break; + + case 4: + goto ProcessNextMessage; + + case 5: + goto ProcessNextMessage; + } + + if (!hwnd.Enabled) { + RedirectMsgToEnabledAncestor (hwnd, msg, xevent.AnyEvent.window, + ref xevent.ButtonEvent.x, ref xevent.ButtonEvent.y); + } + + if (Grab.Hwnd != IntPtr.Zero) + msg.hwnd = Grab.Hwnd; + + msg.lParam=(IntPtr) (xevent.ButtonEvent.y << 16 | xevent.ButtonEvent.x); + MousePosition.X = xevent.ButtonEvent.x; + MousePosition.Y = xevent.ButtonEvent.y; + + // Win32 splurts MouseMove events all over the place, regardless of whether the mouse is actually moving or + // not, especially after mousedown and mouseup. To support apps relying on mousemove events between and after + // mouse clicks to repaint or whatever, we generate a mousemove event here. *sigh* + if (msg.message == Msg.WM_LBUTTONUP || msg.message == Msg.WM_MBUTTONUP || msg.message == Msg.WM_RBUTTONUP) { + XEvent motionEvent = new XEvent (); + motionEvent.type = XEventName.MotionNotify; + motionEvent.MotionEvent.display = display; + motionEvent.MotionEvent.window = xevent.ButtonEvent.window; + motionEvent.MotionEvent.x = xevent.ButtonEvent.x; + motionEvent.MotionEvent.y = xevent.ButtonEvent.y; + hwnd.Queue.Enqueue (motionEvent); + } + return true; + + case XEventName.MotionNotify: + /* XXX move the compression stuff here */ + + if (client) { +#if DriverDebugExtra + Console.WriteLine("GetMessage(): Window {0:X} MotionNotify x={1} y={2}", + client ? hwnd.ClientWindow.ToInt32() : hwnd.WholeWindow.ToInt32(), + xevent.MotionEvent.x, xevent.MotionEvent.y); +#endif + + if (Grab.Hwnd != IntPtr.Zero) + msg.hwnd = Grab.Hwnd; + else + NativeWindow.WndProc(msg.hwnd, Msg.WM_SETCURSOR, msg.hwnd, (IntPtr)HitTest.HTCLIENT); + + msg.message = Msg.WM_MOUSEMOVE; + msg.wParam = GetMousewParam(0); + msg.lParam = (IntPtr) (xevent.MotionEvent.y << 16 | xevent.MotionEvent.x & 0xFFFF); + + if (!hwnd.Enabled) { + RedirectMsgToEnabledAncestor (hwnd, msg, xevent.AnyEvent.window, + ref xevent.MotionEvent.x, ref xevent.MotionEvent.y); + } + + MousePosition.X = xevent.MotionEvent.x; + MousePosition.Y = xevent.MotionEvent.y; + + if ((HoverState.Timer.Enabled) && + (((MousePosition.X + HoverState.Size.Width) < HoverState.X) || + ((MousePosition.X - HoverState.Size.Width) > HoverState.X) || + ((MousePosition.Y + HoverState.Size.Height) < HoverState.Y) || + ((MousePosition.Y - HoverState.Size.Height) > HoverState.Y))) { + + HoverState.Timer.Stop(); + HoverState.Timer.Start(); + HoverState.X = MousePosition.X; + HoverState.Y = MousePosition.Y; + } + } + else { + HitTest ht; + IntPtr dummy; + int screen_x; + int screen_y; + + #if DriverDebugExtra + Console.WriteLine("GetMessage(): non-client area {0:X} MotionNotify x={1} y={2}", + client ? hwnd.ClientWindow.ToInt32() : hwnd.WholeWindow.ToInt32(), + xevent.MotionEvent.x, xevent.MotionEvent.y); + #endif + msg.message = Msg.WM_NCMOUSEMOVE; + + if (!hwnd.Enabled) { + RedirectMsgToEnabledAncestor (hwnd, msg, xevent.AnyEvent.window, + ref xevent.MotionEvent.x, ref xevent.MotionEvent.y); + } + + // The hit test is sent in screen coordinates + Xlib.XTranslateCoordinates (display, xevent.AnyEvent.window, RootWindow.Handle, + xevent.MotionEvent.x, xevent.MotionEvent.y, + out screen_x, out screen_y, out dummy); + + msg.lParam = (IntPtr) (screen_y << 16 | screen_x & 0xFFFF); + ht = (HitTest)NativeWindow.WndProc (hwnd.ClientWindow, Msg.WM_NCHITTEST, + IntPtr.Zero, msg.lParam).ToInt32 (); + NativeWindow.WndProc(hwnd.ClientWindow, Msg.WM_SETCURSOR, msg.hwnd, (IntPtr)ht); + + MousePosition.X = xevent.MotionEvent.x; + MousePosition.Y = xevent.MotionEvent.y; + } + + return true; + + case XEventName.EnterNotify: + if (!hwnd.Enabled) + goto ProcessNextMessage; + + if (xevent.CrossingEvent.mode != NotifyMode.NotifyNormal) + goto ProcessNextMessage; + + msg.message = Msg.WM_MOUSE_ENTER; + HoverState.X = xevent.CrossingEvent.x; + HoverState.Y = xevent.CrossingEvent.y; + HoverState.Timer.Enabled = true; + HoverState.Window = xevent.CrossingEvent.window; + + return true; + + case XEventName.LeaveNotify: + if (!hwnd.Enabled) + goto ProcessNextMessage; + + if ((xevent.CrossingEvent.mode != NotifyMode.NotifyNormal) || + (xevent.CrossingEvent.window != hwnd.ClientWindow)) + goto ProcessNextMessage; + + msg.message=Msg.WM_MOUSELEAVE; + HoverState.Timer.Enabled = false; + HoverState.Window = IntPtr.Zero; + + return true; + + case XEventName.ReparentNotify: + if (hwnd.parent == null) { // Toplevel + if ((xevent.ReparentEvent.parent != IntPtr.Zero) && (xevent.ReparentEvent.window == hwnd.WholeWindow)) { + // We need to adjust x/y + // This sucks ass, part 2 + // Every WM does the reparenting of toplevel windows different, so there's + // no standard way of getting our adjustment considering frames/decorations + // The code below is needed for metacity. KDE doesn't works just fine without this + int dummy_int; + IntPtr dummy_ptr; + int new_x; + int new_y; + int frame_left; + int frame_top; + + hwnd.Reparented = true; + + Xlib.XGetGeometry(display, XGetParent(hwnd.WholeWindow), + out dummy_ptr, out new_x, out new_y, + out dummy_int, out dummy_int, out dummy_int, out dummy_int); + hwnd.FrameExtents(out frame_left, out frame_top); + if ((frame_left != 0) && (frame_top != 0) && (new_x != frame_left) && (new_y != frame_top)) { + hwnd.x = new_x; + hwnd.y = new_y; + hwnd.whacky_wm = true; + } + + if (hwnd.opacity != 0xffffffff) { + IntPtr opacity; + + opacity = (IntPtr)(Int32)hwnd.opacity; + Xlib.XChangeProperty (display, XGetParent(hwnd.WholeWindow), + Atoms._NET_WM_WINDOW_OPACITY, Atoms.XA_CARDINAL, 32, + PropertyMode.Replace, ref opacity, 1); + } + SendMessage(msg.hwnd, Msg.WM_WINDOWPOSCHANGED, msg.wParam, msg.lParam); + goto ProcessNextMessage; + } else { + hwnd.Reparented = false; + goto ProcessNextMessage; + } + } + goto ProcessNextMessage; + + case XEventName.ConfigureNotify: + hwnd.HandleConfigureNotify (xevent); + goto ProcessNextMessage; + + case XEventName.MapNotify: { + if (client && (xevent.ConfigureEvent.xevent == xevent.ConfigureEvent.window)) { // Ignore events for children (SubstructureNotify) and client areas + hwnd.Mapped = true; + msg.message = Msg.WM_SHOWWINDOW; + msg.wParam = (IntPtr) 1; + // XXX we're missing the lParam.. + break; + } + goto ProcessNextMessage; + } + + case XEventName.UnmapNotify: { + if (client && (xevent.ConfigureEvent.xevent == xevent.ConfigureEvent.window)) { // Ignore events for children (SubstructureNotify) and client areas + hwnd.Mapped = false; + msg.message = Msg.WM_SHOWWINDOW; + msg.wParam = (IntPtr) 0; + // XXX we're missing the lParam.. + break; + } + goto ProcessNextMessage; + } + + case XEventName.FocusIn: + // We received focus. We use X11 focus only to know if the app window does or does not have focus + // We do not track the actual focussed window via it. Instead, this is done via FocusWindow internally + // Receiving focus means we've gotten activated and therefore we need to let the actual FocusWindow know + // about it having focus again + if (xevent.FocusChangeEvent.detail != NotifyDetail.NotifyNonlinear) + goto ProcessNextMessage; + + if (FocusWindow == null) { + Widget c = Widget.FromHandle (hwnd.ClientWindow); + if (c == null) + goto ProcessNextMessage; + Form form = c.FindForm (); + if (form == null) + goto ProcessNextMessage; + X11Hwnd new_active = (X11Hwnd)Hwnd.ObjectFromHandle (form.Handle); + if (ActiveWindow != new_active) { + ActiveWindow = new_active; + SendMessage (ActiveWindow.Handle, Msg.WM_ACTIVATE, (IntPtr) WindowActiveFlags.WA_ACTIVE, IntPtr.Zero); + } + goto ProcessNextMessage; + } + Keyboard.FocusIn(FocusWindow.Handle); + SendMessage(FocusWindow.Handle, Msg.WM_SETFOCUS, IntPtr.Zero, IntPtr.Zero); + goto ProcessNextMessage; + + case XEventName.FocusOut: + // Se the comment for our FocusIn handler + if (xevent.FocusChangeEvent.detail != NotifyDetail.NotifyNonlinear) + goto ProcessNextMessage; + + if (FocusWindow == null) + goto ProcessNextMessage; + + Keyboard.FocusOut(FocusWindow.Handle); + + while (Keyboard.ResetKeyState(FocusWindow.Handle, ref msg)) + SendMessage(FocusWindow.Handle, msg.message, msg.wParam, msg.lParam); + + SendMessage(FocusWindow.Handle, Msg.WM_KILLFOCUS, IntPtr.Zero, IntPtr.Zero); + goto ProcessNextMessage; + + case XEventName.Expose: + if (!hwnd.Mapped) { + hwnd.PendingExpose = hwnd.PendingNCExpose = false; + continue; + } + + msg.hwnd = hwnd.Handle; + + if (client) { +#if DriverDebugExtra + Console.WriteLine("GetMessage(): Window {0:X} Exposed area {1},{2} {3}x{4}", + hwnd.client_window.ToInt32(), + xevent.ExposeEvent.x, xevent.ExposeEvent.y, + xevent.ExposeEvent.width, xevent.ExposeEvent.height); +#endif + msg.message = Msg.WM_PAINT; + } + else { + Graphics g; + + switch (hwnd.border_style) { + case FormBorderStyle.Fixed3D: + g = Graphics.FromHwnd(hwnd.WholeWindow); + WidgetPaint.DrawBorder3D(g, new Rectangle(0, 0, hwnd.Width, hwnd.Height), + Border3DStyle.Sunken); + g.Dispose(); + break; + + case FormBorderStyle.FixedSingle: + g = Graphics.FromHwnd(hwnd.WholeWindow); + WidgetPaint.DrawBorder(g, new Rectangle(0, 0, hwnd.Width, hwnd.Height), + Color.Black, ButtonBorderStyle.Solid); + g.Dispose(); + break; + } +#if DriverDebugExtra + Console.WriteLine("GetMessage(): Window {0:X} Exposed non-client area {1},{2} {3}x{4}", + hwnd.ClientWindow.ToInt32(), + xevent.ExposeEvent.x, xevent.ExposeEvent.y, + xevent.ExposeEvent.width, xevent.ExposeEvent.height); +#endif + + Rectangle rect = new Rectangle (xevent.ExposeEvent.x, xevent.ExposeEvent.y, + xevent.ExposeEvent.width, xevent.ExposeEvent.height); + Region region = new Region (rect); + IntPtr hrgn = region.GetHrgn (null); // Graphics object isn't needed + msg.message = Msg.WM_NCPAINT; + msg.wParam = hrgn == IntPtr.Zero ? (IntPtr)1 : hrgn; + msg.refobject = region; + } + + return true; + + case XEventName.DestroyNotify: + + // This is a bit tricky, we don't receive our own DestroyNotify, we only get those for our children + hwnd = (X11Hwnd)Hwnd.ObjectFromHandle(xevent.DestroyWindowEvent.window); + + // We may get multiple for the same window, act only one the first (when Hwnd still knows about it) + if ((hwnd != null) && (hwnd.ClientWindow == xevent.DestroyWindowEvent.window)) { + CleanupCachedWindows (hwnd); + + #if DriverDebugDestroy + Console.WriteLine("Received X11 Destroy Notification for {0}", XplatUI.Window(hwnd.ClientWindow)); + #endif + + msg.hwnd = hwnd.ClientWindow; + msg.message=Msg.WM_DESTROY; + hwnd.Dispose(); + } + else + goto ProcessNextMessage; + + return true; + + case XEventName.ClientMessage: + if (Dnd.HandleClientMessage (ref xevent)) + goto ProcessNextMessage; + + if (xevent.ClientMessageEvent.message_type == Atoms.AsyncAtom) { + XplatUIDriverSupport.ExecuteClientMessage((GCHandle)xevent.ClientMessageEvent.ptr1); + goto ProcessNextMessage; + } + + if (xevent.ClientMessageEvent.message_type == HoverState.Atom) { + msg.message = Msg.WM_MOUSEHOVER; + msg.wParam = GetMousewParam(0); + msg.lParam = (IntPtr) (xevent.ClientMessageEvent.ptr1); + return true; + } + + if (xevent.ClientMessageEvent.message_type == Atoms.PostAtom) { + msg.hwnd = xevent.ClientMessageEvent.ptr1; + msg.message = (Msg) xevent.ClientMessageEvent.ptr2.ToInt32 (); + msg.wParam = xevent.ClientMessageEvent.ptr3; + msg.lParam = xevent.ClientMessageEvent.ptr4; + + // if we posted a WM_QUIT message, make sure we return + // false here as well. + if (msg.message == (Msg)Msg.WM_QUIT) + return false; + else + return true; + } + + if (xevent.ClientMessageEvent.message_type == Atoms._XEMBED) { +#if DriverDebugXEmbed + Console.WriteLine("GOT EMBED MESSAGE {0:X}, detail {1:X}", + xevent.ClientMessageEvent.ptr2.ToInt32(), xevent.ClientMessageEvent.ptr3.ToInt32()); +#endif + + if (xevent.ClientMessageEvent.ptr2.ToInt32() == (int)XEmbedMessage.EmbeddedNotify) { + XSizeHints hints = new XSizeHints(); + IntPtr dummy; + + Xlib.XGetWMNormalHints (display, hwnd.WholeWindow, ref hints, out dummy); + + hwnd.width = hints.max_width; + hwnd.height = hints.max_height; + hwnd.ClientRect = Rectangle.Empty; + SendMessage(msg.hwnd, Msg.WM_WINDOWPOSCHANGED, IntPtr.Zero, IntPtr.Zero); + } + } + + if (xevent.ClientMessageEvent.message_type == Atoms.WM_PROTOCOLS) { + if (xevent.ClientMessageEvent.ptr1 == Atoms.WM_DELETE_WINDOW) { + msg.message = Msg.WM_CLOSE; + return true; + } + + // We should not get this, but I'll leave the code in case we need it in the future + if (xevent.ClientMessageEvent.ptr1 == Atoms.WM_TAKE_FOCUS) { + goto ProcessNextMessage; + } + } + + goto ProcessNextMessage; + + case XEventName.PropertyNotify: + // The Hwnd's themselves handle this + hwnd.PropertyChanged (xevent); + goto ProcessNextMessage; + } + } while (true); + + msg.hwnd= IntPtr.Zero; + msg.message = Msg.WM_ENTERIDLE; + return true; + } + + [MonoTODO("Implement filtering and PM_NOREMOVE")] + public bool PeekMessage (object queue_id, ref MSG msg, IntPtr hWnd, int wFilterMin, int wFilterMax, uint flags) + { + X11ThreadQueue queue = (X11ThreadQueue) queue_id; + bool pending; + + if ((flags & (uint)PeekMessageFlags.PM_REMOVE) == 0) { + throw new NotImplementedException("PeekMessage PM_NOREMOVE is not implemented yet"); // FIXME - Implement PM_NOREMOVE flag + } + + try { + queue.Lock (); + pending = false; + if (queue.CountUnlocked > 0) + pending = true; + } + catch { + return false; + } + finally { + queue.Unlock (); + } + + queue.CheckTimers (); + + if (!pending) + return false; + + return GetMessage(queue_id, ref msg, hWnd, wFilterMin, wFilterMax); + } + + public void DoEvents (X11ThreadQueue queue) + { + MSG msg = new MSG (); + + if (OverrideCursorHandle != IntPtr.Zero) + OverrideCursorHandle = IntPtr.Zero; + + queue.DispatchIdle = false; + + while (PeekMessage(queue, ref msg, IntPtr.Zero, 0, 0, (uint)PeekMessageFlags.PM_REMOVE)) { + TranslateMessage (ref msg); + DispatchMessage (ref msg); + } + + queue.DispatchIdle = true; + } + + // double buffering support + public void CreateOffscreenDrawable (IntPtr handle, + int width, int height, + out object offscreen_drawable) + { + IntPtr root_out; + int x_out, y_out, width_out, height_out, border_width_out, depth_out; + + Xlib.XGetGeometry (display, handle, + out root_out, + out x_out, out y_out, + out width_out, out height_out, + out border_width_out, out depth_out); + + IntPtr pixmap = Xlib.XCreatePixmap (display, handle, width, height, depth_out); + + offscreen_drawable = pixmap; + } + + public void DestroyOffscreenDrawable (object offscreen_drawable) + { + Xlib.XFreePixmap (display, (IntPtr)offscreen_drawable); + } + + public Graphics GetOffscreenGraphics (object offscreen_drawable) + { + return Graphics.FromHwnd ((IntPtr) offscreen_drawable); + } + + public void BlitFromOffscreen (IntPtr dest_handle, + Graphics dest_dc, + object offscreen_drawable, + Graphics offscreen_dc, + Rectangle r) + { + XGCValues gc_values; + IntPtr gc; + + gc_values = new XGCValues(); + + gc = Xlib.XCreateGC (display, dest_handle, IntPtr.Zero, ref gc_values); + + Xlib.XCopyArea (display, (IntPtr)offscreen_drawable, dest_handle, + gc, r.X, r.Y, r.Width, r.Height, r.X, r.Y); + + Xlib.XFreeGC (display, gc); + } + + + // reversible screen-level drawing + IntPtr GetReversibleScreenGC (Color backColor) + { + XGCValues gc_values; + IntPtr gc; + uint pixel; + + XColor xcolor = new XColor(); + xcolor.red = (ushort)(backColor.R * 257); + xcolor.green = (ushort)(backColor.G * 257); + xcolor.blue = (ushort)(backColor.B * 257); + Xlib.XAllocColor (display, DefaultColormap, ref xcolor); + pixel = (uint)xcolor.pixel.ToInt32(); + + + gc_values = new XGCValues(); + + gc_values.subwindow_mode = GCSubwindowMode.IncludeInferiors; + gc_values.foreground = (IntPtr)pixel; + + gc = Xlib.XCreateGC (display, RootWindow.Handle, new IntPtr ((int) (GCFunction.GCSubwindowMode | GCFunction.GCForeground)), ref gc_values); + Xlib.XSetForeground (display, gc, (UIntPtr)pixel); + Xlib.XSetFunction (display, gc, GXFunction.GXxor); + + return gc; + } + + public void DrawReversibleLine (Point start, Point end, Color backColor) + { + if (backColor.GetBrightness() < 0.5) + backColor = Color.FromArgb(255 - backColor.R, 255 - backColor.G, 255 - backColor.B); + + IntPtr gc = GetReversibleScreenGC (backColor); + + Xlib.XDrawLine (display, RootWindow.Handle, gc, start.X, start.Y, end.X, end.Y); + + Xlib.XFreeGC (display, gc); + } + + public void FillReversibleRectangle (Rectangle rectangle, Color backColor) + { + if (backColor.GetBrightness() < 0.5) + backColor = Color.FromArgb(255 - backColor.R, 255 - backColor.G, 255 - backColor.B); + + IntPtr gc = GetReversibleScreenGC (backColor); + + if (rectangle.Width < 0) { + rectangle.X += rectangle.Width; + rectangle.Width = -rectangle.Width; + } + if (rectangle.Height < 0) { + rectangle.Y += rectangle.Height; + rectangle.Height = -rectangle.Height; + } + + Xlib.XFillRectangle (display, RootWindow.Handle, gc, rectangle.Left, rectangle.Top, rectangle.Width, rectangle.Height); + + Xlib.XFreeGC (display, gc); + } + + public void DrawReversibleFrame (Rectangle rectangle, Color backColor, FrameStyle style) + { + if (backColor.GetBrightness() < 0.5) + backColor = Color.FromArgb(255 - backColor.R, 255 - backColor.G, 255 - backColor.B); + + IntPtr gc = GetReversibleScreenGC (backColor); + + if (rectangle.Width < 0) { + rectangle.X += rectangle.Width; + rectangle.Width = -rectangle.Width; + } + if (rectangle.Height < 0) { + rectangle.Y += rectangle.Height; + rectangle.Height = -rectangle.Height; + } + + int line_width = 1; + GCLineStyle line_style = GCLineStyle.LineSolid; + GCCapStyle cap_style = GCCapStyle.CapButt; + GCJoinStyle join_style = GCJoinStyle.JoinMiter; + + switch (style) { + case FrameStyle.Dashed: + line_style = GCLineStyle.LineOnOffDash; + break; + case FrameStyle.Thick: + line_width = 2; + break; + } + + Xlib.XSetLineAttributes (display, gc, line_width, line_style, cap_style, join_style); + + Xlib.XDrawRectangle (display, RootWindow.Handle, gc, rectangle.Left, rectangle.Top, rectangle.Width, rectangle.Height); + + Xlib.XFreeGC (display, gc); + } + } +} |
