view dynamin/gui/x_window.d @ 74:7adc733aca2d

Change Window.quickCreateGraphics() to create the handle if not already created.
author Jordan Miner <jminer7@gmail.com>
date Wed, 12 Aug 2009 05:16:30 -0500
parents e4d290aaa7ed
children 73060bc3f004
line wrap: on
line source

// Written in the D programming language
// www.digitalmars.com/d/

/*
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Dynamin library.
 *
 * The Initial Developer of the Original Code is Jordan Miner.
 * Portions created by the Initial Developer are Copyright (C) 2007-2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Jordan Miner <jminer7@gmail.com>
 *
 */

module dynamin.gui.x_window;

public import dynamin.core.string;
public import dynamin.core.global;
public import dynamin.core.math;
public import dynamin.gui.window;
public import dynamin.gui.key;
public import dynamin.c.xlib;
public import dynamin.c.xlib : XWindow = Window;
public import dynamin.c.xmu;
public import dynamin.c.cairo;
public import dynamin.c.cairo_xlib;
public import tango.stdc.string;
public import tango.io.Stdout;

/*
** Window property:
** _NET_FRAME_EXTENTS(CARDINAL) = 4, 4, 23, 4
**    left, right, top and bottom border sizes
*/

Window[XWindow] windows;
void setControl(XWindow handle, Window win) {
	if(win is null)
		windows.remove(handle);
	else
		windows[handle] = win;
}

Window getControl(XWindow handle) {
	auto tmp = handle in windows;
	return tmp is null ? null : *tmp;
}

/**
 * A simpler method that returns all the data in a property.
 * NOTE: the returned data still has to be freed with XFree()
 */
void* getXWindowProperty(XDisplay* d, XWindow w, XAtom prop, int* numRet = null) {
	XAtom actualType;
	int actualFormat;
	uint nitems, bytesAfter;
	void* ptr;
	XGetWindowProperty(d, w, prop,
		0, 0xFFFFFFFF, false, AnyPropertyType,
		&actualType, &actualFormat, &nitems, &bytesAfter,
		&ptr);
	if(numRet) *numRet = nitems;
	return ptr;
}
bool isWMPropertySupported(XAtom prop) {
	int count;
	XAtom* atoms = cast(XAtom*)getXWindowProperty(display,
		root, XA._NET_SUPPORTED, &count);
	scope(exit) XFree(atoms);
	for(int i = 0; i < count; ++i)
		if(atoms[i] == prop)
			return true;
	return false;
}
bool isTopLevelReparented(XWindow w) {
	XWindow root, parent;
	XWindow* children;
	uint numChildren;
	XQueryTree(display, w,
		&root, &parent, &children, &numChildren);
	return parent != root;
}

