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 }