1 module x11window;
2 
3 import window;
4 import dg2d;
5 
6 version (linux)  : import x11.X;
7 import x11.Xlib;
8 import x11.Xatom;
9 import x11.Xutil;
10 
11 import core.stdc.config : c_ulong;
12 import core.time;
13 import std.stdio;
14 import std..string : toStringz;
15 
16 private __gshared Atom WM_DELETE_WINDOW;
17 private __gshared Atom _NET_WM_NAME;
18 private __gshared Atom UTF8_STRING;
19 private __gshared Display* display;
20 
21 private __gshared X11Window[Window] windowMap;
22 
23 void ConnectX11()
24 {
25     display = XOpenDisplay(null);
26 
27     WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", false);
28     _NET_WM_NAME = XInternAtom(display, "_NET_WM_NAME", false);
29     UTF8_STRING = XInternAtom(display, "UTF8_STRING", false);
30 }
31 
32 bool wait_fd(int fd, Duration d)
33 {
34     import core.sys.posix.sys.select;
35     import std.math : trunc;
36 
37     timeval tv;
38     fd_set in_fds;
39     FD_ZERO(&in_fds);
40     FD_SET(fd, &in_fds);
41     auto dur = d.split!("seconds", "usecs");
42     tv.tv_sec = dur.seconds;
43     tv.tv_usec = dur.usecs;
44     return !!select(fd + 1, &in_fds, null, null, &tv);
45 }
46 
47 bool XWaitForEvent(Display* display, XEvent* event, Duration time)
48 {
49     if (XPending(display) || wait_fd(ConnectionNumber(display), time))
50     {
51         return true;
52     }
53     else
54     {
55         return false;
56     }
57 }
58 
59 /*
60   Simple event loop
61 */
62 void X11EventLoop()
63 {
64     const Duration timer = 1.msecs;
65     Duration remainingTimer = timer;
66     auto lastTime = MonoTime.currTime();
67 
68     XEvent event;
69     while (windowMap.length)
70     {
71         if (remainingTimer <= Duration.zero || !XWaitForEvent(display, &event, remainingTimer))
72         {
73             remainingTimer = timer;
74             foreach (k, window; windowMap)
75                 window.wm_Timer();
76             continue;
77         }
78 
79         scope (exit)
80         {
81             auto now = MonoTime.currTime();
82             auto passed = now - lastTime;
83             remainingTimer -= passed;
84             lastTime = now;
85         }
86 
87         while (XPending(display))
88         {
89             XNextEvent(display, &event);
90             if (auto window = event.xany.window in windowMap)
91                 window.handleEvent(event);
92         }
93     }
94 }
95 
96 /*
97   bare bones X11 window class
98 */
99 class X11Window : IWindow
100 {
101 private:
102     Window handle;
103     GC gc;
104 
105     string m_title;
106     int m_width;
107     int m_height;
108     Canvas m_canvas;
109     bool wantedMorePaint = false;
110 
111     Widget m_client;
112 
113     void handleEvent(ref XEvent event)
114     {
115         switch (event.type)
116         {
117         case Expose:
118             doPaint(event.xexpose);
119             break;
120 
121         case ConfigureNotify:
122             auto xce = event.xconfigure;
123             m_width = xce.width;
124             m_height = xce.height;
125             break;
126 
127         case MotionNotify:
128             handleMouseMove(event.xmotion);
129             break;
130         case ButtonPress:
131         case ButtonRelease:
132             handleMouseClick(event.type == ButtonPress, event.xbutton);
133             break;
134 
135         case ClientMessage:
136             if (event.xclient.data.l[0] == WM_DELETE_WINDOW)
137             {
138                 XUnmapWindow(display, handle);
139                 XDestroyWindow(display, handle);
140                 windowMap.remove(handle);
141             }
142             break;
143 
144         default:
145             break;
146         }
147     }
148 
149     void handleMouseMove(XMotionEvent event)
150     {
151         MouseMsg msg;
152         msg.event = MouseEvent.Move;
153         msg.x = event.x;
154         msg.y = event.y;
155 
156         if (m_client !is null)
157         {
158             m_client.internalMouse(msg);
159         }
160     }
161 
162     void handleMouseClick(bool down, XButtonEvent event)
163     {
164         MouseMsg msg;
165         switch (event.button)
166         {
167         case 1:
168             msg.event = down ? MouseEvent.LeftDown : MouseEvent.LeftUp;
169             break;
170         case 2:
171             msg.event = down ? MouseEvent.MiddleDown : MouseEvent.MiddleUp;
172             break;
173         case 3:
174             msg.event = down ? MouseEvent.RightDown : MouseEvent.RightUp;
175             break;
176         case 4:
177         case 5:
178             msg.event = MouseEvent.Wheel;
179             // scrolling 60
180             msg.w = ((event.button - 4) * 2 - 1) * 60;
181             break;
182         default:
183             writeln("unhandled mouse button ", event.button);
184             return;
185         }
186         msg.x = event.x;
187         msg.y = event.y;
188         msg.left = (event.state & Button1Mask) != 0;
189         msg.middle = (event.state & Button2Mask) != 0;
190         msg.right = (event.state & Button3Mask) != 0;
191         msg.shift = (event.state & ShiftMask) != 0;
192         msg.ctrl = (event.state & ControlMask) != 0;
193         msg.alt = (event.state & Mod1Mask) != 0;
194         msg.super_ = (event.state & Mod4Mask) != 0;
195 
196         if (m_client !is null)
197         {
198             m_client.internalMouse(msg);
199         }
200     }
201 
202     void doPaint(XExposeEvent event)
203     {
204         if (event.count > 0)
205         {
206             wantedMorePaint = true;
207             return;
208         }
209 
210         if (wantedMorePaint)
211         {
212             event.x = 0;
213             event.y = 0;
214             event.width = m_width;
215             event.height = m_height;
216         }
217 
218         int l = event.x;
219         int t = event.y;
220         int r = l + event.width;
221         int b = t + event.height;
222 
223         if (m_canvas is null)
224             m_canvas = new Canvas(r, b);
225 
226         if ((m_canvas.width < r) || (m_canvas.height < b))
227         {
228             m_canvas.resize(r, b);
229         }
230 
231         m_canvas.resetView();
232         m_canvas.setClip(l, t, r, b);
233 
234         onPaint(m_canvas);
235         if (m_client !is null)
236             m_client.internalPaint(m_canvas);
237 
238         XImage info;
239         info.width = m_canvas.width;
240         info.height = m_canvas.height;
241         info.format = ZPixmap;
242         info.data = cast(char*) m_canvas.pixels;
243         info.char_order = LSBFirst;
244         info.bitmap_unit = 32;
245         info.bitmap_bit_order = LSBFirst;
246         info.bitmap_pad = 8;
247         info.depth = 32;
248         info.chars_per_line = m_canvas.stride * 4;
249         info.bits_per_pixel = 32;
250         info.red_mask = 0x00FF0000;
251         info.green_mask = 0x0000FF00;
252         info.blue_mask = 0x000000FF;
253         XInitImage(&info);
254 
255         XPutImage(display, handle, gc, &info, l, t, l, t, event.width, event
256                 .height);
257     }
258 
259     void wm_Timer()
260     {
261         if (m_client !is null)
262             m_client.internalTimer();
263     }
264 
265 public:
266     void create(int x, int y, int w, int h, string title)
267     {
268         m_width = w;
269         m_height = h;
270 
271         auto titlez = cast(char*) title.toStringz;
272 
273         auto root = XDefaultRootWindow(display);
274 
275         XVisualInfo visual;
276         if (XMatchVisualInfo(display, DefaultScreen(display), 32, TrueColor, &visual) == 0)
277             stderr.writeln("Failed finding 32 bit visuals, program might crash");
278 
279         XSetWindowAttributes wa;
280         wa.colormap = XCreateColormap(display, root, visual.visual, AllocNone);
281         wa.background_pixel = 0;
282         wa.border_pixel = 0;
283         wa.event_mask = ExposureMask | StructureNotifyMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask; // | KeyPressMask | ButtonPressMask
284         handle = XCreateWindow(display, root, x, y, w, h, 0, visual.depth, InputOutput, visual
285                 .visual, CWEventMask | CWBackPixel | CWColormap | CWBorderPixel, &wa);
286 
287         XStoreName(display, handle, titlez);
288         XTextProperty unicodeName;
289         unicodeName.value = cast(ubyte*) titlez;
290         unicodeName.encoding = XA_STRING;
291         unicodeName.format = 8;
292         unicodeName.nitems = title.length;
293         XSetWMName(display, handle, &unicodeName);
294         unicodeName.encoding = UTF8_STRING;
295         XSetTextProperty(display, handle, &unicodeName, _NET_WM_NAME);
296 
297         XGCValues values;
298         gc = XCreateGC(display, handle, 0, &values);
299         XSetWMProtocols(display, handle, &WM_DELETE_WINDOW, 1);
300 
301         windowMap[handle] = this;
302         XMapWindow(display, handle);
303     }
304 
305     void repaint()
306     {
307         XEvent event;
308         event.xexpose.type = Expose;
309         event.xexpose.serial = 0;
310         event.xexpose.send_event = true;
311         event.xexpose.display = display;
312         event.xexpose.window = handle;
313         event.xexpose.x = 0;
314         event.xexpose.y = 0;
315         event.xexpose.width = m_width;
316         event.xexpose.height = m_height;
317         event.xexpose.count = 0;
318         XSendEvent(display, handle, false, ExposureMask, &event);
319         XFlush(display);
320     }
321 
322     void repaint(int x0, int y0, int x1, int y1)
323     {
324         XEvent event;
325         event.xexpose.type = Expose;
326         event.xexpose.serial = 0;
327         event.xexpose.send_event = true;
328         event.xexpose.display = display;
329         event.xexpose.window = handle;
330         event.xexpose.x = x0;
331         event.xexpose.y = y0;
332         event.xexpose.width = x1 - x0;
333         event.xexpose.height = y1 - y0;
334         event.xexpose.count = 0;
335         XSendEvent(display, handle, false, ExposureMask, &event);
336         XFlush(display);
337     }
338 
339     void onPaint(Canvas canvas)
340     {
341 
342     }
343 
344     void setContent(Widget widget)
345     {
346         if (widget.m_parent !is null)
347             widget.m_parent.removeChild(widget);
348         widget.m_window = this;
349         m_client = widget;
350     }
351 }