XDisplay* display;
XWindow root;
XWindow msgWin;
abstract class XA { // X atoms
static:
	XAtom _NET_SUPPORTED, _NET_WM_NAME, _NET_WORKAREA, _NET_FRAME_EXTENTS;
	XAtom _NET_REQUEST_FRAME_EXTENTS;
	XAtom _NET_MOVERESIZE_WINDOW;
	XAtom _NET_WM_WINDOW_TYPE;
	XAtom _NET_WM_WINDOW_TYPE_MENU, _NET_WM_WINDOW_TYPE_UTILITY;
	XAtom _NET_WM_WINDOW_TYPE_SPLASH;
	XAtom _NET_WM_WINDOW_TYPE_DIALOG, _NET_WM_WINDOW_TYPE_NORMAL;
	XAtom WM_PROTOCOLS, WM_DELETE_WINDOW, _NET_WM_SYNC_REQUEST;
	XAtom UTF8_STRING, ATOM;
	XAtom _MOTIF_WM_HINTS;
	XAtom CLIPBOARD, PRIMARY, TARGETS, CLIPBOARD_MANAGER, SAVE_TARGETS;
	XAtom DYNAMIN_SELECTION;
}
static this() {
	display = XOpenDisplay(null);
	if(!display)
		Stdout("XOpenDisplay() failed").newline;
	root = XRootWindow(display, XDefaultScreen(display));

	msgWin = XCreateSimpleWindow(display, root, 0, 0, 1, 1, 0, 0, 0);

	XA._NET_SUPPORTED   = XInternAtom(display, "_NET_SUPPORTED", false);
	XA._NET_WM_NAME     = XInternAtom(display, "_NET_WM_NAME", false);
	XA._NET_WORKAREA    = XInternAtom(display, "_NET_WORKAREA", false);
	XA._NET_FRAME_EXTENTS = XInternAtom(display, "_NET_FRAME_EXTENTS", false);
	XA._NET_REQUEST_FRAME_EXTENTS =
		XInternAtom(display, "_NET_REQUEST_FRAME_EXTENTS", false);
	XA._NET_MOVERESIZE_WINDOW =
		XInternAtom(display, "_NET_MOVERESIZE_WINDOW", false);
	XA._NET_WM_WINDOW_TYPE =
		XInternAtom(display, "_NET_WM_WINDOW_TYPE", false);
	XA._NET_WM_WINDOW_TYPE_MENU =
		XInternAtom(display, "_NET_WM_WINDOW_TYPE_MENU", false);
	XA._NET_WM_WINDOW_TYPE_UTILITY =
		XInternAtom(display, "_NET_WM_WINDOW_TYPE_UTILITY", false);
	XA._NET_WM_WINDOW_TYPE_SPLASH =
		XInternAtom(display, "_NET_WM_WINDOW_TYPE_SPLASH", false);
	XA._NET_WM_WINDOW_TYPE_DIALOG =
		XInternAtom(display, "_NET_WM_WINDOW_TYPE_DIALOG", false);
	XA._NET_WM_WINDOW_TYPE_NORMAL =
		XInternAtom(display, "_NET_WM_WINDOW_TYPE_NORMAL", false);
	XA.WM_PROTOCOLS     = XInternAtom(display, "WM_PROTOCOLS", false);
	XA.WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", false);
	XA._NET_WM_SYNC_REQUEST =
		XInternAtom(display, "_NET_WM_SYNC_REQUEST", false);
	XA.UTF8_STRING      = XInternAtom(display, "UTF8_STRING", false);
	XA.ATOM = XInternAtom(display, "ATOM", false);
	XA._MOTIF_WM_HINTS  = XInternAtom(display, "_MOTIF_WM_HINTS", false);
	XA.CLIPBOARD = XInternAtom(display, "CLIPBOARD", false);
	XA.PRIMARY = XInternAtom(display, "PRIMARY", false);
	XA.TARGETS = XInternAtom(display, "TARGETS", false);
	XA.CLIPBOARD_MANAGER = XInternAtom(display, "CLIPBOARD_MANAGER", false);
	XA.SAVE_TARGETS = XInternAtom(display, "SAVE_TARGETS", false);

	XA.DYNAMIN_SELECTION = XInternAtom(display, "DYNAMIN_SELECTION", false);
	if(!isWMPropertySupported(XA._NET_WM_NAME))
		Stdout("warning: WM does not support _NET_WM_NAME").newline;
	if(!isWMPropertySupported(XA._NET_WORKAREA))
		Stdout("warning: WM does not support _NET_WORKAREA").newline;
	if(!isWMPropertySupported(XA._NET_FRAME_EXTENTS))
		Stdout("warning: WM does not support _NET_FRAME_EXTENTS").newline;
	if(!isWMPropertySupported(XA._NET_WORKAREA))
		Stdout("warning: WM does not support _NET_WORKAREA").newline;
}

struct InvalidRect {
	Window window;
	int x, y, width, height;
}

