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