Mercurial > projects > dynamin
annotate dynamin/gui/x_window.d @ 23:d55b5b998412
Implement built-in mouse cursors with X.
author | Jordan Miner <jminer7@gmail.com> |
---|---|
date | Sat, 25 Jul 2009 14:58:43 -0500 |
parents | 836a064828e8 |
children | 43a88caead16 |
rev | line source |
---|---|
0 | 1 // Written in the D programming language |
2 // www.digitalmars.com/d/ | |
3 | |
4 /* | |
5 * The contents of this file are subject to the Mozilla Public License Version | |
6 * 1.1 (the "License"); you may not use this file except in compliance with | |
7 * the License. You may obtain a copy of the License at | |
8 * http://www.mozilla.org/MPL/ | |
9 * | |
10 * Software distributed under the License is distributed on an "AS IS" basis, | |
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License | |
12 * for the specific language governing rights and limitations under the | |
13 * License. | |
14 * | |
15 * The Original Code is the Dynamin library. | |
16 * | |
17 * The Initial Developer of the Original Code is Jordan Miner. | |
18 * Portions created by the Initial Developer are Copyright (C) 2007-2009 | |
19 * the Initial Developer. All Rights Reserved. | |
20 * | |
21 * Contributor(s): | |
22 * Jordan Miner <jminer7@gmail.com> | |
23 * | |
24 */ | |
25 | |
26 module dynamin.gui.x_window; | |
27 | |
28 public import dynamin.core.string; | |
29 public import dynamin.core.global; | |
30 public import dynamin.core.math; | |
31 public import dynamin.gui.window; | |
32 public import dynamin.c.xlib; | |
33 public import dynamin.c.xlib : XWindow = Window; | |
34 public import dynamin.c.xmu; | |
35 public import dynamin.c.cairo; | |
36 public import dynamin.c.cairo_xlib; | |
37 public import tango.stdc.string; | |
38 public import tango.io.Stdout; | |
39 | |
40 /* | |
41 ** Window property: | |
42 ** _NET_FRAME_EXTENTS(CARDINAL) = 4, 4, 23, 4 | |
43 ** left, right, top and bottom border sizes | |
44 */ | |
45 | |
46 Window[XWindow] windows; | |
47 void setControl(XWindow handle, Window win) { | |
48 if(win is null) | |
49 windows.remove(handle); | |
50 else | |
51 windows[handle] = win; | |
52 } | |
53 | |
54 Window getControl(XWindow handle) { | |
55 auto tmp = handle in windows; | |
56 return tmp is null ? null : *tmp; | |
57 } | |
58 | |
59 /** | |
60 * A simpler method that returns all the data in a property. | |
61 * NOTE: the returned data still has to be freed with XFree() | |
62 */ | |
63 void* getXWindowProperty(XDisplay* d, XWindow w, XAtom prop, int* numRet = null) { | |
64 XAtom actualType; | |
65 int actualFormat; | |
66 uint nitems, bytesAfter; | |
67 void* ptr; | |
68 XGetWindowProperty(d, w, prop, | |
69 0, 0xFFFFFFFF, false, AnyPropertyType, | |
70 &actualType, &actualFormat, &nitems, &bytesAfter, | |
71 &ptr); | |
72 if(numRet) *numRet = nitems; | |
73 return ptr; | |
74 } | |
75 bool isWMPropertySupported(XAtom prop) { | |
76 int count; | |
77 XAtom* atoms = cast(XAtom*)getXWindowProperty(display, | |
78 root, XA._NET_SUPPORTED, &count); | |
79 scope(exit) XFree(atoms); | |
80 for(int i = 0; i < count; ++i) | |
81 if(atoms[i] == prop) | |
82 return true; | |
83 return false; | |
84 } | |
85 bool isTopLevelReparented(XWindow w) { | |
86 XWindow root, parent; | |
87 XWindow* children; | |
88 uint numChildren; | |
89 XQueryTree(display, w, | |
90 &root, &parent, &children, &numChildren); | |
91 return parent != root; | |
92 } | |
93 | |
94 XDisplay* display; | |
95 XWindow root; | |
96 XWindow msgWin; | |
97 abstract class XA { // X atoms | |
98 static: | |
99 XAtom _NET_SUPPORTED, _NET_WM_NAME, _NET_WORKAREA, _NET_FRAME_EXTENTS; | |
100 XAtom _NET_REQUEST_FRAME_EXTENTS; | |
101 XAtom _NET_MOVERESIZE_WINDOW; | |
102 XAtom _NET_WM_WINDOW_TYPE; | |
103 XAtom _NET_WM_WINDOW_TYPE_MENU, _NET_WM_WINDOW_TYPE_UTILITY; | |
104 XAtom _NET_WM_WINDOW_TYPE_SPLASH; | |
105 XAtom _NET_WM_WINDOW_TYPE_DIALOG, _NET_WM_WINDOW_TYPE_NORMAL; | |
106 XAtom WM_PROTOCOLS, WM_DELETE_WINDOW, _NET_WM_SYNC_REQUEST; | |
107 XAtom UTF8_STRING, ATOM; | |
108 XAtom _MOTIF_WM_HINTS; | |
109 XAtom CLIPBOARD, PRIMARY, TARGETS, CLIPBOARD_MANAGER, SAVE_TARGETS; | |
110 XAtom DYNAMIN_SELECTION; | |
111 } | |
112 static this() { | |
113 display = XOpenDisplay(null); | |
114 if(!display) | |
115 Stdout("XOpenDisplay() failed").newline; | |
116 root = XRootWindow(display, XDefaultScreen(display)); | |
117 | |
118 msgWin = XCreateSimpleWindow(display, root, 0, 0, 1, 1, 0, 0, 0); | |
119 | |
120 XA._NET_SUPPORTED = XInternAtom(display, "_NET_SUPPORTED", false); | |
121 XA._NET_WM_NAME = XInternAtom(display, "_NET_WM_NAME", false); | |
122 XA._NET_WORKAREA = XInternAtom(display, "_NET_WORKAREA", false); | |
123 XA._NET_FRAME_EXTENTS = XInternAtom(display, "_NET_FRAME_EXTENTS", false); | |
124 XA._NET_REQUEST_FRAME_EXTENTS = | |
125 XInternAtom(display, "_NET_REQUEST_FRAME_EXTENTS", false); | |
126 XA._NET_MOVERESIZE_WINDOW = | |
127 XInternAtom(display, "_NET_MOVERESIZE_WINDOW", false); | |
128 XA._NET_WM_WINDOW_TYPE = | |
129 XInternAtom(display, "_NET_WM_WINDOW_TYPE", false); | |
130 XA._NET_WM_WINDOW_TYPE_MENU = | |
131 XInternAtom(display, "_NET_WM_WINDOW_TYPE_MENU", false); | |
132 XA._NET_WM_WINDOW_TYPE_UTILITY = | |
133 XInternAtom(display, "_NET_WM_WINDOW_TYPE_UTILITY", false); | |
134 XA._NET_WM_WINDOW_TYPE_SPLASH = | |
135 XInternAtom(display, "_NET_WM_WINDOW_TYPE_SPLASH", false); | |
136 XA._NET_WM_WINDOW_TYPE_DIALOG = | |
137 XInternAtom(display, "_NET_WM_WINDOW_TYPE_DIALOG", false); | |
138 XA._NET_WM_WINDOW_TYPE_NORMAL = | |
139 XInternAtom(display, "_NET_WM_WINDOW_TYPE_NORMAL", false); | |
140 XA.WM_PROTOCOLS = XInternAtom(display, "WM_PROTOCOLS", false); | |
141 XA.WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", false); | |
142 XA._NET_WM_SYNC_REQUEST = | |
143 XInternAtom(display, "_NET_WM_SYNC_REQUEST", false); | |
144 XA.UTF8_STRING = XInternAtom(display, "UTF8_STRING", false); | |
145 XA.ATOM = XInternAtom(display, "ATOM", false); | |
146 XA._MOTIF_WM_HINTS = XInternAtom(display, "_MOTIF_WM_HINTS", false); | |
147 XA.CLIPBOARD = XInternAtom(display, "CLIPBOARD", false); | |
148 XA.PRIMARY = XInternAtom(display, "PRIMARY", false); | |
149 XA.TARGETS = XInternAtom(display, "TARGETS", false); | |
150 XA.CLIPBOARD_MANAGER = XInternAtom(display, "CLIPBOARD_MANAGER", false); | |
151 XA.SAVE_TARGETS = XInternAtom(display, "SAVE_TARGETS", false); | |
152 | |
153 XA.DYNAMIN_SELECTION = XInternAtom(display, "DYNAMIN_SELECTION", false); | |
154 if(!isWMPropertySupported(XA._NET_WM_NAME)) | |
155 Stdout("warning: WM does not support _NET_WM_NAME").newline; | |
156 if(!isWMPropertySupported(XA._NET_WORKAREA)) | |
157 Stdout("warning: WM does not support _NET_WORKAREA").newline; | |
158 if(!isWMPropertySupported(XA._NET_FRAME_EXTENTS)) | |
159 Stdout("warning: WM does not support _NET_FRAME_EXTENTS").newline; | |
160 if(!isWMPropertySupported(XA._NET_WORKAREA)) | |
161 Stdout("warning: WM does not support _NET_WORKAREA").newline; | |
162 } | |
163 | |
164 struct _InvalidRect { | |
165 XWindow window; | |
166 int x, y, width, height; | |
167 _InvalidRect getUnion(_InvalidRect rect) { | |
168 auto x2 = min(x, rect.x); | |
169 auto y2 = min(y, rect.y); | |
170 _InvalidRect rect2; | |
171 rect2.window = window; | |
172 rect2.width = max(x+width, rect.x+rect.width)-x2; | |
173 rect2.height = max(y+height, rect.y+rect.height)-y2; | |
174 rect2.x = x2; | |
175 rect2.y = y2; | |
176 return rect2; | |
177 } | |
178 } | |
179 /+struct InvalidRect { | |
180 XWindow window; | |
181 int x, y, width, height; | |
182 } | |
183 | |
184 static class PaintQueue { | |
185 static: | |
186 //LinkedList!(InvalidRect) rects; | |
187 InvalidRect[] rects; | |
188 static this() { | |
189 //rects = new LinkedList!(InvalidRect)(20); | |
190 rects.length = 20; | |
191 rects.length = 0; | |
192 } | |
193 void add(XWindow win, int x, int y, int width, int height) { | |
194 rects.length = rects.length + 1; | |
195 rects[$-1].window = win; | |
196 rects[$-1].x = x; | |
197 rects[$-1].y = y; | |
198 rects[$-1].width = width; | |
199 rects[$-1].height = height; | |
200 } | |
201 bool shouldMerge(int x1, int y1, int width1, int height1, | |
202 int x2, int y2, int width2, int height2) { | |
203 return x2 <= x1 + width1 && y2 <= y1 + height1 && | |
204 x2 + width2 >= x1 && y2 + height2 >= y1; | |
205 } | |
206 void Union(inout int x, inout int y, inout int width, inout int height, | |
207 int x2, int y2, int width2, int height2) { | |
208 } | |
209 void compress() { | |
210 } | |
211 }+/ | |
212 | |
213 //{{{ ApplicationBackend | |
214 template ApplicationBackend() { | |
215 void backend_run(Window w) { | |
216 bool isWindowVisible() { | |
217 if(w is null) return true; | |
218 return w.visible; | |
219 } | |
220 XEvent ev; | |
221 while(isWindowVisible()) { | |
222 if(XEventsQueued(display, QueuedAlready) == 0) { | |
223 _InvalidRect[] rects = Window.invalidRects; | |
224 Window.invalidRects.length = 0; | |
225 while(rects.length > 0) { | |
226 auto rect = rects[0]; | |
227 // TODO: fix this...right now it gens one Expose with | |
228 // the union of invalid rects | |
229 for(int i = rects.length-1; i >= 0; --i) { | |
230 if(rect.window == rects[i].window) { | |
231 rect = rect.getUnion(rects[i]); | |
232 | |
233 arrayCopy!(_InvalidRect)(rects, i+1, rects, i, rects.length-i-1); | |
234 rects.length = rects.length-1; | |
235 } | |
236 } | |
237 ev.xexpose.type = Expose; | |
238 ev.xexpose.display = display; | |
239 ev.xexpose.window = rect.window; | |
240 ev.xexpose.x = rect.x; | |
241 ev.xexpose.y = rect.y; | |
242 ev.xexpose.width = rect.width; | |
243 ev.xexpose.height = rect.height; | |
244 ev.xexpose.count = -2; // came from here | |
245 XPutBackEvent(display, &ev); | |
246 } | |
247 } | |
248 XNextEvent(display, &ev); | |
249 auto evDisplay = ev.xany.display; | |
250 auto evWindow = ev.xany.window; | |
251 Window c = getControl(evWindow); | |
252 // c will be null for SelectionRequest events | |
253 //if(c is null) | |
254 // continue; | |
255 //{{{ helper functions | |
256 void createMouseEvent(void delegate(MouseEventArgs args) func) { | |
257 MouseButton button; | |
258 auto buttonEv = ev.xbutton; | |
259 switch(buttonEv.button) { | |
260 case 1: button = MouseButton.Left; break; | |
261 case 2: button = MouseButton.Middle; break; | |
262 case 3: button = MouseButton.Right; break; | |
263 default: return; | |
264 } | |
265 scope args = new MouseEventArgs(buttonEv.x+c._borderSize.left, buttonEv.y+c._borderSize.top, button); | |
266 func(args); | |
267 } | |
268 //}}} | |
269 switch(ev.type) { | |
270 case MapNotify: | |
271 c.mapped = true; | |
272 break; | |
273 case UnmapNotify: | |
274 _InvalidRect[] rects = Window.invalidRects; | |
275 for(int i = rects.length-1; i >= 0; --i) { | |
276 if(rects[i].window == evWindow) { | |
277 arrayCopy!(_InvalidRect)( | |
278 rects, i+1, rects, i, rects.length-i-1); | |
279 rects.length = rects.length-1; | |
280 } | |
281 } | |
282 Window.invalidRects = rects; | |
283 c.mapped = false; | |
284 break; | |
285 case DestroyNotify: | |
286 setControl(evWindow, null); | |
287 break; | |
288 case ClientMessage: | |
289 auto clientEv = ev.xclient; | |
290 if(clientEv.message_type != XA.WM_PROTOCOLS) | |
291 break; | |
292 if(clientEv.data.l[0] == XA.WM_DELETE_WINDOW) { | |
293 XDestroyWindow(evDisplay, evWindow); | |
294 return; // TODO: check event, and figure out what to do | |
295 } | |
296 break; | |
297 case KeyPress: | |
298 break; | |
299 case KeyRelease: | |
300 break; | |
301 case ButtonPress: | |
302 //Button4 is wheel scroll up | |
303 //Button5 is wheel scroll down | |
304 createMouseEvent((MouseEventArgs args) { c.mouseDown(args); }); | |
305 break; | |
306 case ButtonRelease: | |
307 createMouseEvent((MouseEventArgs args) { c.mouseUp(args); }); | |
308 break; | |
309 case MotionNotify: | |
310 auto motionEv = ev.xmotion; | |
311 Control captor = getCaptorControl(); | |
312 Point pt = Point(motionEv.x+c.borderSize.left, motionEv.y+c.borderSize.top); | |
313 if(captor) | |
314 pt = c.contentToContent(pt, captor); | |
315 else | |
316 captor = c; | |
317 scope args = new MouseEventArgs(pt.x, pt.y, MouseButton.None); | |
318 if(motionEv.state & | |
319 (Button1Mask | Button2Mask | Button3Mask)) { | |
320 captor.mouseDragged(args); | |
321 } else | |
322 captor.mouseMoved(args); | |
323 break; | |
324 case EnterNotify: | |
325 break; | |
326 case LeaveNotify: | |
327 break; | |
328 case FocusIn: | |
329 break; | |
330 case FocusOut: | |
331 break; | |
332 case Expose: | |
333 // TODO: move the painting code out of here and: | |
334 // make a PaintQueue class and put this here: | |
335 // PaintQueue.add(c, exposeEv.x, exposeEv.y, exposeEv.width, exposeEv.height); | |
336 // then, in Window.repaint(), have this: | |
337 // PaintQueue.add(this, cast(int)x, cast(int)exposeEv.y, cast(int)exposeEv.width, cast(int)exposeEv.height); | |
338 // Have a PaintQueue.Compress method that merges | |
339 // all invalidated rects that touch or overlap. | |
340 // In the if(!XPending(..)) above, just loop over all the | |
341 // rects in the PaintQueue, painting them. | |
342 auto exposeEv = ev.xexpose; | |
343 if(exposeEv.count != -2) { | |
344 c.repaint(exposeEv.x, exposeEv.y, exposeEv.width, exposeEv.height); | |
345 break; | |
346 } | |
347 //printf("repainting x=%d, y=%d, width=%d, height=%d\n", | |
348 // exposeEv.x, exposeEv.y, exposeEv.width, exposeEv.height); | |
349 | |
350 auto surfaceWin = cairo_xlib_surface_create( | |
351 evDisplay, evWindow, | |
352 XDefaultVisual(evDisplay, XDefaultScreen(evDisplay)), | |
353 cast(int)c.width, cast(int)c.height); | |
354 // TODO: ^ should be contentWidth/height or got from evWindow | |
355 auto crWin = cairo_create(surfaceWin); | |
356 cairo_surface_destroy(surfaceWin); | |
357 | |
358 auto surfaceBuff = cairo_surface_create_similar(surfaceWin, CAIRO_CONTENT_COLOR, exposeEv.width, exposeEv.height); | |
359 // TODO: use cairo_translate instead, I guess, as | |
360 // I had to change the Windows backend to it... | |
361 cairo_surface_set_device_offset(surfaceBuff, -exposeEv.x-c._borderSize.left, -exposeEv.y-c._borderSize.top); | |
362 auto crBuff = cairo_create(surfaceBuff); | |
363 cairo_surface_destroy(surfaceBuff); | |
364 | |
365 cairo_set_source_rgb(crBuff, w.content.backColor.R/255.0, w.content.backColor.G/255.0, w.content.backColor.B/255.0); | |
366 cairo_paint(crBuff); | |
367 | |
368 cairo_set_source_rgb(crBuff, 0, 0, 0); | |
369 cairo_set_line_width(crBuff, 1.0); | |
370 | |
371 auto g = new Graphics(crBuff); | |
372 scope args = new PaintingEventArgs(g); | |
373 c.painting(args); | |
374 delete g; | |
375 | |
376 cairo_surface_set_device_offset(surfaceBuff, -exposeEv.x, -exposeEv.y); | |
377 cairo_set_source_surface(crWin, surfaceBuff, 0, 0); | |
378 cairo_rectangle(crWin, exposeEv.x, exposeEv.y, exposeEv.width, exposeEv.height); | |
379 cairo_fill(crWin); | |
380 | |
381 cairo_destroy(crBuff); | |
382 cairo_destroy(crWin); | |
383 break; | |
384 case PropertyNotify: | |
385 auto propertyEv = ev.xproperty; | |
386 if(propertyEv.atom == XA._NET_FRAME_EXTENTS && | |
387 propertyEv.state != PropertyDelete) | |
388 c.backend_nativeToBorderSize(); | |
389 break; | |
390 case ConfigureNotify: | |
391 auto configureEv = ev.xconfigure; | |
392 c.repaint(); | |
393 c.backend_nativeToLocationSize(); | |
394 break; | |
395 case SelectionRequest: | |
396 auto selReqEv = ev.xselectionrequest; | |
397 XEvent fullEv; | |
398 auto selEv = &fullEv.xselection; | |
399 selEv.type = SelectionNotify; | |
400 selEv.requestor = selReqEv.requestor; | |
401 selEv.selection = selReqEv.selection; | |
402 selEv.target = selReqEv.target; | |
403 if(selReqEv.property != None) | |
404 selEv.property = selReqEv.property; | |
405 else | |
406 selEv.property = XA.DYNAMIN_SELECTION; | |
407 selEv.time = selReqEv.time; | |
408 Stdout.format("requestor: {}", selReqEv.requestor).newline; | |
409 Stdout.format("target: {}", selReqEv.target).newline; | |
410 ClipboardData* data; // change to array when supporting multiple | |
411 if(selReqEv.selection == XA.CLIPBOARD) | |
412 data = &Clipboard.data; | |
413 else if(selReqEv.selection == XA.PRIMARY) | |
414 data = &Selection.data; | |
415 else { | |
416 selEv.property = None; | |
417 XSendEvent(display, selEv.requestor, false, 0, &fullEv); | |
418 break; | |
419 } | |
420 if(selReqEv.target == XA.TARGETS) { | |
421 XChangeProperty(display, selEv.requestor, selEv.property, | |
422 selEv.target, 32, PropModeReplace, &data.target, 1); | |
423 XSendEvent(display, selEv.requestor, false, 0, &fullEv); | |
424 break; | |
425 } else if(selReqEv.target != data.target) { | |
426 selEv.property = None; | |
427 XSendEvent(display, selEv.requestor, false, 0, &fullEv); | |
428 break; | |
429 } | |
430 XChangeProperty(display, selEv.requestor, selEv.property, | |
431 data.target, 8, PropModeReplace, data.data, data.length); | |
432 XSendEvent(display, selEv.requestor, false, 0, &fullEv); | |
433 break; | |
434 default: | |
435 break; | |
436 } | |
437 } | |
438 } | |
18
836a064828e8
Implement FileDialog/DirectoryDialog with GTK
Jordan Miner <jminer7@gmail.com>
parents:
0
diff
changeset
|
439 void backend_invoke(void delegate() dg) { |
836a064828e8
Implement FileDialog/DirectoryDialog with GTK
Jordan Miner <jminer7@gmail.com>
parents:
0
diff
changeset
|
440 // TODO: |
836a064828e8
Implement FileDialog/DirectoryDialog with GTK
Jordan Miner <jminer7@gmail.com>
parents:
0
diff
changeset
|
441 } |
836a064828e8
Implement FileDialog/DirectoryDialog with GTK
Jordan Miner <jminer7@gmail.com>
parents:
0
diff
changeset
|
442 void backend_invokeNow(void delegate() dg) { |
836a064828e8
Implement FileDialog/DirectoryDialog with GTK
Jordan Miner <jminer7@gmail.com>
parents:
0
diff
changeset
|
443 // TODO: |
836a064828e8
Implement FileDialog/DirectoryDialog with GTK
Jordan Miner <jminer7@gmail.com>
parents:
0
diff
changeset
|
444 } |
836a064828e8
Implement FileDialog/DirectoryDialog with GTK
Jordan Miner <jminer7@gmail.com>
parents:
0
diff
changeset
|
445 |
0 | 446 } |
447 //}}} | |
448 | |
449 public import tango.stdc.time; | |
450 template WindowBackend() { | |
451 invariant { | |
452 //if(_handle == 0) | |
453 // return; | |
454 //XWindow root, parent; | |
455 //XWindow* children; | |
456 //uint numChildren; | |
457 //XQueryTree(display, _handle, | |
458 // &root, &parent, &children, &numChildren); | |
459 //XFree(children); | |
460 //int x, y; | |
461 //XWindow child; | |
462 //XTranslateCoordinates(display, _handle, root, 0, 0, &x, &y, &child); | |
463 //assert(_location.X == x-_borderSize.Left); | |
464 //assert(_location.Y == y-_borderSize.Top); | |
465 //XWindowAttributes attribs; | |
466 //XGetWindowAttributes(display, _handle, &attribs); | |
467 } | |
468 XWindow _handle; | |
469 bool mapped = false; | |
470 bool backend_handleCreated() { return _handle > 0; } | |
471 void backend_recreateHandle() { | |
472 auto primaryScreenNum = XDefaultScreen(display); | |
473 //XColor color; | |
474 //color.red = 65535*backColor.R/255; | |
475 //color.green = 65535*backColor.G/255; | |
476 //color.blue = 65535*backColor.B/255; | |
477 //if(XAllocColor(display, XDefaultColormap(display, primaryScreenNum), &color)) | |
478 // printf("XAllocColor() failed\n"); | |
479 | |
480 XSetWindowAttributes attribs; | |
481 attribs.bit_gravity = NorthWestGravity; | |
482 // TODO: should be backColor, and should change when backColor changes | |
483 // call XSetWindowBackground() for this | |
484 attribs.background_pixel = XWhitePixel(display, primaryScreenNum); | |
485 attribs.event_mask = | |
486 KeyPressMask | | |
487 KeyReleaseMask | | |
488 ButtonPressMask | | |
489 ButtonReleaseMask | | |
490 EnterWindowMask | | |
491 LeaveWindowMask | | |
492 PointerMotionMask | | |
493 ButtonMotionMask | | |
494 ExposureMask | | |
495 FocusChangeMask | | |
496 StructureNotifyMask | | |
497 PropertyChangeMask; | |
498 XWindow newHandle = XCreateWindow( | |
499 display, root, | |
500 cast(int)x, cast(int)y, | |
501 backend_insideWidth, backend_insideHeight, | |
502 0, CopyFromParent, InputOutput, null, | |
503 CWBitGravity | CWBackPixel | CWEventMask, &attribs); | |
504 | |
505 setControl(newHandle, this); | |
506 auto protocols = [XA.WM_DELETE_WINDOW]; | |
507 XSetWMProtocols(display, newHandle, protocols.ptr, protocols.length); | |
508 if(handleCreated) { | |
509 XDestroyWindow(display, _handle); | |
510 XSync(display, false); | |
511 } | |
512 _handle = newHandle; | |
513 text = _text; // move the text over to the new window | |
514 visible = _visible; | |
515 borderStyle = _borderStyle; | |
516 //backend_nativeToBorderSize(); | |
517 } | |
518 Graphics backend_quickCreateGraphics() { | |
519 auto surface = cairo_xlib_surface_create(display, _handle, | |
520 XDefaultVisual(display, XDefaultScreen(display)), | |
521 cast(int)width, cast(int)height); | |
522 auto cr = cairo_create(surface); | |
523 cairo_surface_destroy(surface); | |
524 cairo_translate(cr, -borderSize.left, -borderSize.top); | |
525 auto g = new Graphics(cr); | |
526 cairo_destroy(cr); | |
527 return g; | |
528 } | |
529 void backend_visible(bool b) { | |
530 if(b) | |
531 // if not created, create the handle by calling handle() | |
532 XMapWindow(display, handle); | |
533 else | |
534 XUnmapWindow(display, _handle); | |
535 } | |
536 void backend_borderStyle(WindowBorderStyle border) { | |
537 backend_update_NET_WM_WINDOW_TYPE(); | |
538 backend_update_MOTIF_WM_HINTS(); | |
539 backend_nativeToBorderSize(); | |
540 } | |
23
d55b5b998412
Implement built-in mouse cursors with X.
Jordan Miner <jminer7@gmail.com>
parents:
18
diff
changeset
|
541 void backend_setCurrentCursor(Cursor cur) { |
d55b5b998412
Implement built-in mouse cursors with X.
Jordan Miner <jminer7@gmail.com>
parents:
18
diff
changeset
|
542 XDefineCursor(display, _handle, cur.handle); |
d55b5b998412
Implement built-in mouse cursors with X.
Jordan Miner <jminer7@gmail.com>
parents:
18
diff
changeset
|
543 } |
d55b5b998412
Implement built-in mouse cursors with X.
Jordan Miner <jminer7@gmail.com>
parents:
18
diff
changeset
|
544 |
0 | 545 static _InvalidRect[] invalidRects; |
546 void backend_repaint(Rect rect) { | |
547 invalidRects.length = invalidRects.length+1; | |
548 invalidRects[$-1].window = _handle; | |
549 invalidRects[$-1].x = cast(int)(rect.x-borderSize.left); | |
550 invalidRects[$-1].y = cast(int)(rect.y-borderSize.top); | |
551 invalidRects[$-1].width = cast(int)rect.width+1; | |
552 invalidRects[$-1].height = cast(int)rect.height+1; | |
553 //printf("invalidating x=%.1f, y=%.1f, width=%.1f, height=%.1f\n", rect.X, rect.Y, rect.width, rect.height); | |
554 } | |
555 void backend_resizable(bool b) { | |
556 backend_updateWM_NORMAL_HINTS(); | |
557 } | |
558 void backend_contentMinSizeChanged() { | |
559 backend_updateWM_NORMAL_HINTS(); | |
560 } | |
561 void backend_contentMaxSizeChanged() { | |
562 backend_updateWM_NORMAL_HINTS(); | |
563 } | |
564 void backend_location(Point pt) { | |
565 backend_updateWM_NORMAL_HINTS(); | |
566 backend_locationSizeToNative(); | |
567 } | |
568 void backend_size(Size size) { | |
569 backend_updateWM_NORMAL_HINTS(); | |
570 backend_locationSizeToNative(); | |
571 } | |
572 void backend_text(string str) { | |
573 //auto tmp = str.ToCharPtr(); | |
574 //XTextProperty strProperty; | |
575 //if(!XStringListToTextProperty(&tmp, 1, &strProperty)) | |
576 //printf("XStringListToTextProperty() failed - out of memory\n"); | |
577 //XSetWMName(display, _handle, &strProperty); | |
578 XChangeProperty(display, _handle, XA._NET_WM_NAME, XA.UTF8_STRING, 8, PropModeReplace, str.ptr, str.length); | |
579 } | |
580 //{{{ backend specific | |
581 void backend_updateWM_NORMAL_HINTS() { | |
582 XSizeHints* sizeHints = XAllocSizeHints(); | |
583 scope(exit) XFree(sizeHints); | |
584 sizeHints.flags = PMinSize | PMaxSize | PPosition | PSize; | |
585 if(resizable) { | |
586 sizeHints.min_width = cast(int)content.minWidth; | |
587 sizeHints.min_height = cast(int)content.minHeight; | |
588 sizeHints.max_width = | |
589 content.maxWidth > 0 ? cast(int)content.maxWidth : 30_000; | |
590 sizeHints.max_height = | |
591 content.maxHeight > 0 ? cast(int)content.maxHeight : 30_000; | |
592 } else { | |
593 sizeHints.min_width = sizeHints.max_width = backend_insideWidth; | |
594 sizeHints.min_height = sizeHints.max_height = backend_insideHeight; | |
595 } | |
596 sizeHints.x = cast(int)x; | |
597 sizeHints.y = cast(int)y; | |
598 sizeHints.width = backend_insideWidth; | |
599 sizeHints.height = backend_insideHeight; | |
600 XSetWMNormalHints(display, _handle, sizeHints); | |
601 } | |
602 void backend_update_MOTIF_WM_HINTS() { | |
603 int[4] mwmHints; | |
604 mwmHints[0] = 1 << 1; // flags | |
605 mwmHints[2] = borderStyle == WindowBorderStyle.None ? 0 : 1; // decor | |
606 XChangeProperty(display, _handle, | |
607 XA._MOTIF_WM_HINTS, XA._MOTIF_WM_HINTS, 32, PropModeReplace, mwmHints.ptr, mwmHints.length); | |
608 } | |
609 void backend_update_NET_WM_WINDOW_TYPE() { | |
610 XAtom[] atoms; | |
611 // with Metacity, the decor is not being repainted from normal>dialog | |
612 // because they are the same size | |
613 if(borderStyle == WindowBorderStyle.Dialog) | |
614 atoms = [XA._NET_WM_WINDOW_TYPE_DIALOG]; | |
615 else if(borderStyle == WindowBorderStyle.Tool) | |
616 atoms = [XA._NET_WM_WINDOW_TYPE_UTILITY]; | |
617 else | |
618 atoms = [XA._NET_WM_WINDOW_TYPE_NORMAL]; | |
619 XChangeProperty(display, _handle, | |
620 XA._NET_WM_WINDOW_TYPE, XA.ATOM, 32, PropModeReplace, atoms.ptr, atoms.length); | |
621 } | |
622 // returns what width the x window should be...not including borders | |
623 int backend_insideWidth() { | |
624 return cast(int)(size.width-borderSize.left-borderSize.right); | |
625 } | |
626 // returns what height the x window should be...not including borders | |
627 int backend_insideHeight() { | |
628 return cast(int)(size.height-borderSize.top-borderSize.bottom); | |
629 } | |
630 // applies the currently set location and size to the native X Window | |
631 void backend_locationSizeToNative() { | |
632 Point pt = _location; | |
633 if(!isTopLevelReparented(_handle)) { | |
634 pt.x = pt.x + _borderSize.left; | |
635 pt.y = pt.y + _borderSize.top; | |
636 } | |
637 XMoveResizeWindow(display, _handle, cast(int)pt.x, cast(int)pt.y, | |
638 backend_insideWidth, backend_insideHeight); | |
639 // XMoveWindow: | |
640 // Under Metacity, sets the location of the WM's frame. | |
641 // Under Compiz, sets the location of the window. | |
642 // XResizeWindow: | |
643 // Under Metacity and Compiz, sets the size of the window not | |
644 // including the WM's frame. | |
645 } | |
646 // sets location and size based on where the native X Window is | |
647 void backend_nativeToLocationSize() { | |
648 XWindow root, parent; | |
649 XWindow* children; | |
650 uint numChildren; | |
651 XQueryTree(display, _handle, | |
652 &root, &parent, &children, &numChildren); | |
653 XFree(children); | |
654 | |
655 int x, y; | |
656 XWindow child; | |
657 XTranslateCoordinates(display, _handle, root, 0, 0, &x, &y, &child); | |
658 _location.x = x - _borderSize.left; | |
659 _location.y = y - _borderSize.top; | |
660 scope args = new EventArgs; | |
661 moved(args); | |
662 XWindowAttributes attribs; | |
663 XGetWindowAttributes(display, _handle, &attribs); | |
664 _size.width = attribs.width+_borderSize.left+_borderSize.right; | |
665 _size.height = attribs.height+_borderSize.top+_borderSize.bottom; | |
666 resized(args); | |
667 | |
668 //content._location = Point(_borderSize.Left, _borderSize.Top); | |
669 //content._size = Size(attribs.width, attribs.height); | |
670 //Stdout.format("location updated to {}", _location).newline; | |
671 //Stdout.format("size updated to {}", _size).newline; | |
672 } | |
673 void backend_nativeToBorderSize() { | |
674 _borderSize = backend_getBorderSize(); | |
675 //Stdout("borderSize updated to ", _borderSize).newline; | |
676 backend_nativeToLocationSize(); | |
677 } | |
678 BorderSize backend_getBorderSize() { | |
679 if(!isWMPropertySupported(XA._NET_FRAME_EXTENTS) || | |
680 borderStyle == WindowBorderStyle.None) | |
681 return BorderSize(); | |
682 // create handle if necessary | |
683 auto reqHandle = handle; | |
684 bool requested = false; | |
685 | |
686 //{{{ requestExtents() | |
687 void requestExtents() { | |
688 if(isWMPropertySupported(XA._NET_REQUEST_FRAME_EXTENTS)) { | |
689 XEvent ev; | |
690 ev.xclient.type = ClientMessage; | |
691 ev.xclient.window = handle; | |
692 ev.xclient.message_type = XA._NET_REQUEST_FRAME_EXTENTS; | |
693 ev.xclient.format = 8; | |
694 XSendEvent(display, root, false, | |
695 SubstructureNotifyMask | SubstructureRedirectMask, &ev); | |
696 } else { // compiz and beryl do not yet support requesting | |
697 XSetWindowAttributes attribs; | |
698 reqHandle = XCreateWindow(display, | |
699 root, 0, 0, 1, 1, | |
700 0, CopyFromParent, InputOnly, null, 0, &attribs); | |
701 | |
702 XWMHints* hints = XAllocWMHints(); | |
703 scope(exit) XFree(hints); | |
704 hints.flags = InputHint; | |
705 hints.input = false; | |
706 XSetWMHints(display, reqHandle, hints); | |
707 | |
708 auto mainHandle = _handle; | |
709 _handle = reqHandle; | |
710 backend_updateWM_NORMAL_HINTS(); | |
711 backend_update_NET_WM_WINDOW_TYPE(); | |
712 backend_update_MOTIF_WM_HINTS(); | |
713 backend_visible = true; | |
714 backend_visible = false; | |
715 _handle = mainHandle; | |
716 } | |
717 requested = true; | |
718 } | |
719 //}}} | |
720 | |
721 if(!mapped) | |
722 requestExtents(); | |
723 int* extents; | |
724 while(true) { | |
725 XSync(display, false); | |
726 extents = cast(int*)getXWindowProperty(display, reqHandle, | |
727 XA._NET_FRAME_EXTENTS); | |
728 if(extents !is null) | |
729 break; | |
730 if(!requested) | |
731 requestExtents(); | |
732 } | |
733 scope(exit) XFree(extents); | |
734 if(reqHandle != _handle) | |
735 XDestroyWindow(display, reqHandle); | |
736 return BorderSize(extents[0], extents[2], extents[1], extents[3]); | |
737 } | |
738 //}}} | |
739 } | |
740 |