static class PaintQueue {
import tango.util.container.LinkedList;
import dynamin.painting.graphics;
import dynamin.gui.events;
static:
	LinkedList!(InvalidRect) rects;
	static this() {
		rects = new LinkedList!(InvalidRect)();
	}
	void add(Window win, int x, int y, int width, int height) {
		auto iter = rects.iterator;
		InvalidRect* r;
		while((r = iter.next()) !is null) {
			if(r.window == win && shouldMerge(r.x, r.y, r.width, r.height,
					x, y, width, height)) {
				join(r.x, r.y, r.width, r.height,
					x, y, width, height);
				return;
			}
		}
		InvalidRect rect;
		rect.window = win;
		rect.x = x;
		rect.y = y;
		rect.width = width;
		rect.height = height;
		rects.add(rect);
	}
	bool shouldMerge(int x1, int y1, int width1, int height1,
		int x2, int y2, int width2, int height2) {
		return x2 <= x1 + width1 && y2 <= y1 + height1 &&
			x2 + width2 >= x1 && y2 + height2 >= y1;
	}
	void join(inout int x, inout int y, inout int width, inout int height,
			int x2, int y2, int width2, int height2) {
		auto minx = min(x, x2);
		auto miny = min(y, y2);
		width = max(x+width, x2+width2)-minx;
		height = max(y+height, y2+height2)-miny;
		x = minx;
		y = miny;
	}
	void paint() {
		if(rects.size == 0)
			return;
		auto iter = rects.iterator;
		InvalidRect* r;
		while((r = iter.next()) !is null)
			paint(r);
		rects.clear();
	}
	void paint(InvalidRect* rect) {
		auto surfaceWin = cairo_xlib_surface_create(
			display, rect.window.handle,
			XDefaultVisual(display, XDefaultScreen(display)),
			cast(int)rect.window.width, cast(int)rect.window.height);
		// TODO: ^ should be contentWidth/height or got from evWindow
		auto crWin = cairo_create(surfaceWin);
		cairo_surface_destroy(surfaceWin);

		auto surfaceBuff = cairo_surface_create_similar(surfaceWin, CAIRO_CONTENT_COLOR, rect.width, rect.height);
		auto crBuff = cairo_create(surfaceBuff);
		cairo_translate(crBuff,
			-rect.x-rect.window.borderSize.left,
			-rect.y-rect.window.borderSize.top);
		cairo_surface_destroy(surfaceBuff);

		cairo_set_source_rgb(crBuff, rect.window.content.backColor.R/255.0, rect.window.content.backColor.G/255.0, rect.window.content.backColor.B/255.0);
		cairo_paint(crBuff);

		cairo_set_source_rgb(crBuff, 0, 0, 0);
		cairo_set_line_width(crBuff, 1.0);

		auto g = new Graphics(crBuff);
		scope args = new PaintingEventArgs(g);
		rect.window.painting(args);
		delete g;

		cairo_set_source_surface(crWin, surfaceBuff, rect.x, rect.y);
		cairo_rectangle(crWin, rect.x, rect.y, rect.width, rect.height);
		cairo_fill(crWin);

		cairo_destroy(crBuff);
		cairo_destroy(crWin);
	}
}

Key prevKey = Key.None;

