1 module ws.wm.x11.window; 2 3 version(Posix): 4 5 import 6 std.conv, 7 std.string, 8 ws.wm, 9 ws.gui.base, 10 ws.list, 11 ws.x.draw, 12 derelict.opengl3.gl3, 13 ws.wm.x11.api; 14 15 import derelictX = derelict.util.xtypes; 16 17 __gshared: 18 19 20 class X11Window: Base { 21 22 Mouse.cursor cursor = Mouse.cursor.inherit; 23 string title; 24 bool isActive = true; 25 WindowHandle windowHandle; 26 GraphicsContext graphicsContext; 27 List!Event eventQueue; 28 29 XIC inputContext; 30 int oldX, oldY; 31 int jumpTargetX, jumpTargetY; 32 33 bool mouseFocus; 34 35 this(WindowHandle handle){ 36 assert(handle); 37 windowHandle = handle; 38 } 39 40 XSetWindowAttributes windowAttributes; 41 42 Atom wmDelete; 43 44 this(int w, int h, string t, bool override_redirect=false){ 45 hidden = true; 46 title = t; 47 size = [w, h]; 48 eventQueue = new List!Event; 49 50 auto eventMask = 51 ExposureMask | 52 StructureNotifyMask | 53 KeyPressMask | 54 KeyReleaseMask | 55 KeymapStateMask | 56 PointerMotionMask | 57 ButtonPressMask | 58 ButtonReleaseMask | 59 EnterWindowMask | 60 LeaveWindowMask; 61 62 auto windowMask = 63 CWBorderPixel | 64 CWBitGravity | 65 CWEventMask | 66 CWColormap | 67 CWBackPixmap | 68 (override_redirect ? CWOverrideRedirect : 0); 69 70 auto root = XDefaultRootWindow(wm.displayHandle); 71 72 windowAttributes.override_redirect = override_redirect; 73 windowAttributes.background_pixmap = None; 74 windowAttributes.event_mask = eventMask; 75 windowAttributes.border_pixel = 0; 76 windowAttributes.bit_gravity = StaticGravity; 77 windowAttributes.colormap = XCreateColormap(wm.displayHandle, root, wm.graphicsInfo.visual, AllocNone); 78 79 windowHandle = XCreateWindow( 80 wm.displayHandle, 81 root, 82 0, 0, cast(size_t)size.x, cast(size_t)size.y, 0, 83 wm.graphicsInfo.depth, 84 InputOutput, 85 wm.graphicsInfo.visual, 86 windowMask, 87 &windowAttributes 88 ); 89 90 inputContext = XCreateIC( 91 XOpenIM(wm.displayHandle, null, null, null), 92 XNClientWindow, windowHandle, 93 XNFocusWindow, windowHandle, 94 XNInputStyle, XIMPreeditNothing | XIMStatusNothing, 95 null 96 ); 97 XSelectInput(wm.displayHandle, windowHandle, eventMask); 98 wmDelete = XInternAtom(wm.displayHandle, "WM_DELETE_WINDOW".toStringz, True); 99 XSetWMProtocols(wm.displayHandle, windowHandle, &wmDelete, 1); 100 gcInit; 101 drawInit; 102 assert(windowHandle); 103 } 104 105 106 override void show(){ 107 if(!hidden) 108 return; 109 XMapWindow(wm.displayHandle, windowHandle); 110 gcActivate; 111 onShow; 112 onKeyboardFocus(true); 113 resized(size); 114 } 115 116 override void onShow(){ 117 hidden = false; 118 } 119 120 void close(){ 121 isActive = false; 122 XDestroyWindow(wm.displayHandle, windowHandle); 123 } 124 125 override void hide(){ 126 if(hidden) 127 return; 128 XUnmapWindow(wm.displayHandle, windowHandle); 129 onHide; 130 //wm.windows.remove(this); 131 } 132 133 override void onHide(){ 134 hidden = true; 135 } 136 137 void swapBuffers(){ 138 glXSwapBuffers(wm.displayHandle, cast(uint)windowHandle); 139 } 140 141 override void onDraw(){ 142 super.onDraw; 143 draw.finishFrame; 144 } 145 146 void onDestroy(){ 147 isActive = false; 148 draw.destroy; 149 } 150 151 @property 152 bool active(){ 153 return isActive; 154 } 155 156 void onRawMouse(int x, int y){} 157 158 159 override void setCursor(Mouse.cursor cursor){ 160 struct cursorCacheEntry { 161 uint shape; 162 int cached; 163 } 164 static cursorCacheEntry[] cursorCache = [ 165 {XC_arrow, 0}, 166 {XC_top_left_arrow, 0}, 167 {XC_xterm, 0}, 168 {XC_crosshair, 0}, 169 {XC_sb_v_double_arrow, 0}, 170 {XC_sb_h_double_arrow, 0}, 171 {XC_top_right_corner, 0}, 172 {XC_top_left_corner, 0}, 173 {XC_bottom_right_corner, 0}, 174 {XC_bottom_left_corner, 0}, 175 {XC_hand1, 0}, 176 ]; 177 int c = 0; 178 if((cast(int)cursor >= 0) && (cast(int)cursor < cursorCache.sizeof / cursorCache[0].sizeof)){ 179 cursorCacheEntry *entry = &cursorCache[cast(int)cursor]; 180 if(entry.cached == 0){ 181 entry.cached = cast(int)XCreateFontCursor(wm.displayHandle, entry.shape); 182 } 183 c = entry.cached; 184 }else{ 185 switch(cursor){ 186 case Mouse.cursor.none: 187 static Cursor cursorNone = 0; 188 if(cursorNone == 0){ 189 char[32] cursorNoneBits; 190 foreach(ref ch; cursorNoneBits) 191 ch = 0; 192 XColor dontCare; 193 Pixmap cursorNonePixmap; 194 //memset(cursorNoneBits, 0, cursorNoneBits.sizeof); 195 //memset(&dontCare, 0, dontCare.sizeof); 196 cursorNonePixmap = XCreateBitmapFromData( 197 wm.displayHandle, 198 XDefaultRootWindow(wm.displayHandle), 199 cursorNoneBits.ptr, 16, 16 200 ); 201 if(cursorNonePixmap != 0){ 202 cursorNone = XCreatePixmapCursor( 203 wm.displayHandle, cursorNonePixmap, 204 cursorNonePixmap, &dontCare, 205 &dontCare, 0, 0 206 ); 207 XFreePixmap(wm.displayHandle, cursorNonePixmap); 208 } 209 } 210 c = cast(int)cursorNone; 211 break; 212 case Mouse.cursor.inherit: 213 c = 0; 214 break; 215 default: break; 216 } 217 } 218 if(cursor == Mouse.cursor.inherit) 219 XUndefineCursor(wm.displayHandle, windowHandle); 220 else 221 XDefineCursor(wm.displayHandle, windowHandle, c); 222 } 223 224 225 void setCursorPos(int x, int y){ 226 jumpTargetX = x; 227 jumpTargetY = y; 228 XWarpPointer( 229 wm.displayHandle, XDefaultRootWindow(wm.displayHandle), 230 windowHandle, 0,0,0,0, x, size.y - y 231 ); 232 XFlush(wm.displayHandle); 233 } 234 235 void setTitle(string t){ 236 title = t; 237 if(!isActive) 238 return; 239 XTextProperty tp; 240 char* c = cast(char*)title.toStringz; 241 XStringListToTextProperty(&c, 1, &tp); 242 XSetWMName(wm.displayHandle, windowHandle, &tp); 243 } 244 245 string getTitle(){ 246 Atom netWmName, utf8, actType; 247 size_t nItems, bytes; 248 int actFormat; 249 ubyte* data; 250 netWmName = XInternAtom(wm.displayHandle, "_NET_WM_NAME".toStringz, False); 251 utf8 = XInternAtom(wm.displayHandle, "UTF8_STRING".toStringz, False); 252 XGetWindowProperty( 253 wm.displayHandle, windowHandle, netWmName, 0, 0x77777777, False, utf8, 254 &actType, &actFormat, &nItems, &bytes, &data 255 ); 256 auto text = to!string(cast(char*)data); 257 XFree(data); 258 return text; 259 } 260 261 GraphicsContext gcShare(){ 262 return glXCreateContext(wm.displayHandle, cast(derelictX.XVisualInfo*)wm.graphicsInfo, cast(__GLXcontextRec*)graphicsContext, True); 263 } 264 265 266 void makeCurrent(GraphicsContext c){ 267 glXMakeCurrent(wm.displayHandle, cast(uint)windowHandle, cast(__GLXcontextRec*)c); 268 } 269 270 271 void processEvent(Event* e){ 272 switch(e.type){ 273 case ConfigureNotify: 274 if(size.x != e.xconfigure.width || size.y != e.xconfigure.height){ 275 resized([e.xconfigure.width, e.xconfigure.height]); 276 } 277 if(pos.x != e.xconfigure.x || pos.y != e.xconfigure.y){ 278 moved([e.xconfigure.x, e.xconfigure.y]); 279 } 280 break; 281 case KeyPress: 282 onKeyboard(cast(Keyboard.key)XLookupKeysym(&e.xkey,0), true); 283 char[25] str; 284 KeySym ks; 285 Status st; 286 size_t l = Xutf8LookupString(inputContext, &e.xkey, str.ptr, 25, &ks, &st); 287 foreach(dchar c; str[0..l]) 288 onKeyboard(c); 289 break; 290 case KeyRelease: onKeyboard(cast(Keyboard.key)XLookupKeysym(&e.xkey,0), false); break; 291 case MotionNotify: 292 onMouseMove(e.xmotion.x, size.y - e.xmotion.y); 293 if(distance(e.xmotion.x, jumpTargetX) > 1 || distance(e.xmotion.y, jumpTargetY) > 1) 294 //if(e.xmotion.x != jumpTargetX || e.xmotion.y != jumpTargetY) 295 onRawMouse((e.xmotion.x - oldX), (e.xmotion.y - oldY)); 296 else{ 297 jumpTargetX = int.max; 298 jumpTargetY = int.max; 299 } 300 oldX = e.xmotion.x; 301 oldY = e.xmotion.y; 302 break; 303 case ButtonPress: onMouseButton(e.xbutton.button, true, e.xbutton.x, size.h-e.xbutton.y); break; 304 case ButtonRelease: onMouseButton(e.xbutton.button, false, e.xbutton.x, size.h-e.xbutton.y); break; 305 case EnterNotify: onMouseFocus(true); mouseFocus=true; break; 306 case LeaveNotify: onMouseFocus(false); mouseFocus=false; break; 307 case MapNotify: onShow; break; 308 case UnmapNotify: onHide; break; 309 case DestroyNotify: onDestroy; break; 310 case Expose: onDraw(); break; 311 case ClientMessage: 312 if(e.xclient.message_type == wmDelete){ 313 close(); 314 } 315 break; 316 case KeymapNotify: XRefreshKeyboardMapping(&e.xmapping); break; 317 default:break; 318 } 319 } 320 321 @property 322 override bool hasMouseFocus(){ 323 return mouseFocus; 324 } 325 326 override void resize(int[2] size){ 327 XResizeWindow(wm.displayHandle, windowHandle, size.w, size.h); 328 } 329 330 override void move(int[2] pos){ 331 XMoveWindow(wm.displayHandle, windowHandle, pos.x, pos.y); 332 } 333 334 void resized(int[2] size){ 335 this.size = size; 336 if(draw) 337 draw.resize(size); 338 } 339 340 void moved(int[2] pos){ 341 this.pos = pos; 342 } 343 344 void gcInit(){ 345 try { 346 if(!wm.glCore) 347 throw new Exception("disabled"); 348 int[] attribs = [ 349 GLX_CONTEXT_MAJOR_VERSION_ARB, 3, 350 GLX_CONTEXT_MINOR_VERSION_ARB, 3, 351 0 352 ]; 353 graphicsContext = wm.glXCreateContextAttribsARB( 354 wm.displayHandle, wm.mFBConfig[0], null, cast(int)True, attribs.ptr 355 ); 356 if(!graphicsContext) 357 throw new Exception("glXCreateContextAttribsARB failed"); 358 }catch(Exception e){ 359 /+wm.glCore = false; 360 GLint att[] = [GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, 0]; 361 wm.graphicsInfo = glXChooseVisual(wm.displayHandle, 0, att.ptr); 362 graphicsContext = glXCreateContext(wm.displayHandle, wm.graphicsInfo, null, True);+/ 363 364 graphicsContext = glXCreateContext(wm.displayHandle, cast(derelictX.XVisualInfo*)wm.graphicsInfo, null, True); 365 if(!graphicsContext) 366 throw new Exception("glXCreateContext failed"); 367 } 368 gcActivate(); 369 DerelictGL3.reload(); 370 } 371 372 373 void gcActivate(){ 374 if(!wm.activeWindow) 375 wm.activeWindow = this; 376 if(graphicsContext) 377 makeCurrent(graphicsContext); 378 } 379 380 void drawInit(){ 381 _draw = new XDraw(this); 382 } 383 384 long[2] getScreenSize(){ 385 version(Windows){ 386 RECT size; 387 GetWindowRect(windowHandle, &size); 388 return [ 389 size.right - size.left, 390 size.bottom - size.top 391 ]; 392 }else 393 assert(false, "Not implemented"); 394 } 395 396 } 397 398 399 import std.math; 400 401 int distance(int a, int b){ 402 return abs(a-b); 403 } 404 405