//{{{ ApplicationBackend
template ApplicationBackend() {
	void backend_run(Window w) {
		bool isWindowVisible() {
			if(w is null) return true;
			return w.visible;
		}
		XEvent ev;
		while(isWindowVisible()) {
			if(XEventsQueued(display, QueuedAlready) == 0)
				PaintQueue.paint();
			XNextEvent(display, &ev);
			auto evDisplay = ev.xany.display;
			auto evWindow = ev.xany.window;
			Window c = getControl(evWindow);
			// c will be null for SelectionRequest events
			//if(c is null)
			//	continue;
			//{{{ helper functions
			void createMouseEvent(void delegate(MouseEventArgs args) func) {
				MouseButton button;
				auto buttonEv = &ev.xbutton;
				switch(buttonEv.button) {
				case 1: button = MouseButton.Left; break;
				case 2: button = MouseButton.Middle; break;
				case 3: button = MouseButton.Right; break;
				default: return;
				}
				scope args = new MouseEventArgs(buttonEv.x+c._borderSize.left, buttonEv.y+c._borderSize.top, button);
				func(args);
			}
			bool isKeyDown(uint keycode) {
				ubyte[32] keys;
				XQueryKeymap(display, keys.ptr);
				return cast(bool)( (keys[keycode / 8] >> (keycode % 8)) & 1 );
			}
			//}}}
			switch(ev.type) {
			case MapNotify:
				c.mapped = true;
				break;
			case UnmapNotify:
				c.mapped = false;
				break;
			case DestroyNotify:
				setControl(evWindow, null);
				break;
			case ClientMessage:
				auto clientEv = &ev.xclient;
				if(clientEv.message_type != XA.WM_PROTOCOLS)
					break;
				if(clientEv.data.l[0] == XA.WM_DELETE_WINDOW) {
					XDestroyWindow(evDisplay, evWindow);
					return; // TODO: check event, and figure out what to do
				}
				break;
			case KeyPress:
				auto sym = XLookupKeysym(&ev.xkey, 0);
				if(sym == NoSymbol)
					break;
				// Since X gives no way to tell if a KeyPress is generated by
				// auto-repeat, prevKey is used here to tell.
				auto k = KeysymToKey(sym);
				scope args = new KeyEventArgs(k, k == prevKey);
				prevKey = k;
				Control focused = c.focusedControl ? c.focusedControl : c;
				focused.keyDown(args);
				break;
			case KeyRelease:
				// When X does auto-repeat for a held down key, it sends
				// a KeyPress and KeyRelease every time, even though the key is
				// down constantly. Here we check if the key is down, and if so,
				// not send a keyUp event.
				if(isKeyDown(ev.xkey.keycode))
					break;
				auto sym = XLookupKeysym(&ev.xkey, 0);
				if(sym == NoSymbol)
					break;
				auto k = KeysymToKey(sym);
				if(k == prevKey)
					prevKey = Key.None; // can't repeat after released
				scope args = new KeyEventArgs(k, false);
				Control focused = c.focusedControl ? c.focusedControl : c;
				focused.keyUp(args);
				break;
			case ButtonPress:
				// 4 = scroll up,   5 = scroll down
				// 6 = scroll left, 7 = scroll right
				auto b = ev.xbutton.button;
				if(b == 4 || b == 5) {
					scope args = new MouseTurnedEventArgs(b == 4 ? -3:3, false);
					c.getDescendantAtPoint(
						ev.xbutton.x+c._borderSize.left,
						ev.xbutton.y+c._borderSize.top).mouseTurned(args);
					break;
				}
				createMouseEvent((MouseEventArgs args) { c.mouseDown(args); });
				break;
			case ButtonRelease:
				createMouseEvent((MouseEventArgs args) { c.mouseUp(args); });
				break;
			case MotionNotify:
				auto motionEv = &ev.xmotion;
				Control captor = getCaptorControl();
				Point pt = Point(motionEv.x+c.borderSize.left, motionEv.y+c.borderSize.top);
				if(captor)
					pt = c.contentToContent(pt, captor);
				else
					captor = c;
				scope args = new MouseEventArgs(pt.x, pt.y, MouseButton.None);
				if(motionEv.state &
					(Button1Mask | Button2Mask | Button3Mask)) {
					captor.mouseDragged(args);
				} else
					captor.mouseMoved(args);
				break;
			case EnterNotify:
				auto enterEv = &ev.xcrossing;
				scope args = new MouseEventArgs(enterEv.x+c.borderSize.left,
					enterEv.y+c.borderSize.top, MouseButton.None);
				c.mouseMoved(args);
				break;
			case LeaveNotify:
				setHotControl(null);
				break;
			case FocusIn:
				break;
			case FocusOut:
				break;
			case Expose:
				auto exposeEv = &ev.xexpose;
				PaintQueue.add(c, exposeEv.x, exposeEv.y, exposeEv.width, exposeEv.height);
				break;
			case PropertyNotify:
				auto propertyEv = &ev.xproperty;
				if(propertyEv.atom == XA._NET_FRAME_EXTENTS &&
					propertyEv.state != PropertyDelete)
					c.backend_nativeToBorderSize();
				break;
			case ConfigureNotify:
				auto configureEv = &ev.xconfigure;
				c.repaint();
				c.backend_nativeToLocationSize();
				break;
			case SelectionRequest:
				auto selReqEv = &ev.xselectionrequest;
				XEvent fullEv;
				auto selEv = &fullEv.xselection;
				selEv.type = SelectionNotify;
				selEv.requestor = selReqEv.requestor;
				selEv.selection = selReqEv.selection;
				selEv.target = selReqEv.target;
				if(selReqEv.property != None)
					selEv.property = selReqEv.property;
				else
					selEv.property = XA.DYNAMIN_SELECTION;
				selEv.time = selReqEv.time;
				Stdout.format("requestor: {}", selReqEv.requestor).newline;
				Stdout.format("target: {}", selReqEv.target).newline;
				ClipboardData* data; // change to array when supporting multiple
				if(selReqEv.selection == XA.CLIPBOARD)
					data = &Clipboard.data;
				else if(selReqEv.selection == XA.PRIMARY)
					data = &Selection.data;
				else {
					selEv.property = None;
					XSendEvent(display, selEv.requestor, false, 0, &fullEv);
					break;
				}
				if(selReqEv.target == XA.TARGETS) {
					XChangeProperty(display, selEv.requestor, selEv.property,
						selEv.target, 32, PropModeReplace, &data.target, 1);
					XSendEvent(display, selEv.requestor, false, 0, &fullEv);
					break;
				} else if(selReqEv.target != data.target) {
					selEv.property = None;
					XSendEvent(display, selEv.requestor, false, 0, &fullEv);
					break;
				}
				XChangeProperty(display, selEv.requestor, selEv.property,
					data.target, 8, PropModeReplace, data.data, data.length);
				XSendEvent(display, selEv.requestor, false, 0, &fullEv);
				break;
			case MappingNotify:
				XRefreshKeyboardMapping(&ev.xmapping);
				break;
			default:
				break;
			}
		}
	}
	void backend_invoke(void delegate() dg) {
		// TODO:
	}
	void backend_invokeNow(void delegate() dg) {
		// TODO:
	}

}
//}}}

//{{{ KeysymToKey()
Key KeysymToKey(int sym) {
	switch(sym) {
	case XK_parenright:
	case XK_0: return Key.D0;
	case XK_exclam:
	case XK_1: return Key.D1;
	case XK_at:
	case XK_2: return Key.D2;
	case XK_numbersign:
	case XK_3: return Key.D3;
	case XK_dollar:
	case XK_4: return Key.D4;
	case XK_percent:
	case XK_5: return Key.D5;
	case XK_asciicircum:
	case XK_6: return Key.D6;
	case XK_ampersand:
	case XK_7: return Key.D7;
	case XK_asterisk:
	case XK_8: return Key.D8;
	case XK_parenleft:
	case XK_9: return Key.D9;

	case XK_F1: return Key.F1;
	case XK_F2: return Key.F2;
	case XK_F3: return Key.F3;
	case XK_F4: return Key.F4;
	case XK_F5: return Key.F5;
	case XK_F6: return Key.F6;
	case XK_F7: return Key.F7;
	case XK_F8: return Key.F8;
	case XK_F9: return Key.F9;
	case XK_F10: return Key.F10;
	case XK_F11: return Key.F11;
	case XK_F12: return Key.F12;

	case XK_Escape: return Key.Escape;
	case XK_Tab: return Key.Tab;
	case XK_BackSpace: return Key.Backspace;
	case XK_Return: return Key.Enter;
	case XK_KP_Enter: return Key.Enter;
	case XK_space: return Key.Space;

	case XK_KP_Left:
	case XK_Left: return Key.Left;
	case XK_KP_Right:
	case XK_Right: return Key.Right;
	case XK_KP_Up:
	case XK_Up: return Key.Up;
	case XK_KP_Down:
	case XK_Down: return Key.Down;

	case XK_KP_Insert:
	case XK_Insert: return Key.Insert;
	case XK_KP_Delete:
	case XK_Delete: return Key.Delete;
	case XK_KP_Home:
	case XK_Home: return Key.Home;
	case XK_KP_End:
	case XK_End: return Key.End;
	case XK_KP_Prior:
	case XK_Prior: return Key.PageUp;
	case XK_KP_Next:
	case XK_Next: return Key.PageDown;

	case XK_Print:
	case XK_Sys_Req: return Key.PrintScreen;
	case XK_Pause:
	case XK_Break: return Key.Pause;

	case XK_Caps_Lock: return Key.CapsLock;
	case XK_Num_Lock: return Key.NumLock;
	case XK_Scroll_Lock: return Key.ScrollLock;

	case XK_KP_0: return Key.NumPad0;
	case XK_KP_1: return Key.NumPad1;
	case XK_KP_2: return Key.NumPad2;
	case XK_KP_3: return Key.NumPad3;
	case XK_KP_4: return Key.NumPad4;
	case XK_KP_5: return Key.NumPad5;
	case XK_KP_6: return Key.NumPad6;
	case XK_KP_7: return Key.NumPad7;
	case XK_KP_8: return Key.NumPad8;
	case XK_KP_9: return Key.NumPad9;
	case XK_KP_Divide: return Key.NumPadDivide;
	case XK_KP_Multiply: return Key.NumPadMultiply;
	case XK_KP_Subtract: return Key.NumPadSubtract;
	case XK_KP_Add: return Key.NumPadAdd;
	case XK_KP_Decimal: return Key.NumPadDecimal;

	case XK_grave:
	case XK_asciitilde: return Key.Backquote;
	case XK_minus:
	case XK_underscore: return Key.Minus;
	case XK_equal:
	case XK_plus: return Key.Equals;
	case XK_bracketleft:
	case XK_braceleft: return Key.OpenBracket;
	case XK_bracketright:
	case XK_braceright: return Key.CloseBracket;
	case XK_backslash:
	case XK_bar: return Key.Backslash;
	case XK_semicolon:
	case XK_colon: return Key.Semicolon;
	case XK_apostrophe:
	case XK_quotedbl: return Key.Quote;
	case XK_comma:
	case XK_less: return Key.Comma;
	case XK_period:
	case XK_greater: return Key.Period;
	case XK_slash:
	case XK_question: return Key.Slash;

	//case XK_Menu: return Key.Menu;

	case XK_Shift_L:
	case XK_Shift_R: return Key.Shift;
	case XK_Control_L:
	case XK_Control_R: return Key.Control;
	case XK_Alt_L:
	case XK_Alt_R: return Key.Alt;

	//case XK_: return Key.;
	default:
		if(sym >= 0x41 && sym <= 0x5A) // Key.A - Key.Z
			return cast(Key)sym;
		if(sym >= 0x61 && sym <= 0x7A) // Key.A - Key.Z
			return cast(Key)(sym-32);
		return cast(Key)0;
	}
}
//}}}

public import tango.stdc.time;
template WindowBackend() {
	invariant {
		//if(_handle == 0)
		//	return;
		//XWindow root, parent;
		//XWindow* children;
		//uint numChildren;
		//XQueryTree(display, _handle,
		//	&root, &parent, &children, &numChildren);
		//XFree(children);
		//int x, y;
		//XWindow child;
		//XTranslateCoordinates(display, _handle, root, 0, 0, &x, &y, &child);
		//assert(_location.X == x-_borderSize.Left);
		//assert(_location.Y == y-_borderSize.Top);
		//XWindowAttributes attribs;
		//XGetWindowAttributes(display, _handle, &attribs);
	}
	XWindow _handle;
	bool mapped = false;
	bool backend_handleCreated() { return _handle > 0; }
	void backend_recreateHandle() {
		auto primaryScreenNum = XDefaultScreen(display);
		//XColor color;
		//color.red = 65535*backColor.R/255;
		//color.green = 65535*backColor.G/255;
		//color.blue = 65535*backColor.B/255;
		//if(XAllocColor(display, XDefaultColormap(display, primaryScreenNum), &color))
		//	printf("XAllocColor() failed\n");

		XSetWindowAttributes attribs;
		attribs.bit_gravity = NorthWestGravity;
		// TODO: should be backColor, and should change when backColor changes
		// call XSetWindowBackground() for this
		attribs.background_pixel = XWhitePixel(display, primaryScreenNum);
		attribs.event_mask =
			KeyPressMask |
			KeyReleaseMask |
			ButtonPressMask |
			ButtonReleaseMask |
			EnterWindowMask |
			LeaveWindowMask |
			PointerMotionMask |
			ButtonMotionMask |
			ExposureMask |
			FocusChangeMask |
			StructureNotifyMask |
			PropertyChangeMask;
		XWindow newHandle = XCreateWindow(
			display, root,
			cast(int)x, cast(int)y,
			backend_insideWidth, backend_insideHeight,
			0, CopyFromParent, InputOutput, null,
			CWBitGravity | CWBackPixel | CWEventMask, &attribs);

		setControl(newHandle, this);
		auto protocols = [XA.WM_DELETE_WINDOW];
		XSetWMProtocols(display, newHandle, protocols.ptr, protocols.length);
		if(handleCreated) {
			XDestroyWindow(display, _handle);
			XSync(display, false);
		}
		_handle = newHandle;
		text = _text; // move the text over to the new window
		visible = _visible;
		borderStyle = _borderStyle;
		//backend_nativeToBorderSize();
	}
	Graphics backend_quickCreateGraphics() {
		auto surface = cairo_xlib_surface_create(display, handle,
			XDefaultVisual(display, XDefaultScreen(display)),
			cast(int)width, cast(int)height);
		auto cr = cairo_create(surface);
		cairo_surface_destroy(surface);
		cairo_translate(cr, -borderSize.left, -borderSize.top);
		auto g = new Graphics(cr);
		cairo_destroy(cr);
		return g;
	}
	void backend_visible(bool b) {
		if(b)
			// if not created, create the handle by calling handle()
			XMapWindow(display, handle);
		else
			XUnmapWindow(display, _handle);
	}
	void backend_borderStyle(WindowBorderStyle border) {
		backend_update_NET_WM_WINDOW_TYPE();
		backend_update_MOTIF_WM_HINTS();
		backend_nativeToBorderSize();
	}
	void backend_setCurrentCursor(Cursor cur) {
		XDefineCursor(display, _handle, cur.handle);
	}

	void backend_repaint(Rect rect) {
		PaintQueue.add(this,
			cast(int)(rect.x-borderSize.left), cast(int)(rect.y-borderSize.top),
			cast(int)rect.width+1, cast(int)rect.height+1);
		//Stdout.format("invalidating x={}, y={}, width={}, height={}", rect.x, rect.y, rect.width, rect.height).newline;
	}
	void backend_resizable(bool b) {
		backend_updateWM_NORMAL_HINTS();
	}
	void backend_contentMinSizeChanged() {
		backend_updateWM_NORMAL_HINTS();
	}
	void backend_contentMaxSizeChanged() {
		backend_updateWM_NORMAL_HINTS();
	}
	void backend_location(Point pt) {
		backend_updateWM_NORMAL_HINTS();
		backend_locationSizeToNative();
	}
	void backend_size(Size size) {
		backend_updateWM_NORMAL_HINTS();
		backend_locationSizeToNative();
	}
	void backend_text(string str) {
		//auto tmp = str.ToCharPtr();
		//XTextProperty strProperty;
		//if(!XStringListToTextProperty(&tmp, 1, &strProperty))
			//printf("XStringListToTextProperty() failed - out of memory\n");
		//XSetWMName(display, _handle, &strProperty);
		XChangeProperty(display, _handle, XA._NET_WM_NAME, XA.UTF8_STRING, 8, PropModeReplace, str.ptr, str.length);
	}
	//{{{ backend specific
	void backend_updateWM_NORMAL_HINTS() {
		XSizeHints* sizeHints = XAllocSizeHints();
		scope(exit) XFree(sizeHints);
		sizeHints.flags = PMinSize | PMaxSize | PPosition | PSize;
		if(resizable) {
			sizeHints.min_width  = cast(int)content.minWidth;
			sizeHints.min_height = cast(int)content.minHeight;
			sizeHints.max_width  =
				content.maxWidth > 0 ? cast(int)content.maxWidth : 30_000;
			sizeHints.max_height =
				content.maxHeight > 0 ? cast(int)content.maxHeight : 30_000;
		} else {
			sizeHints.min_width  = sizeHints.max_width  = backend_insideWidth;
			sizeHints.min_height = sizeHints.max_height = backend_insideHeight;
		}
		sizeHints.x = cast(int)x;
		sizeHints.y = cast(int)y;
		sizeHints.width = backend_insideWidth;
		sizeHints.height = backend_insideHeight;
		XSetWMNormalHints(display, _handle, sizeHints);
	}
	void backend_update_MOTIF_WM_HINTS() {
		int[4] mwmHints;
		mwmHints[0] = 1 << 1;  // flags
		mwmHints[2] = borderStyle == WindowBorderStyle.None ? 0 : 1;  // decor
		XChangeProperty(display, _handle,
			XA._MOTIF_WM_HINTS, XA._MOTIF_WM_HINTS, 32, PropModeReplace, mwmHints.ptr, mwmHints.length);
	}
	void backend_update_NET_WM_WINDOW_TYPE() {
		XAtom[] atoms;
		// with Metacity, the decor is not being repainted from normal>dialog
		// because they are the same size
		if(borderStyle == WindowBorderStyle.Dialog)
			atoms = [XA._NET_WM_WINDOW_TYPE_DIALOG];
		else if(borderStyle == WindowBorderStyle.Tool)
			atoms = [XA._NET_WM_WINDOW_TYPE_UTILITY];
		else
			atoms = [XA._NET_WM_WINDOW_TYPE_NORMAL];
		XChangeProperty(display, _handle,
			XA._NET_WM_WINDOW_TYPE, XA.ATOM, 32, PropModeReplace, atoms.ptr, atoms.length);
	}
	// returns what width the x window should be...not including borders
	int backend_insideWidth() {
		return cast(int)(size.width-borderSize.left-borderSize.right);
	}
	// returns what height the x window should be...not including borders
	int backend_insideHeight() {
		return cast(int)(size.height-borderSize.top-borderSize.bottom);
	}
	// applies the currently set location and size to the native X Window
	void backend_locationSizeToNative() {
		Point pt = _location;
		if(!isTopLevelReparented(_handle)) {
			pt.x = pt.x + _borderSize.left;
			pt.y = pt.y + _borderSize.top;
		}
		XMoveResizeWindow(display, _handle, cast(int)pt.x, cast(int)pt.y,
			backend_insideWidth, backend_insideHeight);
		// XMoveWindow:
		//   Under Metacity, sets the location of the WM's frame.
		//   Under Compiz, sets the location of the window.
		// XResizeWindow:
		//   Under Metacity and Compiz, sets the size of the window not
		//   including the WM's frame.
	}
	// sets location and size based on where the native X Window is
	void backend_nativeToLocationSize() {
		XWindow root, parent;
		XWindow* children;
		uint numChildren;
		XQueryTree(display, _handle,
			&root, &parent, &children, &numChildren);
		XFree(children);

		int x, y;
		XWindow child;
		XTranslateCoordinates(display, _handle, root, 0, 0, &x, &y, &child);
		_location.x = x - _borderSize.left;
		_location.y = y - _borderSize.top;
		scope args = new EventArgs;
		moved(args);
		XWindowAttributes attribs;
		XGetWindowAttributes(display, _handle, &attribs);
		_size.width = attribs.width+_borderSize.left+_borderSize.right;
		_size.height = attribs.height+_borderSize.top+_borderSize.bottom;
		resized(args);

		//content._location = Point(_borderSize.Left, _borderSize.Top);
		//content._size = Size(attribs.width, attribs.height);
		//Stdout.format("location updated to {}", _location).newline;
		//Stdout.format("size updated to {}", _size).newline;
	}
	void backend_nativeToBorderSize() {
		_borderSize = backend_getBorderSize();
		//Stdout("borderSize updated to ", _borderSize).newline;
		backend_nativeToLocationSize();
	}
	BorderSize backend_getBorderSize() {
		if(!isWMPropertySupported(XA._NET_FRAME_EXTENTS) ||
			borderStyle == WindowBorderStyle.None)
			return BorderSize();
		// create handle if necessary
		auto reqHandle = handle;
		bool requested = false;

		//{{{ requestExtents()
		void requestExtents() {
			if(isWMPropertySupported(XA._NET_REQUEST_FRAME_EXTENTS)) {
				XEvent ev;
				ev.xclient.type = ClientMessage;
				ev.xclient.window = handle;
				ev.xclient.message_type = XA._NET_REQUEST_FRAME_EXTENTS;
				ev.xclient.format = 8;
				XSendEvent(display, root, false,
					SubstructureNotifyMask | SubstructureRedirectMask, &ev);
			} else { // compiz and beryl do not yet support requesting
				XSetWindowAttributes attribs;
				reqHandle = XCreateWindow(display,
					root, 0, 0, 1, 1,
					0, CopyFromParent, InputOnly, null, 0, &attribs);

				XWMHints* hints = XAllocWMHints();
				scope(exit) XFree(hints);
				hints.flags = InputHint;
				hints.input = false;
				XSetWMHints(display, reqHandle, hints);

				auto mainHandle = _handle;
				_handle = reqHandle;
				backend_updateWM_NORMAL_HINTS();
				backend_update_NET_WM_WINDOW_TYPE();
				backend_update_MOTIF_WM_HINTS();
				backend_visible = true;
				backend_visible = false;
				_handle = mainHandle;
			}
			requested = true;
		}
		//}}}

		if(!mapped)
			requestExtents();
		int* extents;
		while(true) {
			XSync(display, false);
			extents = cast(int*)getXWindowProperty(display, reqHandle,
				XA._NET_FRAME_EXTENTS);
			if(extents !is null)
				break;
			if(!requested)
				requestExtents();
		}
		scope(exit) XFree(extents);
		if(reqHandle != _handle)
			XDestroyWindow(display, reqHandle);
		return BorderSize(extents[0], extents[2], extents[1], extents[3]);
	}
	//}}}
}