1 // https://dpaste.dzfl.pl/7a77355acaec 2 3 // https://freedesktop.org/wiki/Specifications/XDND/ 4 5 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format 6 7 8 // on Mac with X11: -L-L/usr/X11/lib 9 10 /+ 11 12 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works 13 14 Progress bar in taskbar 15 - i can probably just set a property on the window... 16 it sets that prop to an integer 0 .. 100. Taskbar 17 deletes it or window deletes it when it is handled. 18 - prolly display it as a nice little line at the bottom. 19 20 21 from gtk: 22 23 #define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS" 24 #define PROGRESS_PULSE_HINT "_NET_WM_XAPP_PROGRESS_PULSE" 25 26 >+ if (cardinal > 0) 27 >+ { 28 >+ XChangeProperty (GDK_DISPLAY_XDISPLAY (display), 29 >+ xid, 30 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name), 31 >+ XA_CARDINAL, 32, 32 >+ PropModeReplace, 33 >+ (guchar *) &cardinal, 1); 34 >+ } 35 >+ else 36 >+ { 37 >+ XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), 38 >+ xid, 39 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name)); 40 >+ } 41 42 from Windows: 43 44 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3 45 46 interface 47 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 ); 48 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”)); 49 listen for msg, return TRUE 50 interface->SetProgressState(hwnd, TBPF_NORMAL); 51 interface->SetProgressValue(hwnd, 40, 100); 52 53 54 My new notification system. 55 - use a unix socket? or a x property? or a udp port? 56 - could of course also get on the dbus train but ugh. 57 - it could also reply with the info as a string for easy remote examination. 58 59 +/ 60 61 /* 62 Event Loop would be nices: 63 64 * add on idle - runs when nothing else happens 65 * which can specify how long to yield for 66 * send messages without a recipient window 67 * setTimeout 68 * setInterval 69 */ 70 71 /* 72 Classic games I want to add: 73 * my tetris clone 74 * pac man 75 */ 76 77 /* 78 Text layout needs a lot of work. Plain drawText is useful but too 79 limited. It will need some kind of text context thing which it will 80 update and you can pass it on and get more details out of it. 81 82 It will need a bounding box, a current cursor location that is updated 83 as drawing continues, and various changable facts (which can also be 84 changed on the painter i guess) like font, color, size, background, 85 etc. 86 87 We can also fetch the caret location from it somehow. 88 89 Should prolly be an overload of drawText 90 91 blink taskbar / demand attention cross platform. FlashWindow and demandAttention 92 93 WS_EX_NOACTIVATE 94 WS_CHILD - owner and owned vs parent and child. Does X have something similar? 95 full screen windows. Can just set the atom on X. Windows will be harder. 96 97 moving windows. resizing windows. 98 99 hide cursor, capture cursor, change cursor. 100 101 REMEMBER: simpledisplay does NOT have to do everything! It just needs to make 102 sure the pieces are there to do its job easily and make other jobs possible. 103 */ 104 105 /++ 106 simpledisplay.d provides basic cross-platform GUI-related functionality, 107 including creating windows, drawing on them, working with the clipboard, 108 timers, OpenGL, and more. However, it does NOT provide high level GUI 109 widgets. See my minigui.d, an extension to this module, for that 110 functionality. 111 112 simpledisplay provides cross-platform wrapping for Windows and Linux 113 (and perhaps other OSes that use X11), but also does not prevent you 114 from using the underlying facilities if you need them. It has a goal 115 of working efficiently over a remote X link (at least as far as Xlib 116 reasonably allows.) 117 118 simpledisplay depends on [arsd.color|color.d], which should be available from the 119 same place where you got this file. Other than that, however, it has 120 very few dependencies and ones that don't come with the OS and/or the 121 compiler are all opt-in. 122 123 simpledisplay.d's home base is on my arsd repo on Github. The file is: 124 https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d 125 126 simpledisplay is basically stable. I plan to refactor the internals, 127 and may add new features and fix bugs, but It do not expect to 128 significantly change the API. It has been stable a few years already now. 129 130 Installation_instructions: 131 132 `simpledisplay.d` does not have any dependencies outside the 133 operating system and `color.d`, so it should just work most the 134 time, but there are a few caveats on some systems: 135 136 Please note when compiling on Win64, you need to explicitly list 137 `-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows 138 subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`. 139 140 If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`; 141 note the "w". 142 143 On Win32, you can pass `-L/subsystem:windows` if you don't want a 144 console to be automatically allocated. 145 146 On Mac, when compiling with X11, you need XQuartz and -L-L/usr/X11R6/lib passed to dmd. If using the Cocoa implementation on Mac, you need to pass `-L-framework -LCocoa` to dmd. For OpenGL, add `-L-framework -LOpenGL` to the build command. 147 148 On Ubuntu, you might need to install X11 development libraries to 149 successfully link. 150 151 $(CONSOLE 152 $ sudo apt-get install libglc-dev 153 $ sudo apt-get install libx11-dev 154 ) 155 156 157 Jump_list: 158 159 Don't worry, you don't have to read this whole documentation file! 160 161 Check out the [#Event-example] and [#Pong-example] to get started quickly. 162 163 The main classes you may want to create are [SimpleWindow], [Timer], 164 [Image], and [Sprite]. 165 166 The main functions you'll want are [setClipboardText] and [getClipboardText]. 167 168 There are also platform-specific functions available such as [XDisplayConnection] 169 and [GetAtom] for X11, among others. 170 171 See the examples and topics list below to learn more. 172 173 $(WARNING 174 There should only be one GUI thread per application, 175 and all windows should be created in it and your 176 event loop should run there. 177 178 To do otherwise is undefined behavior and has no 179 cross platform guarantees. 180 ) 181 182 $(H2 About this documentation) 183 184 The goal here is to give some complete programs as overview examples first, then a look at each major feature with working examples first, then, finally, the inline class and method list will follow. 185 186 Scan for headers for a topic - $(B they will visually stand out) - you're interested in to get started quickly and feel free to copy and paste any example as a starting point for your program. I encourage you to learn the library by experimenting with the examples! 187 188 All examples are provided with no copyright restrictions whatsoever. You do not need to credit me or carry any kind of notice with the source if you copy and paste from them. 189 190 To get started, download `simpledisplay.d` and `color.d` to a working directory. Copy an example info a file called `example.d` and compile using the command given at the top of each example. 191 192 If you need help, email me: destructionator@gmail.com or IRC us, #d on Freenode (I am destructionator or adam_d_ruppe there). If you learn something that isn't documented, I appreciate pull requests on github to this file. 193 194 At points, I will talk about implementation details in the documentation. These are sometimes 195 subject to change, but nevertheless useful to understand what is really going on. You can learn 196 more about some of the referenced things by searching the web for info about using them from C. 197 You can always look at the source of simpledisplay.d too for the most authoritative source on 198 its specific implementation. If you disagree with how I did something, please contact me so we 199 can discuss it! 200 201 $(H2 Using with fibers) 202 203 simpledisplay can be used with [core.thread.Fiber], but be warned many of the functions can use a significant amount of stack space. I recommend at least 64 KB stack for each fiber (just set through the second argument to Fiber's constructor). 204 205 Examples: 206 207 $(H3 Event-example) 208 This program creates a window and draws events inside them as they 209 happen, scrolling the text in the window as needed. Run this program 210 and experiment to get a feel for where basic input events take place 211 in the library. 212 213 --- 214 // dmd example.d simpledisplay.d color.d 215 import arsd.simpledisplay; 216 import std.conv; 217 218 void main() { 219 auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d"); 220 221 int y = 0; 222 223 void addLine(string text) { 224 auto painter = window.draw(); 225 226 if(y + painter.fontHeight >= window.height) { 227 painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight); 228 y -= painter.fontHeight; 229 } 230 231 painter.outlineColor = Color.red; 232 painter.fillColor = Color.black; 233 painter.drawRectangle(Point(0, y), window.width, painter.fontHeight); 234 235 painter.outlineColor = Color.white; 236 237 painter.drawText(Point(10, y), text); 238 239 y += painter.fontHeight; 240 } 241 242 window.eventLoop(1000, 243 () { 244 addLine("Timer went off!"); 245 }, 246 (KeyEvent event) { 247 addLine(to!string(event)); 248 }, 249 (MouseEvent event) { 250 addLine(to!string(event)); 251 }, 252 (dchar ch) { 253 addLine(to!string(ch)); 254 } 255 ); 256 } 257 --- 258 259 If you are interested in more game writing with D, check out my gamehelpers.d which builds upon simpledisplay, and its other stand-alone support modules, simpleaudio.d and joystick.d, too. 260 261 This program displays a pie chart. Clicking on a color will increase its share of the pie. 262 263 --- 264 265 --- 266 267 $(H2 Topics) 268 269 $(H3 $(ID topic-windows) Windows) 270 The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single 271 window on the user's screen. 272 273 You may create multiple windows, if the underlying platform supports it. You may check 274 `static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by 275 SimpleWindow's constructor at runtime to handle those cases. 276 277 A single running event loop will handle as many windows as needed. 278 279 setEventHandlers function 280 eventLoop function 281 draw function 282 title property 283 284 $(H3 $(ID topic-event-loops) Event loops) 285 The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries. 286 287 The most common scenario is creating a window, then calling [SimpleWindow.eventLoop|window.eventLoop] when setup is complete. You can pass several handlers to the `eventLoop` method right there: 288 289 --- 290 // dmd example.d simpledisplay.d color.d 291 import arsd.simpledisplay; 292 void main() { 293 auto window = new SimpleWindow(200, 200); 294 window.eventLoop(0, 295 delegate (dchar) { /* got a character key press */ } 296 ); 297 } 298 --- 299 300 $(TIP If you get a compile error saying "I can't use this event handler", the most common thing in my experience is passing a function instead of a delegate. The simple solution is to use the `delegate` keyword, like I did in the example above.) 301 302 On Linux, the event loop is implemented with the `epoll` system call for efficiency an extensibility to other files. On Windows, it runs a traditional `GetMessage` + `DispatchMessage` loop, with a call to `SleepEx` in each iteration to allow the thread to enter an alertable wait state regularly, primarily so Overlapped I/O callbacks will get a chance to run. 303 304 On Linux, simpledisplay also supports my [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch. 305 306 It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried. 307 308 $(H3 $(ID topic-notification-areas) Notification area (aka systray) icons) 309 Notification area icons are currently implemented on X11 and Windows. On X11, it defaults to using `libnotify` to show bubbles, if available, and will do a custom bubble window if not. You can `version=without_libnotify` to avoid this run-time dependency, if you like. 310 311 $(H3 $(ID topic-input-handling) Input handling) 312 There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events. 313 314 $(H3 $(ID topic-2d-drawing) 2d Drawing) 315 To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods. 316 317 Important: `ScreenPainter` double-buffers and will not actually update the window until its destructor is run. Always ensure the painter instance goes out-of-scope before proceeding. You can do this by calling it inside an event handler, a timer callback, or an small scope inside main. For example: 318 319 --- 320 // dmd example.d simpledisplay.d color.d 321 import arsd.simpledisplay; 322 void main() { 323 auto window = new SimpleWindow(200, 200); 324 { // introduce sub-scope 325 auto painter = window.draw(); // begin drawing 326 /* draw here */ 327 painter.outlineColor = Color.red; 328 painter.fillColor = Color.black; 329 painter.drawRectangle(Point(0, 0), 200, 200); 330 } // end scope, calling `painter`'s destructor, drawing to the screen. 331 window.eventLoop(0); // handle events 332 } 333 --- 334 335 Painting is done based on two color properties, a pen and a brush. 336 337 At this time, the 2d drawing does not support alpha blending. If you need that, use a 2d OpenGL context instead. 338 FIXME add example of 2d opengl drawing here 339 $(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL)) 340 simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing. 341 342 Note that it is still possible to draw 2d on top of an OpenGL window, using the `draw` method, though I don't recommend it. 343 344 To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor. 345 346 Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame. 347 348 To force a redraw of the scene, call [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlSceneNow()]. 349 350 Please note that my experience with OpenGL is very out-of-date, and the bindings in simpledisplay reflect that. If you want to use more modern functions, you may have to define the bindings yourself, or import them from another module. However, the OpenGL context creation done in simpledisplay will work for any version. 351 352 This example program will draw a rectangle on your window: 353 354 --- 355 // dmd example.d simpledisplay.d color.d 356 import arsd.simpledisplay; 357 358 void main() { 359 360 } 361 --- 362 $(H3 $(ID topic-modern-opengl) Modern OpenGL) 363 simpledisplay's opengl support, by default, is for "legacy" opengl. To use "modern" functions, you must opt-into them with a little more setup. But the library providers helpers for this too. 364 365 This example program shows how you can set up a shader to draw a rectangle: 366 367 --- 368 module opengl3test; 369 import arsd.simpledisplay; 370 371 // based on https://learnopengl.com/Getting-started/Hello-Triangle 372 373 void main() { 374 // First thing we do, before creating the window, is declare what version we want. 375 setOpenGLContextVersion(3, 3); 376 // turning off legacy compat is required to use version 3.3 and newer 377 openGLContextCompatible = false; 378 379 uint VAO; 380 OpenGlShader shader; 381 382 // then we can create the window. 383 auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing); 384 385 // additional setup needs to be done when it is visible, simpledisplay offers a property 386 // for exactly that: 387 window.visibleForTheFirstTime = delegate() { 388 // now with the window loaded, we can start loading the modern opengl functions. 389 390 // you MUST set the context first. 391 window.setAsCurrentOpenGlContext; 392 // then load the remainder of the library 393 gl3.loadDynamicLibrary(); 394 395 // now you can create the shaders, etc. 396 shader = new OpenGlShader( 397 OpenGlShader.Source(GL_VERTEX_SHADER, ` 398 #version 330 core 399 layout (location = 0) in vec3 aPos; 400 void main() { 401 gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); 402 } 403 `), 404 OpenGlShader.Source(GL_FRAGMENT_SHADER, ` 405 #version 330 core 406 out vec4 FragColor; 407 uniform vec4 mycolor; 408 void main() { 409 FragColor = mycolor; 410 } 411 `), 412 ); 413 414 // and do whatever other setup you want. 415 416 float[] vertices = [ 417 0.5f, 0.5f, 0.0f, // top right 418 0.5f, -0.5f, 0.0f, // bottom right 419 -0.5f, -0.5f, 0.0f, // bottom left 420 -0.5f, 0.5f, 0.0f // top left 421 ]; 422 uint[] indices = [ // note that we start from 0! 423 0, 1, 3, // first Triangle 424 1, 2, 3 // second Triangle 425 ]; 426 uint VBO, EBO; 427 glGenVertexArrays(1, &VAO); 428 // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 429 glBindVertexArray(VAO); 430 431 glGenBuffers(1, &VBO); 432 glGenBuffers(1, &EBO); 433 434 glBindBuffer(GL_ARRAY_BUFFER, VBO); 435 glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW); 436 437 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 438 glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); 439 440 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null); 441 glEnableVertexAttribArray(0); 442 443 // the library will set the initial viewport and trigger our first draw, 444 // so these next two lines are NOT needed. they are just here as comments 445 // to show what would happen next. 446 447 // glViewport(0, 0, window.width, window.height); 448 // window.redrawOpenGlSceneNow(); 449 }; 450 451 // this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;` 452 // it is our render method. 453 window.redrawOpenGlScene = delegate() { 454 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); 455 glClear(GL_COLOR_BUFFER_BIT); 456 457 glUseProgram(shader.shaderProgram); 458 459 // the shader helper class has methods to set uniforms too 460 shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0); 461 462 glBindVertexArray(VAO); 463 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null); 464 }; 465 466 window.eventLoop(0); 467 } 468 --- 469 470 This program only draws the image once because that's all that is necessary, since it is static. If you want to do animation, you might set a pulse timer (which would be a fixed max fps, not necessarily consistent) or use a render loop in a separate thread. 471 472 473 $(H3 $(ID topic-images) Displaying images) 474 You can also load PNG images using [arsd.png]. 475 476 --- 477 // dmd example.d simpledisplay.d color.d png.d 478 import arsd.simpledisplay; 479 import arsd.png; 480 481 void main() { 482 auto image = Image.fromMemoryImage(readPng("image.png")); 483 displayImage(image); 484 } 485 --- 486 487 Compile with `dmd example.d simpledisplay.d png.d`. 488 489 If you find an image file which is a valid png that [arsd.png] fails to load, please let me know. In the mean time of fixing the bug, you can probably convert the file into an easier-to-load format. Be sure to turn OFF png interlacing, as that isn't supported. Other things to try would be making the image smaller, or trying 24 bit truecolor mode with an alpha channel. 490 491 $(H3 $(ID topic-sprites) Sprites) 492 The [Sprite] class is used to make images on the display server for fast blitting to screen. This is especially important to use to support fast drawing of repeated images on a remote X11 link. 493 494 $(H3 $(ID topic-clipboard) Clipboard) 495 The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time. 496 497 It also has helpers for handling X-specific events. 498 499 $(H3 $(ID topic-timers) Timers) 500 There are two timers in simpledisplay: one is the pulse timeout you can set on the call to `window.eventLoop`, and the other is a customizable class, [Timer]. 501 502 The pulse timeout is used by setting a non-zero interval as the first argument to `eventLoop` function and adding a zero-argument delegate to handle the pulse. 503 504 --- 505 import arsd.simpledisplay; 506 507 void main() { 508 auto window = new SimpleWindow(400, 400); 509 // every 100 ms, it will draw a random line 510 // on the window. 511 window.eventLoop(100, { 512 auto painter = window.draw(); 513 514 import std.random; 515 // random color 516 painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256)); 517 // random line 518 painter.drawLine( 519 Point(uniform(0, window.width), uniform(0, window.height)), 520 Point(uniform(0, window.width), uniform(0, window.height))); 521 522 }); 523 } 524 --- 525 526 The `Timer` class works similarly, but is created separately from the event loop. (It still fires through the event loop, though.) You may make as many instances of `Timer` as you wish. 527 528 The pulse timer and instances of the [Timer] class may be combined at will. 529 530 --- 531 import arsd.simpledisplay; 532 533 void main() { 534 auto window = new SimpleWindow(400, 400); 535 auto timer = new Timer(1000, delegate { 536 auto painter = window.draw(); 537 painter.clear(); 538 }); 539 540 window.eventLoop(0); 541 } 542 --- 543 544 Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop. 545 546 $(H3 $(ID topic-os-helpers) OS-specific helpers) 547 simpledisplay carries a lot of code to help implement itself without extra dependencies, and much of this code is available for you too, so you may extend the functionality yourself. 548 549 See also: `xwindows.d` from my github. 550 551 $(H3 $(ID topic-os-extension) Extending with OS-specific functionality) 552 `handleNativeEvent` and `handleNativeGlobalEvent`. 553 554 $(H3 $(ID topic-integration) Integration with other libraries) 555 Integration with a third-party event loop is possible. 556 557 On Linux, you might want to support both terminal input and GUI input. You can do this by using simpledisplay together with eventloop.d and terminal.d. 558 559 $(H3 $(ID topic-guis) GUI widgets) 560 simpledisplay does not provide GUI widgets such as text areas, buttons, checkboxes, etc. It only gives basic windows, the ability to draw on it, receive input from it, and access native information for extension. You may write your own gui widgets with these, but you don't have to because I already did for you! 561 562 Download `minigui.d` from my github repository and add it to your project. minigui builds these things on top of simpledisplay and offers its own Window class (and subclasses) to use that wrap SimpleWindow, adding a new event and drawing model that is hookable by subwidgets, represented by their own classes. 563 564 Migrating to minigui from simpledisplay is often easy though, because they both use the same ScreenPainter API, and the same simpledisplay events are available, if you want them. (Though you may like using the minigui model, especially if you are familiar with writing web apps in the browser with Javascript.) 565 566 minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes. 567 568 $(H2 Platform-specific tips and tricks) 569 570 Windows_tips: 571 572 You can add icons or manifest files to your exe using a resource file. 573 574 To create a Windows .ico file, use the gimp or something. I'll write a helper 575 program later. 576 577 Create `yourapp.rc`: 578 579 ```rc 580 1 ICON filename.ico 581 CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest" 582 ``` 583 584 And `yourapp.exe.manifest`: 585 586 ```xml 587 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 588 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 589 <assemblyIdentity 590 version="1.0.0.0" 591 processorArchitecture="*" 592 name="CompanyName.ProductName.YourApplication" 593 type="win32" 594 /> 595 <description>Your application description here.</description> 596 <dependency> 597 <dependentAssembly> 598 <assemblyIdentity 599 type="win32" 600 name="Microsoft.Windows.Common-Controls" 601 version="6.0.0.0" 602 processorArchitecture="*" 603 publicKeyToken="6595b64144ccf1df" 604 language="*" 605 /> 606 </dependentAssembly> 607 </dependency> 608 </assembly> 609 ``` 610 611 612 $(H2 $(ID developer-notes) Developer notes) 613 614 I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa 615 implementation though. 616 617 The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both 618 suck. If I was rewriting it, I wouldn't do it that way again. 619 620 This file must not have any more required dependencies. If you need bindings, add 621 them right to this file. Once it gets into druntime and is there for a while, remove 622 bindings from here to avoid conflicts (or put them in an appropriate version block 623 so it continues to just work on old dmd), but wait a couple releases before making the 624 transition so this module remains usable with older versions of dmd. 625 626 You may have optional dependencies if needed by putting them in version blocks or 627 template functions. You may also extend the module with other modules with UFCS without 628 actually editing this - that is nice to do if you can. 629 630 Try to make functions work the same way across operating systems. I typically make 631 it thinly wrap Windows, then emulate that on Linux. 632 633 A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding 634 Phobos! So try to avoid it. 635 636 See more comments throughout the source. 637 638 I realize this file is fairly large, but over half that is just bindings at the bottom 639 or documentation at the top. Some of the classes are a bit big too, but hopefully easy 640 to understand. I suggest you jump around the source by looking for a particular 641 declaration you're interested in, like `class SimpleWindow` using your editor's search 642 function, then look at one piece at a time. 643 644 Authors: Adam D. Ruppe with the help of others. If you need help, please email me with 645 destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode. I go by 646 Destructionator or adam_d_ruppe, depending on which computer I'm logged into. 647 648 I live in the eastern United States, so I will most likely not be around at night in 649 that US east timezone. 650 651 License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License. 652 653 Building documentation: You may wish to use the `arsd.ddoc` file from my github with 654 building the documentation for simpledisplay yourself. It will give it a bit more style. 655 Simply download the arsd.ddoc file and add it to your compile command when building docs. 656 `dmd -c simpledisplay.d color.d -D arsd.ddoc` 657 +/ 658 module arsd.simpledisplay; 659 660 // FIXME: tetris demo 661 // FIXME: space invaders demo 662 // FIXME: asteroids demo 663 664 /++ $(ID Pong-example) 665 $(H3 Pong) 666 667 This program creates a little Pong-like game. Player one is controlled 668 with the keyboard. Player two is controlled with the mouse. It demos 669 the pulse timer, event handling, and some basic drawing. 670 +/ 671 version(demos) 672 unittest { 673 // dmd example.d simpledisplay.d color.d 674 import arsd.simpledisplay; 675 676 enum paddleMovementSpeed = 8; 677 enum paddleHeight = 48; 678 679 void main() { 680 auto window = new SimpleWindow(600, 400, "Pong game!"); 681 682 int playerOnePosition, playerTwoPosition; 683 int playerOneMovement, playerTwoMovement; 684 int playerOneScore, playerTwoScore; 685 686 int ballX, ballY; 687 int ballDx, ballDy; 688 689 void serve() { 690 import std.random; 691 692 ballX = window.width / 2; 693 ballY = window.height / 2; 694 ballDx = uniform(-4, 4) * 3; 695 ballDy = uniform(-4, 4) * 3; 696 if(ballDx == 0) 697 ballDx = uniform(0, 2) == 0 ? 3 : -3; 698 } 699 700 serve(); 701 702 window.eventLoop(50, // set a 50 ms timer pulls 703 // This runs once per timer pulse 704 delegate () { 705 auto painter = window.draw(); 706 707 painter.clear(); 708 709 // Update everyone's motion 710 playerOnePosition += playerOneMovement; 711 playerTwoPosition += playerTwoMovement; 712 713 ballX += ballDx; 714 ballY += ballDy; 715 716 // Bounce off the top and bottom edges of the window 717 if(ballY + 7 >= window.height) 718 ballDy = -ballDy; 719 if(ballY - 8 <= 0) 720 ballDy = -ballDy; 721 722 // Bounce off the paddle, if it is in position 723 if(ballX - 8 <= 16) { 724 if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) { 725 ballDx = -ballDx + 1; // add some speed to keep it interesting 726 ballDy += playerOneMovement; // and y movement based on your controls too 727 ballX = 24; // move it past the paddle so it doesn't wiggle inside 728 } else { 729 // Missed it 730 playerTwoScore ++; 731 serve(); 732 } 733 } 734 735 if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1 736 if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) { 737 ballDx = -ballDx - 1; 738 ballDy += playerTwoMovement; 739 ballX = window.width - 24; 740 } else { 741 // Missed it 742 playerOneScore ++; 743 serve(); 744 } 745 } 746 747 // Draw the paddles 748 painter.outlineColor = Color.black; 749 painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight)); 750 painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight)); 751 752 // Draw the ball 753 painter.fillColor = Color.red; 754 painter.outlineColor = Color.yellow; 755 painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7)); 756 757 // Draw the score 758 painter.outlineColor = Color.blue; 759 import std.conv; 760 painter.drawText(Point(64, 4), to!string(playerOneScore)); 761 painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore)); 762 763 }, 764 delegate (KeyEvent event) { 765 // Player 1's controls are the arrow keys on the keyboard 766 if(event.key == Key.Down) 767 playerOneMovement = event.pressed ? paddleMovementSpeed : 0; 768 if(event.key == Key.Up) 769 playerOneMovement = event.pressed ? -paddleMovementSpeed : 0; 770 771 }, 772 delegate (MouseEvent event) { 773 // Player 2's controls are mouse movement while the left button is held down 774 if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) { 775 if(event.dy > 0) 776 playerTwoMovement = paddleMovementSpeed; 777 else if(event.dy < 0) 778 playerTwoMovement = -paddleMovementSpeed; 779 } else { 780 playerTwoMovement = 0; 781 } 782 } 783 ); 784 } 785 } 786 787 /++ $(ID example-minesweeper) 788 789 This minesweeper demo shows how we can implement another classic 790 game with simpledisplay and shows some mouse input and basic output 791 code. 792 +/ 793 version(demos) 794 unittest { 795 import arsd.simpledisplay; 796 797 enum GameSquare { 798 mine = 0, 799 clear, 800 m1, m2, m3, m4, m5, m6, m7, m8 801 } 802 803 enum UserSquare { 804 unknown, 805 revealed, 806 flagged, 807 questioned 808 } 809 810 enum GameState { 811 inProgress, 812 lose, 813 win 814 } 815 816 GameSquare[] board; 817 UserSquare[] userState; 818 GameState gameState; 819 int boardWidth; 820 int boardHeight; 821 822 bool isMine(int x, int y) { 823 if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight) 824 return false; 825 return board[y * boardWidth + x] == GameSquare.mine; 826 } 827 828 GameState reveal(int x, int y) { 829 if(board[y * boardWidth + x] == GameSquare.clear) { 830 floodFill(userState, boardWidth, boardHeight, 831 UserSquare.unknown, UserSquare.revealed, 832 x, y, 833 (x, y) { 834 if(board[y * boardWidth + x] == GameSquare.clear) 835 return true; 836 else { 837 userState[y * boardWidth + x] = UserSquare.revealed; 838 return false; 839 } 840 }); 841 } else { 842 userState[y * boardWidth + x] = UserSquare.revealed; 843 if(isMine(x, y)) 844 return GameState.lose; 845 } 846 847 foreach(state; userState) { 848 if(state == UserSquare.unknown || state == UserSquare.questioned) 849 return GameState.inProgress; 850 } 851 852 return GameState.win; 853 } 854 855 void initializeBoard(int width, int height, int numberOfMines) { 856 boardWidth = width; 857 boardHeight = height; 858 board.length = width * height; 859 860 userState.length = width * height; 861 userState[] = UserSquare.unknown; 862 863 import std.algorithm, std.random, std.range; 864 865 board[] = GameSquare.clear; 866 867 foreach(minePosition; randomSample(iota(0, board.length), numberOfMines)) 868 board[minePosition] = GameSquare.mine; 869 870 int x; 871 int y; 872 foreach(idx, ref square; board) { 873 if(square == GameSquare.clear) { 874 int danger = 0; 875 danger += isMine(x-1, y-1)?1:0; 876 danger += isMine(x-1, y)?1:0; 877 danger += isMine(x-1, y+1)?1:0; 878 danger += isMine(x, y-1)?1:0; 879 danger += isMine(x, y+1)?1:0; 880 danger += isMine(x+1, y-1)?1:0; 881 danger += isMine(x+1, y)?1:0; 882 danger += isMine(x+1, y+1)?1:0; 883 884 square = cast(GameSquare) (danger + 1); 885 } 886 887 x++; 888 if(x == width) { 889 x = 0; 890 y++; 891 } 892 } 893 } 894 895 void redraw(SimpleWindow window) { 896 import std.conv; 897 898 auto painter = window.draw(); 899 900 painter.clear(); 901 902 final switch(gameState) with(GameState) { 903 case inProgress: 904 break; 905 case win: 906 painter.fillColor = Color.green; 907 painter.drawRectangle(Point(0, 0), window.width, window.height); 908 return; 909 case lose: 910 painter.fillColor = Color.red; 911 painter.drawRectangle(Point(0, 0), window.width, window.height); 912 return; 913 } 914 915 int x = 0; 916 int y = 0; 917 918 foreach(idx, square; board) { 919 auto state = userState[idx]; 920 921 final switch(state) with(UserSquare) { 922 case unknown: 923 painter.outlineColor = Color.black; 924 painter.fillColor = Color(128,128,128); 925 926 painter.drawRectangle( 927 Point(x * 20, y * 20), 928 20, 20 929 ); 930 break; 931 case revealed: 932 if(square == GameSquare.clear) { 933 painter.outlineColor = Color.white; 934 painter.fillColor = Color.white; 935 936 painter.drawRectangle( 937 Point(x * 20, y * 20), 938 20, 20 939 ); 940 } else { 941 painter.outlineColor = Color.black; 942 painter.fillColor = Color.white; 943 944 painter.drawText( 945 Point(x * 20, y * 20), 946 to!string(square)[1..2], 947 Point(x * 20 + 20, y * 20 + 20), 948 TextAlignment.Center | TextAlignment.VerticalCenter); 949 } 950 break; 951 case flagged: 952 painter.outlineColor = Color.black; 953 painter.fillColor = Color.red; 954 painter.drawRectangle( 955 Point(x * 20, y * 20), 956 20, 20 957 ); 958 break; 959 case questioned: 960 painter.outlineColor = Color.black; 961 painter.fillColor = Color.yellow; 962 painter.drawRectangle( 963 Point(x * 20, y * 20), 964 20, 20 965 ); 966 break; 967 } 968 969 x++; 970 if(x == boardWidth) { 971 x = 0; 972 y++; 973 } 974 } 975 976 } 977 978 void main() { 979 auto window = new SimpleWindow(200, 200); 980 981 initializeBoard(10, 10, 10); 982 983 redraw(window); 984 window.eventLoop(0, 985 delegate (MouseEvent me) { 986 if(me.type != MouseEventType.buttonPressed) 987 return; 988 auto x = me.x / 20; 989 auto y = me.y / 20; 990 if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) { 991 if(me.button == MouseButton.left) { 992 gameState = reveal(x, y); 993 } else { 994 userState[y*boardWidth+x] = UserSquare.flagged; 995 } 996 redraw(window); 997 } 998 } 999 ); 1000 } 1001 } 1002 1003 /* 1004 version(OSX) { 1005 version=without_opengl; 1006 version=allow_unimplemented_features; 1007 version=OSXCocoa; 1008 pragma(linkerDirective, "-framework Cocoa"); 1009 } 1010 */ 1011 1012 version(without_opengl) { 1013 enum SdpyIsUsingIVGLBinds = false; 1014 } else /*version(Posix)*/ { 1015 static if (__traits(compiles, (){import iv.glbinds;})) { 1016 enum SdpyIsUsingIVGLBinds = true; 1017 public import iv.glbinds; 1018 //pragma(msg, "SDPY: using iv.glbinds"); 1019 } else { 1020 enum SdpyIsUsingIVGLBinds = false; 1021 } 1022 //} else { 1023 // enum SdpyIsUsingIVGLBinds = false; 1024 } 1025 1026 1027 version(Windows) { 1028 //import core.sys.windows.windows; 1029 import core.sys.windows.winnls; 1030 import core.sys.windows.windef; 1031 import core.sys.windows.basetyps; 1032 import core.sys.windows.winbase; 1033 import core.sys.windows.winuser; 1034 import core.sys.windows.shellapi; 1035 import core.sys.windows.wingdi; 1036 static import gdi = core.sys.windows.wingdi; // so i 1037 1038 pragma(lib, "gdi32"); 1039 pragma(lib, "user32"); 1040 } else version (linux) { 1041 //k8: this is hack for rdmd. sorry. 1042 static import core.sys.linux.epoll; 1043 static import core.sys.linux.timerfd; 1044 } 1045 1046 1047 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off. 1048 1049 // http://wiki.dlang.org/Simpledisplay.d 1050 1051 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led 1052 1053 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl 1054 // but can i control the scroll lock led 1055 1056 1057 // Note: if you are using Image on X, you might want to do: 1058 /* 1059 static if(UsingSimpledisplayX11) { 1060 if(!Image.impl.xshmAvailable) { 1061 // the images will use the slower XPutImage, you might 1062 // want to consider an alternative method to get better speed 1063 } 1064 } 1065 1066 If the shared memory extension is available though, simpledisplay uses it 1067 for a significant speed boost whenever you draw large Images. 1068 */ 1069 1070 // CHANGE FROM LAST VERSION: the window background is no longer fixed, so you might want to fill the screen with a particular color before drawing. 1071 1072 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()! 1073 1074 /* 1075 Biggest FIXME: 1076 make sure the key event numbers match between X and Windows OR provide symbolic constants on each system 1077 1078 clean up opengl contexts when their windows close 1079 1080 fix resizing the bitmaps/pixmaps 1081 */ 1082 1083 // BTW on Windows: 1084 // -L/SUBSYSTEM:WINDOWS:5.0 1085 // to dmd will make a nice windows binary w/o a console if you want that. 1086 1087 /* 1088 Stuff to add: 1089 1090 use multibyte functions everywhere we can 1091 1092 OpenGL windows 1093 more event stuff 1094 extremely basic windows w/ no decoration for tooltips, splash screens, etc. 1095 1096 1097 resizeEvent 1098 and make the windows non-resizable by default, 1099 or perhaps stretched (if I can find something in X like StretchBlt) 1100 1101 take a screenshot function! 1102 1103 Pens and brushes? 1104 Maybe a global event loop? 1105 1106 Mouse deltas 1107 Key items 1108 */ 1109 1110 /* 1111 From MSDN: 1112 1113 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate. 1114 1115 Important Do not use the LOWORD or HIWORD macros to extract the x- and y- coordinates of the cursor position because these macros return incorrect results on systems with multiple monitors. Systems with multiple monitors can have negative x- and y- coordinates, and LOWORD and HIWORD treat the coordinates as unsigned quantities. 1116 1117 */ 1118 1119 version(linux) { 1120 version = X11; 1121 version(without_libnotify) { 1122 // we cool 1123 } 1124 else 1125 version = libnotify; 1126 } 1127 1128 version(libnotify) { 1129 pragma(lib, "dl"); 1130 import core.sys.posix.dlfcn; 1131 1132 void delegate()[int] libnotify_action_delegates; 1133 int libnotify_action_delegates_count; 1134 extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) { 1135 auto idx = cast(int) user_data; 1136 if(auto dgptr = idx in libnotify_action_delegates) { 1137 (*dgptr)(); 1138 libnotify_action_delegates.remove(idx); 1139 } 1140 } 1141 1142 struct C_DynamicLibrary { 1143 void* handle; 1144 this(string name) { 1145 handle = dlopen((name ~ "\0").ptr, RTLD_NOW); 1146 if(handle is null) 1147 throw new Exception("dlopen"); 1148 } 1149 1150 void close() { 1151 dlclose(handle); 1152 } 1153 1154 ~this() { 1155 // close 1156 } 1157 1158 // FIXME: this looks up by name every time.... 1159 template call(string func, Ret, Args...) { 1160 extern(C) Ret function(Args) fptr; 1161 typeof(fptr) call() { 1162 fptr = cast(typeof(fptr)) dlsym(handle, func); 1163 return fptr; 1164 } 1165 } 1166 } 1167 1168 C_DynamicLibrary* libnotify; 1169 } 1170 1171 version(OSX) { 1172 version(OSXCocoa) {} 1173 else { version = X11; } 1174 } 1175 //version = OSXCocoa; // this was written by KennyTM 1176 version(FreeBSD) 1177 version = X11; 1178 version(Solaris) 1179 version = X11; 1180 1181 version(X11) { 1182 version(without_xft) {} 1183 else version=with_xft; 1184 } 1185 1186 void featureNotImplemented()() { 1187 version(allow_unimplemented_features) 1188 throw new NotYetImplementedException(); 1189 else 1190 static assert(0); 1191 } 1192 1193 // these are so the static asserts don't trigger unless you want to 1194 // add support to it for an OS 1195 version(Windows) 1196 version = with_timer; 1197 version(linux) 1198 version = with_timer; 1199 1200 version(with_timer) 1201 enum bool SimpledisplayTimerAvailable = true; 1202 else 1203 enum bool SimpledisplayTimerAvailable = false; 1204 1205 /// If you have to get down and dirty with implementation details, this helps figure out if Windows is available you can `static if(UsingSimpledisplayWindows) ...` more reliably than `version()` because `version` is module-local. 1206 version(Windows) 1207 enum bool UsingSimpledisplayWindows = true; 1208 else 1209 enum bool UsingSimpledisplayWindows = false; 1210 1211 /// If you have to get down and dirty with implementation details, this helps figure out if X is available you can `static if(UsingSimpledisplayX11) ...` more reliably than `version()` because `version` is module-local. 1212 version(X11) 1213 enum bool UsingSimpledisplayX11 = true; 1214 else 1215 enum bool UsingSimpledisplayX11 = false; 1216 1217 /// If you have to get down and dirty with implementation details, this helps figure out if Cocoa is available you can `static if(UsingSimpledisplayCocoa) ...` more reliably than `version()` because `version` is module-local. 1218 version(OSXCocoa) 1219 enum bool UsingSimpledisplayCocoa = true; 1220 else 1221 enum bool UsingSimpledisplayCocoa = false; 1222 1223 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception. 1224 version(Windows) 1225 enum multipleWindowsSupported = true; 1226 else version(X11) 1227 enum multipleWindowsSupported = true; 1228 else version(OSXCocoa) 1229 enum multipleWindowsSupported = true; 1230 else 1231 static assert(0); 1232 1233 version(without_opengl) 1234 enum bool OpenGlEnabled = false; 1235 else 1236 enum bool OpenGlEnabled = true; 1237 1238 1239 /++ 1240 After selecting a type from [WindowTypes], you may further customize 1241 its behavior by setting one or more of these flags. 1242 1243 1244 The different window types have different meanings of `normal`. If the 1245 window type already is a good match for what you want to do, you should 1246 just use [WindowFlags.normal], the default, which will do the right thing 1247 for your users. 1248 1249 The window flags will not always be honored by the operating system 1250 and window managers; they are hints, not commands. 1251 +/ 1252 enum WindowFlags : int { 1253 normal = 0, /// 1254 skipTaskbar = 1, /// 1255 alwaysOnTop = 2, /// 1256 alwaysOnBottom = 4, /// 1257 cannotBeActivated = 8, /// 1258 alwaysRequestMouseMotionEvents = 16, /// By default, simpledisplay will attempt to optimize mouse motion event reporting when it detects a remote connection, causing them to only be issued if input is grabbed (see: [SimpleWindow.grabInput]). This means doing hover effects and mouse game control on a remote X connection may not work right. Include this flag to override this optimization and always request the motion events. However btw, if you are doing mouse game control, you probably want to grab input anyway, and hover events are usually expendable! So think before you use this flag. 1259 extraComposite = 32, /// On windows this will make this a layered windows (not supported for child windows before windows 8) to support transparency and improve animation performance. 1260 /++ 1261 Sets the window as a short-lived child of its parent, but unlike an ordinary child, 1262 it is still a top-level window. This should NOT be set separately for most window types. 1263 1264 A transient window will not keep the application open if its main window closes. 1265 1266 $(PITFALL This may not be correctly implemented and its behavior is subject to change.) 1267 1268 1269 From the ICCM: 1270 1271 $(BLOCKQUOTE 1272 It is important not to confuse WM_TRANSIENT_FOR with override-redirect. WM_TRANSIENT_FOR should be used in those cases where the pointer is not grabbed while the window is mapped (in other words, if other windows are allowed to be active while the transient is up). If other windows must be prevented from processing input (for example, when implementing pop-up menus), use override-redirect and grab the pointer while the window is mapped. 1273 1274 $(CITE https://tronche.com/gui/x/icccm/sec-4.html) 1275 ) 1276 1277 So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag. 1278 1279 History: 1280 Added February 23, 2021 but not yet stabilized. 1281 +/ 1282 transient = 64, 1283 dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually. 1284 } 1285 1286 /++ 1287 When creating a window, you can pass a type to SimpleWindow's constructor, 1288 then further customize the window by changing `WindowFlags`. 1289 1290 1291 You should mostly only need [normal], [undecorated], and [eventOnly] for normal 1292 use. The others are there to build a foundation for a higher level GUI toolkit, 1293 but are themselves not as high level as you might think from their names. 1294 1295 This list is based on the EMWH spec for X11. 1296 http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896 1297 +/ 1298 enum WindowTypes : int { 1299 /// An ordinary application window. 1300 normal, 1301 /// A generic window without a title bar or border. You can draw on the entire area of the screen it takes up and use it as you wish. Remember that users don't really expect these though, so don't use it where a window of any other type is appropriate. 1302 undecorated, 1303 /// A window that doesn't actually display on screen. You can use it for cases where you need a dummy window handle to communicate with or something. 1304 eventOnly, 1305 /// A drop down menu, such as from a menu bar 1306 dropdownMenu, 1307 /// A popup menu, such as from a right click 1308 popupMenu, 1309 /// A popup bubble notification 1310 notification, 1311 /* 1312 menu, /// a tearable menu bar 1313 splashScreen, /// a loading splash screen for your application 1314 tooltip, /// A tiny window showing temporary help text or something. 1315 comboBoxDropdown, 1316 dialog, 1317 toolbar 1318 */ 1319 /// a child nested inside the parent. You must pass a parent window to the ctor 1320 nestedChild, 1321 } 1322 1323 1324 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call 1325 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features 1326 private __gshared char* sdpyWindowClassStr = null; 1327 private __gshared bool sdpyOpenGLContextAllowFallback = false; 1328 1329 /** 1330 Set OpenGL context version to use. This has no effect on non-OpenGL windows. 1331 You may want to change context version if you want to use advanced shaders or 1332 other modern OpenGL techinques. This setting doesn't affect already created 1333 windows. You may use version 2.1 as your default, which should be supported 1334 by any box since 2006, so seems to be a reasonable choice. 1335 1336 Note that by default version is set to `0`, which forces SimpleDisplay to use 1337 old context creation code without any version specified. This is the safest 1338 way to init OpenGL, but it may not give you access to advanced features. 1339 1340 See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL 1341 */ 1342 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); } 1343 1344 /** 1345 Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed 1346 pipeline functions, and without "compatible" mode you won't be able to use 1347 your old non-shader-based code with such contexts. By default SimpleDisplay 1348 creates compatible context, so you can gradually upgrade your OpenGL code if 1349 you want to (or leave it as is, as it should "just work"). 1350 */ 1351 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; } 1352 1353 /** 1354 Set to `true` to allow creating OpenGL context with lower version than requested 1355 instead of throwing. If fallback was activated (or legacy OpenGL was requested), 1356 `openGLContextFallbackActivated()` will return `true`. 1357 */ 1358 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; } 1359 1360 /** 1361 After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context. 1362 */ 1363 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); } 1364 1365 1366 /** 1367 Set window class name for all following `new SimpleWindow()` calls. 1368 1369 WARNING! For Windows, you should set your class name before creating any 1370 window, and NEVER change it after that! 1371 */ 1372 void sdpyWindowClass (const(char)[] v) { 1373 import core.stdc.stdlib : realloc; 1374 if (v.length == 0) v = "SimpleDisplayWindow"; 1375 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1); 1376 if (sdpyWindowClassStr is null) return; // oops 1377 sdpyWindowClassStr[0..v.length+1] = 0; 1378 sdpyWindowClassStr[0..v.length] = v[]; 1379 } 1380 1381 /** 1382 Get current window class name. 1383 */ 1384 string sdpyWindowClass () { 1385 if (sdpyWindowClassStr is null) return null; 1386 foreach (immutable idx; 0..size_t.max-1) { 1387 if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup; 1388 } 1389 return null; 1390 } 1391 1392 /++ 1393 Returns the DPI of the default monitor. [0] is width, [1] is height (they are usually the same though). You may wish to round the numbers off. 1394 +/ 1395 float[2] getDpi() { 1396 float[2] dpi; 1397 version(Windows) { 1398 HDC screen = GetDC(null); 1399 dpi[0] = GetDeviceCaps(screen, LOGPIXELSX); 1400 dpi[1] = GetDeviceCaps(screen, LOGPIXELSY); 1401 } else version(X11) { 1402 auto display = XDisplayConnection.get; 1403 auto screen = DefaultScreen(display); 1404 1405 void fallback() { 1406 // 25.4 millimeters in an inch... 1407 dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4; 1408 dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4; 1409 } 1410 1411 char* resourceString = XResourceManagerString(display); 1412 XrmInitialize(); 1413 1414 auto db = XrmGetStringDatabase(resourceString); 1415 1416 if (resourceString) { 1417 XrmValue value; 1418 char* type; 1419 if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) { 1420 if (value.addr) { 1421 import core.stdc.stdlib; 1422 dpi[0] = atof(cast(char*) value.addr); 1423 dpi[1] = dpi[0]; 1424 } else { 1425 fallback(); 1426 } 1427 } else { 1428 fallback(); 1429 } 1430 } else { 1431 fallback(); 1432 } 1433 } 1434 1435 return dpi; 1436 } 1437 1438 TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width, int height) { 1439 TrueColorImage got; 1440 version(X11) { 1441 auto display = XDisplayConnection.get; 1442 auto image = XGetImage(display, handle, 0, 0, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap); 1443 1444 // https://github.com/adamdruppe/arsd/issues/98 1445 1446 auto i = new Image(image); 1447 got = i.toTrueColorImage(); 1448 1449 XDestroyImage(image); 1450 } else version(Windows) { 1451 // I just need to BitBlt that shit... BUT WAIT IT IS ALREADY IN A DIB!!!!!!! 1452 1453 auto hdc = GetDC(handle); 1454 scope(exit) ReleaseDC(handle, hdc); 1455 auto i = new Image(width, height); 1456 HDC hdcMem = CreateCompatibleDC(hdc); 1457 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 1458 BitBlt(hdcMem, 0, 0, width, height, hdc, 0, 0, SRCCOPY); 1459 SelectObject(hdcMem, hbmOld); 1460 DeleteDC(hdcMem); 1461 1462 got = i.toTrueColorImage(); 1463 } else featureNotImplemented(); 1464 1465 return got; 1466 } 1467 1468 /++ 1469 The flagship window class. 1470 1471 1472 SimpleWindow tries to make ordinary windows very easy to create and use without locking you 1473 out of more advanced or complex features of the underlying windowing system. 1474 1475 For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")` 1476 and get a suitable window to work with. 1477 1478 From there, you can opt into additional features, like custom resizability and OpenGL support 1479 with the next two constructor arguments. Or, if you need even more, you can set a window type 1480 and customization flags with the final two constructor arguments. 1481 1482 If none of that works for you, you can also create a window using native function calls, then 1483 wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember, 1484 though, if you do this, managing the window is still your own responsibility! Notably, you 1485 will need to destroy it yourself. 1486 +/ 1487 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { 1488 1489 /++ 1490 Copies the window's current state into a [TrueColorImage]. 1491 1492 Be warned: this can be a very slow operation 1493 1494 History: 1495 Actually implemented on March 14, 2021 1496 +/ 1497 TrueColorImage takeScreenshot() { 1498 version(Windows) 1499 return trueColorImageFromNativeHandle(impl.hwnd, width, height); 1500 else version(OSXCocoa) 1501 throw new NotYetImplementedException(); 1502 else 1503 return trueColorImageFromNativeHandle(impl.window, width, height); 1504 } 1505 1506 version(X11) { 1507 void recreateAfterDisconnect() { 1508 if(!stateDiscarded) return; 1509 1510 if(_parent !is null && _parent.stateDiscarded) 1511 _parent.recreateAfterDisconnect(); 1512 1513 bool wasHidden = hidden; 1514 1515 activeScreenPainter = null; // should already be done but just to confirm 1516 1517 impl.createWindow(_width, _height, _title, openglMode, _parent); 1518 1519 if(auto dh = dropHandler) { 1520 dropHandler = null; 1521 enableDragAndDrop(this, dh); 1522 } 1523 1524 if(recreateAdditionalConnectionState) 1525 recreateAdditionalConnectionState(); 1526 1527 hidden = wasHidden; 1528 stateDiscarded = false; 1529 } 1530 1531 bool stateDiscarded; 1532 void discardConnectionState() { 1533 if(XDisplayConnection.display) 1534 impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway 1535 if(discardAdditionalConnectionState) 1536 discardAdditionalConnectionState(); 1537 stateDiscarded = true; 1538 } 1539 1540 void delegate() discardAdditionalConnectionState; 1541 void delegate() recreateAdditionalConnectionState; 1542 1543 } 1544 1545 private DropHandler dropHandler; 1546 1547 SimpleWindow _parent; 1548 bool beingOpenKeepsAppOpen = true; 1549 /++ 1550 This creates a window with the given options. The window will be visible and able to receive input as soon as you start your event loop. You may draw on it immediately after creating the window, without needing to wait for the event loop to start if you want. 1551 1552 The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them. 1553 1554 Params: 1555 1556 width = the width of the window's client area, in pixels 1557 height = the height of the window's client area, in pixels 1558 title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property. 1559 opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window. 1560 resizable = [Resizability] has three options: 1561 $(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.) 1562 $(P `fixedSize` will not allow the user to resize the window.) 1563 $(P `automaticallyScaleIfPossible` will allow the user to resize, but will still present the original size to the API user. The contents you draw will be scaled to the size the user chose. If this scaling is not efficient, the window will be fixed size. The `windowResized` event handler will never be called. This is the default.) 1564 windowType = The type of window you want to make. 1565 customizationFlags = A way to make a window without a border, always on top, skip taskbar, and more. Do not use this if one of the pre-defined [WindowTypes], given in the `windowType` argument, is a good match for what you need. 1566 parent = the parent window, if applicable. This makes the child window nested inside the parent unless you set [WindowFlags.transient], which makes it a top-level window merely owned by the "parent". 1567 +/ 1568 this(int width = 640, int height = 480, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 1569 claimGuiThread(); 1570 version(sdpy_thread_checks) assert(thisIsGuiThread); 1571 this._width = width; 1572 this._height = height; 1573 this.openglMode = opengl; 1574 this.resizability = resizable; 1575 this.windowType = windowType; 1576 this.customizationFlags = customizationFlags; 1577 this._title = (title is null ? "D Application" : title); 1578 this._parent = parent; 1579 impl.createWindow(width, height, this._title, opengl, parent); 1580 1581 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient)) 1582 beingOpenKeepsAppOpen = false; 1583 } 1584 1585 /// ditto 1586 this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 1587 this(width, height, title, opengl, resizable, windowType, customizationFlags, parent); 1588 } 1589 1590 /// Same as above, except using the `Size` struct instead of separate width and height. 1591 this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) { 1592 this(size.width, size.height, title, opengl, resizable); 1593 } 1594 1595 /// ditto 1596 this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) { 1597 this(size, title, opengl, resizable); 1598 } 1599 1600 1601 /++ 1602 Creates a window based on the given [Image]. It's client area 1603 width and height is equal to the image. (A window's client area 1604 is the drawable space inside; it excludes the title bar, etc.) 1605 1606 Windows based on images will not be resizable and do not use OpenGL. 1607 1608 It will draw the image in upon creation, but this will be overwritten 1609 upon any draws, including the initial window visible event. 1610 1611 You probably do not want to use this and it may be removed from 1612 the library eventually, or I might change it to be a "permanent" 1613 background image; one that is automatically drawn on it before any 1614 other drawing event. idk. 1615 +/ 1616 this(Image image, string title = null) { 1617 this(image.width, image.height, title); 1618 this.image = image; 1619 } 1620 1621 /++ 1622 Wraps a native window handle with very little additional processing - notably no destruction 1623 this is incomplete so don't use it for much right now. The purpose of this is to make native 1624 windows created through the low level API (so you can use platform-specific options and 1625 other details SimpleWindow does not expose) available to the event loop wrappers. 1626 +/ 1627 this(NativeWindowHandle nativeWindow) { 1628 version(Windows) 1629 impl.hwnd = nativeWindow; 1630 else version(X11) { 1631 impl.window = nativeWindow; 1632 display = XDisplayConnection.get(); // get initial display to not segfault 1633 } else version(OSXCocoa) 1634 throw new NotYetImplementedException(); 1635 else featureNotImplemented(); 1636 // FIXME: set the size correctly 1637 _width = 1; 1638 _height = 1; 1639 nativeMapping[nativeWindow] = this; 1640 1641 beingOpenKeepsAppOpen = false; 1642 1643 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 1644 _suppressDestruction = true; // so it doesn't try to close 1645 } 1646 1647 /// Experimental, do not use yet 1648 /++ 1649 Grabs exclusive input from the user until you release it with 1650 [releaseInputGrab]. 1651 1652 1653 Note: it is extremely rude to do this without good reason. 1654 Reasons may include doing some kind of mouse drag operation 1655 or popping up a temporary menu that should get events and will 1656 be dismissed at ease by the user clicking away. 1657 1658 Params: 1659 keyboard = do you want to grab keyboard input? 1660 mouse = grab mouse input? 1661 confine = confine the mouse cursor to inside this window? 1662 1663 History: 1664 Prior to March 11, 2021, grabbing the keyboard would always also 1665 set the X input focus. Now, it only focuses if it is a non-transient 1666 window and otherwise manages the input direction internally. 1667 1668 This means spurious focus/blur events will no longer be sent and the 1669 application will not steal focus from other applications (which the 1670 window manager may have rejected anyway). 1671 +/ 1672 void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) { 1673 static if(UsingSimpledisplayX11) { 1674 XSync(XDisplayConnection.get, 0); 1675 if(keyboard) { 1676 if(isTransient && _parent) { 1677 /* 1678 FIXME: 1679 setting the keyboard focus is not actually that helpful, what I more likely want 1680 is the events from the parent window to be sent over here if we're transient. 1681 */ 1682 1683 _parent.inputProxy = this; 1684 } else { 1685 XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime); 1686 } 1687 } 1688 if(mouse) { 1689 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 1690 EventMask.PointerMotionMask // FIXME: not efficient 1691 | EventMask.ButtonPressMask 1692 | EventMask.ButtonReleaseMask 1693 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 1694 ) 1695 { 1696 XSync(XDisplayConnection.get, 0); 1697 import core.stdc.stdio; 1698 printf("Grab input failed %d\n", res); 1699 //throw new Exception("Grab input failed"); 1700 } else { 1701 // cool 1702 } 1703 } 1704 1705 } else version(Windows) { 1706 // FIXME: keyboard? 1707 SetCapture(impl.hwnd); 1708 if(confine) { 1709 RECT rcClip; 1710 //RECT rcOldClip; 1711 //GetClipCursor(&rcOldClip); 1712 GetWindowRect(hwnd, &rcClip); 1713 ClipCursor(&rcClip); 1714 } 1715 } else version(OSXCocoa) { 1716 throw new NotYetImplementedException(); 1717 } else static assert(0); 1718 } 1719 1720 private bool isTransient() { 1721 with(WindowTypes) 1722 final switch(windowType) { 1723 case normal, undecorated, eventOnly: 1724 case nestedChild: 1725 return (customizationFlags & WindowFlags.transient) ? true : false; 1726 case dropdownMenu, popupMenu, notification: 1727 return true; 1728 } 1729 } 1730 1731 private SimpleWindow inputProxy; 1732 1733 /++ 1734 Releases the grab acquired by [grabInput]. 1735 +/ 1736 void releaseInputGrab() { 1737 static if(UsingSimpledisplayX11) { 1738 XUngrabPointer(XDisplayConnection.get, CurrentTime); 1739 if(_parent) 1740 _parent.inputProxy = null; 1741 } else version(Windows) { 1742 ReleaseCapture(); 1743 ClipCursor(null); 1744 } else version(OSXCocoa) { 1745 throw new NotYetImplementedException(); 1746 } else static assert(0); 1747 } 1748 1749 /++ 1750 Sets the input focus to this window. 1751 1752 You shouldn't call this very often - please let the user control the input focus. 1753 +/ 1754 void focus() { 1755 static if(UsingSimpledisplayX11) { 1756 XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime); 1757 } else version(Windows) { 1758 SetFocus(this.impl.hwnd); 1759 } else version(OSXCocoa) { 1760 throw new NotYetImplementedException(); 1761 } else static assert(0); 1762 } 1763 1764 /++ 1765 Requests attention from the user for this window. 1766 1767 1768 The typical result of this function is to change the color 1769 of the taskbar icon, though it may be tweaked on specific 1770 platforms. 1771 1772 It is meant to unobtrusively tell the user that something 1773 relevant to them happened in the background and they should 1774 check the window when they get a chance. Upon receiving the 1775 keyboard focus, the window will automatically return to its 1776 natural state. 1777 1778 If the window already has the keyboard focus, this function 1779 may do nothing, because the user is presumed to already be 1780 giving the window attention. 1781 1782 Implementation_note: 1783 1784 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 1785 atom on X11 and the FlashWindow function on Windows. 1786 +/ 1787 void requestAttention() { 1788 if(_focused) 1789 return; 1790 1791 version(Windows) { 1792 FLASHWINFO info; 1793 info.cbSize = info.sizeof; 1794 info.hwnd = impl.hwnd; 1795 info.dwFlags = FLASHW_TRAY; 1796 info.uCount = 1; 1797 1798 FlashWindowEx(&info); 1799 1800 } else version(X11) { 1801 demandingAttention = true; 1802 demandAttention(this, true); 1803 } else version(OSXCocoa) { 1804 throw new NotYetImplementedException(); 1805 } else static assert(0); 1806 } 1807 1808 private bool _focused; 1809 1810 version(X11) private bool demandingAttention; 1811 1812 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 1813 /// You'll have to call `close()` manually if you set this delegate. 1814 void delegate () closeQuery; 1815 1816 /// This will be called when window visibility was changed. 1817 void delegate (bool becomesVisible) visibilityChanged; 1818 1819 /// This will be called when window becomes visible for the first time. 1820 /// You can do OpenGL initialization here. Note that in X11 you can't call 1821 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 1822 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 1823 private bool _visibleForTheFirstTimeCalled; 1824 void delegate () visibleForTheFirstTime; 1825 1826 /// Returns true if the window has been closed. 1827 final @property bool closed() { return _closed; } 1828 1829 /// Returns true if the window is focused. 1830 final @property bool focused() { return _focused; } 1831 1832 private bool _visible; 1833 /// Returns true if the window is visible (mapped). 1834 final @property bool visible() { return _visible; } 1835 1836 /// Closes the window. If there are no more open windows, the event loop will terminate. 1837 void close() { 1838 if (!_closed) { 1839 runInGuiThread( { 1840 if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued 1841 if (onClosing !is null) onClosing(); 1842 impl.closeWindow(); 1843 _closed = true; 1844 } ); 1845 } 1846 } 1847 1848 /++ 1849 `close` is one of the few methods that can be called from other threads. This `shared` overload reflects that. 1850 1851 History: 1852 Overload added on March 7, 2021. 1853 +/ 1854 void close() shared { 1855 (cast() this).close(); 1856 } 1857 1858 /++ 1859 1860 +/ 1861 void maximize() { 1862 version(Windows) 1863 ShowWindow(impl.hwnd, SW_MAXIMIZE); 1864 else version(X11) { 1865 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get)); 1866 1867 // also note _NET_WM_STATE_FULLSCREEN 1868 } 1869 1870 } 1871 1872 private bool _fullscreen; 1873 1874 /// not fully implemented but planned for a future release 1875 void fullscreen(bool yes) { 1876 version(X11) 1877 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes); 1878 1879 _fullscreen = yes; 1880 1881 } 1882 1883 bool fullscreen() { 1884 return _fullscreen; 1885 } 1886 1887 /++ 1888 Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead. 1889 1890 +/ 1891 void minimize() { 1892 version(Windows) 1893 ShowWindow(impl.hwnd, SW_MINIMIZE); 1894 //else version(X11) 1895 //setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true); 1896 } 1897 1898 /// Alias for `hidden = false` 1899 void show() { 1900 hidden = false; 1901 } 1902 1903 /// Alias for `hidden = true` 1904 void hide() { 1905 hidden = true; 1906 } 1907 1908 /// Hide cursor when it enters the window. 1909 void hideCursor() { 1910 version(OSXCocoa) throw new NotYetImplementedException(); else 1911 if (!_closed) impl.hideCursor(); 1912 } 1913 1914 /// Don't hide cursor when it enters the window. 1915 void showCursor() { 1916 version(OSXCocoa) throw new NotYetImplementedException(); else 1917 if (!_closed) impl.showCursor(); 1918 } 1919 1920 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 1921 * 1922 * Please remember that the cursor is a shared resource that should usually be left to the user's 1923 * control. Try to think for other approaches before using this function. 1924 * 1925 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 1926 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 1927 * receive "mouse moved here" event. 1928 */ 1929 bool warpMouse (int x, int y) { 1930 version(X11) { 1931 if (!_closed) { impl.warpMouse(x, y); return true; } 1932 } else version(Windows) { 1933 if (!_closed) { 1934 POINT point; 1935 point.x = x; 1936 point.y = y; 1937 if(ClientToScreen(impl.hwnd, &point)) { 1938 SetCursorPos(point.x, point.y); 1939 return true; 1940 } 1941 } 1942 } 1943 return false; 1944 } 1945 1946 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 1947 void sendDummyEvent () { 1948 version(X11) { 1949 if (!_closed) { impl.sendDummyEvent(); } 1950 } 1951 } 1952 1953 /// Set window minimal size. 1954 void setMinSize (int minwidth, int minheight) { 1955 version(OSXCocoa) throw new NotYetImplementedException(); else 1956 if (!_closed) impl.setMinSize(minwidth, minheight); 1957 } 1958 1959 /// Set window maximal size. 1960 void setMaxSize (int maxwidth, int maxheight) { 1961 version(OSXCocoa) throw new NotYetImplementedException(); else 1962 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 1963 } 1964 1965 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 1966 /// Currently only supported on X11. 1967 void setResizeGranularity (int granx, int grany) { 1968 version(OSXCocoa) throw new NotYetImplementedException(); else 1969 if (!_closed) impl.setResizeGranularity(granx, grany); 1970 } 1971 1972 /// Move window. 1973 void move(int x, int y) { 1974 version(OSXCocoa) throw new NotYetImplementedException(); else 1975 if (!_closed) impl.move(x, y); 1976 } 1977 1978 /// ditto 1979 void move(Point p) { 1980 version(OSXCocoa) throw new NotYetImplementedException(); else 1981 if (!_closed) impl.move(p.x, p.y); 1982 } 1983 1984 /++ 1985 Resize window. 1986 1987 Note that the width and height of the window are NOT instantly 1988 updated - it waits for the window manager to approve the resize 1989 request, which means you must return to the event loop before the 1990 width and height are actually changed. 1991 +/ 1992 void resize(int w, int h) { 1993 if(!_closed && _fullscreen) fullscreen = false; 1994 version(OSXCocoa) throw new NotYetImplementedException(); else 1995 if (!_closed) impl.resize(w, h); 1996 } 1997 1998 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 1999 void moveResize (int x, int y, int w, int h) { 2000 if(!_closed && _fullscreen) fullscreen = false; 2001 version(OSXCocoa) throw new NotYetImplementedException(); else 2002 if (!_closed) impl.moveResize(x, y, w, h); 2003 } 2004 2005 private bool _hidden; 2006 2007 /// Returns true if the window is hidden. 2008 final @property bool hidden() { 2009 return _hidden; 2010 } 2011 2012 /// Shows or hides the window based on the bool argument. 2013 final @property void hidden(bool b) { 2014 _hidden = b; 2015 version(Windows) { 2016 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 2017 } else version(X11) { 2018 if(b) 2019 //XUnmapWindow(impl.display, impl.window); 2020 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 2021 else 2022 XMapWindow(impl.display, impl.window); 2023 } else version(OSXCocoa) { 2024 throw new NotYetImplementedException(); 2025 } else static assert(0); 2026 } 2027 2028 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 2029 void opacity(double opacity) @property 2030 in { 2031 assert(opacity >= 0 && opacity <= 1); 2032 } do { 2033 version (Windows) { 2034 impl.setOpacity(cast(ubyte)(255 * opacity)); 2035 } else version (X11) { 2036 impl.setOpacity(cast(uint)(uint.max * opacity)); 2037 } else throw new NotYetImplementedException(); 2038 } 2039 2040 /++ 2041 Sets your event handlers, without entering the event loop. Useful if you 2042 have multiple windows - set the handlers on each window, then only do eventLoop on your main window. 2043 +/ 2044 void setEventHandlers(T...)(T eventHandlers) { 2045 // FIXME: add more events 2046 foreach(handler; eventHandlers) { 2047 static if(__traits(compiles, handleKeyEvent = handler)) { 2048 handleKeyEvent = handler; 2049 } else static if(__traits(compiles, handleCharEvent = handler)) { 2050 handleCharEvent = handler; 2051 } else static if(__traits(compiles, handlePulse = handler)) { 2052 handlePulse = handler; 2053 } else static if(__traits(compiles, handleMouseEvent = handler)) { 2054 handleMouseEvent = handler; 2055 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 2056 } 2057 } 2058 2059 /// The event loop automatically returns when the window is closed 2060 /// pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 2061 /// pulse timer is created. The event loop will block until an event 2062 /// arrives or the pulse timer goes off. 2063 final int eventLoop(T...)( 2064 long pulseTimeout, /// set to zero if you don't want a pulse. 2065 T eventHandlers) /// delegate list like std.concurrency.receive 2066 { 2067 setEventHandlers(eventHandlers); 2068 2069 version(with_eventloop) { 2070 // delegates event loop to my other module 2071 version(X11) 2072 XFlush(display); 2073 2074 import arsd.eventloop; 2075 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 2076 scope(exit) clearInterval(handle); 2077 2078 loop(); 2079 return 0; 2080 } else version(OSXCocoa) { 2081 // FIXME 2082 if (handlePulse !is null && pulseTimeout != 0) { 2083 timer = scheduledTimer(pulseTimeout*1e-3, 2084 view, sel_registerName("simpledisplay_pulse"), 2085 null, true); 2086 } 2087 2088 setNeedsDisplay(view, true); 2089 run(NSApp); 2090 return 0; 2091 } else { 2092 EventLoop el = EventLoop(pulseTimeout, handlePulse); 2093 return el.run(); 2094 } 2095 } 2096 2097 /++ 2098 This lets you draw on the window (or its backing buffer) using basic 2099 2D primitives. 2100 2101 Be sure to call this in a limited scope because your changes will not 2102 actually appear on the window until ScreenPainter's destructor runs. 2103 2104 Returns: an instance of [ScreenPainter], which has the drawing methods 2105 on it to draw on this window. 2106 +/ 2107 ScreenPainter draw() { 2108 return impl.getPainter(); 2109 } 2110 2111 // This is here to implement the interface we use for various native handlers. 2112 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 2113 2114 // maps native window handles to SimpleWindow instances, if there are any 2115 // you shouldn't need this, but it is public in case you do in a native event handler or something 2116 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 2117 2118 /// Width of the window's drawable client area, in pixels. 2119 @scriptable 2120 final @property int width() const pure nothrow @safe @nogc { return _width; } 2121 2122 /// Height of the window's drawable client area, in pixels. 2123 @scriptable 2124 final @property int height() const pure nothrow @safe @nogc { return _height; } 2125 2126 private int _width; 2127 private int _height; 2128 2129 // HACK: making the best of some copy constructor woes with refcounting 2130 private ScreenPainterImplementation* activeScreenPainter_; 2131 2132 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 2133 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 2134 2135 private OpenGlOptions openglMode; 2136 private Resizability resizability; 2137 private WindowTypes windowType; 2138 private int customizationFlags; 2139 2140 /// `true` if OpenGL was initialized for this window. 2141 @property bool isOpenGL () const pure nothrow @safe @nogc { 2142 version(without_opengl) 2143 return false; 2144 else 2145 return (openglMode == OpenGlOptions.yes); 2146 } 2147 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 2148 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 2149 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 2150 2151 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 2152 /// to call this, as it's not recommended to share window between threads. 2153 void mtLock () { 2154 version(X11) { 2155 XLockDisplay(this.display); 2156 } 2157 } 2158 2159 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 2160 /// to call this, as it's not recommended to share window between threads. 2161 void mtUnlock () { 2162 version(X11) { 2163 XUnlockDisplay(this.display); 2164 } 2165 } 2166 2167 /// Emit a beep to get user's attention. 2168 void beep () { 2169 version(X11) { 2170 XBell(this.display, 100); 2171 } else version(Windows) { 2172 MessageBeep(0xFFFFFFFF); 2173 } 2174 } 2175 2176 2177 2178 version(without_opengl) {} else { 2179 2180 /// Put your code in here that you want to be drawn automatically when your window is uncovered. Set a handler here *before* entering your event loop any time you pass `OpenGlOptions.yes` to the constructor. Ideally, you will set this delegate immediately after constructing the `SimpleWindow`. 2181 void delegate() redrawOpenGlScene; 2182 2183 /// This will allow you to change OpenGL vsync state. 2184 final @property void vsync (bool wait) { 2185 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 2186 version(X11) { 2187 setAsCurrentOpenGlContext(); 2188 glxSetVSync(display, impl.window, wait); 2189 } else version(Windows) { 2190 setAsCurrentOpenGlContext(); 2191 wglSetVSync(wait); 2192 } 2193 } 2194 2195 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 2196 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 2197 /// enough without waiting 'em to finish their frame bussiness. 2198 bool useGLFinish = true; 2199 2200 // FIXME: it should schedule it for the end of the current iteration of the event loop... 2201 /// call this to invoke your delegate. It automatically sets up the context and flips the buffer. If you need to redraw the scene in response to an event, call this. 2202 void redrawOpenGlSceneNow() { 2203 version(X11) if (!this._visible) return; // no need to do this if window is invisible 2204 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 2205 if(redrawOpenGlScene is null) 2206 return; 2207 2208 this.mtLock(); 2209 scope(exit) this.mtUnlock(); 2210 2211 this.setAsCurrentOpenGlContext(); 2212 2213 redrawOpenGlScene(); 2214 2215 this.swapOpenGlBuffers(); 2216 // at least nvidia proprietary crap segfaults on exit if you won't do this and will call glTexSubImage2D() too fast; no, `glFlush()` won't work. 2217 if (useGLFinish) glFinish(); 2218 } 2219 2220 private bool redrawOpenGlSceneSoonSet = false; 2221 private static class RedrawOpenGlSceneEvent { 2222 SimpleWindow w; 2223 this(SimpleWindow w) { this.w = w; } 2224 } 2225 private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent; 2226 void redrawOpenGlSceneSoon() { 2227 if(!redrawOpenGlSceneSoonSet) { 2228 redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this); 2229 this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); }); 2230 redrawOpenGlSceneSoonSet = true; 2231 } 2232 this.postEvent(redrawOpenGlSceneEvent, true); 2233 } 2234 2235 2236 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 2237 void setAsCurrentOpenGlContext() { 2238 assert(openglMode == OpenGlOptions.yes); 2239 version(X11) { 2240 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 2241 throw new Exception("glXMakeCurrent"); 2242 } else version(Windows) { 2243 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 2244 if (!wglMakeCurrent(ghDC, ghRC)) 2245 throw new Exception("wglMakeCurrent"); // let windows users suffer too 2246 } 2247 } 2248 2249 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 2250 /// This doesn't throw, returning success flag instead. 2251 bool setAsCurrentOpenGlContextNT() nothrow { 2252 assert(openglMode == OpenGlOptions.yes); 2253 version(X11) { 2254 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 2255 } else version(Windows) { 2256 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 2257 return wglMakeCurrent(ghDC, ghRC) ? true : false; 2258 } 2259 } 2260 2261 /// Releases OpenGL context, so it can be reused in, for example, different thread. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 2262 /// This doesn't throw, returning success flag instead. 2263 bool releaseCurrentOpenGlContext() nothrow { 2264 assert(openglMode == OpenGlOptions.yes); 2265 version(X11) { 2266 return (glXMakeCurrent(display, 0, null) != 0); 2267 } else version(Windows) { 2268 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 2269 return wglMakeCurrent(ghDC, null) ? true : false; 2270 } 2271 } 2272 2273 /++ 2274 simpledisplay always uses double buffering, usually automatically. This 2275 manually swaps the OpenGL buffers. 2276 2277 2278 You should not need to call this yourself because simpledisplay will do it 2279 for you after calling your `redrawOpenGlScene`. 2280 2281 Remember that this may throw an exception, which you can catch in a multithreaded 2282 application to keep your thread from dying from an unhandled exception. 2283 +/ 2284 void swapOpenGlBuffers() { 2285 assert(openglMode == OpenGlOptions.yes); 2286 version(X11) { 2287 if (!this._visible) return; // no need to do this if window is invisible 2288 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 2289 glXSwapBuffers(display, impl.window); 2290 } else version(Windows) { 2291 SwapBuffers(ghDC); 2292 } 2293 } 2294 } 2295 2296 /++ 2297 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 2298 2299 2300 --- 2301 auto window = new SimpleWindow(100, 100, "First title"); 2302 window.title = "A new title"; 2303 --- 2304 2305 You may call this function at any time. 2306 +/ 2307 @property void title(string title) { 2308 _title = title; 2309 version(OSXCocoa) throw new NotYetImplementedException(); else 2310 impl.setTitle(title); 2311 } 2312 2313 private string _title; 2314 2315 /// Gets the title 2316 @property string title() { 2317 if(_title is null) 2318 _title = getRealTitle(); 2319 return _title; 2320 } 2321 2322 /++ 2323 Get the title as set by the window manager. 2324 May not match what you attempted to set. 2325 +/ 2326 string getRealTitle() { 2327 static if(is(typeof(impl.getTitle()))) 2328 return impl.getTitle(); 2329 else 2330 return null; 2331 } 2332 2333 // don't use this generally it is not yet really released 2334 version(X11) 2335 @property Image secret_icon() { 2336 return secret_icon_inner; 2337 } 2338 private Image secret_icon_inner; 2339 2340 2341 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. 2342 @property void icon(MemoryImage icon) { 2343 auto tci = icon.getAsTrueColorImage(); 2344 version(Windows) { 2345 winIcon = new WindowsIcon(icon); 2346 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 2347 } else version(X11) { 2348 secret_icon_inner = Image.fromMemoryImage(icon); 2349 // FIXME: ensure this is correct 2350 auto display = XDisplayConnection.get; 2351 arch_ulong[] buffer; 2352 buffer ~= icon.width; 2353 buffer ~= icon.height; 2354 foreach(c; tci.imageData.colors) { 2355 arch_ulong b; 2356 b |= c.a << 24; 2357 b |= c.r << 16; 2358 b |= c.g << 8; 2359 b |= c.b; 2360 buffer ~= b; 2361 } 2362 2363 XChangeProperty( 2364 display, 2365 impl.window, 2366 GetAtom!("_NET_WM_ICON", true)(display), 2367 GetAtom!"CARDINAL"(display), 2368 32 /* bits */, 2369 0 /*PropModeReplace*/, 2370 buffer.ptr, 2371 cast(int) buffer.length); 2372 } else version(OSXCocoa) { 2373 throw new NotYetImplementedException(); 2374 } else static assert(0); 2375 } 2376 2377 version(Windows) 2378 private WindowsIcon winIcon; 2379 2380 bool _suppressDestruction; 2381 2382 ~this() { 2383 if(_suppressDestruction) 2384 return; 2385 impl.dispose(); 2386 } 2387 2388 private bool _closed; 2389 2390 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 2391 /* 2392 ScreenPainter drawTransiently() { 2393 return impl.getPainter(); 2394 } 2395 */ 2396 2397 /// Draws an image on the window. This is meant to provide quick look 2398 /// of a static image generated elsewhere. 2399 @property void image(Image i) { 2400 version(Windows) { 2401 BITMAP bm; 2402 HDC hdc = GetDC(hwnd); 2403 HDC hdcMem = CreateCompatibleDC(hdc); 2404 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 2405 2406 GetObject(i.handle, bm.sizeof, &bm); 2407 2408 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 2409 2410 SelectObject(hdcMem, hbmOld); 2411 DeleteDC(hdcMem); 2412 ReleaseDC(hwnd, hdc); 2413 2414 /* 2415 RECT r; 2416 r.right = i.width; 2417 r.bottom = i.height; 2418 InvalidateRect(hwnd, &r, false); 2419 */ 2420 } else 2421 version(X11) { 2422 if(!destroyed) { 2423 if(i.usingXshm) 2424 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 2425 else 2426 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 2427 } 2428 } else 2429 version(OSXCocoa) { 2430 draw().drawImage(Point(0, 0), i); 2431 setNeedsDisplay(view, true); 2432 } else static assert(0); 2433 } 2434 2435 /++ 2436 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 2437 2438 --- 2439 window.cursor = GenericCursor.Help; 2440 // now the window mouse cursor is set to a generic help 2441 --- 2442 2443 +/ 2444 @property void cursor(MouseCursor cursor) { 2445 version(OSXCocoa) 2446 featureNotImplemented(); 2447 else 2448 if(this.impl.curHidden <= 0) { 2449 static if(UsingSimpledisplayX11) { 2450 auto ch = cursor.cursorHandle; 2451 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 2452 } else version(Windows) { 2453 auto ch = cursor.cursorHandle; 2454 impl.currentCursor = ch; 2455 SetCursor(ch); // redraw without waiting for mouse movement to update 2456 } else featureNotImplemented(); 2457 } 2458 2459 } 2460 2461 /// What follows are the event handlers. These are set automatically 2462 /// by the eventLoop function, but are still public so you can change 2463 /// them later. wasPressed == true means key down. false == key up. 2464 2465 /// Handles a low-level keyboard event. Settable through setEventHandlers. 2466 void delegate(KeyEvent ke) handleKeyEvent; 2467 2468 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 2469 void delegate(dchar c) handleCharEvent; 2470 2471 /// Handles a timer pulse. Settable through setEventHandlers. 2472 void delegate() handlePulse; 2473 2474 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 2475 void delegate(bool) onFocusChange; 2476 2477 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 2478 * Sometimes it is easier to setup the delegate instead of subclassing. */ 2479 void delegate() onClosing; 2480 2481 /** Called when we received destroy notification. At this stage we cannot do much with our window 2482 * (as it is already dead, and it's native handle cannot be used), but we still can do some 2483 * last minute cleanup. */ 2484 void delegate() onDestroyed; 2485 2486 static if (UsingSimpledisplayX11) 2487 /** Called when Expose event comes. See Xlib manual to understand the arguments. 2488 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 2489 * You will probably never need to setup this handler, it is for very low-level stuff. 2490 * 2491 * WARNING! Xlib is multithread-locked when this handles is called! */ 2492 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 2493 2494 //version(Windows) 2495 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 2496 2497 private { 2498 int lastMouseX = int.min; 2499 int lastMouseY = int.min; 2500 void mdx(ref MouseEvent ev) { 2501 if(lastMouseX == int.min || lastMouseY == int.min) { 2502 ev.dx = 0; 2503 ev.dy = 0; 2504 } else { 2505 ev.dx = ev.x - lastMouseX; 2506 ev.dy = ev.y - lastMouseY; 2507 } 2508 2509 lastMouseX = ev.x; 2510 lastMouseY = ev.y; 2511 } 2512 } 2513 2514 /// Mouse event handler. Settable through setEventHandlers. 2515 void delegate(MouseEvent) handleMouseEvent; 2516 2517 /// use to redraw child widgets if you use system apis to add stuff 2518 void delegate() paintingFinished; 2519 2520 void delegate() paintingFinishedDg() { 2521 return paintingFinished; 2522 } 2523 2524 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 2525 /// for this to ever happen. 2526 void delegate(int width, int height) windowResized; 2527 2528 /** Platform specific - handle any native messages this window gets. 2529 * 2530 * Note: this is called *in addition to* other event handlers, unless you return zero indicating that you handled it. 2531 2532 * On Windows, it takes the form of int delegate(HWND,UINT, WPARAM, LPARAM). 2533 2534 * On X11, it takes the form of int delegate(XEvent). 2535 2536 * IMPORTANT: it used to be static in old versions of simpledisplay.d, but I always used 2537 * it as if it wasn't static... so now I just fixed it so it isn't anymore. 2538 **/ 2539 NativeEventHandler handleNativeEvent; 2540 2541 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 2542 /// If you used to use handleNativeEvent depending on it being static, just change it to use 2543 /// this instead and it will work the same way. 2544 __gshared NativeEventHandler handleNativeGlobalEvent; 2545 2546 // private: 2547 /// The native implementation is available, but you shouldn't use it unless you are 2548 /// familiar with the underlying operating system, don't mind depending on it, and 2549 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 2550 /// do what you need to do with handleNativeEvent instead. 2551 /// 2552 /// This is likely to eventually change to be just a struct holding platform-specific 2553 /// handles instead of a template mixin at some point because I'm not happy with the 2554 /// code duplication here (ironically). 2555 mixin NativeSimpleWindowImplementation!() impl; 2556 2557 /** 2558 This is in-process one-way (from anything to window) event sending mechanics. 2559 It is thread-safe, so it can be used in multi-threaded applications to send, 2560 for example, "wake up and repaint" events when thread completed some operation. 2561 This will allow to avoid using timer pulse to check events with synchronization, 2562 'cause event handler will be called in UI thread. You can stop guessing which 2563 pulse frequency will be enough for your app. 2564 Note that events handlers may be called in arbitrary order, i.e. last registered 2565 handler can be called first, and vice versa. 2566 */ 2567 public: 2568 /** Is our custom event queue empty? Can be used in simple cases to prevent 2569 * "spamming" window with events it can't cope with. 2570 * It is safe to call this from non-UI threads. 2571 */ 2572 @property bool eventQueueEmpty() () { 2573 synchronized(this) { 2574 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 2575 } 2576 return true; 2577 } 2578 2579 /** Does our custom event queue contains at least one with the given type? 2580 * Can be used in simple cases to prevent "spamming" window with events 2581 * it can't cope with. 2582 * It is safe to call this from non-UI threads. 2583 */ 2584 @property bool eventQueued(ET:Object) () { 2585 synchronized(this) { 2586 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 2587 if (!o.doProcess) { 2588 if (cast(ET)(o.evt)) return true; 2589 } 2590 } 2591 } 2592 return false; 2593 } 2594 2595 /** Add listener for custom event. Can be used like this: 2596 * 2597 * --------------------- 2598 * auto eid = win.addEventListener((MyStruct evt) { ... }); 2599 * ... 2600 * win.removeEventListener(eid); 2601 * --------------------- 2602 * 2603 * Returns: 0 on failure (should never happen, so ignore it) 2604 * 2605 * $(WARNING Don't use this method in object destructors!) 2606 * 2607 * $(WARNING It is better to register all event handlers and don't remove 'em, 2608 * 'cause if event handler id counter will overflow, you won't be able 2609 * to register any more events.) 2610 */ 2611 uint addEventListener(ET:Object) (void delegate (ET) dg) { 2612 if (dg is null) return 0; // ignore empty handlers 2613 synchronized(this) { 2614 //FIXME: abort on overflow? 2615 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 2616 EventHandlerEntry e; 2617 e.dg = delegate (Object o) { 2618 if (auto co = cast(ET)o) { 2619 try { 2620 dg(co); 2621 } catch (Exception) { 2622 // sorry! 2623 } 2624 return true; 2625 } 2626 return false; 2627 }; 2628 e.id = lastUsedHandlerId; 2629 auto optr = eventHandlers.ptr; 2630 eventHandlers ~= e; 2631 if (eventHandlers.ptr !is optr) { 2632 import core.memory : GC; 2633 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 2634 } 2635 return lastUsedHandlerId; 2636 } 2637 } 2638 2639 /// Remove event listener. It is safe to pass invalid event id here. 2640 /// $(WARNING Don't use this method in object destructors!) 2641 void removeEventListener() (uint id) { 2642 if (id == 0 || id > lastUsedHandlerId) return; 2643 synchronized(this) { 2644 foreach (immutable idx; 0..eventHandlers.length) { 2645 if (eventHandlers[idx].id == id) { 2646 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 2647 eventHandlers[$-1].dg = null; 2648 eventHandlers.length -= 1; 2649 eventHandlers.assumeSafeAppend; 2650 return; 2651 } 2652 } 2653 } 2654 } 2655 2656 /// Post event to queue. It is safe to call this from non-UI threads. 2657 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 2658 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 2659 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 2660 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) { 2661 if (this.closed) return false; // closed windows can't handle events 2662 2663 // remove all events of type `ET` 2664 void removeAllET () { 2665 uint eidx = 0, ec = eventQueueUsed; 2666 auto eptr = eventQueue.ptr; 2667 while (eidx < ec) { 2668 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 2669 if (cast(ET)eptr.evt !is null) { 2670 // i found her! 2671 if (inCustomEventProcessor) { 2672 // if we're in custom event processing loop, processor will clear it for us 2673 eptr.evt = null; 2674 ++eidx; 2675 ++eptr; 2676 } else { 2677 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 2678 ec = --eventQueueUsed; 2679 // clear last event (it is already copied) 2680 eventQueue.ptr[ec].evt = null; 2681 } 2682 } else { 2683 ++eidx; 2684 ++eptr; 2685 } 2686 } 2687 } 2688 2689 if (evt is null) { 2690 if (replace) { synchronized(this) removeAllET(); } 2691 // ignore empty events, they can't be handled anyway 2692 return false; 2693 } 2694 2695 // add events even if no event FD/event object created yet 2696 synchronized(this) { 2697 if (replace) removeAllET(); 2698 if (eventQueueUsed == uint.max) return false; // just in case 2699 if (eventQueueUsed < eventQueue.length) { 2700 eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs); 2701 } else { 2702 if (eventQueue.capacity == eventQueue.length) { 2703 // need to reallocate; do a trick to ensure that old array is cleared 2704 auto oarr = eventQueue; 2705 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 2706 // just in case, do yet another check 2707 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 2708 import core.memory : GC; 2709 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 2710 } else { 2711 auto optr = eventQueue.ptr; 2712 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 2713 assert(eventQueue.ptr is optr); 2714 } 2715 ++eventQueueUsed; 2716 assert(eventQueueUsed == eventQueue.length); 2717 } 2718 if (!eventWakeUp()) { 2719 // can't wake up event processor, so there is no reason to keep the event 2720 assert(eventQueueUsed > 0); 2721 eventQueue[--eventQueueUsed].evt = null; 2722 return false; 2723 } 2724 return true; 2725 } 2726 } 2727 2728 /// Post event to queue. It is safe to call this from non-UI threads. 2729 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 2730 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 2731 bool postEvent(ET:Object) (ET evt, bool replace=false) { 2732 return postTimeout!ET(evt, 0, replace); 2733 } 2734 2735 private: 2736 private import core.time : MonoTime; 2737 2738 version(Posix) { 2739 __gshared int customEventFDRead = -1; 2740 __gshared int customEventFDWrite = -1; 2741 __gshared int customSignalFD = -1; 2742 } else version(Windows) { 2743 __gshared HANDLE customEventH = null; 2744 } 2745 2746 // wake up event processor 2747 static bool eventWakeUp () { 2748 version(X11) { 2749 import core.sys.posix.unistd : write; 2750 ulong n = 1; 2751 if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof); 2752 return true; 2753 } else version(Windows) { 2754 if (customEventH !is null) SetEvent(customEventH); 2755 return true; 2756 } else { 2757 // not implemented for other OSes 2758 return false; 2759 } 2760 } 2761 2762 static struct QueuedEvent { 2763 Object evt; 2764 bool timed = false; 2765 MonoTime hittime = MonoTime.zero; 2766 bool doProcess = false; // process event at the current iteration (internal flag) 2767 2768 this (Object aevt, uint toutmsecs) { 2769 evt = aevt; 2770 if (toutmsecs > 0) { 2771 import core.time : msecs; 2772 timed = true; 2773 hittime = MonoTime.currTime+toutmsecs.msecs; 2774 } 2775 } 2776 } 2777 2778 alias CustomEventHandler = bool delegate (Object o) nothrow; 2779 static struct EventHandlerEntry { 2780 CustomEventHandler dg; 2781 uint id; 2782 } 2783 2784 uint lastUsedHandlerId; 2785 EventHandlerEntry[] eventHandlers; 2786 QueuedEvent[] eventQueue = null; 2787 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 2788 bool inCustomEventProcessor = false; // required to properly remove events 2789 2790 // process queued events and call custom event handlers 2791 // this will not process events posted from called handlers (such events are postponed for the next iteration) 2792 void processCustomEvents () { 2793 bool hasSomethingToDo = false; 2794 uint ecount; 2795 bool ocep; 2796 synchronized(this) { 2797 ocep = inCustomEventProcessor; 2798 inCustomEventProcessor = true; 2799 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 2800 auto ctt = MonoTime.currTime; 2801 bool hasEmpty = false; 2802 // mark events to process (this is required for `eventQueued()`) 2803 foreach (ref qe; eventQueue[0..ecount]) { 2804 if (qe.evt is null) { hasEmpty = true; continue; } 2805 if (qe.timed) { 2806 qe.doProcess = (qe.hittime <= ctt); 2807 } else { 2808 qe.doProcess = true; 2809 } 2810 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 2811 } 2812 if (!hasSomethingToDo) { 2813 // remove empty events 2814 if (hasEmpty) { 2815 uint eidx = 0, ec = eventQueueUsed; 2816 auto eptr = eventQueue.ptr; 2817 while (eidx < ec) { 2818 if (eptr.evt is null) { 2819 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 2820 ec = --eventQueueUsed; 2821 eventQueue.ptr[ec].evt = null; // make GC life easier 2822 } else { 2823 ++eidx; 2824 ++eptr; 2825 } 2826 } 2827 } 2828 inCustomEventProcessor = ocep; 2829 return; 2830 } 2831 } 2832 // process marked events 2833 uint efree = 0; // non-processed events will be put at this index 2834 EventHandlerEntry[] eh; 2835 Object evt; 2836 foreach (immutable eidx; 0..ecount) { 2837 synchronized(this) { 2838 if (!eventQueue[eidx].doProcess) { 2839 // skip this event 2840 assert(efree <= eidx); 2841 if (efree != eidx) { 2842 // copy this event to queue start 2843 eventQueue[efree] = eventQueue[eidx]; 2844 eventQueue[eidx].evt = null; // just in case 2845 } 2846 ++efree; 2847 continue; 2848 } 2849 evt = eventQueue[eidx].evt; 2850 eventQueue[eidx].evt = null; // in case event handler will hit GC 2851 if (evt is null) continue; // just in case 2852 // try all handlers; this can be slow, but meh... 2853 eh = eventHandlers; 2854 } 2855 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 2856 evt = null; 2857 eh = null; 2858 } 2859 synchronized(this) { 2860 // move all unprocessed events to queue top; efree holds first "free index" 2861 foreach (immutable eidx; ecount..eventQueueUsed) { 2862 assert(efree <= eidx); 2863 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 2864 ++efree; 2865 } 2866 eventQueueUsed = efree; 2867 // wake up event processor on next event loop iteration if we have more queued events 2868 // also, remove empty events 2869 bool awaken = false; 2870 uint eidx = 0, ec = eventQueueUsed; 2871 auto eptr = eventQueue.ptr; 2872 while (eidx < ec) { 2873 if (eptr.evt is null) { 2874 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 2875 ec = --eventQueueUsed; 2876 eventQueue.ptr[ec].evt = null; // make GC life easier 2877 } else { 2878 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 2879 ++eidx; 2880 ++eptr; 2881 } 2882 } 2883 inCustomEventProcessor = ocep; 2884 } 2885 } 2886 2887 // for all windows in nativeMapping 2888 static void processAllCustomEvents () { 2889 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 2890 if (sw is null || sw.closed) continue; 2891 sw.processCustomEvents(); 2892 } 2893 2894 runPendingRunInGuiThreadDelegates(); 2895 } 2896 2897 // 0: infinite (i.e. no scheduled events in queue) 2898 uint eventQueueTimeoutMSecs () { 2899 synchronized(this) { 2900 if (eventQueueUsed == 0) return 0; 2901 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 2902 uint res = int.max; 2903 auto ctt = MonoTime.currTime; 2904 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 2905 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 2906 if (qe.doProcess) continue; // just in case 2907 if (!qe.timed) return 1; // minimal 2908 if (qe.hittime <= ctt) return 1; // minimal 2909 auto tms = (qe.hittime-ctt).total!"msecs"; 2910 if (tms < 1) tms = 1; // safety net 2911 if (tms >= int.max) tms = int.max-1; // and another safety net 2912 if (res > tms) res = cast(uint)tms; 2913 } 2914 return (res >= int.max ? 0 : res); 2915 } 2916 } 2917 2918 // for all windows in nativeMapping 2919 static uint eventAllQueueTimeoutMSecs () { 2920 uint res = uint.max; 2921 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 2922 if (sw is null || sw.closed) continue; 2923 uint to = sw.eventQueueTimeoutMSecs(); 2924 if (to && to < res) { 2925 res = to; 2926 if (to == 1) break; // can't have less than this 2927 } 2928 } 2929 return (res >= int.max ? 0 : res); 2930 } 2931 } 2932 2933 /* Drag and drop support { */ 2934 version(X11) { 2935 2936 } else version(Windows) { 2937 import core.sys.windows.uuid; 2938 import core.sys.windows.ole2; 2939 import core.sys.windows.oleidl; 2940 import core.sys.windows.objidl; 2941 import core.sys.windows.wtypes; 2942 2943 pragma(lib, "ole32"); 2944 void initDnd() { 2945 auto err = OleInitialize(null); 2946 if(err != S_OK && err != S_FALSE) 2947 throw new Exception("init");//err); 2948 } 2949 } 2950 /* } End drag and drop support */ 2951 2952 2953 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 2954 /// See [GenericCursor] 2955 class MouseCursor { 2956 int osId; 2957 bool isStockCursor; 2958 private this(int osId) { 2959 this.osId = osId; 2960 this.isStockCursor = true; 2961 } 2962 2963 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 2964 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 2965 2966 version(Windows) { 2967 HCURSOR cursor_; 2968 HCURSOR cursorHandle() { 2969 if(cursor_ is null) 2970 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 2971 return cursor_; 2972 } 2973 2974 } else static if(UsingSimpledisplayX11) { 2975 Cursor cursor_ = None; 2976 int xDisplaySequence; 2977 2978 Cursor cursorHandle() { 2979 if(this.osId == None) 2980 return None; 2981 2982 // we need to reload if we on a new X connection 2983 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 2984 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 2985 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 2986 } 2987 return cursor_; 2988 } 2989 } 2990 } 2991 2992 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 2993 // https://tronche.com/gui/x/xlib/appendix/b/ 2994 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 2995 /// Note that there is no exact appearance guaranteed for any of these items; it may change appearance on different operating systems or future simpledisplay versions 2996 enum GenericCursorType { 2997 Default, /// The default arrow pointer. 2998 Wait, /// A cursor indicating something is loading and the user must wait. 2999 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 3000 Help, /// A cursor indicating the user can get help about the pointer location. 3001 Cross, /// A crosshair. 3002 Text, /// An i-beam shape, typically used to indicate text selection is possible. 3003 Move, /// Pointer indicating movement is possible. May also be used as SizeAll 3004 UpArrow, /// An arrow pointing straight up. 3005 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 3006 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 3007 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator) 3008 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator) 3009 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator) 3010 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator) 3011 3012 } 3013 3014 /* 3015 X_plus == css cell == Windows ? 3016 */ 3017 3018 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 3019 static struct GenericCursor { 3020 static: 3021 /// 3022 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 3023 static MouseCursor mc; 3024 3025 auto type = __traits(getMember, GenericCursorType, str); 3026 3027 if(mc is null) { 3028 3029 version(Windows) { 3030 int osId; 3031 final switch(type) { 3032 case GenericCursorType.Default: osId = IDC_ARROW; break; 3033 case GenericCursorType.Wait: osId = IDC_WAIT; break; 3034 case GenericCursorType.Hand: osId = IDC_HAND; break; 3035 case GenericCursorType.Help: osId = IDC_HELP; break; 3036 case GenericCursorType.Cross: osId = IDC_CROSS; break; 3037 case GenericCursorType.Text: osId = IDC_IBEAM; break; 3038 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 3039 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 3040 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 3041 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 3042 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 3043 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 3044 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 3045 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 3046 } 3047 } else static if(UsingSimpledisplayX11) { 3048 int osId; 3049 final switch(type) { 3050 case GenericCursorType.Default: osId = None; break; 3051 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 3052 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 3053 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 3054 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 3055 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 3056 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 3057 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 3058 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 3059 3060 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 3061 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 3062 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 3063 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 3064 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 3065 } 3066 3067 } else featureNotImplemented(); 3068 3069 mc = new MouseCursor(osId); 3070 } 3071 return mc; 3072 } 3073 } 3074 3075 3076 /++ 3077 If you want to get more control over the event loop, you can use this. 3078 3079 Typically though, you can just call [SimpleWindow.eventLoop]. 3080 +/ 3081 struct EventLoop { 3082 @disable this(); 3083 3084 /// Gets a reference to an existing event loop 3085 static EventLoop get() { 3086 return EventLoop(0, null); 3087 } 3088 3089 __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi 3090 3091 /// Construct an application-global event loop for yourself 3092 /// See_Also: [SimpleWindow.setEventHandlers] 3093 this(long pulseTimeout, void delegate() handlePulse) { 3094 synchronized(monitor) { 3095 if(impl is null) { 3096 claimGuiThread(); 3097 version(sdpy_thread_checks) assert(thisIsGuiThread); 3098 impl = new EventLoopImpl(pulseTimeout, handlePulse); 3099 } else { 3100 if(pulseTimeout) { 3101 impl.pulseTimeout = pulseTimeout; 3102 impl.handlePulse = handlePulse; 3103 } 3104 } 3105 impl.refcount++; 3106 } 3107 } 3108 3109 ~this() { 3110 if(impl is null) 3111 return; 3112 impl.refcount--; 3113 if(impl.refcount == 0) { 3114 impl.dispose(); 3115 if(thisIsGuiThread) 3116 guiThreadFinalize(); 3117 } 3118 3119 } 3120 3121 this(this) { 3122 if(impl is null) 3123 return; 3124 impl.refcount++; 3125 } 3126 3127 /// Runs the event loop until the whileCondition, if present, returns false 3128 int run(bool delegate() whileCondition = null) { 3129 assert(impl !is null); 3130 impl.notExited = true; 3131 return impl.run(whileCondition); 3132 } 3133 3134 /// Exits the event loop 3135 void exit() { 3136 assert(impl !is null); 3137 impl.notExited = false; 3138 } 3139 3140 version(linux) 3141 ref void delegate(int) signalHandler() { 3142 assert(impl !is null); 3143 return impl.signalHandler; 3144 } 3145 3146 __gshared static EventLoopImpl* impl; 3147 } 3148 3149 version(linux) 3150 void delegate(int, int) globalHupHandler; 3151 3152 version(Posix) 3153 void makeNonBlocking(int fd) { 3154 import fcntl = core.sys.posix.fcntl; 3155 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 3156 if(flags == -1) 3157 throw new Exception("fcntl get"); 3158 flags |= fcntl.O_NONBLOCK; 3159 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 3160 if(s == -1) 3161 throw new Exception("fcntl set"); 3162 } 3163 3164 struct EventLoopImpl { 3165 int refcount; 3166 3167 bool notExited = true; 3168 3169 version(linux) { 3170 static import ep = core.sys.linux.epoll; 3171 static import unix = core.sys.posix.unistd; 3172 static import err = core.stdc.errno; 3173 import core.sys.linux.timerfd; 3174 3175 void delegate(int) signalHandler; 3176 } 3177 3178 version(X11) { 3179 int pulseFd = -1; 3180 version(linux) ep.epoll_event[16] events = void; 3181 } else version(Windows) { 3182 Timer pulser; 3183 HANDLE[] handles; 3184 } 3185 3186 3187 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 3188 /// to call this, as it's not recommended to share window between threads. 3189 void mtLock () { 3190 version(X11) { 3191 XLockDisplay(this.display); 3192 } 3193 } 3194 3195 version(X11) 3196 auto display() { return XDisplayConnection.get; } 3197 3198 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 3199 /// to call this, as it's not recommended to share window between threads. 3200 void mtUnlock () { 3201 version(X11) { 3202 XUnlockDisplay(this.display); 3203 } 3204 } 3205 3206 version(with_eventloop) 3207 void initialize(long pulseTimeout) {} 3208 else 3209 void initialize(long pulseTimeout) { 3210 version(Windows) { 3211 if(pulseTimeout && handlePulse !is null) 3212 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 3213 3214 if (customEventH is null) { 3215 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 3216 if (customEventH !is null) { 3217 handles ~= customEventH; 3218 } else { 3219 // this is something that should not be; better be safe than sorry 3220 throw new Exception("can't create eventfd for custom event processing"); 3221 } 3222 } 3223 3224 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 3225 } 3226 3227 version(linux) { 3228 prepareEventLoop(); 3229 { 3230 auto display = XDisplayConnection.get; 3231 // adding Xlib file 3232 ep.epoll_event ev = void; 3233 { import core.stdc..string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3234 ev.events = ep.EPOLLIN; 3235 ev.data.fd = display.fd; 3236 //import std.conv; 3237 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 3238 throw new Exception("add x fd");// ~ to!string(epollFd)); 3239 displayFd = display.fd; 3240 } 3241 3242 if(pulseTimeout && handlePulse !is null) { 3243 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 3244 if(pulseFd == -1) 3245 throw new Exception("pulse timer create failed"); 3246 3247 itimerspec value; 3248 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 3249 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 3250 3251 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 3252 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 3253 3254 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 3255 throw new Exception("couldn't make pulse timer"); 3256 3257 ep.epoll_event ev = void; 3258 { import core.stdc..string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3259 ev.events = ep.EPOLLIN; 3260 ev.data.fd = pulseFd; 3261 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 3262 } 3263 3264 // eventfd for custom events 3265 if (customEventFDWrite == -1) { 3266 customEventFDWrite = eventfd(0, 0); 3267 customEventFDRead = customEventFDWrite; 3268 if (customEventFDRead >= 0) { 3269 ep.epoll_event ev = void; 3270 { import core.stdc..string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3271 ev.events = ep.EPOLLIN; 3272 ev.data.fd = customEventFDRead; 3273 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev); 3274 } else { 3275 // this is something that should not be; better be safe than sorry 3276 throw new Exception("can't create eventfd for custom event processing"); 3277 } 3278 } 3279 3280 if (customSignalFD == -1) { 3281 import core.sys.linux.sys.signalfd; 3282 3283 sigset_t sigset; 3284 auto err = sigemptyset(&sigset); 3285 assert(!err); 3286 err = sigaddset(&sigset, SIGINT); 3287 assert(!err); 3288 err = sigaddset(&sigset, SIGHUP); 3289 assert(!err); 3290 err = sigprocmask(SIG_BLOCK, &sigset, null); 3291 assert(!err); 3292 3293 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 3294 assert(customSignalFD != -1); 3295 3296 ep.epoll_event ev = void; 3297 { import core.stdc..string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3298 ev.events = ep.EPOLLIN; 3299 ev.data.fd = customSignalFD; 3300 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 3301 } 3302 } else version(Posix) { 3303 prepareEventLoop(); 3304 if (customEventFDRead == -1) { 3305 int[2] bfr; 3306 import core.sys.posix.unistd; 3307 auto ret = pipe(bfr); 3308 if(ret == -1) throw new Exception("pipe"); 3309 customEventFDRead = bfr[0]; 3310 customEventFDWrite = bfr[1]; 3311 } 3312 3313 } 3314 3315 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 3316 3317 version(linux) { 3318 this.mtLock(); 3319 scope(exit) this.mtUnlock(); 3320 XPending(display); // no, really 3321 } 3322 3323 disposed = false; 3324 } 3325 3326 bool disposed = true; 3327 version(X11) 3328 int displayFd = -1; 3329 3330 version(with_eventloop) 3331 void dispose() {} 3332 else 3333 void dispose() { 3334 disposed = true; 3335 version(X11) { 3336 if(pulseFd != -1) { 3337 import unix = core.sys.posix.unistd; 3338 unix.close(pulseFd); 3339 pulseFd = -1; 3340 } 3341 3342 version(linux) 3343 if(displayFd != -1) { 3344 // clean up xlib fd when we exit, in case we come back later e.g. X disconnect and reconnect with new FD, don't want to still keep the old one around 3345 ep.epoll_event ev = void; 3346 { import core.stdc..string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3347 ev.events = ep.EPOLLIN; 3348 ev.data.fd = displayFd; 3349 //import std.conv; 3350 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 3351 displayFd = -1; 3352 } 3353 3354 } else version(Windows) { 3355 if(pulser !is null) { 3356 pulser.destroy(); 3357 pulser = null; 3358 } 3359 if (customEventH !is null) { 3360 CloseHandle(customEventH); 3361 customEventH = null; 3362 } 3363 } 3364 } 3365 3366 this(long pulseTimeout, void delegate() handlePulse) { 3367 this.pulseTimeout = pulseTimeout; 3368 this.handlePulse = handlePulse; 3369 initialize(pulseTimeout); 3370 } 3371 3372 private long pulseTimeout; 3373 void delegate() handlePulse; 3374 3375 ~this() { 3376 dispose(); 3377 } 3378 3379 version(Posix) 3380 ref int customEventFDRead() { return SimpleWindow.customEventFDRead; } 3381 version(Posix) 3382 ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; } 3383 version(linux) 3384 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 3385 version(Windows) 3386 ref auto customEventH() { return SimpleWindow.customEventH; } 3387 3388 version(with_eventloop) { 3389 int loopHelper(bool delegate() whileCondition) { 3390 // FIXME: whileCondition 3391 import arsd.eventloop; 3392 loop(); 3393 return 0; 3394 } 3395 } else 3396 int loopHelper(bool delegate() whileCondition) { 3397 version(X11) { 3398 bool done = false; 3399 3400 XFlush(display); 3401 insideXEventLoop = true; 3402 scope(exit) insideXEventLoop = false; 3403 3404 version(linux) { 3405 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 3406 bool forceXPending = false; 3407 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 3408 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 3409 { 3410 this.mtLock(); 3411 scope(exit) this.mtUnlock(); 3412 if (XEventsQueued(this.display, QueueMode.QueuedAlready)) { forceXPending = true; if (wto > 10 || wto <= 0) wto = 10; } // so libX event loop will be able to do it's work 3413 } 3414 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 3415 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 3416 if(nfds == -1) { 3417 if(err.errno == err.EINTR) { 3418 //if(forceXPending) goto xpending; 3419 continue; // interrupted by signal, just try again 3420 } 3421 throw new Exception("epoll wait failure"); 3422 } 3423 3424 SimpleWindow.processAllCustomEvents(); // anyway 3425 //version(sdddd) { import std.stdio; writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 3426 foreach(idx; 0 .. nfds) { 3427 if(done) break; 3428 auto fd = events[idx].data.fd; 3429 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 3430 auto flags = events[idx].events; 3431 if(flags & ep.EPOLLIN) { 3432 if (fd == customSignalFD) { 3433 version(linux) { 3434 import core.sys.linux.sys.signalfd; 3435 import core.sys.posix.unistd : read; 3436 signalfd_siginfo info; 3437 read(customSignalFD, &info, info.sizeof); 3438 3439 auto sig = info.ssi_signo; 3440 3441 if(EventLoop.get.signalHandler !is null) { 3442 EventLoop.get.signalHandler()(sig); 3443 } else { 3444 EventLoop.get.exit(); 3445 } 3446 } 3447 } else if(fd == display.fd) { 3448 version(sdddd) { import std.stdio; writeln("X EVENT PENDING!"); } 3449 this.mtLock(); 3450 scope(exit) this.mtUnlock(); 3451 while(!done && XPending(display)) { 3452 done = doXNextEvent(this.display); 3453 } 3454 forceXPending = false; 3455 } else if(fd == pulseFd) { 3456 long expirationCount; 3457 // if we go over the count, I ignore it because i don't want the pulse to go off more often and eat tons of cpu time... 3458 3459 handlePulse(); 3460 3461 // read just to clear the buffer so poll doesn't trigger again 3462 // BTW I read AFTER the pulse because if the pulse handler takes 3463 // a lot of time to execute, we don't want the app to get stuck 3464 // in a loop of timer hits without a chance to do anything else 3465 // 3466 // IOW handlePulse happens at most once per pulse interval. 3467 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 3468 forceXPending = true; // some events might have been added while the pulse was going off and xlib already read it from the fd (like as a result of a draw done in the timer handler). if so we need to flush that separately to ensure it is not delayed 3469 } else if (fd == customEventFDRead) { 3470 // we have some custom events; process 'em 3471 import core.sys.posix.unistd : read; 3472 ulong n; 3473 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 3474 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 3475 //SimpleWindow.processAllCustomEvents(); 3476 } else { 3477 // some other timer 3478 version(sdddd) { import std.stdio; writeln("unknown fd: ", fd); } 3479 3480 if(Timer* t = fd in Timer.mapping) 3481 (*t).trigger(); 3482 3483 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 3484 (*pfr).ready(flags); 3485 3486 // or i might add support for other FDs too 3487 // but for now it is just timer 3488 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 3489 } 3490 } 3491 if(flags & ep.EPOLLHUP) { 3492 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 3493 (*pfr).hup(flags); 3494 if(globalHupHandler) 3495 globalHupHandler(fd, flags); 3496 } 3497 /+ 3498 } else { 3499 // not interested in OUT, we are just reading here. 3500 // 3501 // error or hup might also be reported 3502 // but it shouldn't here since we are only 3503 // using a few types of FD and Xlib will report 3504 // if it dies. 3505 // so instead of thoughtfully handling it, I'll 3506 // just throw. for now at least 3507 3508 throw new Exception("epoll did something else"); 3509 } 3510 +/ 3511 } 3512 // if we won't call `XPending()` here, libX may delay some internal event delivery. 3513 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 3514 xpending: 3515 if (!done && forceXPending) { 3516 this.mtLock(); 3517 scope(exit) this.mtUnlock(); 3518 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 3519 while(!done && XPending(display)) { 3520 done = doXNextEvent(this.display); 3521 } 3522 } 3523 } 3524 } else { 3525 // Generic fallback: yes to simple pulse support, 3526 // but NO timer support! 3527 3528 // FIXME: we could probably support the POSIX timer_create 3529 // signal-based option, but I'm in no rush to write it since 3530 // I prefer the fd-based functions. 3531 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 3532 3533 import core.sys.posix.poll; 3534 3535 pollfd[] pfds; 3536 pollfd[32] pfdsBuffer; 3537 auto len = PosixFdReader.mapping.length + 2; 3538 // FIXME: i should just reuse the buffer 3539 if(len < pfdsBuffer.length) 3540 pfds = pfdsBuffer[0 .. len]; 3541 else 3542 pfds = new pollfd[](len); 3543 3544 pfds[0].fd = display.fd; 3545 pfds[0].events = POLLIN; 3546 pfds[0].revents = 0; 3547 3548 int slot = 1; 3549 3550 if(customEventFDRead != -1) { 3551 pfds[slot].fd = customEventFDRead; 3552 pfds[slot].events = POLLIN; 3553 pfds[slot].revents = 0; 3554 3555 slot++; 3556 } 3557 3558 foreach(fd, obj; PosixFdReader.mapping) { 3559 if(!obj.enabled) continue; 3560 pfds[slot].fd = fd; 3561 pfds[slot].events = POLLIN; 3562 pfds[slot].revents = 0; 3563 3564 slot++; 3565 } 3566 3567 auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1); 3568 if(ret == -1) throw new Exception("poll"); 3569 3570 if(ret == 0) { 3571 // FIXME it may not necessarily time out if events keep coming 3572 if(handlePulse !is null) 3573 handlePulse(); 3574 } else { 3575 foreach(s; 0 .. slot) { 3576 if(pfds[s].revents == 0) continue; 3577 3578 if(pfds[s].fd == display.fd) { 3579 while(!done && XPending(display)) { 3580 this.mtLock(); 3581 scope(exit) this.mtUnlock(); 3582 done = doXNextEvent(this.display); 3583 } 3584 } else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) { 3585 3586 import core.sys.posix.unistd : read; 3587 ulong n; 3588 read(customEventFDRead, &n, n.sizeof); 3589 SimpleWindow.processAllCustomEvents(); 3590 } else { 3591 auto obj = PosixFdReader.mapping[pfds[s].fd]; 3592 if(pfds[s].revents & POLLNVAL) { 3593 obj.dispose(); 3594 } else { 3595 obj.ready(pfds[s].revents); 3596 } 3597 } 3598 3599 ret--; 3600 if(ret == 0) break; 3601 } 3602 } 3603 } 3604 } 3605 } 3606 3607 version(Windows) { 3608 int ret = -1; 3609 MSG message; 3610 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 3611 eventLoopRound++; 3612 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 3613 auto waitResult = MsgWaitForMultipleObjectsEx( 3614 cast(int) handles.length, handles.ptr, 3615 (wto == 0 ? INFINITE : wto), /* timeout */ 3616 0x04FF, /* QS_ALLINPUT */ 3617 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 3618 3619 SimpleWindow.processAllCustomEvents(); // anyway 3620 enum WAIT_OBJECT_0 = 0; 3621 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 3622 auto h = handles[waitResult - WAIT_OBJECT_0]; 3623 if(auto e = h in WindowsHandleReader.mapping) { 3624 (*e).ready(); 3625 } 3626 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 3627 // message ready 3628 while(PeekMessage(&message, null, 0, 0, PM_NOREMOVE)) { // need to peek since sometimes MsgWaitForMultipleObjectsEx returns even though GetMessage can block. tbh i don't fully understand it but the docs say it is foreground activation 3629 ret = GetMessage(&message, null, 0, 0); 3630 if(ret == -1) 3631 throw new Exception("GetMessage failed"); 3632 TranslateMessage(&message); 3633 DispatchMessage(&message); 3634 3635 if(ret == 0) // WM_QUIT 3636 break; 3637 } 3638 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 3639 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 3640 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 3641 // timeout, should never happen since we aren't using it 3642 } else if(waitResult == 0xFFFFFFFF) { 3643 // failed 3644 throw new Exception("MsgWaitForMultipleObjectsEx failed"); 3645 } else { 3646 // idk.... 3647 } 3648 } 3649 3650 // return message.wParam; 3651 return 0; 3652 } else { 3653 return 0; 3654 } 3655 } 3656 3657 int run(bool delegate() whileCondition = null) { 3658 if(disposed) 3659 initialize(this.pulseTimeout); 3660 3661 version(X11) { 3662 try { 3663 return loopHelper(whileCondition); 3664 } catch(XDisconnectException e) { 3665 if(e.userRequested) { 3666 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 3667 item.discardConnectionState(); 3668 XCloseDisplay(XDisplayConnection.display); 3669 } 3670 3671 XDisplayConnection.display = null; 3672 3673 this.dispose(); 3674 3675 throw e; 3676 } 3677 } else { 3678 return loopHelper(whileCondition); 3679 } 3680 } 3681 } 3682 3683 3684 /++ 3685 Provides an icon on the system notification area (also known as the system tray). 3686 3687 3688 If a notification area is not available with the NotificationIcon object is created, 3689 it will silently succeed and simply attempt to create one when an area becomes available. 3690 3691 3692 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. 3693 If this is wrong, pass -version=WindowsXP to dmd when compiling and it will 3694 use the older version. 3695 +/ 3696 version(OSXCocoa) {} else // NotYetImplementedException 3697 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 3698 3699 version(X11) { 3700 void recreateAfterDisconnect() { 3701 stateDiscarded = false; 3702 clippixmap = None; 3703 throw new Exception("NOT IMPLEMENTED"); 3704 } 3705 3706 bool stateDiscarded; 3707 void discardConnectionState() { 3708 stateDiscarded = true; 3709 } 3710 } 3711 3712 3713 version(X11) { 3714 Image img; 3715 3716 NativeEventHandler getNativeEventHandler() { 3717 return delegate int(XEvent e) { 3718 switch(e.type) { 3719 case EventType.Expose: 3720 //case EventType.VisibilityNotify: 3721 redraw(); 3722 break; 3723 case EventType.ClientMessage: 3724 version(sddddd) { 3725 import std.stdio; 3726 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 3727 writeln("\t", e.xclient.format); 3728 writeln("\t", e.xclient.data.l); 3729 } 3730 break; 3731 case EventType.ButtonPress: 3732 auto event = e.xbutton; 3733 if (onClick !is null || onClickEx !is null) { 3734 MouseButton mb = cast(MouseButton)0; 3735 switch (event.button) { 3736 case 1: mb = MouseButton.left; break; // left 3737 case 2: mb = MouseButton.middle; break; // middle 3738 case 3: mb = MouseButton.right; break; // right 3739 case 4: mb = MouseButton.wheelUp; break; // scroll up 3740 case 5: mb = MouseButton.wheelDown; break; // scroll down 3741 case 6: break; // idk 3742 case 7: break; // idk 3743 case 8: mb = MouseButton.backButton; break; 3744 case 9: mb = MouseButton.forwardButton; break; 3745 default: 3746 } 3747 if (mb) { 3748 try { onClick()(mb); } catch (Exception) {} 3749 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 3750 } 3751 } 3752 break; 3753 case EventType.EnterNotify: 3754 if (onEnter !is null) { 3755 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 3756 } 3757 break; 3758 case EventType.LeaveNotify: 3759 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 3760 break; 3761 case EventType.DestroyNotify: 3762 active = false; 3763 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 3764 break; 3765 case EventType.ConfigureNotify: 3766 auto event = e.xconfigure; 3767 this.width = event.width; 3768 this.height = event.height; 3769 //import std.stdio; writeln(width, " x " , height, " @ ", event.x, " ", event.y); 3770 redraw(); 3771 break; 3772 default: return 1; 3773 } 3774 return 1; 3775 }; 3776 } 3777 3778 /* private */ void hideBalloon() { 3779 balloon.close(); 3780 version(with_timer) 3781 timer.destroy(); 3782 balloon = null; 3783 version(with_timer) 3784 timer = null; 3785 } 3786 3787 void redraw() { 3788 if (!active) return; 3789 3790 auto display = XDisplayConnection.get; 3791 auto gc = DefaultGC(display, DefaultScreen(display)); 3792 XClearWindow(display, nativeHandle); 3793 3794 XSetClipMask(display, gc, clippixmap); 3795 3796 XSetForeground(display, gc, 3797 cast(uint) 0 << 16 | 3798 cast(uint) 0 << 8 | 3799 cast(uint) 0); 3800 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 3801 3802 if (img is null) { 3803 XSetForeground(display, gc, 3804 cast(uint) 0 << 16 | 3805 cast(uint) 127 << 8 | 3806 cast(uint) 0); 3807 XFillArc(display, nativeHandle, 3808 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 3809 } else { 3810 int dx = 0; 3811 int dy = 0; 3812 if(width > img.width) 3813 dx = (width - img.width) / 2; 3814 if(height > img.height) 3815 dy = (height - img.height) / 2; 3816 XSetClipOrigin(display, gc, dx, dy); 3817 3818 if (img.usingXshm) 3819 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false); 3820 else 3821 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height); 3822 } 3823 XSetClipMask(display, gc, None); 3824 flushGui(); 3825 } 3826 3827 static Window getTrayOwner() { 3828 auto display = XDisplayConnection.get; 3829 auto i = cast(int) DefaultScreen(display); 3830 if(i < 10 && i >= 0) { 3831 static Atom atom; 3832 if(atom == None) 3833 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 3834 return XGetSelectionOwner(display, atom); 3835 } 3836 return None; 3837 } 3838 3839 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 3840 auto to = getTrayOwner(); 3841 auto display = XDisplayConnection.get; 3842 XEvent ev; 3843 ev.xclient.type = EventType.ClientMessage; 3844 ev.xclient.window = to; 3845 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 3846 ev.xclient.format = 32; 3847 ev.xclient.data.l[0] = CurrentTime; 3848 ev.xclient.data.l[1] = message; 3849 ev.xclient.data.l[2] = d1; 3850 ev.xclient.data.l[3] = d2; 3851 ev.xclient.data.l[4] = d3; 3852 3853 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 3854 } 3855 3856 private static NotificationAreaIcon[] activeIcons; 3857 3858 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 3859 private void newManager() { 3860 close(); 3861 createXWin(); 3862 3863 if(this.clippixmap) 3864 XFreePixmap(XDisplayConnection.get, clippixmap); 3865 if(this.originalMemoryImage) 3866 this.icon = this.originalMemoryImage; 3867 else if(this.img) 3868 this.icon = this.img; 3869 } 3870 3871 private void createXWin () { 3872 // create window 3873 auto display = XDisplayConnection.get; 3874 3875 // to check for MANAGER on root window to catch new/changed tray owners 3876 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 3877 // so if a thing does appear, we can handle it 3878 foreach(ai; activeIcons) 3879 if(ai is this) 3880 goto alreadythere; 3881 activeIcons ~= this; 3882 alreadythere: 3883 3884 // and check for an existing tray 3885 auto trayOwner = getTrayOwner(); 3886 if(trayOwner == None) 3887 return; 3888 //throw new Exception("No notification area found"); 3889 3890 Visual* v = cast(Visual*) CopyFromParent; 3891 /+ 3892 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 3893 if(visualProp !is null) { 3894 c_ulong[] info = cast(c_ulong[]) visualProp; 3895 if(info.length == 1) { 3896 auto vid = info[0]; 3897 int returned; 3898 XVisualInfo t; 3899 t.visualid = vid; 3900 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 3901 if(got !is null) { 3902 if(returned == 1) { 3903 v = got.visual; 3904 import std.stdio; 3905 writeln("using special visual ", *got); 3906 } 3907 XFree(got); 3908 } 3909 } 3910 } 3911 +/ 3912 3913 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null); 3914 assert(nativeWindow); 3915 3916 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 3917 3918 nativeHandle = nativeWindow; 3919 3920 ///+ 3921 arch_ulong[2] info; 3922 info[0] = 0; 3923 info[1] = 1; 3924 3925 string title = this.name is null ? "simpledisplay.d program" : this.name; 3926 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 3927 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 3928 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 3929 3930 XChangeProperty( 3931 display, 3932 nativeWindow, 3933 GetAtom!("_XEMBED_INFO", true)(display), 3934 GetAtom!("_XEMBED_INFO", true)(display), 3935 32 /* bits */, 3936 0 /*PropModeReplace*/, 3937 info.ptr, 3938 2); 3939 3940 import core.sys.posix.unistd; 3941 arch_ulong pid = getpid(); 3942 3943 XChangeProperty( 3944 display, 3945 nativeWindow, 3946 GetAtom!("_NET_WM_PID", true)(display), 3947 XA_CARDINAL, 3948 32 /* bits */, 3949 0 /*PropModeReplace*/, 3950 &pid, 3951 1); 3952 3953 updateNetWmIcon(); 3954 3955 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 3956 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 3957 XClassHint klass; 3958 XWMHints wh; 3959 XSizeHints size; 3960 klass.res_name = sdpyWindowClassStr; 3961 klass.res_class = sdpyWindowClassStr; 3962 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 3963 } 3964 3965 // believe it or not, THIS is what xfce needed for the 9999 issue 3966 XSizeHints sh; 3967 c_long spr; 3968 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 3969 sh.flags |= PMaxSize | PMinSize; 3970 // FIXME maybe nicer resizing 3971 sh.min_width = 16; 3972 sh.min_height = 16; 3973 sh.max_width = 16; 3974 sh.max_height = 16; 3975 XSetWMNormalHints(display, nativeWindow, &sh); 3976 3977 3978 //+/ 3979 3980 3981 XSelectInput(display, nativeWindow, 3982 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 3983 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 3984 3985 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 3986 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 3987 active = true; 3988 } 3989 3990 void updateNetWmIcon() { 3991 if(img is null) return; 3992 auto display = XDisplayConnection.get; 3993 // FIXME: ensure this is correct 3994 arch_ulong[] buffer; 3995 auto imgMi = img.toTrueColorImage; 3996 buffer ~= imgMi.width; 3997 buffer ~= imgMi.height; 3998 foreach(c; imgMi.imageData.colors) { 3999 arch_ulong b; 4000 b |= c.a << 24; 4001 b |= c.r << 16; 4002 b |= c.g << 8; 4003 b |= c.b; 4004 buffer ~= b; 4005 } 4006 4007 XChangeProperty( 4008 display, 4009 nativeHandle, 4010 GetAtom!"_NET_WM_ICON"(display), 4011 GetAtom!"CARDINAL"(display), 4012 32 /* bits */, 4013 0 /*PropModeReplace*/, 4014 buffer.ptr, 4015 cast(int) buffer.length); 4016 } 4017 4018 4019 4020 private SimpleWindow balloon; 4021 version(with_timer) 4022 private Timer timer; 4023 4024 private Window nativeHandle; 4025 private Pixmap clippixmap = None; 4026 private int width = 16; 4027 private int height = 16; 4028 private bool active = false; 4029 4030 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 4031 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 4032 void delegate () onLeave; /// X11 only. 4033 4034 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 4035 4036 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 4037 void getWindowRect (out int x, out int y, out int width, out int height) { 4038 if (!active) { width = 1; height = 1; return; } // 1: just in case 4039 Window dummyw; 4040 auto dpy = XDisplayConnection.get; 4041 //XWindowAttributes xwa; 4042 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 4043 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 4044 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 4045 width = this.width; 4046 height = this.height; 4047 } 4048 } 4049 4050 /+ 4051 What I actually want from this: 4052 4053 * set / change: icon, tooltip 4054 * handle: mouse click, right click 4055 * show: notification bubble. 4056 +/ 4057 4058 version(Windows) { 4059 WindowsIcon win32Icon; 4060 HWND hwnd; 4061 4062 NOTIFYICONDATAW data; 4063 4064 NativeEventHandler getNativeEventHandler() { 4065 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { 4066 if(msg == WM_USER) { 4067 auto event = LOWORD(lParam); 4068 auto iconId = HIWORD(lParam); 4069 //auto x = GET_X_LPARAM(wParam); 4070 //auto y = GET_Y_LPARAM(wParam); 4071 switch(event) { 4072 case WM_LBUTTONDOWN: 4073 onClick()(MouseButton.left); 4074 break; 4075 case WM_RBUTTONDOWN: 4076 onClick()(MouseButton.right); 4077 break; 4078 case WM_MBUTTONDOWN: 4079 onClick()(MouseButton.middle); 4080 break; 4081 case WM_MOUSEMOVE: 4082 // sent, we could use it. 4083 break; 4084 case WM_MOUSEWHEEL: 4085 // NOT SENT 4086 break; 4087 //case NIN_KEYSELECT: 4088 //case NIN_SELECT: 4089 //break; 4090 default: {} 4091 } 4092 } 4093 return 0; 4094 }; 4095 } 4096 4097 enum NIF_SHOWTIP = 0x00000080; 4098 4099 private static struct NOTIFYICONDATAW { 4100 DWORD cbSize; 4101 HWND hWnd; 4102 UINT uID; 4103 UINT uFlags; 4104 UINT uCallbackMessage; 4105 HICON hIcon; 4106 WCHAR[128] szTip; 4107 DWORD dwState; 4108 DWORD dwStateMask; 4109 WCHAR[256] szInfo; 4110 union { 4111 UINT uTimeout; 4112 UINT uVersion; 4113 } 4114 WCHAR[64] szInfoTitle; 4115 DWORD dwInfoFlags; 4116 GUID guidItem; 4117 HICON hBalloonIcon; 4118 } 4119 4120 } 4121 4122 /++ 4123 Note that on Windows, only left, right, and middle buttons are sent. 4124 Mouse wheel buttons are NOT set, so don't rely on those events if your 4125 program is meant to be used on Windows too. 4126 +/ 4127 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 4128 // The canonical constructor for Windows needs the MemoryImage, so it is here, 4129 // but on X, we need an Image, so its canonical ctor is there. They should 4130 // forward to each other though. 4131 version(X11) { 4132 this.name = name; 4133 this.onClick = onClick; 4134 createXWin(); 4135 this.icon = icon; 4136 } else version(Windows) { 4137 this.onClick = onClick; 4138 this.win32Icon = new WindowsIcon(icon); 4139 4140 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 4141 4142 static bool registered = false; 4143 if(!registered) { 4144 WNDCLASSEX wc; 4145 wc.cbSize = wc.sizeof; 4146 wc.hInstance = hInstance; 4147 wc.lpfnWndProc = &WndProc; 4148 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 4149 if(!RegisterClassExW(&wc)) 4150 throw new WindowsApiException("RegisterClass"); 4151 registered = true; 4152 } 4153 4154 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 4155 if(hwnd is null) 4156 throw new Exception("CreateWindow"); 4157 4158 data.cbSize = data.sizeof; 4159 data.hWnd = hwnd; 4160 data.uID = cast(uint) cast(void*) this; 4161 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 4162 // NIF_INFO means show balloon 4163 data.uCallbackMessage = WM_USER; 4164 data.hIcon = this.win32Icon.hIcon; 4165 data.szTip = ""; // FIXME 4166 data.dwState = 0; // NIS_HIDDEN; // windows vista 4167 data.dwStateMask = NIS_HIDDEN; // windows vista 4168 4169 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 4170 4171 4172 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 4173 4174 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 4175 } else version(OSXCocoa) { 4176 throw new NotYetImplementedException(); 4177 } else static assert(0); 4178 } 4179 4180 /// ditto 4181 this(string name, Image icon, void delegate(MouseButton button) onClick) { 4182 version(X11) { 4183 this.onClick = onClick; 4184 this.name = name; 4185 createXWin(); 4186 this.icon = icon; 4187 } else version(Windows) { 4188 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 4189 } else version(OSXCocoa) { 4190 throw new NotYetImplementedException(); 4191 } else static assert(0); 4192 } 4193 4194 version(X11) { 4195 /++ 4196 X-specific extension (for now at least) 4197 +/ 4198 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 4199 this.onClickEx = onClickEx; 4200 createXWin(); 4201 if (icon !is null) this.icon = icon; 4202 } 4203 4204 /// ditto 4205 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 4206 this.onClickEx = onClickEx; 4207 createXWin(); 4208 this.icon = icon; 4209 } 4210 } 4211 4212 private void delegate (MouseButton button) onClick_; 4213 4214 /// 4215 @property final void delegate(MouseButton) onClick() { 4216 if(onClick_ is null) 4217 onClick_ = delegate void(MouseButton) {}; 4218 return onClick_; 4219 } 4220 4221 /// ditto 4222 @property final void onClick(void delegate(MouseButton) handler) { 4223 // I made this a property setter so we can wrap smaller arg 4224 // delegates and just forward all to onClickEx or something. 4225 onClick_ = handler; 4226 } 4227 4228 4229 string name_; 4230 @property void name(string n) { 4231 name_ = n; 4232 } 4233 4234 @property string name() { 4235 return name_; 4236 } 4237 4238 private MemoryImage originalMemoryImage; 4239 4240 /// 4241 @property void icon(MemoryImage i) { 4242 version(X11) { 4243 this.originalMemoryImage = i; 4244 if (!active) return; 4245 if (i !is null) { 4246 this.img = Image.fromMemoryImage(i); 4247 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 4248 //import std.stdio; writeln("using pixmap ", clippixmap); 4249 updateNetWmIcon(); 4250 redraw(); 4251 } else { 4252 if (this.img !is null) { 4253 this.img = null; 4254 redraw(); 4255 } 4256 } 4257 } else version(Windows) { 4258 this.win32Icon = new WindowsIcon(i); 4259 4260 data.uFlags = NIF_ICON; 4261 data.hIcon = this.win32Icon.hIcon; 4262 4263 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 4264 } else version(OSXCocoa) { 4265 throw new NotYetImplementedException(); 4266 } else static assert(0); 4267 } 4268 4269 /// ditto 4270 @property void icon (Image i) { 4271 version(X11) { 4272 if (!active) return; 4273 if (i !is img) { 4274 originalMemoryImage = null; 4275 img = i; 4276 redraw(); 4277 } 4278 } else version(Windows) { 4279 this.icon(i is null ? null : i.toTrueColorImage()); 4280 } else version(OSXCocoa) { 4281 throw new NotYetImplementedException(); 4282 } else static assert(0); 4283 } 4284 4285 /++ 4286 Shows a balloon notification. You can only show one balloon at a time, if you call 4287 it twice while one is already up, the first balloon will be replaced. 4288 4289 4290 The user is free to block notifications and they will automatically disappear after 4291 a timeout period. 4292 4293 Params: 4294 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 4295 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 4296 icon = the icon to display with the notification. If null, it uses your existing icon. 4297 onclick = delegate called if the user clicks the balloon. (not yet implemented) 4298 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 4299 +/ 4300 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 4301 bool useCustom = true; 4302 version(libnotify) { 4303 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 4304 try { 4305 if(!active) return; 4306 4307 if(libnotify is null) { 4308 libnotify = new C_DynamicLibrary("libnotify.so"); 4309 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 4310 } 4311 4312 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 4313 4314 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 4315 4316 if(onclick) { 4317 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 4318 libnotify.call!("notify_notification_add_action", void, void*, const char*, const char*, typeof(&libnotify_action_callback_sdpy), void*, void*)()(n, "DEFAULT".ptr, "Go".ptr, &libnotify_action_callback_sdpy, cast(void*) libnotify_action_delegates_count, null); 4319 libnotify_action_delegates_count++; 4320 } 4321 4322 // FIXME icon 4323 4324 // set hint image-data 4325 // set default action for onclick 4326 4327 void* error; 4328 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 4329 4330 useCustom = false; 4331 } catch(Exception e) { 4332 4333 } 4334 } 4335 4336 version(X11) { 4337 if(useCustom) { 4338 if(!active) return; 4339 if(balloon) { 4340 hideBalloon(); 4341 } 4342 // I know there are two specs for this, but one is never 4343 // implemented by any window manager I have ever seen, and 4344 // the other is a bloated mess and too complicated for simpledisplay... 4345 // so doing my own little window instead. 4346 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 4347 4348 int x, y, width, height; 4349 getWindowRect(x, y, width, height); 4350 4351 int bx = x - balloon.width; 4352 int by = y - balloon.height; 4353 if(bx < 0) 4354 bx = x + width + balloon.width; 4355 if(by < 0) 4356 by = y + height; 4357 4358 // just in case, make sure it is actually on scren 4359 if(bx < 0) 4360 bx = 0; 4361 if(by < 0) 4362 by = 0; 4363 4364 balloon.move(bx, by); 4365 auto painter = balloon.draw(); 4366 painter.fillColor = Color(220, 220, 220); 4367 painter.outlineColor = Color.black; 4368 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 4369 auto iconWidth = icon is null ? 0 : icon.width; 4370 if(icon) 4371 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 4372 iconWidth += 6; // margin around the icon 4373 4374 // draw a close button 4375 painter.outlineColor = Color(44, 44, 44); 4376 painter.fillColor = Color(255, 255, 255); 4377 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 4378 painter.pen = Pen(Color.black, 3); 4379 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 4380 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 4381 painter.pen = Pen(Color.black, 1); 4382 painter.fillColor = Color(220, 220, 220); 4383 4384 // Draw the title and message 4385 painter.drawText(Point(4 + iconWidth, 4), title); 4386 painter.drawLine( 4387 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 4388 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 4389 ); 4390 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 4391 4392 balloon.setEventHandlers( 4393 (MouseEvent ev) { 4394 if(ev.type == MouseEventType.buttonPressed) { 4395 if(ev.x > balloon.width - 16 && ev.y < 16) 4396 hideBalloon(); 4397 else if(onclick) 4398 onclick(); 4399 } 4400 } 4401 ); 4402 balloon.show(); 4403 4404 version(with_timer) 4405 timer = new Timer(timeout, &hideBalloon); 4406 else {} // FIXME 4407 } 4408 } else version(Windows) { 4409 enum NIF_INFO = 0x00000010; 4410 4411 data.uFlags = NIF_INFO; 4412 4413 // FIXME: go back to the last valid unicode code point 4414 if(title.length > 40) 4415 title = title[0 .. 40]; 4416 if(message.length > 220) 4417 message = message[0 .. 220]; 4418 4419 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 4420 enum NIIF_LARGE_ICON = 0x00000020; 4421 enum NIIF_NOSOUND = 0x00000010; 4422 enum NIIF_USER = 0x00000004; 4423 enum NIIF_ERROR = 0x00000003; 4424 enum NIIF_WARNING = 0x00000002; 4425 enum NIIF_INFO = 0x00000001; 4426 enum NIIF_NONE = 0; 4427 4428 WCharzBuffer t = WCharzBuffer(title); 4429 WCharzBuffer m = WCharzBuffer(message); 4430 4431 t.copyInto(data.szInfoTitle); 4432 m.copyInto(data.szInfo); 4433 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 4434 4435 if(icon !is null) { 4436 auto i = new WindowsIcon(icon); 4437 data.hBalloonIcon = i.hIcon; 4438 data.dwInfoFlags |= NIIF_USER; 4439 } 4440 4441 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 4442 } else version(OSXCocoa) { 4443 throw new NotYetImplementedException(); 4444 } else static assert(0); 4445 } 4446 4447 /// 4448 //version(Windows) 4449 void show() { 4450 version(X11) { 4451 if(!hidden) 4452 return; 4453 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 4454 hidden = false; 4455 } else version(Windows) { 4456 data.uFlags = NIF_STATE; 4457 data.dwState = 0; // NIS_HIDDEN; // windows vista 4458 data.dwStateMask = NIS_HIDDEN; // windows vista 4459 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 4460 } else version(OSXCocoa) { 4461 throw new NotYetImplementedException(); 4462 } else static assert(0); 4463 } 4464 4465 version(X11) 4466 bool hidden = false; 4467 4468 /// 4469 //version(Windows) 4470 void hide() { 4471 version(X11) { 4472 if(hidden) 4473 return; 4474 hidden = true; 4475 XUnmapWindow(XDisplayConnection.get, nativeHandle); 4476 } else version(Windows) { 4477 data.uFlags = NIF_STATE; 4478 data.dwState = NIS_HIDDEN; // windows vista 4479 data.dwStateMask = NIS_HIDDEN; // windows vista 4480 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 4481 } else version(OSXCocoa) { 4482 throw new NotYetImplementedException(); 4483 } else static assert(0); 4484 } 4485 4486 /// 4487 void close () { 4488 version(X11) { 4489 if (active) { 4490 active = false; // event handler will set this too, but meh 4491 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 4492 XDestroyWindow(XDisplayConnection.get, nativeHandle); 4493 flushGui(); 4494 } 4495 } else version(Windows) { 4496 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 4497 } else version(OSXCocoa) { 4498 throw new NotYetImplementedException(); 4499 } else static assert(0); 4500 } 4501 4502 ~this() { 4503 version(X11) 4504 if(clippixmap != None) 4505 XFreePixmap(XDisplayConnection.get, clippixmap); 4506 close(); 4507 } 4508 } 4509 4510 version(X11) 4511 /// call XFreePixmap on the return value 4512 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 4513 char[] data = new char[](i.width * i.height / 8 + 2); 4514 data[] = 0; 4515 4516 int bitOffset = 0; 4517 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 4518 ubyte v = c.a > 128 ? 1 : 0; 4519 data[bitOffset / 8] |= v << (bitOffset%8); 4520 bitOffset++; 4521 } 4522 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 4523 return handle; 4524 } 4525 4526 4527 // basic functions to make timers 4528 /** 4529 A timer that will trigger your function on a given interval. 4530 4531 4532 You create a timer with an interval and a callback. It will continue 4533 to fire on the interval until it is destroyed. 4534 4535 There are currently no one-off timers (instead, just create one and 4536 destroy it when it is triggered) nor are there pause/resume functions - 4537 the timer must again be destroyed and recreated if you want to pause it. 4538 4539 auto timer = new Timer(50, { it happened!; }); 4540 timer.destroy(); 4541 4542 Timers can only be expected to fire when the event loop is running and only 4543 once per iteration through the event loop. 4544 4545 History: 4546 Prior to December 9, 2020, a timer pulse set too high with a handler too 4547 slow could lock up the event loop. It now guarantees other things will 4548 get a chance to run between timer calls, even if that means not keeping up 4549 with the requested interval. 4550 */ 4551 version(with_timer) { 4552 class Timer { 4553 // FIXME: needs pause and unpause 4554 // FIXME: I might add overloads for ones that take a count of 4555 // how many elapsed since last time (on Windows, it will divide 4556 // the ticks thing given, on Linux it is just available) and 4557 // maybe one that takes an instance of the Timer itself too 4558 /// Create a timer with a callback when it triggers. 4559 this(int intervalInMilliseconds, void delegate() onPulse) { 4560 assert(onPulse !is null); 4561 4562 this.intervalInMilliseconds = intervalInMilliseconds; 4563 this.onPulse = onPulse; 4564 4565 version(Windows) { 4566 /* 4567 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 4568 if(handle == 0) 4569 throw new Exception("SetTimer fail"); 4570 */ 4571 4572 // thanks to Archival 998 for the WaitableTimer blocks 4573 handle = CreateWaitableTimer(null, false, null); 4574 long initialTime = -intervalInMilliseconds; 4575 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 4576 throw new Exception("SetWaitableTimer Failed"); 4577 4578 mapping[handle] = this; 4579 4580 } else version(linux) { 4581 static import ep = core.sys.linux.epoll; 4582 4583 import core.sys.linux.timerfd; 4584 4585 fd = timerfd_create(CLOCK_MONOTONIC, 0); 4586 if(fd == -1) 4587 throw new Exception("timer create failed"); 4588 4589 mapping[fd] = this; 4590 4591 itimerspec value; 4592 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 4593 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 4594 4595 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 4596 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 4597 4598 if(timerfd_settime(fd, 0, &value, null) == -1) 4599 throw new Exception("couldn't make pulse timer"); 4600 4601 version(with_eventloop) { 4602 import arsd.eventloop; 4603 addFileEventListeners(fd, &trigger, null, null); 4604 } else { 4605 prepareEventLoop(); 4606 4607 ep.epoll_event ev = void; 4608 ev.events = ep.EPOLLIN; 4609 ev.data.fd = fd; 4610 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 4611 } 4612 } else featureNotImplemented(); 4613 } 4614 4615 private int intervalInMilliseconds; 4616 4617 /// Stop and destroy the timer object. 4618 void destroy() { 4619 version(Windows) { 4620 if(handle) { 4621 // KillTimer(null, handle); 4622 CancelWaitableTimer(cast(void*)handle); 4623 mapping.remove(handle); 4624 CloseHandle(handle); 4625 handle = null; 4626 } 4627 } else version(linux) { 4628 if(fd != -1) { 4629 import unix = core.sys.posix.unistd; 4630 static import ep = core.sys.linux.epoll; 4631 4632 version(with_eventloop) { 4633 import arsd.eventloop; 4634 removeFileEventListeners(fd); 4635 } else { 4636 ep.epoll_event ev = void; 4637 ev.events = ep.EPOLLIN; 4638 ev.data.fd = fd; 4639 4640 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 4641 } 4642 unix.close(fd); 4643 mapping.remove(fd); 4644 fd = -1; 4645 } 4646 } else featureNotImplemented(); 4647 } 4648 4649 ~this() { 4650 destroy(); 4651 } 4652 4653 4654 void changeTime(int intervalInMilliseconds) 4655 { 4656 this.intervalInMilliseconds = intervalInMilliseconds; 4657 version(Windows) 4658 { 4659 if(handle) 4660 { 4661 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 4662 long initialTime = -intervalInMilliseconds; 4663 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 4664 throw new Exception("couldn't change pulse timer"); 4665 } 4666 } 4667 } 4668 4669 4670 private: 4671 4672 void delegate() onPulse; 4673 4674 int lastEventLoopRoundTriggered; 4675 4676 void trigger() { 4677 version(linux) { 4678 import unix = core.sys.posix.unistd; 4679 long val; 4680 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 4681 } else version(Windows) { 4682 if(this.lastEventLoopRoundTriggered == eventLoopRound) 4683 return; // never try to actually run faster than the event loop 4684 lastEventLoopRoundTriggered = eventLoopRound; 4685 } else featureNotImplemented(); 4686 4687 onPulse(); 4688 } 4689 4690 version(Windows) 4691 void rearm() { 4692 4693 } 4694 4695 version(Windows) 4696 extern(Windows) 4697 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 4698 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 4699 if(Timer* t = timer in mapping) { 4700 try 4701 (*t).trigger(); 4702 catch(Exception e) { sdpy_abort(e); assert(0); } 4703 } 4704 } 4705 4706 version(Windows) { 4707 //UINT_PTR handle; 4708 //static Timer[UINT_PTR] mapping; 4709 HANDLE handle; 4710 __gshared Timer[HANDLE] mapping; 4711 } else version(linux) { 4712 int fd = -1; 4713 __gshared Timer[int] mapping; 4714 } else static assert(0, "timer not supported"); 4715 } 4716 } 4717 4718 version(Windows) 4719 private int eventLoopRound; 4720 4721 version(Windows) 4722 /// Lets you add HANDLEs to the event loop. Not meant to be used for async I/O per se, but for other handles (it can only handle a few handles at a time.) Only works on certain types of handles! see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-msgwaitformultipleobjectsex 4723 class WindowsHandleReader { 4724 /// 4725 this(void delegate() onReady, HANDLE handle) { 4726 this.onReady = onReady; 4727 this.handle = handle; 4728 4729 mapping[handle] = this; 4730 4731 enable(); 4732 } 4733 4734 /// 4735 void enable() { 4736 auto el = EventLoop.get().impl; 4737 el.handles ~= handle; 4738 } 4739 4740 /// 4741 void disable() { 4742 auto el = EventLoop.get().impl; 4743 for(int i = 0; i < el.handles.length; i++) { 4744 if(el.handles[i] is handle) { 4745 el.handles[i] = el.handles[$-1]; 4746 el.handles = el.handles[0 .. $-1]; 4747 return; 4748 } 4749 } 4750 } 4751 4752 void dispose() { 4753 disable(); 4754 if(handle) 4755 mapping.remove(handle); 4756 handle = null; 4757 } 4758 4759 void ready() { 4760 if(onReady) 4761 onReady(); 4762 } 4763 4764 HANDLE handle; 4765 void delegate() onReady; 4766 4767 __gshared WindowsHandleReader[HANDLE] mapping; 4768 } 4769 4770 version(Posix) 4771 /// Lets you add files to the event loop for reading. Use at your own risk. 4772 class PosixFdReader { 4773 /// 4774 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 4775 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 4776 } 4777 4778 /// 4779 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 4780 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 4781 } 4782 4783 /// 4784 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 4785 this.onReady = onReady; 4786 this.fd = fd; 4787 this.captureWrites = captureWrites; 4788 this.captureReads = captureReads; 4789 4790 mapping[fd] = this; 4791 4792 version(with_eventloop) { 4793 import arsd.eventloop; 4794 addFileEventListeners(fd, &readyel); 4795 } else { 4796 enable(); 4797 } 4798 } 4799 4800 bool captureReads; 4801 bool captureWrites; 4802 4803 version(with_eventloop) {} else 4804 /// 4805 void enable() { 4806 prepareEventLoop(); 4807 4808 enabled = true; 4809 4810 version(linux) { 4811 static import ep = core.sys.linux.epoll; 4812 ep.epoll_event ev = void; 4813 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 4814 //import std.stdio; writeln("enable ", fd, " ", captureReads, " ", captureWrites); 4815 ev.data.fd = fd; 4816 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 4817 } else { 4818 4819 } 4820 } 4821 4822 version(with_eventloop) {} else 4823 /// 4824 void disable() { 4825 prepareEventLoop(); 4826 4827 enabled = false; 4828 4829 version(linux) { 4830 static import ep = core.sys.linux.epoll; 4831 ep.epoll_event ev = void; 4832 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 4833 //import std.stdio; writeln("disable ", fd, " ", captureReads, " ", captureWrites); 4834 ev.data.fd = fd; 4835 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 4836 } 4837 } 4838 4839 version(with_eventloop) {} else 4840 /// 4841 void dispose() { 4842 if(enabled) 4843 disable(); 4844 if(fd != -1) 4845 mapping.remove(fd); 4846 fd = -1; 4847 } 4848 4849 void delegate(int, bool, bool) onReady; 4850 4851 version(with_eventloop) 4852 void readyel() { 4853 onReady(fd, true, true); 4854 } 4855 4856 void ready(uint flags) { 4857 version(linux) { 4858 static import ep = core.sys.linux.epoll; 4859 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 4860 } else { 4861 import core.sys.posix.poll; 4862 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 4863 } 4864 } 4865 4866 void hup(uint flags) { 4867 if(onHup) 4868 onHup(); 4869 } 4870 4871 void delegate() onHup; 4872 4873 int fd = -1; 4874 private bool enabled; 4875 __gshared PosixFdReader[int] mapping; 4876 } 4877 4878 // basic functions to access the clipboard 4879 /+ 4880 4881 4882 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 4883 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 4884 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 4885 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 4886 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx 4887 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 4888 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx 4889 4890 +/ 4891 4892 /++ 4893 this does a delegate because it is actually an async call on X... 4894 the receiver may never be called if the clipboard is empty or unavailable 4895 gets plain text from the clipboard 4896 +/ 4897 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) { 4898 version(Windows) { 4899 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 4900 if(OpenClipboard(hwndOwner) == 0) 4901 throw new Exception("OpenClipboard"); 4902 scope(exit) 4903 CloseClipboard(); 4904 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat 4905 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 4906 4907 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 4908 scope(exit) 4909 GlobalUnlock(dataHandle); 4910 4911 // FIXME: CR/LF conversions 4912 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 4913 int len = 0; 4914 auto d = data; 4915 while(*d) { 4916 d++; 4917 len++; 4918 } 4919 string s; 4920 s.reserve(len); 4921 foreach(dchar ch; data[0 .. len]) { 4922 s ~= ch; 4923 } 4924 receiver(s); 4925 } 4926 } 4927 } else version(X11) { 4928 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 4929 } else version(OSXCocoa) { 4930 throw new NotYetImplementedException(); 4931 } else static assert(0); 4932 } 4933 4934 // FIXME: a clipboard listener might be cool btw 4935 4936 /++ 4937 this does a delegate because it is actually an async call on X... 4938 the receiver may never be called if the clipboard is empty or unavailable 4939 gets image from the clipboard 4940 4941 templated because it introduces an optional dependency on arsd.bmp 4942 +/ 4943 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) { 4944 version(Windows) { 4945 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 4946 if(OpenClipboard(hwndOwner) == 0) 4947 throw new Exception("OpenClipboard"); 4948 scope(exit) 4949 CloseClipboard(); 4950 if(auto dataHandle = GetClipboardData(CF_DIBV5)) { 4951 if(auto data = cast(ubyte*) GlobalLock(dataHandle)) { 4952 scope(exit) 4953 GlobalUnlock(dataHandle); 4954 4955 auto len = GlobalSize(dataHandle); 4956 4957 import arsd.bmp; 4958 auto img = readBmp(data[0 .. len], false); 4959 receiver(img); 4960 } 4961 } 4962 } else version(X11) { 4963 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 4964 } else version(OSXCocoa) { 4965 throw new NotYetImplementedException(); 4966 } else static assert(0); 4967 } 4968 4969 version(Windows) 4970 struct WCharzBuffer { 4971 wchar[256] staticBuffer; 4972 wchar[] buffer; 4973 4974 size_t length() { 4975 return buffer.length; 4976 } 4977 4978 wchar* ptr() { 4979 return buffer.ptr; 4980 } 4981 4982 wchar[] slice() { 4983 return buffer; 4984 } 4985 4986 void copyInto(R)(ref R r) { 4987 static if(is(R == wchar[N], size_t N)) { 4988 r[0 .. this.length] = slice[]; 4989 r[this.length] = 0; 4990 } else static assert(0, "can only copy into wchar[n], not " ~ R.stringof); 4991 } 4992 4993 /++ 4994 conversionFlags = [WindowsStringConversionFlags] 4995 +/ 4996 this(in char[] data, int conversionFlags = 0) { 4997 conversionFlags |= WindowsStringConversionFlags.zeroTerminate; // this ALWAYS zero terminates cuz of its name 4998 auto sz = sizeOfConvertedWstring(data, conversionFlags); 4999 if(sz > staticBuffer.length) 5000 buffer = new wchar[](sz); 5001 else 5002 buffer = staticBuffer[]; 5003 5004 buffer = makeWindowsString(data, buffer, conversionFlags); 5005 } 5006 } 5007 5008 version(Windows) 5009 int sizeOfConvertedWstring(in char[] s, int conversionFlags) { 5010 int size = 0; 5011 5012 if(conversionFlags & WindowsStringConversionFlags.convertNewLines) { 5013 // need to convert line endings, which means the length will get bigger. 5014 5015 // BTW I betcha this could be faster with some simd stuff. 5016 char last; 5017 foreach(char ch; s) { 5018 if(ch == 10 && last != 13) 5019 size++; // will add a 13 before it... 5020 size++; 5021 last = ch; 5022 } 5023 } else { 5024 // no conversion necessary, just estimate based on length 5025 /* 5026 I don't think there's any string with a longer length 5027 in code units when encoded in UTF-16 than it has in UTF-8. 5028 This will probably over allocate, but that's OK. 5029 */ 5030 size = cast(int) s.length; 5031 } 5032 5033 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) 5034 size++; 5035 5036 return size; 5037 } 5038 5039 version(Windows) 5040 enum WindowsStringConversionFlags : int { 5041 zeroTerminate = 1, 5042 convertNewLines = 2, 5043 } 5044 5045 version(Windows) 5046 class WindowsApiException : Exception { 5047 char[256] buffer; 5048 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 5049 assert(msg.length < 100); 5050 5051 auto error = GetLastError(); 5052 buffer[0 .. msg.length] = msg; 5053 buffer[msg.length] = ' '; 5054 5055 int pos = cast(int) msg.length + 1; 5056 5057 if(error == 0) 5058 buffer[pos++] = '0'; 5059 else { 5060 5061 auto ec = error; 5062 auto init = pos; 5063 while(ec) { 5064 buffer[pos++] = (ec % 10) + '0'; 5065 ec /= 10; 5066 } 5067 5068 buffer[pos++] = ' '; 5069 5070 size_t size = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, null, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), &(buffer[pos]), cast(DWORD) buffer.length - pos, null); 5071 5072 pos += size; 5073 } 5074 5075 5076 super(cast(string) buffer[0 .. pos], file, line, next); 5077 } 5078 } 5079 5080 class ErrnoApiException : Exception { 5081 char[256] buffer; 5082 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 5083 assert(msg.length < 100); 5084 5085 import core.stdc.errno; 5086 auto error = errno; 5087 buffer[0 .. msg.length] = msg; 5088 buffer[msg.length] = ' '; 5089 5090 int pos = cast(int) msg.length + 1; 5091 5092 if(error == 0) 5093 buffer[pos++] = '0'; 5094 else { 5095 auto init = pos; 5096 while(error) { 5097 buffer[pos++] = (error % 10) + '0'; 5098 error /= 10; 5099 } 5100 for(int i = 0; i < (pos - init) / 2; i++) { 5101 char c = buffer[i + init]; 5102 buffer[i + init] = buffer[pos - (i + init) - 1]; 5103 buffer[pos - (i + init) - 1] = c; 5104 } 5105 } 5106 5107 5108 super(cast(string) buffer[0 .. pos], file, line, next); 5109 } 5110 5111 } 5112 5113 version(Windows) 5114 wchar[] makeWindowsString(in char[] str, wchar[] buffer, int conversionFlags = WindowsStringConversionFlags.zeroTerminate) { 5115 if(str.length == 0) 5116 return null; 5117 5118 int pos = 0; 5119 dchar last; 5120 foreach(dchar c; str) { 5121 if(c <= 0xFFFF) { 5122 if((conversionFlags & WindowsStringConversionFlags.convertNewLines) && c == 10 && last != 13) 5123 buffer[pos++] = 13; 5124 buffer[pos++] = cast(wchar) c; 5125 } else if(c <= 0x10FFFF) { 5126 buffer[pos++] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); 5127 buffer[pos++] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00); 5128 } 5129 5130 last = c; 5131 } 5132 5133 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) { 5134 buffer[pos] = 0; 5135 } 5136 5137 return buffer[0 .. pos]; 5138 } 5139 5140 version(Windows) 5141 char[] makeUtf8StringFromWindowsString(in wchar[] str, char[] buffer) { 5142 if(str.length == 0) 5143 return null; 5144 5145 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, buffer.ptr, cast(int) buffer.length, null, null); 5146 if(got == 0) { 5147 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 5148 throw new Exception("not enough buffer"); 5149 else 5150 throw new Exception("conversion"); // FIXME: GetLastError 5151 } 5152 return buffer[0 .. got]; 5153 } 5154 5155 version(Windows) 5156 string makeUtf8StringFromWindowsString(in wchar[] str) { 5157 char[] buffer; 5158 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, null, 0, null, null); 5159 buffer.length = got; 5160 5161 // it is unique because we just allocated it above! 5162 return cast(string) makeUtf8StringFromWindowsString(str, buffer); 5163 } 5164 5165 version(Windows) 5166 string makeUtf8StringFromWindowsString(wchar* str) { 5167 char[] buffer; 5168 auto got = WideCharToMultiByte(CP_UTF8, 0, str, -1, null, 0, null, null); 5169 buffer.length = got; 5170 5171 got = WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.ptr, cast(int) buffer.length, null, null); 5172 if(got == 0) { 5173 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 5174 throw new Exception("not enough buffer"); 5175 else 5176 throw new Exception("conversion"); // FIXME: GetLastError 5177 } 5178 return cast(string) buffer[0 .. got]; 5179 } 5180 5181 int findIndexOfZero(in wchar[] str) { 5182 foreach(idx, wchar ch; str) 5183 if(ch == 0) 5184 return cast(int) idx; 5185 return cast(int) str.length; 5186 } 5187 int findIndexOfZero(in char[] str) { 5188 foreach(idx, char ch; str) 5189 if(ch == 0) 5190 return cast(int) idx; 5191 return cast(int) str.length; 5192 } 5193 5194 /// copies some text to the clipboard 5195 void setClipboardText(SimpleWindow clipboardOwner, string text) { 5196 assert(clipboardOwner !is null); 5197 version(Windows) { 5198 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 5199 throw new Exception("OpenClipboard"); 5200 scope(exit) 5201 CloseClipboard(); 5202 EmptyClipboard(); 5203 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 5204 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 5205 if(handle is null) throw new Exception("GlobalAlloc"); 5206 if(auto data = cast(wchar*) GlobalLock(handle)) { 5207 auto slice = data[0 .. sz]; 5208 scope(failure) 5209 GlobalUnlock(handle); 5210 5211 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 5212 5213 GlobalUnlock(handle); 5214 SetClipboardData(CF_UNICODETEXT, handle); 5215 } 5216 } else version(X11) { 5217 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 5218 } else version(OSXCocoa) { 5219 throw new NotYetImplementedException(); 5220 } else static assert(0); 5221 } 5222 5223 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) { 5224 assert(clipboardOwner !is null); 5225 version(Windows) { 5226 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 5227 throw new Exception("OpenClipboard"); 5228 scope(exit) 5229 CloseClipboard(); 5230 EmptyClipboard(); 5231 5232 5233 import arsd.bmp; 5234 ubyte[] mdata; 5235 mdata.reserve(img.width * img.height); 5236 void sink(ubyte b) { 5237 mdata ~= b; 5238 } 5239 writeBmpIndirect(img, &sink, false); 5240 5241 auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length); 5242 if(handle is null) throw new Exception("GlobalAlloc"); 5243 if(auto data = cast(ubyte*) GlobalLock(handle)) { 5244 auto slice = data[0 .. mdata.length]; 5245 scope(failure) 5246 GlobalUnlock(handle); 5247 5248 slice[] = mdata[]; 5249 5250 GlobalUnlock(handle); 5251 SetClipboardData(CF_DIB, handle); 5252 } 5253 } else version(X11) { 5254 static class X11SetSelectionHandler_Image : X11SetSelectionHandler { 5255 mixin X11SetSelectionHandler_Basics; 5256 private const(ubyte)[] mdata; 5257 private const(ubyte)[] mdata_original; 5258 this(MemoryImage img) { 5259 import arsd.bmp; 5260 5261 mdata.reserve(img.width * img.height); 5262 void sink(ubyte b) { 5263 mdata ~= b; 5264 } 5265 writeBmpIndirect(img, &sink, true); 5266 5267 mdata_original = mdata; 5268 } 5269 5270 Atom[] availableFormats() { 5271 auto display = XDisplayConnection.get; 5272 return [ 5273 GetAtom!"image/bmp"(display), 5274 GetAtom!"TARGETS"(display) 5275 ]; 5276 } 5277 5278 ubyte[] getData(Atom format, return scope ubyte[] data) { 5279 if(mdata.length < data.length) { 5280 data[0 .. mdata.length] = mdata[]; 5281 auto ret = data[0 .. mdata.length]; 5282 mdata = mdata[$..$]; 5283 return ret; 5284 } else { 5285 data[] = mdata[0 .. data.length]; 5286 mdata = mdata[data.length .. $]; 5287 return data[]; 5288 } 5289 } 5290 5291 void done() { 5292 mdata = mdata_original; 5293 } 5294 } 5295 5296 setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img)); 5297 } else version(OSXCocoa) { 5298 throw new NotYetImplementedException(); 5299 } else static assert(0); 5300 } 5301 5302 5303 version(X11) { 5304 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 5305 5306 private Atom*[] interredAtoms; // for discardAndRecreate 5307 5308 // FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all. 5309 /// Platform specific for X11 5310 /// History: On February 21, 2021, I changed the default value of `create` to be true. 5311 @property Atom GetAtom(string name, bool create = true)(Display* display) { 5312 static Atom a; 5313 if(!a) { 5314 a = XInternAtom(display, name, !create); 5315 interredAtoms ~= &a; 5316 } 5317 if(a == None) 5318 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 5319 return a; 5320 } 5321 5322 /// Platform specific for X11 - gets atom names as a string 5323 string getAtomName(Atom atom, Display* display) { 5324 auto got = XGetAtomName(display, atom); 5325 scope(exit) XFree(got); 5326 import core.stdc..string; 5327 string s = got[0 .. strlen(got)].idup; 5328 return s; 5329 } 5330 5331 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later 5332 void setPrimarySelection(SimpleWindow window, string text) { 5333 setX11Selection!"PRIMARY"(window, text); 5334 } 5335 5336 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later 5337 void setSecondarySelection(SimpleWindow window, string text) { 5338 setX11Selection!"SECONDARY"(window, text); 5339 } 5340 5341 interface X11SetSelectionHandler { 5342 // should include TARGETS right now 5343 Atom[] availableFormats(); 5344 // Return the slice of data you filled, empty slice if done. 5345 // this is to support the incremental thing 5346 ubyte[] getData(Atom format, return scope ubyte[] data); 5347 5348 void done(); 5349 5350 void handleRequest(XEvent); 5351 5352 bool matchesIncr(Window, Atom); 5353 void sendMoreIncr(XPropertyEvent*); 5354 } 5355 5356 mixin template X11SetSelectionHandler_Basics() { 5357 Window incrWindow; 5358 Atom incrAtom; 5359 Atom selectionAtom; 5360 Atom formatAtom; 5361 ubyte[] toSend; 5362 bool matchesIncr(Window w, Atom a) { 5363 return incrAtom && incrAtom == a && w == incrWindow; 5364 } 5365 void sendMoreIncr(XPropertyEvent* event) { 5366 auto display = XDisplayConnection.get; 5367 5368 XChangeProperty (display, 5369 incrWindow, 5370 incrAtom, 5371 formatAtom, 5372 8 /* bits */, PropModeReplace, 5373 toSend.ptr, cast(int) toSend.length); 5374 5375 if(toSend.length != 0) { 5376 toSend = this.getData(formatAtom, toSend[]); 5377 } else { 5378 this.done(); 5379 incrWindow = None; 5380 incrAtom = None; 5381 selectionAtom = None; 5382 formatAtom = None; 5383 toSend = null; 5384 } 5385 } 5386 void handleRequest(XEvent ev) { 5387 5388 auto display = XDisplayConnection.get; 5389 5390 XSelectionRequestEvent* event = &ev.xselectionrequest; 5391 XSelectionEvent selectionEvent; 5392 selectionEvent.type = EventType.SelectionNotify; 5393 selectionEvent.display = event.display; 5394 selectionEvent.requestor = event.requestor; 5395 selectionEvent.selection = event.selection; 5396 selectionEvent.time = event.time; 5397 selectionEvent.target = event.target; 5398 5399 bool supportedType() { 5400 foreach(t; this.availableFormats()) 5401 if(t == event.target) 5402 return true; 5403 return false; 5404 } 5405 5406 if(event.property == None) { 5407 selectionEvent.property = event.target; 5408 5409 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 5410 XFlush(display); 5411 } if(event.target == GetAtom!"TARGETS"(display)) { 5412 /* respond with the supported types */ 5413 auto tlist = this.availableFormats(); 5414 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length); 5415 selectionEvent.property = event.property; 5416 5417 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 5418 XFlush(display); 5419 } else if(supportedType()) { 5420 auto buffer = new ubyte[](1024 * 64); 5421 auto toSend = this.getData(event.target, buffer[]); 5422 5423 if(toSend.length < 32 * 1024) { 5424 // small enough to send directly... 5425 selectionEvent.property = event.property; 5426 XChangeProperty (display, 5427 selectionEvent.requestor, 5428 selectionEvent.property, 5429 event.target, 5430 8 /* bits */, 0 /* PropModeReplace */, 5431 toSend.ptr, cast(int) toSend.length); 5432 5433 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 5434 XFlush(display); 5435 } else { 5436 // large, let's send incrementally 5437 arch_ulong l = toSend.length; 5438 5439 // if I wanted other events from this window don't want to clear that out.... 5440 XWindowAttributes xwa; 5441 XGetWindowAttributes(display, selectionEvent.requestor, &xwa); 5442 5443 XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask)); 5444 5445 incrWindow = event.requestor; 5446 incrAtom = event.property; 5447 formatAtom = event.target; 5448 selectionAtom = event.selection; 5449 this.toSend = toSend; 5450 5451 selectionEvent.property = event.property; 5452 XChangeProperty (display, 5453 selectionEvent.requestor, 5454 selectionEvent.property, 5455 GetAtom!"INCR"(display), 5456 32 /* bits */, PropModeReplace, 5457 &l, 1); 5458 5459 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 5460 XFlush(display); 5461 } 5462 //if(after) 5463 //after(); 5464 } else { 5465 debug(sdpy_clip) { 5466 import std.stdio; writeln("Unsupported data ", getAtomName(event.target, display)); 5467 } 5468 selectionEvent.property = None; // I don't know how to handle this type... 5469 XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent); 5470 XFlush(display); 5471 } 5472 } 5473 } 5474 5475 class X11SetSelectionHandler_Text : X11SetSelectionHandler { 5476 mixin X11SetSelectionHandler_Basics; 5477 private const(ubyte)[] text; 5478 private const(ubyte)[] text_original; 5479 this(string text) { 5480 this.text = cast(const ubyte[]) text; 5481 this.text_original = this.text; 5482 } 5483 Atom[] availableFormats() { 5484 auto display = XDisplayConnection.get; 5485 return [ 5486 GetAtom!"UTF8_STRING"(display), 5487 GetAtom!"text/plain"(display), 5488 XA_STRING, 5489 GetAtom!"TARGETS"(display) 5490 ]; 5491 } 5492 5493 ubyte[] getData(Atom format, return scope ubyte[] data) { 5494 if(text.length < data.length) { 5495 data[0 .. text.length] = text[]; 5496 return data[0 .. text.length]; 5497 } else { 5498 data[] = text[0 .. data.length]; 5499 text = text[data.length .. $]; 5500 return data[]; 5501 } 5502 } 5503 5504 void done() { 5505 text = text_original; 5506 } 5507 } 5508 5509 /// The `after` delegate is called after a client requests the UTF8_STRING thing. it is a mega-hack right now! (note to self july 2020... why did i do that?!) 5510 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 5511 setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after); 5512 } 5513 5514 void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) { 5515 assert(window !is null); 5516 5517 auto display = XDisplayConnection.get(); 5518 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 5519 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 5520 else Atom a = GetAtom!atomName(display); 5521 5522 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 5523 5524 window.impl.setSelectionHandlers[a] = data; 5525 } 5526 5527 /// 5528 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 5529 getX11Selection!"PRIMARY"(window, handler); 5530 } 5531 5532 // added July 28, 2020 5533 // undocumented as experimental tho 5534 interface X11GetSelectionHandler { 5535 void handleData(Atom target, in ubyte[] data); 5536 Atom findBestFormat(Atom[] answer); 5537 5538 void prepareIncremental(Window, Atom); 5539 bool matchesIncr(Window, Atom); 5540 void handleIncrData(Atom, in ubyte[] data); 5541 } 5542 5543 mixin template X11GetSelectionHandler_Basics() { 5544 Window incrWindow; 5545 Atom incrAtom; 5546 5547 void prepareIncremental(Window w, Atom a) { 5548 incrWindow = w; 5549 incrAtom = a; 5550 } 5551 bool matchesIncr(Window w, Atom a) { 5552 return incrWindow == w && incrAtom == a; 5553 } 5554 5555 Atom incrFormatAtom; 5556 ubyte[] incrData; 5557 void handleIncrData(Atom format, in ubyte[] data) { 5558 incrFormatAtom = format; 5559 5560 if(data.length) 5561 incrData ~= data; 5562 else 5563 handleData(incrFormatAtom, incrData); 5564 5565 } 5566 } 5567 5568 /// 5569 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) { 5570 assert(window !is null); 5571 5572 auto display = XDisplayConnection.get(); 5573 auto atom = GetAtom!atomName(display); 5574 5575 static class X11GetSelectionHandler_Text : X11GetSelectionHandler { 5576 this(void delegate(in char[]) handler) { 5577 this.handler = handler; 5578 } 5579 5580 mixin X11GetSelectionHandler_Basics; 5581 5582 void delegate(in char[]) handler; 5583 5584 void handleData(Atom target, in ubyte[] data) { 5585 if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 5586 handler(cast(const char[]) data); 5587 } 5588 5589 Atom findBestFormat(Atom[] answer) { 5590 Atom best = None; 5591 foreach(option; answer) { 5592 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 5593 best = option; 5594 break; 5595 } else if(option == XA_STRING) { 5596 best = option; 5597 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 5598 best = option; 5599 } 5600 } 5601 return best; 5602 } 5603 } 5604 5605 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler); 5606 5607 auto target = GetAtom!"TARGETS"(display); 5608 5609 // SDD_DATA is "simpledisplay.d data" 5610 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp); 5611 } 5612 5613 /// Gets the image on the clipboard, if there is one. Added July 2020. 5614 void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) { 5615 assert(window !is null); 5616 5617 auto display = XDisplayConnection.get(); 5618 auto atom = GetAtom!atomName(display); 5619 5620 static class X11GetSelectionHandler_Image : X11GetSelectionHandler { 5621 this(void delegate(MemoryImage) handler) { 5622 this.handler = handler; 5623 } 5624 5625 mixin X11GetSelectionHandler_Basics; 5626 5627 void delegate(MemoryImage) handler; 5628 5629 void handleData(Atom target, in ubyte[] data) { 5630 if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) { 5631 import arsd.bmp; 5632 handler(readBmp(data)); 5633 } 5634 } 5635 5636 Atom findBestFormat(Atom[] answer) { 5637 Atom best = None; 5638 foreach(option; answer) { 5639 if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) { 5640 best = option; 5641 } 5642 } 5643 return best; 5644 } 5645 5646 } 5647 5648 5649 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler); 5650 5651 auto target = GetAtom!"TARGETS"(display); 5652 5653 // SDD_DATA is "simpledisplay.d data" 5654 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 5655 } 5656 5657 5658 /// 5659 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 5660 Atom actualType; 5661 int actualFormat; 5662 arch_ulong actualItems; 5663 arch_ulong bytesRemaining; 5664 void* data; 5665 5666 auto display = XDisplayConnection.get(); 5667 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 5668 if(actualFormat == 0) 5669 return null; 5670 else { 5671 int byteLength; 5672 if(actualFormat == 32) { 5673 // 32 means it is a C long... which is variable length 5674 actualFormat = cast(int) arch_long.sizeof * 8; 5675 } 5676 5677 // then it is just a bit count 5678 byteLength = cast(int) (actualItems * actualFormat / 8); 5679 5680 auto d = new ubyte[](byteLength); 5681 d[] = cast(ubyte[]) data[0 .. byteLength]; 5682 XFree(data); 5683 return d; 5684 } 5685 } 5686 return null; 5687 } 5688 5689 /* defined in the systray spec */ 5690 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 5691 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 5692 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 5693 5694 5695 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 5696 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 5697 public class GlobalHotkey { 5698 KeyEvent key; 5699 void delegate () handler; 5700 5701 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 5702 5703 /// Create from initialzed KeyEvent object 5704 this (KeyEvent akey, void delegate () ahandler=null) { 5705 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 5706 key = akey; 5707 handler = ahandler; 5708 } 5709 5710 /// Create from emacs-like key name ("C-M-Y", etc.) 5711 this (const(char)[] akey, void delegate () ahandler=null) { 5712 key = KeyEvent.parse(akey); 5713 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 5714 handler = ahandler; 5715 } 5716 5717 } 5718 5719 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 5720 //conwriteln("failed to grab key"); 5721 GlobalHotkeyManager.ghfailed = true; 5722 return 0; 5723 } 5724 5725 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 5726 import core.stdc.stdio; 5727 char[265] buffer; 5728 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 5729 printf("ERROR: %s\n", buffer.ptr); 5730 return 0; 5731 } 5732 5733 /++ 5734 Global hotkey manager. It contains static methods to manage global hotkeys. 5735 5736 --- 5737 try { 5738 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 5739 } catch (Exception e) { 5740 conwriteln("ERROR registering hotkey!"); 5741 } 5742 --- 5743 5744 The key strings are based on Emacs. In practical terms, 5745 `M` means `alt` and `H` means the Windows logo key. `C` 5746 is `ctrl`. 5747 5748 $(WARNING 5749 This is X-specific right now. If you are on 5750 Windows, try [registerHotKey] instead. 5751 5752 We will probably merge these into a single 5753 interface later. 5754 ) 5755 +/ 5756 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 5757 version(X11) { 5758 void recreateAfterDisconnect() { 5759 throw new Exception("NOT IMPLEMENTED"); 5760 } 5761 void discardConnectionState() { 5762 throw new Exception("NOT IMPLEMENTED"); 5763 } 5764 } 5765 5766 private static immutable uint[8] masklist = [ 0, 5767 KeyOrButtonMask.LockMask, 5768 KeyOrButtonMask.Mod2Mask, 5769 KeyOrButtonMask.Mod3Mask, 5770 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 5771 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 5772 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 5773 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 5774 ]; 5775 private __gshared GlobalHotkeyManager ghmanager; 5776 private __gshared bool ghfailed = false; 5777 5778 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 5779 if (modmask == 0) return false; 5780 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 5781 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 5782 return true; 5783 } 5784 5785 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 5786 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 5787 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 5788 return modmask; 5789 } 5790 5791 private static uint keyEvent2KeyCode() (in auto ref KeyEvent ke) { 5792 uint keycode = cast(uint)ke.key; 5793 auto dpy = XDisplayConnection.get; 5794 return XKeysymToKeycode(dpy, keycode); 5795 } 5796 5797 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 5798 5799 private __gshared GlobalHotkey[ulong] globalHotkeyList; 5800 5801 NativeEventHandler getNativeEventHandler () { 5802 return delegate int (XEvent e) { 5803 if (e.type != EventType.KeyPress) return 1; 5804 auto kev = cast(const(XKeyEvent)*)&e; 5805 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 5806 if (auto ghkp = hash in globalHotkeyList) { 5807 try { 5808 ghkp.doHandle(); 5809 } catch (Exception e) { 5810 import core.stdc.stdio : stderr, fprintf; 5811 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 5812 } 5813 } 5814 return 1; 5815 }; 5816 } 5817 5818 private this () { 5819 auto dpy = XDisplayConnection.get; 5820 auto root = RootWindow(dpy, DefaultScreen(dpy)); 5821 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 5822 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 5823 } 5824 5825 /// Register new global hotkey with initialized `GlobalHotkey` object. 5826 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 5827 static void register (GlobalHotkey gh) { 5828 if (gh is null) return; 5829 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 5830 5831 auto dpy = XDisplayConnection.get; 5832 immutable keycode = keyEvent2KeyCode(gh.key); 5833 5834 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 5835 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 5836 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 5837 XSync(dpy, 0/*False*/); 5838 5839 Window root = RootWindow(dpy, DefaultScreen(dpy)); 5840 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 5841 ghfailed = false; 5842 foreach (immutable uint ormask; masklist[]) { 5843 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 5844 } 5845 XSync(dpy, 0/*False*/); 5846 XSetErrorHandler(savedErrorHandler); 5847 5848 if (ghfailed) { 5849 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 5850 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 5851 XSync(dpy, 0/*False*/); 5852 XSetErrorHandler(savedErrorHandler); 5853 throw new Exception("cannot register global hotkey"); 5854 } 5855 5856 globalHotkeyList[hash] = gh; 5857 } 5858 5859 /// Ditto 5860 static void register (const(char)[] akey, void delegate () ahandler) { 5861 register(new GlobalHotkey(akey, ahandler)); 5862 } 5863 5864 private static void removeByHash (ulong hash) { 5865 if (auto ghp = hash in globalHotkeyList) { 5866 auto dpy = XDisplayConnection.get; 5867 immutable keycode = keyEvent2KeyCode(ghp.key); 5868 Window root = RootWindow(dpy, DefaultScreen(dpy)); 5869 XSync(dpy, 0/*False*/); 5870 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 5871 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 5872 XSync(dpy, 0/*False*/); 5873 XSetErrorHandler(savedErrorHandler); 5874 globalHotkeyList.remove(hash); 5875 } 5876 } 5877 5878 /// Register new global hotkey with previously used `GlobalHotkey` object. 5879 /// It is safe to unregister unknown or invalid hotkey. 5880 static void unregister (GlobalHotkey gh) { 5881 //TODO: add second AA for faster search? prolly doesn't worth it. 5882 if (gh is null) return; 5883 foreach (const ref kv; globalHotkeyList.byKeyValue) { 5884 if (kv.value is gh) { 5885 removeByHash(kv.key); 5886 return; 5887 } 5888 } 5889 } 5890 5891 /// Ditto. 5892 static void unregister (const(char)[] key) { 5893 auto kev = KeyEvent.parse(key); 5894 immutable keycode = keyEvent2KeyCode(kev); 5895 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 5896 } 5897 } 5898 } 5899 5900 version(Windows) { 5901 /// Platform-specific for Windows. Sends a string as key press and release events to the actively focused window (not necessarily your application) 5902 void sendSyntheticInput(wstring s) { 5903 INPUT[] inputs; 5904 inputs.reserve(s.length * 2); 5905 5906 foreach(wchar c; s) { 5907 INPUT input; 5908 input.type = INPUT_KEYBOARD; 5909 input.ki.wScan = c; 5910 input.ki.dwFlags = KEYEVENTF_UNICODE; 5911 inputs ~= input; 5912 5913 input.ki.dwFlags |= KEYEVENTF_KEYUP; 5914 inputs ~= input; 5915 } 5916 5917 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 5918 throw new Exception("SendInput failed"); 5919 } 5920 } 5921 5922 5923 // global hotkey helper function 5924 5925 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. 5926 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) { 5927 __gshared int hotkeyId = 0; 5928 int id = ++hotkeyId; 5929 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 5930 throw new Exception("RegisterHotKey failed"); 5931 5932 __gshared void delegate()[WPARAM][HWND] handlers; 5933 5934 handlers[window.impl.hwnd][id] = handler; 5935 5936 int delegate(HWND, UINT, WPARAM, LPARAM) oldHandler; 5937 5938 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { 5939 switch(msg) { 5940 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 5941 case WM_HOTKEY: 5942 if(auto list = hwnd in handlers) { 5943 if(auto h = wParam in *list) { 5944 (*h)(); 5945 return 0; 5946 } 5947 } 5948 goto default; 5949 default: 5950 } 5951 if(oldHandler) 5952 return oldHandler(hwnd, msg, wParam, lParam); 5953 return 1; // pass it on 5954 }; 5955 5956 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 5957 oldHandler = window.handleNativeEvent; 5958 window.handleNativeEvent = nativeEventHandler; 5959 } 5960 5961 return id; 5962 } 5963 5964 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by registerHotKey. 5965 void unregisterHotKey(SimpleWindow window, int id) { 5966 if(!UnregisterHotKey(window.impl.hwnd, id)) 5967 throw new Exception("UnregisterHotKey"); 5968 } 5969 } 5970 5971 version (X11) { 5972 pragma(lib, "dl"); 5973 import core.sys.posix.dlfcn; 5974 5975 /++ 5976 Allows for sending synthetic input to the X server via the Xtst 5977 extension. 5978 5979 Please remember user input is meant to be user - don't use this 5980 if you have some other alternative! 5981 5982 If you need this on Windows btw, the top-level [sendSyntheticInput] shows 5983 the Win32 api to start it, but I only did basics there, PR welcome if you like, 5984 it is an easy enough function to use. 5985 5986 History: Added May 17, 2020. 5987 +/ 5988 struct SyntheticInput { 5989 @disable this(); 5990 5991 private void* lib; 5992 private int* refcount; 5993 5994 private extern(C) { 5995 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 5996 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 5997 } 5998 5999 /// The dummy param must be 0. 6000 this(int dummy) { 6001 lib = dlopen("libXtst.so", RTLD_NOW); 6002 if(lib is null) 6003 throw new Exception("cannot load xtest lib extension"); 6004 scope(failure) 6005 dlclose(lib); 6006 6007 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 6008 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 6009 6010 if(XTestFakeKeyEvent is null) 6011 throw new Exception("No XTestFakeKeyEvent"); 6012 if(XTestFakeButtonEvent is null) 6013 throw new Exception("No XTestFakeButtonEvent"); 6014 6015 refcount = new int; 6016 *refcount = 1; 6017 } 6018 6019 this(this) { 6020 if(refcount) 6021 *refcount += 1; 6022 } 6023 6024 ~this() { 6025 if(refcount) { 6026 *refcount -= 1; 6027 if(*refcount == 0) 6028 // I commented this because if I close the lib before 6029 // XCloseDisplay, it is liable to segfault... so just 6030 // gonna keep it loaded if it is loaded, no big deal 6031 // anyway. 6032 {} // dlclose(lib); 6033 } 6034 } 6035 6036 /// This ONLY works with basic ascii! 6037 void sendSyntheticInput(string s) { 6038 int delay = 0; 6039 foreach(ch; s) { 6040 pressKey(cast(Key) ch, true, delay); 6041 pressKey(cast(Key) ch, false, delay); 6042 delay += 5; 6043 } 6044 } 6045 6046 /// 6047 void pressKey(Key key, bool pressed, int delay = 0) { 6048 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 6049 } 6050 6051 /// 6052 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 6053 int btn; 6054 6055 switch(button) { 6056 case MouseButton.left: btn = 1; break; 6057 case MouseButton.middle: btn = 2; break; 6058 case MouseButton.right: btn = 3; break; 6059 case MouseButton.wheelUp: btn = 4; break; 6060 case MouseButton.wheelDown: btn = 5; break; 6061 case MouseButton.backButton: btn = 8; break; 6062 case MouseButton.forwardButton: btn = 9; break; 6063 default: 6064 } 6065 6066 assert(btn); 6067 6068 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 6069 } 6070 6071 /// 6072 static void moveMouseArrowBy(int dx, int dy) { 6073 auto disp = XDisplayConnection.get(); 6074 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 6075 XFlush(disp); 6076 } 6077 6078 /// 6079 static void moveMouseArrowTo(int x, int y) { 6080 auto disp = XDisplayConnection.get(); 6081 auto root = RootWindow(disp, DefaultScreen(disp)); 6082 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 6083 XFlush(disp); 6084 } 6085 } 6086 } 6087 6088 6089 6090 /++ 6091 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 6092 6093 See_Also: 6094 $(LIST 6095 *[ScreenPainter] 6096 *[ScreenPainter.rasterOp] 6097 ) 6098 +/ 6099 enum RasterOp { 6100 normal, /// Replaces the pixel. 6101 xor, /// Uses bitwise xor to draw. 6102 } 6103 6104 // being phobos-free keeps the size WAY down 6105 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 6106 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 6107 package(arsd) const(wchar)* toWStringz(string s) { 6108 wstring r; 6109 foreach(dchar c; s) 6110 r ~= c; 6111 r ~= '\0'; 6112 return r.ptr; 6113 } 6114 private string[] split(in void[] a, char c) { 6115 string[] ret; 6116 size_t previous = 0; 6117 foreach(i, char ch; cast(ubyte[]) a) { 6118 if(ch == c) { 6119 ret ~= cast(string) a[previous .. i]; 6120 previous = i + 1; 6121 } 6122 } 6123 if(previous != a.length) 6124 ret ~= cast(string) a[previous .. $]; 6125 return ret; 6126 } 6127 6128 version(without_opengl) { 6129 enum OpenGlOptions { 6130 no, 6131 } 6132 } else { 6133 /++ 6134 Determines if you want an OpenGL context created on the new window. 6135 6136 6137 See more: [#topics-3d|in the 3d topic]. 6138 6139 --- 6140 import arsd.simpledisplay; 6141 void main() { 6142 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 6143 6144 // Set up the matrix 6145 window.setAsCurrentOpenGlContext(); // make this window active 6146 6147 // This is called on each frame, we will draw our scene 6148 window.redrawOpenGlScene = delegate() { 6149 6150 }; 6151 6152 window.eventLoop(0); 6153 } 6154 --- 6155 +/ 6156 enum OpenGlOptions { 6157 no, /// No OpenGL context is created 6158 yes, /// Yes, create an OpenGL context 6159 } 6160 6161 version(X11) { 6162 static if (!SdpyIsUsingIVGLBinds) { 6163 6164 6165 struct __GLXFBConfigRec {} 6166 alias GLXFBConfig = __GLXFBConfigRec*; 6167 6168 //pragma(lib, "GL"); 6169 //pragma(lib, "GLU"); 6170 interface GLX { 6171 extern(C) nothrow @nogc { 6172 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 6173 const int *attrib_list); 6174 6175 void glXCopyContext(Display *dpy, GLXContext src, 6176 GLXContext dst, arch_ulong mask); 6177 6178 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 6179 GLXContext share_list, Bool direct); 6180 6181 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 6182 Pixmap pixmap); 6183 6184 void glXDestroyContext(Display *dpy, GLXContext ctx); 6185 6186 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 6187 6188 int glXGetConfig(Display *dpy, XVisualInfo *vis, 6189 int attrib, int *value); 6190 6191 GLXContext glXGetCurrentContext(); 6192 6193 GLXDrawable glXGetCurrentDrawable(); 6194 6195 Bool glXIsDirect(Display *dpy, GLXContext ctx); 6196 6197 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 6198 GLXContext ctx); 6199 6200 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 6201 6202 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 6203 6204 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 6205 6206 void glXUseXFont(Font font, int first, int count, int list_base); 6207 6208 void glXWaitGL(); 6209 6210 void glXWaitX(); 6211 6212 6213 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 6214 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 6215 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 6216 6217 char* glXQueryExtensionsString (Display*, int); 6218 void* glXGetProcAddress (const(char)*); 6219 6220 } 6221 } 6222 6223 version(OSX) 6224 mixin DynamicLoad!(GLX, "GL", 0, true) glx; 6225 else 6226 mixin DynamicLoad!(GLX, "GLX", 0, true) glx; 6227 shared static this() { 6228 glx.loadDynamicLibrary(); 6229 } 6230 6231 alias glbindGetProcAddress = glXGetProcAddress; 6232 } 6233 } else version(Windows) { 6234 /* it is done below by interface GL */ 6235 } else 6236 static assert(0, "OpenGL not supported on your system yet. Try -version=X11 if you have X Windows available, or -version=without_opengl to go without."); 6237 } 6238 6239 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 6240 alias Resizablity = Resizability; 6241 6242 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 6243 enum Resizability { 6244 fixedSize, /// the window cannot be resized 6245 allowResizing, /// the window can be resized. The buffer (if there is one) will automatically adjust size, but not stretch the contents. the windowResized delegate will be called so you can respond to the new size yourself. 6246 automaticallyScaleIfPossible, /// if possible, your drawing buffer will remain the same size and simply be automatically scaled to the new window size. If this is impossible, it will not allow the user to resize the window at all. Note: window.width and window.height WILL be adjusted, which might throw you off if you draw based on them, so keep track of your expected width and height separately. That way, when it is scaled, things won't be thrown off. 6247 6248 // FIXME: automaticallyScaleIfPossible should adjust the OpenGL viewport on resize events 6249 } 6250 6251 6252 /++ 6253 Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 6254 +/ 6255 enum TextAlignment : uint { 6256 Left = 0, /// 6257 Center = 1, /// 6258 Right = 2, /// 6259 6260 VerticalTop = 0, /// 6261 VerticalCenter = 4, /// 6262 VerticalBottom = 8, /// 6263 } 6264 6265 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 6266 alias Rectangle = arsd.color.Rectangle; 6267 6268 6269 /++ 6270 Keyboard press and release events 6271 +/ 6272 struct KeyEvent { 6273 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 6274 Key key; 6275 ubyte hardwareCode; /// A platform and hardware specific code for the key 6276 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 6277 6278 dchar character; /// 6279 6280 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 6281 6282 SimpleWindow window; /// associated Window 6283 6284 // convert key event to simplified string representation a-la emacs 6285 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 6286 uint dpos = 0; 6287 void put (const(char)[] s...) nothrow @trusted { 6288 static if (growdest) { 6289 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 6290 } else { 6291 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 6292 } 6293 } 6294 6295 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 6296 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 6297 } 6298 6299 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 6300 6301 // put modifiers 6302 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 6303 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 6304 putMod(ModifierState.alt, Key.Alt, "Alt+"); 6305 putMod(ModifierState.windows, Key.Shift, "Windows+"); 6306 putMod(ModifierState.shift, Key.Shift, "Shift+"); 6307 6308 if (this.key) { 6309 foreach (string kn; __traits(allMembers, Key)) { 6310 if (this.key == __traits(getMember, Key, kn)) { 6311 // HACK! 6312 static if (kn == "N0") put("0"); 6313 else static if (kn == "N1") put("1"); 6314 else static if (kn == "N2") put("2"); 6315 else static if (kn == "N3") put("3"); 6316 else static if (kn == "N4") put("4"); 6317 else static if (kn == "N5") put("5"); 6318 else static if (kn == "N6") put("6"); 6319 else static if (kn == "N7") put("7"); 6320 else static if (kn == "N8") put("8"); 6321 else static if (kn == "N9") put("9"); 6322 else put(kn); 6323 return dest[0..dpos]; 6324 } 6325 } 6326 put("Unknown"); 6327 } else { 6328 if (dpos && dest[dpos-1] == '+') --dpos; 6329 } 6330 return dest[0..dpos]; 6331 } 6332 6333 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 6334 6335 /** Parse string into key name with modifiers. It accepts things like: 6336 * 6337 * C-H-1 -- emacs style (ctrl, and windows, and 1) 6338 * 6339 * Ctrl+Win+1 -- windows style 6340 * 6341 * Ctrl-Win-1 -- '-' is a valid delimiter too 6342 * 6343 * Ctrl Win 1 -- and space 6344 * 6345 * and even "Win + 1 + Ctrl". 6346 */ 6347 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 6348 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 6349 6350 // remove trailing spaces 6351 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 6352 6353 // tokens delimited by blank, '+', or '-' 6354 // null on eol 6355 const(char)[] getToken () nothrow @trusted @nogc { 6356 // remove leading spaces and delimiters 6357 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 6358 if (name.length == 0) return null; // oops, no more tokens 6359 // get token 6360 size_t epos = 0; 6361 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 6362 assert(epos > 0 && epos <= name.length); 6363 auto res = name[0..epos]; 6364 name = name[epos..$]; 6365 return res; 6366 } 6367 6368 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 6369 if (s0.length != s1.length) return false; 6370 foreach (immutable ci, char c0; s0) { 6371 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 6372 char c1 = s1[ci]; 6373 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 6374 if (c0 != c1) return false; 6375 } 6376 return true; 6377 } 6378 6379 if (ignoreModsOut !is null) *ignoreModsOut = false; 6380 if (updown !is null) *updown = -1; 6381 KeyEvent res; 6382 res.key = cast(Key)0; // just in case 6383 const(char)[] tk, tkn; // last token 6384 bool allowEmascStyle = true; 6385 bool ignoreModifiers = false; 6386 tokenloop: for (;;) { 6387 tk = tkn; 6388 tkn = getToken(); 6389 //k8: yay, i took "Bloody Mess" trait from Fallout! 6390 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 6391 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 6392 if (allowEmascStyle && tkn.length != 0) { 6393 if (tk.length == 1) { 6394 char mdc = tk[0]; 6395 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 6396 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 6397 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 6398 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 6399 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 6400 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 6401 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 6402 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 6403 } 6404 } 6405 allowEmascStyle = false; 6406 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 6407 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 6408 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 6409 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 6410 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 6411 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 6412 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 6413 if (tk.length == 0) continue; 6414 // try key name 6415 if (res.key == 0) { 6416 // little hack 6417 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 6418 final switch (tk[0]) { 6419 case '0': tk = "N0"; break; 6420 case '1': tk = "N1"; break; 6421 case '2': tk = "N2"; break; 6422 case '3': tk = "N3"; break; 6423 case '4': tk = "N4"; break; 6424 case '5': tk = "N5"; break; 6425 case '6': tk = "N6"; break; 6426 case '7': tk = "N7"; break; 6427 case '8': tk = "N8"; break; 6428 case '9': tk = "N9"; break; 6429 } 6430 } 6431 foreach (string kn; __traits(allMembers, Key)) { 6432 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 6433 } 6434 } 6435 // unknown or duplicate key name, get out of here 6436 break; 6437 } 6438 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 6439 return res; // something 6440 } 6441 6442 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 6443 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 6444 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 6445 if (kk == k) { mask |= mst; kk = cast(Key)0; } 6446 } 6447 bool ignoreMods; 6448 int updown; 6449 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 6450 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 6451 if (this.key != ke.key) { 6452 // things like "ctrl+alt" are complicated 6453 uint tkm = this.modifierState&modmask; 6454 uint kkm = ke.modifierState&modmask; 6455 Key tk = this.key; 6456 // ke 6457 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 6458 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 6459 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 6460 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 6461 // this 6462 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 6463 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 6464 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 6465 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 6466 return (tk == ke.key && tkm == kkm); 6467 } 6468 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 6469 } 6470 } 6471 6472 /// sets the application name. 6473 @property string ApplicationName(string name) { 6474 return _applicationName = name; 6475 } 6476 6477 string _applicationName; 6478 6479 /// ditto 6480 @property string ApplicationName() { 6481 if(_applicationName is null) { 6482 import core.runtime; 6483 return Runtime.args[0]; 6484 } 6485 return _applicationName; 6486 } 6487 6488 6489 /// Type of a [MouseEvent] 6490 enum MouseEventType : int { 6491 motion = 0, /// The mouse moved inside the window 6492 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 6493 buttonReleased = 2, /// A mouse button was released 6494 } 6495 6496 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 6497 /++ 6498 Listen for this on your event listeners if you are interested in mouse action. 6499 6500 Note that [button] is used on mouse press and release events. If you are curious about which button is being held in during motion, use [modifierState] and check the bitmask for [ModifierState.leftButtonDown], etc. 6501 6502 Examples: 6503 6504 This will draw boxes on the window with the mouse as you hold the left button. 6505 --- 6506 import arsd.simpledisplay; 6507 6508 void main() { 6509 auto window = new SimpleWindow(); 6510 6511 window.eventLoop(0, 6512 (MouseEvent ev) { 6513 if(ev.modifierState & ModifierState.leftButtonDown) { 6514 auto painter = window.draw(); 6515 painter.fillColor = Color.red; 6516 painter.outlineColor = Color.black; 6517 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 6518 } 6519 } 6520 ); 6521 } 6522 --- 6523 +/ 6524 struct MouseEvent { 6525 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 6526 6527 int x; /// Current X position of the cursor when the event fired, relative to the upper-left corner of the window, reported in pixels. (0, 0) is the upper left, (window.width - 1, window.height - 1) is the lower right corner of the window. 6528 int y; /// Current Y position of the cursor when the event fired. 6529 6530 int dx; /// Change in X position since last report 6531 int dy; /// Change in Y position since last report 6532 6533 MouseButton button; /// See [MouseButton] 6534 int modifierState; /// See [ModifierState] 6535 6536 version(X11) 6537 private Time timestamp; 6538 6539 /// Returns a linear representation of mouse button, 6540 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 6541 /// 6542 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 6543 @property ubyte buttonLinear() const { 6544 import core.bitop; 6545 if(button == 0) 6546 return 0; 6547 return (bsf(button) + 1) & 0b1111; 6548 } 6549 6550 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 6551 6552 SimpleWindow window; /// The window in which the event happened. 6553 6554 Point globalCoordinates() { 6555 Point p; 6556 if(window is null) 6557 throw new Exception("wtf"); 6558 static if(UsingSimpledisplayX11) { 6559 Window child; 6560 XTranslateCoordinates( 6561 XDisplayConnection.get, 6562 window.impl.window, 6563 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 6564 x, y, &p.x, &p.y, &child); 6565 return p; 6566 } else version(Windows) { 6567 POINT[1] points; 6568 points[0].x = x; 6569 points[0].y = y; 6570 MapWindowPoints( 6571 window.impl.hwnd, 6572 null, 6573 points.ptr, 6574 points.length 6575 ); 6576 p.x = points[0].x; 6577 p.y = points[0].y; 6578 6579 return p; 6580 } else version(OSXCocoa) { 6581 throw new NotYetImplementedException(); 6582 } else static assert(0); 6583 } 6584 6585 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 6586 6587 /** 6588 can contain emacs-like modifier prefix 6589 case-insensitive names: 6590 lmbX/leftX 6591 rmbX/rightX 6592 mmbX/middleX 6593 wheelX 6594 motion (no prefix allowed) 6595 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 6596 */ 6597 static bool equStr() (in auto ref MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 6598 if (str.length == 0) return false; // just in case 6599 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 6600 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 6601 auto anchor = str; 6602 uint mods = 0; // uint.max == any 6603 // interesting bits in kmod 6604 uint kmodmask = 6605 ModifierState.shift| 6606 ModifierState.ctrl| 6607 ModifierState.alt| 6608 ModifierState.windows| 6609 ModifierState.leftButtonDown| 6610 ModifierState.middleButtonDown| 6611 ModifierState.rightButtonDown| 6612 0; 6613 uint lastButt = uint.max; // otherwise, bit 31 means "down" 6614 bool wasButtons = false; 6615 while (str.length) { 6616 if (str.ptr[0] <= ' ') { 6617 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 6618 continue; 6619 } 6620 // one-letter modifier? 6621 if (str.length >= 2 && str.ptr[1] == '-') { 6622 switch (str.ptr[0]) { 6623 case '*': // "any" modifier (cannot be undone) 6624 mods = mods.max; 6625 break; 6626 case 'C': case 'c': // emacs "ctrl" 6627 if (mods != mods.max) mods |= ModifierState.ctrl; 6628 break; 6629 case 'M': case 'm': // emacs "meta" 6630 if (mods != mods.max) mods |= ModifierState.alt; 6631 break; 6632 case 'S': case 's': // emacs "shift" 6633 if (mods != mods.max) mods |= ModifierState.shift; 6634 break; 6635 case 'H': case 'h': // emacs "hyper" (aka winkey) 6636 if (mods != mods.max) mods |= ModifierState.windows; 6637 break; 6638 default: 6639 return false; // unknown modifier 6640 } 6641 str = str[2..$]; 6642 continue; 6643 } 6644 // word 6645 char[16] buf = void; // locased 6646 auto wep = 0; 6647 while (str.length) { 6648 immutable char ch = str.ptr[0]; 6649 if (ch <= ' ' || ch == '-') break; 6650 str = str[1..$]; 6651 if (wep > buf.length) return false; // too long 6652 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 6653 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 6654 else return false; // invalid char 6655 } 6656 if (wep == 0) return false; // just in case 6657 uint bnum; 6658 enum UpDown { None = -1, Up, Down, Any } 6659 auto updown = UpDown.None; // 0: up; 1: down 6660 switch (buf[0..wep]) { 6661 // left button 6662 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 6663 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 6664 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 6665 case "lmb": case "left": bnum = 0; break; 6666 // middle button 6667 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 6668 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 6669 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 6670 case "mmb": case "middle": bnum = 1; break; 6671 // right button 6672 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 6673 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 6674 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 6675 case "rmb": case "right": bnum = 2; break; 6676 // wheel 6677 case "wheelup": updown = UpDown.Up; goto case "wheel"; 6678 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 6679 case "wheelany": updown = UpDown.Any; goto case "wheel"; 6680 case "wheel": bnum = 3; break; 6681 // motion 6682 case "motion": bnum = 7; break; 6683 // unknown 6684 default: return false; 6685 } 6686 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 6687 // parse possible "-up" or "-down" 6688 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 6689 wep = 0; 6690 foreach (immutable idx, immutable char ch; str[1..$]) { 6691 if (ch <= ' ' || ch == '-') break; 6692 assert(idx == wep); // for now; trick 6693 if (wep > buf.length) { wep = 0; break; } // too long 6694 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 6695 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 6696 else { wep = 0; break; } // invalid char 6697 } 6698 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 6699 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 6700 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 6701 // remove parsed part 6702 if (updown != UpDown.None) str = str[wep+1..$]; 6703 } 6704 if (updown == UpDown.None) { 6705 updown = UpDown.Down; 6706 } 6707 wasButtons = wasButtons || (bnum <= 2); 6708 //assert(updown != UpDown.None); 6709 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 6710 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 6711 if (lastButt != lastButt.max) { 6712 if ((lastButt&0xff) >= 3) return false; // wheel or motion 6713 if (mods != mods.max) { 6714 uint butbit = 0; 6715 final switch (lastButt&0x03) { 6716 case 0: butbit = ModifierState.leftButtonDown; break; 6717 case 1: butbit = ModifierState.middleButtonDown; break; 6718 case 2: butbit = ModifierState.rightButtonDown; break; 6719 } 6720 if (lastButt&Flag.Down) mods |= butbit; 6721 else if (lastButt&Flag.Up) mods &= ~butbit; 6722 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 6723 } 6724 } 6725 // remember last button 6726 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 6727 } 6728 // no button -- nothing to do 6729 if (lastButt == lastButt.max) return false; 6730 // done parsing, check if something's left 6731 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 6732 // remove action button from mask 6733 if ((lastButt&0xff) < 3) { 6734 final switch (lastButt&0x03) { 6735 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 6736 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 6737 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 6738 } 6739 } 6740 // special case: "Motion" means "ignore buttons" 6741 if ((lastButt&0xff) == 7 && !wasButtons) { 6742 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 6743 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 6744 } 6745 uint kmod = event.modifierState&kmodmask; 6746 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 6747 // check modifier state 6748 if (mods != mods.max) { 6749 if (kmod != mods) return false; 6750 } 6751 // now check type 6752 if ((lastButt&0xff) == 7) { 6753 // motion 6754 if (event.type != MouseEventType.motion) return false; 6755 } else if ((lastButt&0xff) == 3) { 6756 // wheel 6757 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 6758 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 6759 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 6760 return false; 6761 } else { 6762 // buttons 6763 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 6764 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 6765 { 6766 return false; 6767 } 6768 // button number 6769 switch (lastButt&0x03) { 6770 case 0: if (event.button != MouseButton.left) return false; break; 6771 case 1: if (event.button != MouseButton.middle) return false; break; 6772 case 2: if (event.button != MouseButton.right) return false; break; 6773 default: return false; 6774 } 6775 } 6776 return true; 6777 } 6778 } 6779 6780 version(arsd_mevent_strcmp_test) unittest { 6781 MouseEvent event; 6782 event.type = MouseEventType.buttonPressed; 6783 event.button = MouseButton.left; 6784 event.modifierState = ModifierState.ctrl; 6785 assert(event == "C-LMB"); 6786 assert(event != "C-LMBUP"); 6787 assert(event != "C-LMB-UP"); 6788 assert(event != "C-S-LMB"); 6789 assert(event == "*-LMB"); 6790 assert(event != "*-LMB-UP"); 6791 6792 event.type = MouseEventType.buttonReleased; 6793 assert(event != "C-LMB"); 6794 assert(event == "C-LMBUP"); 6795 assert(event == "C-LMB-UP"); 6796 assert(event != "C-S-LMB"); 6797 assert(event != "*-LMB"); 6798 assert(event == "*-LMB-UP"); 6799 6800 event.button = MouseButton.right; 6801 event.modifierState |= ModifierState.shift; 6802 event.type = MouseEventType.buttonPressed; 6803 assert(event != "C-LMB"); 6804 assert(event != "C-LMBUP"); 6805 assert(event != "C-LMB-UP"); 6806 assert(event != "C-S-LMB"); 6807 assert(event != "*-LMB"); 6808 assert(event != "*-LMB-UP"); 6809 6810 assert(event != "C-RMB"); 6811 assert(event != "C-RMBUP"); 6812 assert(event != "C-RMB-UP"); 6813 assert(event == "C-S-RMB"); 6814 assert(event == "*-RMB"); 6815 assert(event != "*-RMB-UP"); 6816 } 6817 6818 /// This gives a few more options to drawing lines and such 6819 struct Pen { 6820 Color color; /// the foreground color 6821 int width = 1; /// width of the line 6822 Style style; /// See [Style] 6823 /+ 6824 // From X.h 6825 6826 #define LineSolid 0 6827 #define LineOnOffDash 1 6828 #define LineDoubleDash 2 6829 LineDou- The full path of the line is drawn, but the 6830 bleDash even dashes are filled differently from the 6831 odd dashes (see fill-style) with CapButt 6832 style used where even and odd dashes meet. 6833 6834 6835 6836 /* capStyle */ 6837 6838 #define CapNotLast 0 6839 #define CapButt 1 6840 #define CapRound 2 6841 #define CapProjecting 3 6842 6843 /* joinStyle */ 6844 6845 #define JoinMiter 0 6846 #define JoinRound 1 6847 #define JoinBevel 2 6848 6849 /* fillStyle */ 6850 6851 #define FillSolid 0 6852 #define FillTiled 1 6853 #define FillStippled 2 6854 #define FillOpaqueStippled 3 6855 6856 6857 +/ 6858 /// Style of lines drawn 6859 enum Style { 6860 Solid, /// a solid line 6861 Dashed, /// a dashed line 6862 Dotted, /// a dotted line 6863 } 6864 } 6865 6866 6867 /++ 6868 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 6869 6870 6871 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 6872 6873 $(NOTE If you are writing platform-aware code and need to know low-level details, uou may check `if(Image.impl.xshmAvailable)` to see if MIT-SHM is used on X11 targets to draw `Image`s and `Sprite`s. Use `static if(UsingSimpledisplayX11)` to determine if you are compiling for an X11 target.) 6874 6875 Drawing an image to screen is not necessarily fast, but applying algorithms to draw to the image itself should be fast. An `Image` is also the first step in loading and displaying images loaded from files. 6876 6877 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 6878 6879 $(PITFALL `Image` may represent a scarce, shared resource that persists across process termination, and should be disposed of properly. On X11, it uses the MIT-SHM extension, if available, which uses shared memory handles with the X server, which is a long-lived process that holds onto them after your program terminates if you don't free it. 6880 6881 It is possible for your user's system to run out of these handles over time, forcing them to clean it up with extraordinary measures - their GUI is liable to stop working! 6882 6883 Be sure these are cleaned up properly. simpledisplay will do its best to do the right thing, including cleaning them up in garbage collection sweeps (one of which is run at most normal program terminations) and catching some deadly signals. It will almost always do the right thing. But, this is no substitute for you managing the resource properly yourself. (And try not to segfault, as recovery from them is alway dicey!) 6884 6885 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 6886 6887 --- 6888 auto image = new Image(256, 256); 6889 scope(exit) destroy(image); 6890 --- 6891 6892 As long as you don't hold on to it outside the scope. 6893 6894 I might change it to be an owned pointer at some point in the future. 6895 6896 ) 6897 6898 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 6899 you can also often get a fair amount of speedup by getting the raw data format and 6900 writing some custom code. 6901 6902 FIXME INSERT EXAMPLES HERE 6903 6904 6905 +/ 6906 final class Image { 6907 /// 6908 this(int width, int height, bool forcexshm=false) { 6909 this.width = width; 6910 this.height = height; 6911 6912 impl.createImage(width, height, forcexshm); 6913 } 6914 6915 /// 6916 this(Size size, bool forcexshm=false) { 6917 this(size.width, size.height, forcexshm); 6918 } 6919 6920 private bool suppressDestruction; 6921 6922 version(X11) 6923 this(XImage* handle) { 6924 this.handle = handle; 6925 this.rawData = cast(ubyte*) handle.data; 6926 this.width = handle.width; 6927 this.height = handle.height; 6928 suppressDestruction = true; 6929 } 6930 6931 ~this() { 6932 if(suppressDestruction) return; 6933 impl.dispose(); 6934 } 6935 6936 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 6937 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 6938 pure const @system nothrow { 6939 /* 6940 To use these to draw a blue rectangle with size WxH at position X,Y... 6941 6942 // make certain that it will fit before we proceed 6943 enforce(X + W <= img.width && Y + H <= img.height); // you could also adjust the size to clip it, but be sure not to run off since this here will do raw pointers with no bounds checks! 6944 6945 // gather all the values you'll need up front. These can be kept until the image changes size if you want 6946 // (though calculating them isn't really that expensive). 6947 auto nextLineAdjustment = img.adjustmentForNextLine(); 6948 auto offR = img.redByteOffset(); 6949 auto offB = img.blueByteOffset(); 6950 auto offG = img.greenByteOffset(); 6951 auto bpp = img.bytesPerPixel(); 6952 6953 auto data = img.getDataPointer(); 6954 6955 // figure out the starting byte offset 6956 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 6957 6958 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 6959 6960 // and now our drawing loop for the rectangle 6961 foreach(y; 0 .. H) { 6962 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 6963 foreach(x; 0 .. W) { 6964 // write our color 6965 data[offR] = 0; 6966 data[offG] = 0; 6967 data[offB] = 255; 6968 6969 data += bpp; // moving to the next pixel is just an addition... 6970 } 6971 startOfLine += nextLineAdjustment; 6972 } 6973 6974 6975 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 6976 6977 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 6978 can be made into a bitmask or something so we can write them as *uint... 6979 */ 6980 6981 /// 6982 int offsetForTopLeftPixel() { 6983 version(X11) { 6984 return 0; 6985 } else version(Windows) { 6986 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 6987 } else version(OSXCocoa) { 6988 return 0 ; //throw new NotYetImplementedException(); 6989 } else static assert(0, "fill in this info for other OSes"); 6990 } 6991 6992 /// 6993 int offsetForPixel(int x, int y) { 6994 version(X11) { 6995 auto offset = (y * width + x) * 4; 6996 return offset; 6997 } else version(Windows) { 6998 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 6999 // remember, bmps are upside down 7000 auto offset = itemsPerLine * (height - y - 1) + x * 3; 7001 return offset; 7002 } else version(OSXCocoa) { 7003 return 0 ; //throw new NotYetImplementedException(); 7004 } else static assert(0, "fill in this info for other OSes"); 7005 } 7006 7007 /// 7008 int adjustmentForNextLine() { 7009 version(X11) { 7010 return width * 4; 7011 } else version(Windows) { 7012 // windows bmps are upside down, so the adjustment is actually negative 7013 return -((cast(int) width * 3 + 3) / 4) * 4; 7014 } else version(OSXCocoa) { 7015 return 0 ; //throw new NotYetImplementedException(); 7016 } else static assert(0, "fill in this info for other OSes"); 7017 } 7018 7019 /// once you have the position of a pixel, use these to get to the proper color 7020 int redByteOffset() { 7021 version(X11) { 7022 return 2; 7023 } else version(Windows) { 7024 return 2; 7025 } else version(OSXCocoa) { 7026 return 0 ; //throw new NotYetImplementedException(); 7027 } else static assert(0, "fill in this info for other OSes"); 7028 } 7029 7030 /// 7031 int greenByteOffset() { 7032 version(X11) { 7033 return 1; 7034 } else version(Windows) { 7035 return 1; 7036 } else version(OSXCocoa) { 7037 return 0 ; //throw new NotYetImplementedException(); 7038 } else static assert(0, "fill in this info for other OSes"); 7039 } 7040 7041 /// 7042 int blueByteOffset() { 7043 version(X11) { 7044 return 0; 7045 } else version(Windows) { 7046 return 0; 7047 } else version(OSXCocoa) { 7048 return 0 ; //throw new NotYetImplementedException(); 7049 } else static assert(0, "fill in this info for other OSes"); 7050 } 7051 } 7052 7053 /// 7054 final void putPixel(int x, int y, Color c) { 7055 if(x < 0 || x >= width) 7056 return; 7057 if(y < 0 || y >= height) 7058 return; 7059 7060 impl.setPixel(x, y, c); 7061 } 7062 7063 /// 7064 final Color getPixel(int x, int y) { 7065 if(x < 0 || x >= width) 7066 return Color.transparent; 7067 if(y < 0 || y >= height) 7068 return Color.transparent; 7069 7070 version(OSXCocoa) throw new NotYetImplementedException(); else 7071 return impl.getPixel(x, y); 7072 } 7073 7074 /// 7075 final void opIndexAssign(Color c, int x, int y) { 7076 putPixel(x, y, c); 7077 } 7078 7079 /// 7080 TrueColorImage toTrueColorImage() { 7081 auto tci = new TrueColorImage(width, height); 7082 convertToRgbaBytes(tci.imageData.bytes); 7083 return tci; 7084 } 7085 7086 /// 7087 static Image fromMemoryImage(MemoryImage i) { 7088 auto tci = i.getAsTrueColorImage(); 7089 auto img = new Image(tci.width, tci.height); 7090 img.setRgbaBytes(tci.imageData.bytes); 7091 return img; 7092 } 7093 7094 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 7095 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 7096 /// if you pass null, it will allocate a new one. 7097 ubyte[] getRgbaBytes(ubyte[] where = null) { 7098 if(where is null) 7099 where = new ubyte[this.width*this.height*4]; 7100 convertToRgbaBytes(where); 7101 return where; 7102 } 7103 7104 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 7105 void setRgbaBytes(in ubyte[] from ) { 7106 assert(from.length == this.width * this.height * 4); 7107 setFromRgbaBytes(from); 7108 } 7109 7110 // FIXME: make properly cross platform by getting rgba right 7111 7112 /// warning: this is not portable across platforms because the data format can change 7113 ubyte* getDataPointer() { 7114 return impl.rawData; 7115 } 7116 7117 /// for use with getDataPointer 7118 final int bytesPerLine() const pure @safe nothrow { 7119 version(Windows) 7120 return ((cast(int) width * 3 + 3) / 4) * 4; 7121 else version(X11) 7122 return 4 * width; 7123 else version(OSXCocoa) 7124 return 4 * width; 7125 else static assert(0); 7126 } 7127 7128 /// for use with getDataPointer 7129 final int bytesPerPixel() const pure @safe nothrow { 7130 version(Windows) 7131 return 3; 7132 else version(X11) 7133 return 4; 7134 else version(OSXCocoa) 7135 return 4; 7136 else static assert(0); 7137 } 7138 7139 /// 7140 immutable int width; 7141 7142 /// 7143 immutable int height; 7144 //private: 7145 mixin NativeImageImplementation!() impl; 7146 } 7147 7148 /// A convenience function to pop up a window displaying the image. 7149 /// If you pass a win, it will draw the image in it. Otherwise, it will 7150 /// create a window with the size of the image and run its event loop, closing 7151 /// when a key is pressed. 7152 void displayImage(Image image, SimpleWindow win = null) { 7153 if(win is null) { 7154 win = new SimpleWindow(image); 7155 { 7156 auto p = win.draw; 7157 p.drawImage(Point(0, 0), image); 7158 } 7159 win.eventLoop(0, 7160 (KeyEvent ev) { 7161 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 7162 } ); 7163 } else { 7164 win.image = image; 7165 } 7166 } 7167 7168 enum FontWeight : int { 7169 dontcare = 0, 7170 thin = 100, 7171 extralight = 200, 7172 light = 300, 7173 regular = 400, 7174 medium = 500, 7175 semibold = 600, 7176 bold = 700, 7177 extrabold = 800, 7178 heavy = 900 7179 } 7180 7181 /++ 7182 Represents a font loaded off the operating system or the X server. 7183 7184 7185 While the api here is unified cross platform, the fonts are not necessarily 7186 available, even across machines of the same platform, so be sure to always check 7187 for null (using [isNull]) and have a fallback plan. 7188 7189 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 7190 7191 Worst case, a null font will automatically fall back to the default font loaded 7192 for your system. 7193 +/ 7194 class OperatingSystemFont { 7195 // FIXME: when the X Connection is lost, these need to be invalidated! 7196 // that means I need to store the original stuff again to reconstruct it too. 7197 7198 version(X11) { 7199 XFontStruct* font; 7200 XFontSet fontset; 7201 7202 version(with_xft) { 7203 XftFont* xftFont; 7204 bool isXft; 7205 } 7206 } else version(Windows) { 7207 HFONT font; 7208 int width_; 7209 int height_; 7210 } else version(OSXCocoa) { 7211 // FIXME 7212 } else static assert(0); 7213 7214 /++ 7215 Constructs the class and immediately calls [load]. 7216 +/ 7217 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 7218 load(name, size, weight, italic); 7219 } 7220 7221 /++ 7222 Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object. 7223 7224 You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you. 7225 7226 History: 7227 Added January 24, 2021. 7228 +/ 7229 this() { 7230 // this space intentionally left blank 7231 } 7232 7233 /++ 7234 Loads specifically with the Xft library - a freetype font from a fontconfig string. 7235 7236 History: 7237 Added November 13, 2020. 7238 +/ 7239 version(with_xft) 7240 bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 7241 unload(); 7242 7243 if(!XftLibrary.attempted) { 7244 XftLibrary.loadDynamicLibrary(); 7245 } 7246 7247 if(!XftLibrary.loadSuccessful) 7248 return false; 7249 7250 auto display = XDisplayConnection.get; 7251 7252 char[256] nameBuffer = void; 7253 int nbp = 0; 7254 7255 void add(in char[] a) { 7256 nameBuffer[nbp .. nbp + a.length] = a[]; 7257 nbp += a.length; 7258 } 7259 add(name); 7260 7261 if(size) { 7262 add(":size="); 7263 add(toInternal!string(size)); 7264 } 7265 if(weight != FontWeight.dontcare) { 7266 add(":weight="); 7267 add(weightToString(weight)); 7268 } 7269 if(italic) 7270 add(":slant=100"); 7271 7272 nameBuffer[nbp] = 0; 7273 7274 this.xftFont = XftFontOpenName( 7275 display, 7276 DefaultScreen(display), 7277 nameBuffer.ptr 7278 ); 7279 7280 this.isXft = true; 7281 7282 if(xftFont !is null) { 7283 isMonospace_ = stringWidth("x") == stringWidth("M"); 7284 ascent_ = xftFont.ascent; 7285 descent_ = xftFont.descent; 7286 } 7287 7288 return !isNull(); 7289 } 7290 7291 // see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352 7292 7293 private string weightToString(FontWeight weight) { 7294 with(FontWeight) 7295 final switch(weight) { 7296 case dontcare: return "*"; 7297 case thin: return "extralight"; 7298 case extralight: return "extralight"; 7299 case light: return "light"; 7300 case regular: return "regular"; 7301 case medium: return "medium"; 7302 case semibold: return "demibold"; 7303 case bold: return "bold"; 7304 case extrabold: return "demibold"; 7305 case heavy: return "black"; 7306 } 7307 } 7308 7309 /++ 7310 Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance. 7311 7312 History: 7313 Added November 13, 2020. Before then, this code was integrated in the [load] function. 7314 +/ 7315 version(X11) 7316 bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 7317 unload(); 7318 7319 string xfontstr; 7320 7321 if(name.length > 3 && name[0 .. 3] == "-*-") { 7322 // this is kinda a disgusting hack but if the user sends an exact 7323 // string I'd like to honor it... 7324 xfontstr = name; 7325 } else { 7326 string weightstr = weightToString(weight); 7327 string sizestr; 7328 if(size == 0) 7329 sizestr = "*"; 7330 else 7331 sizestr = toInternal!string(size); 7332 xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 7333 } 7334 7335 //import std.stdio; writeln(xfontstr); 7336 7337 auto display = XDisplayConnection.get; 7338 7339 font = XLoadQueryFont(display, xfontstr.ptr); 7340 if(font is null) 7341 return false; 7342 7343 char** lol; 7344 int lol2; 7345 char* lol3; 7346 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 7347 7348 prepareFontInfo(); 7349 7350 return !isNull(); 7351 } 7352 7353 version(X11) 7354 private void prepareFontInfo() { 7355 if(font !is null) { 7356 isMonospace_ = stringWidth("l") == stringWidth("M"); 7357 ascent_ = font.max_bounds.ascent; 7358 descent_ = font.max_bounds.descent; 7359 } 7360 } 7361 7362 /++ 7363 Loads a Windows font. You probably want to use [load] instead to be more generic. 7364 7365 History: 7366 Added November 13, 2020. Before then, this code was integrated in the [load] function. 7367 +/ 7368 version(Windows) 7369 bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) { 7370 unload(); 7371 7372 WCharzBuffer buffer = WCharzBuffer(name); 7373 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 7374 7375 prepareFontInfo(hdc); 7376 7377 return !isNull(); 7378 } 7379 7380 version(Windows) 7381 void prepareFontInfo(HDC hdc = null) { 7382 if(font is null) 7383 return; 7384 7385 TEXTMETRIC tm; 7386 auto dc = hdc ? hdc : GetDC(null); 7387 auto orig = SelectObject(dc, font); 7388 GetTextMetrics(dc, &tm); 7389 SelectObject(dc, orig); 7390 if(hdc is null) 7391 ReleaseDC(null, dc); 7392 7393 width_ = tm.tmAveCharWidth; 7394 height_ = tm.tmHeight; 7395 ascent_ = tm.tmAscent; 7396 descent_ = tm.tmDescent; 7397 // If this bit is set the font is a variable pitch font. If this bit is clear the font is a fixed pitch font. Note very carefully that those meanings are the opposite of what the constant name implies. 7398 isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0; 7399 } 7400 7401 7402 /++ 7403 `name` is a font name, but it can also be a more complicated string parsed in an OS-specific way. 7404 7405 On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise, 7406 it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX]. 7407 7408 On Windows, it forwards directly to [loadWin32]. 7409 7410 Params: 7411 name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences. 7412 size = font size. This may be interpreted differently by different systems and different fonts. Size 0 means load a default, which may not exist and cause [isNull] to become true. 7413 weight = approximate boldness, results may vary. 7414 italic = try to get a slanted version of the given font. 7415 7416 History: 7417 Xft support was added on November 13, 2020. It would only load core fonts. Xft inclusion changed font lookup and interpretation of the `size` parameter, requiring a major version bump. This caused release v9.0. 7418 +/ 7419 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 7420 version(X11) { 7421 version(with_xft) { 7422 if(name.length > 5 && name[0 .. 5] == "core:") { 7423 goto core; 7424 } 7425 7426 if(loadXft(name, size, weight, italic)) 7427 return true; 7428 // if xft fails, fallback to core to avoid breaking 7429 // code that already depended on this. 7430 } 7431 7432 core: 7433 7434 if(name.length > 5 && name[0 .. 5] == "core:") { 7435 name = name[5 .. $]; 7436 } 7437 7438 return loadCoreX(name, size, weight, italic); 7439 } else version(Windows) { 7440 return loadWin32(name, size, weight, italic); 7441 } else version(OSXCocoa) { 7442 // FIXME 7443 return false; 7444 } else static assert(0); 7445 } 7446 7447 /// 7448 void unload() { 7449 if(isNull()) 7450 return; 7451 7452 version(X11) { 7453 auto display = XDisplayConnection.display; 7454 7455 if(display is null) 7456 return; 7457 7458 version(with_xft) { 7459 if(isXft) { 7460 if(xftFont) 7461 XftFontClose(display, xftFont); 7462 isXft = false; 7463 xftFont = null; 7464 return; 7465 } 7466 } 7467 7468 if(font) 7469 XFreeFont(display, font); 7470 if(fontset) 7471 XFreeFontSet(display, fontset); 7472 7473 font = null; 7474 fontset = null; 7475 } else version(Windows) { 7476 DeleteObject(font); 7477 font = null; 7478 } else version(OSXCocoa) { 7479 // FIXME 7480 } else static assert(0); 7481 } 7482 7483 private bool isMonospace_; 7484 7485 /++ 7486 History: 7487 Added January 16, 2021 7488 +/ 7489 bool isMonospace() { 7490 return isMonospace_; 7491 } 7492 7493 /++ 7494 Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character. 7495 7496 History: 7497 Added March 26, 2020 7498 Documented January 16, 2021 7499 +/ 7500 int averageWidth() { 7501 version(X11) { 7502 return stringWidth("x"); 7503 } else version(Windows) 7504 return width_; 7505 else assert(0); 7506 } 7507 7508 /++ 7509 Returns the width of the string as drawn on the specified window, or the default screen if the window is null. 7510 7511 History: 7512 Added January 16, 2021 7513 +/ 7514 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 7515 // FIXME: what about tab? 7516 if(isNull) 7517 return 0; 7518 7519 version(X11) { 7520 version(with_xft) 7521 if(isXft && xftFont !is null) { 7522 //return xftFont.max_advance_width; 7523 XGlyphInfo extents; 7524 XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents); 7525 //import std.stdio; writeln(extents); 7526 return extents.xOff; 7527 } 7528 if(font is null) 7529 return 0; 7530 else if(fontset) { 7531 XRectangle rect; 7532 Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect); 7533 7534 return rect.width; 7535 } else { 7536 return XTextWidth(font, s.ptr, cast(int) s.length); 7537 } 7538 } else version(Windows) { 7539 WCharzBuffer buffer = WCharzBuffer(s); 7540 7541 return stringWidth(buffer.slice, window); 7542 } 7543 else assert(0); 7544 } 7545 7546 version(Windows) 7547 /// ditto 7548 int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) { 7549 if(isNull) 7550 return 0; 7551 version(Windows) { 7552 SIZE size; 7553 7554 prepareContext(window); 7555 scope(exit) releaseContext(); 7556 7557 GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size); 7558 7559 return size.cx; 7560 } else { 7561 // std.conv can do this easily but it is slow to import and i don't think it is worth it 7562 static assert(0, "not implemented yet"); 7563 //return stringWidth(s, window); 7564 } 7565 } 7566 7567 private { 7568 int prepRefcount; 7569 7570 version(Windows) { 7571 HDC dc; 7572 HANDLE orig; 7573 HWND hwnd; 7574 } 7575 } 7576 /++ 7577 [stringWidth] can be slow. This helps speed it up if you are doing a lot of calculations. Just prepareContext when you start this work and releaseContext when you are done. Important to release before too long though as it can be a scarce system resource. 7578 7579 History: 7580 Added January 23, 2021 7581 +/ 7582 void prepareContext(SimpleWindow window = null) { 7583 prepRefcount++; 7584 if(prepRefcount == 1) { 7585 version(Windows) { 7586 hwnd = window is null ? null : window.impl.hwnd; 7587 dc = GetDC(hwnd); 7588 orig = SelectObject(dc, font); 7589 } 7590 } 7591 } 7592 /// ditto 7593 void releaseContext() { 7594 prepRefcount--; 7595 if(prepRefcount == 0) { 7596 version(Windows) { 7597 SelectObject(dc, orig); 7598 ReleaseDC(hwnd, dc); 7599 hwnd = null; 7600 dc = null; 7601 orig = null; 7602 } 7603 } 7604 } 7605 7606 /+ 7607 FIXME: I think I need advance and kerning pair 7608 7609 int advance(dchar from, dchar to) { } // use dchar.init for first item in string 7610 +/ 7611 7612 /++ 7613 Returns the height of the font. 7614 7615 History: 7616 Added March 26, 2020 7617 Documented January 16, 2021 7618 +/ 7619 int height() { 7620 version(X11) { 7621 version(with_xft) 7622 if(isXft && xftFont !is null) { 7623 return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel 7624 } 7625 if(font is null) 7626 return 0; 7627 return font.max_bounds.ascent + font.max_bounds.descent; 7628 } else version(Windows) 7629 return height_; 7630 else assert(0); 7631 } 7632 7633 private int ascent_; 7634 private int descent_; 7635 7636 /++ 7637 Max ascent above the baseline. 7638 7639 History: 7640 Added January 22, 2021 7641 +/ 7642 int ascent() { 7643 return ascent_; 7644 } 7645 7646 /++ 7647 Max descent below the baseline. 7648 7649 History: 7650 Added January 22, 2021 7651 +/ 7652 int descent() { 7653 return descent_; 7654 } 7655 7656 /++ 7657 Loads the default font used by [ScreenPainter] if none others are loaded. 7658 7659 Returns: 7660 This method mutates the `this` object, but then returns `this` for 7661 easy chaining like: 7662 7663 --- 7664 auto font = foo.isNull ? foo : foo.loadDefault 7665 --- 7666 7667 History: 7668 Added previously, but left unimplemented until January 24, 2021. 7669 +/ 7670 OperatingSystemFont loadDefault() { 7671 unload(); 7672 7673 version(X11) { 7674 // another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html 7675 // but meh since sdpy does its own thing, this should be ok too 7676 7677 ScreenPainterImplementation.ensureDefaultFontLoaded(); 7678 this.font = ScreenPainterImplementation.defaultfont; 7679 this.fontset = ScreenPainterImplementation.defaultfontset; 7680 7681 prepareFontInfo(); 7682 } else version(Windows) { 7683 ScreenPainterImplementation.ensureDefaultFontLoaded(); 7684 this.font = ScreenPainterImplementation.defaultGuiFont; 7685 7686 prepareFontInfo(); 7687 } else throw new NotYetImplementedException(); 7688 7689 return this; 7690 } 7691 7692 /// 7693 bool isNull() { 7694 version(OSXCocoa) throw new NotYetImplementedException(); else { 7695 version(with_xft) 7696 if(isXft) 7697 return xftFont is null; 7698 return font is null; 7699 } 7700 } 7701 7702 /* Metrics */ 7703 /+ 7704 GetABCWidth 7705 GetKerningPairs 7706 7707 if I do it right, I can size it all here, and match 7708 what happens when I draw the full string with the OS functions. 7709 7710 subclasses might do the same thing while getting the glyphs on images 7711 struct GlyphInfo { 7712 int glyph; 7713 7714 size_t stringIdxStart; 7715 size_t stringIdxEnd; 7716 7717 Rectangle boundingBox; 7718 } 7719 GlyphInfo[] getCharBoxes() { 7720 // XftTextExtentsUtf8 7721 return null; 7722 7723 } 7724 +/ 7725 7726 ~this() { 7727 unload(); 7728 } 7729 } 7730 7731 /** 7732 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 7733 than constructing it directly. Then, it is reference counted so you can pass it 7734 at around and when the last ref goes out of scope, the buffered drawing activities 7735 are all carried out. 7736 7737 7738 Most functions use the outlineColor instead of taking a color themselves. 7739 ScreenPainter is reference counted and draws its buffer to the screen when its 7740 final reference goes out of scope. 7741 */ 7742 struct ScreenPainter { 7743 CapableOfBeingDrawnUpon window; 7744 this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle) { 7745 this.window = window; 7746 if(window.closed) 7747 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 7748 currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 7749 if(window.activeScreenPainter !is null) { 7750 impl = window.activeScreenPainter; 7751 if(impl.referenceCount == 0) { 7752 impl.window = window; 7753 impl.create(handle); 7754 } 7755 impl.referenceCount++; 7756 // writeln("refcount ++ ", impl.referenceCount); 7757 } else { 7758 impl = new ScreenPainterImplementation; 7759 impl.window = window; 7760 impl.create(handle); 7761 impl.referenceCount = 1; 7762 window.activeScreenPainter = impl; 7763 // writeln("constructed"); 7764 } 7765 7766 copyActiveOriginals(); 7767 } 7768 7769 private Pen originalPen; 7770 private Color originalFillColor; 7771 private arsd.color.Rectangle originalClipRectangle; 7772 void copyActiveOriginals() { 7773 if(impl is null) return; 7774 originalPen = impl._activePen; 7775 originalFillColor = impl._fillColor; 7776 originalClipRectangle = impl._clipRectangle; 7777 } 7778 7779 ~this() { 7780 if(impl is null) return; 7781 impl.referenceCount--; 7782 //writeln("refcount -- ", impl.referenceCount); 7783 if(impl.referenceCount == 0) { 7784 //writeln("destructed"); 7785 impl.dispose(); 7786 *window.activeScreenPainter = ScreenPainterImplementation.init; 7787 //import std.stdio; writeln("paint finished"); 7788 } else { 7789 // there is still an active reference, reset stuff so the 7790 // next user doesn't get weirdness via the reference 7791 this.rasterOp = RasterOp.normal; 7792 pen = originalPen; 7793 fillColor = originalFillColor; 7794 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 7795 } 7796 } 7797 7798 this(this) { 7799 if(impl is null) return; 7800 impl.referenceCount++; 7801 //writeln("refcount ++ ", impl.referenceCount); 7802 7803 copyActiveOriginals(); 7804 } 7805 7806 private int _originX; 7807 private int _originY; 7808 @property int originX() { return _originX; } 7809 @property int originY() { return _originY; } 7810 @property int originX(int a) { 7811 //currentClipRectangle.left += a - _originX; 7812 //currentClipRectangle.right += a - _originX; 7813 _originX = a; 7814 return _originX; 7815 } 7816 @property int originY(int a) { 7817 //currentClipRectangle.top += a - _originY; 7818 //currentClipRectangle.bottom += a - _originY; 7819 _originY = a; 7820 return _originY; 7821 } 7822 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 7823 private void transform(ref Point p) { 7824 if(impl is null) return; 7825 p.x += _originX; 7826 p.y += _originY; 7827 } 7828 7829 // this needs to be checked BEFORE the originX/Y transformation 7830 private bool isClipped(Point p) { 7831 return !currentClipRectangle.contains(p); 7832 } 7833 private bool isClipped(Point p, int width, int height) { 7834 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 7835 } 7836 private bool isClipped(Point p, Size s) { 7837 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 7838 } 7839 private bool isClipped(Point p, Point p2) { 7840 // need to ensure the end points are actually included inside, so the +1 does that 7841 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 7842 } 7843 7844 7845 /// Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 7846 void setClipRectangle(Point pt, int width, int height) { 7847 if(impl is null) return; 7848 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 7849 return; // no need to do anything 7850 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 7851 transform(pt); 7852 7853 impl.setClipRectangle(pt.x, pt.y, width, height); 7854 } 7855 7856 /// ditto 7857 void setClipRectangle(arsd.color.Rectangle rect) { 7858 if(impl is null) return; 7859 setClipRectangle(rect.upperLeft, rect.width, rect.height); 7860 } 7861 7862 /// 7863 void setFont(OperatingSystemFont font) { 7864 if(impl is null) return; 7865 impl.setFont(font); 7866 } 7867 7868 /// 7869 int fontHeight() { 7870 if(impl is null) return 0; 7871 return impl.fontHeight(); 7872 } 7873 7874 private Pen activePen; 7875 7876 /// 7877 @property void pen(Pen p) { 7878 if(impl is null) return; 7879 activePen = p; 7880 impl.pen(p); 7881 } 7882 7883 /// 7884 @scriptable 7885 @property void outlineColor(Color c) { 7886 if(impl is null) return; 7887 if(activePen.color == c) 7888 return; 7889 activePen.color = c; 7890 impl.pen(activePen); 7891 } 7892 7893 /// 7894 @scriptable 7895 @property void fillColor(Color c) { 7896 if(impl is null) return; 7897 impl.fillColor(c); 7898 } 7899 7900 /// 7901 @property void rasterOp(RasterOp op) { 7902 if(impl is null) return; 7903 impl.rasterOp(op); 7904 } 7905 7906 7907 void updateDisplay() { 7908 // FIXME this should do what the dtor does 7909 } 7910 7911 /// Scrolls the contents in the bounding rectangle by dx, dy. Positive dx means scroll left (make space available at the right), positive dy means scroll up (make space available at the bottom) 7912 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 7913 if(impl is null) return; 7914 if(isClipped(upperLeft, width, height)) return; 7915 transform(upperLeft); 7916 version(Windows) { 7917 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 7918 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 7919 RECT clip = scroll; 7920 RECT uncovered; 7921 HRGN hrgn; 7922 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 7923 throw new Exception("ScrollDC"); 7924 7925 } else version(X11) { 7926 // FIXME: clip stuff outside this rectangle 7927 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 7928 } else version(OSXCocoa) { 7929 throw new NotYetImplementedException(); 7930 } else static assert(0); 7931 } 7932 7933 /// 7934 void clear(Color color = Color.white()) { 7935 if(impl is null) return; 7936 fillColor = color; 7937 outlineColor = color; 7938 drawRectangle(Point(0, 0), window.width, window.height); 7939 } 7940 7941 /++ 7942 Draws a pixmap (represented by the [Sprite] class) on the drawable. 7943 7944 Params: 7945 upperLeft = point on the window where the upper left corner of the image will be drawn 7946 imageUpperLeft = point on the image to start the slice to draw 7947 sliceSize = size of the slice of the image to draw on the window. If width or height is 0, it uses the entire width or height of the image. 7948 History: 7949 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 7950 +/ 7951 version(OSXCocoa) {} else // NotYetImplementedException 7952 void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 7953 if(impl is null) return; 7954 if(isClipped(upperLeft, s.width, s.height)) return; 7955 transform(upperLeft); 7956 impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height); 7957 } 7958 7959 /// 7960 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 7961 if(impl is null) return; 7962 //if(isClipped(upperLeft, w, h)) return; // FIXME 7963 transform(upperLeft); 7964 if(w == 0 || w > i.width) 7965 w = i.width; 7966 if(h == 0 || h > i.height) 7967 h = i.height; 7968 if(upperLeftOfImage.x < 0) 7969 upperLeftOfImage.x = 0; 7970 if(upperLeftOfImage.y < 0) 7971 upperLeftOfImage.y = 0; 7972 7973 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 7974 } 7975 7976 /// 7977 Size textSize(in char[] text) { 7978 if(impl is null) return Size(0, 0); 7979 return impl.textSize(text); 7980 } 7981 7982 /++ 7983 Draws a string in the window with the set font (see [setFont] to change it). 7984 7985 Params: 7986 upperLeft = the upper left point of the bounding box of the text 7987 text = the string to draw 7988 lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound. 7989 alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags 7990 +/ 7991 @scriptable 7992 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 7993 if(impl is null) return; 7994 if(lowerRight.x != 0 || lowerRight.y != 0) { 7995 if(isClipped(upperLeft, lowerRight)) return; 7996 transform(lowerRight); 7997 } else { 7998 if(isClipped(upperLeft, textSize(text))) return; 7999 } 8000 transform(upperLeft); 8001 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 8002 } 8003 8004 /++ 8005 Draws text using a custom font. 8006 8007 This is still MAJOR work in progress. 8008 8009 Creating a [DrawableFont] can be tricky and require additional dependencies. 8010 +/ 8011 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 8012 if(impl is null) return; 8013 if(isClipped(upperLeft, Point(int.max, int.max))) return; 8014 transform(upperLeft); 8015 font.drawString(this, upperLeft, text); 8016 } 8017 8018 version(Windows) 8019 void drawText(Point upperLeft, scope const(wchar)[] text) { 8020 if(impl is null) return; 8021 if(isClipped(upperLeft, Point(int.max, int.max))) return; 8022 transform(upperLeft); 8023 8024 if(text.length && text[$-1] == '\n') 8025 text = text[0 .. $-1]; // tailing newlines are weird on windows... 8026 8027 TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length); 8028 } 8029 8030 static struct TextDrawingContext { 8031 Point boundingBoxUpperLeft; 8032 Point boundingBoxLowerRight; 8033 8034 Point currentLocation; 8035 8036 Point lastDrewUpperLeft; 8037 Point lastDrewLowerRight; 8038 8039 // how do i do right aligned rich text? 8040 // i kinda want to do a pre-made drawing then right align 8041 // draw the whole block. 8042 // 8043 // That's exactly the diff: inline vs block stuff. 8044 8045 // I need to get coordinates of an inline section out too, 8046 // not just a bounding box, but a series of bounding boxes 8047 // should be ok. Consider what's needed to detect a click 8048 // on a link in the middle of a paragraph breaking a line. 8049 // 8050 // Generally, we should be able to get the rectangles of 8051 // any portion we draw. 8052 // 8053 // It also needs to tell what text is left if it overflows 8054 // out of the box, so we can do stuff like float images around 8055 // it. It should not attempt to draw a letter that would be 8056 // clipped. 8057 // 8058 // I might also turn off word wrap stuff. 8059 } 8060 8061 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 8062 if(impl is null) return; 8063 // FIXME 8064 } 8065 8066 /// Drawing an individual pixel is slow. Avoid it if possible. 8067 void drawPixel(Point where) { 8068 if(impl is null) return; 8069 if(isClipped(where)) return; 8070 transform(where); 8071 impl.drawPixel(where.x, where.y); 8072 } 8073 8074 8075 /// Draws a pen using the current pen / outlineColor 8076 @scriptable 8077 void drawLine(Point starting, Point ending) { 8078 if(impl is null) return; 8079 if(isClipped(starting, ending)) return; 8080 transform(starting); 8081 transform(ending); 8082 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 8083 } 8084 8085 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 8086 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 8087 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 8088 @scriptable 8089 void drawRectangle(Point upperLeft, int width, int height) { 8090 if(impl is null) return; 8091 if(isClipped(upperLeft, width, height)) return; 8092 transform(upperLeft); 8093 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 8094 } 8095 8096 /// ditto 8097 void drawRectangle(Point upperLeft, Size size) { 8098 if(impl is null) return; 8099 if(isClipped(upperLeft, size.width, size.height)) return; 8100 transform(upperLeft); 8101 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 8102 } 8103 8104 /// ditto 8105 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 8106 if(impl is null) return; 8107 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 8108 transform(upperLeft); 8109 transform(lowerRightInclusive); 8110 impl.drawRectangle(upperLeft.x, upperLeft.y, 8111 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 8112 } 8113 8114 /// Arguments are the points of the bounding rectangle 8115 void drawEllipse(Point upperLeft, Point lowerRight) { 8116 if(impl is null) return; 8117 if(isClipped(upperLeft, lowerRight)) return; 8118 transform(upperLeft); 8119 transform(lowerRight); 8120 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 8121 } 8122 8123 /++ 8124 start and finish are units of degrees * 64 8125 +/ 8126 void drawArc(Point upperLeft, int width, int height, int start, int finish) { 8127 if(impl is null) return; 8128 // FIXME: not actually implemented 8129 if(isClipped(upperLeft, width, height)) return; 8130 transform(upperLeft); 8131 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish); 8132 } 8133 8134 //this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 8135 void drawCircle(Point upperLeft, int diameter) { 8136 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 8137 } 8138 8139 /// . 8140 void drawPolygon(Point[] vertexes) { 8141 if(impl is null) return; 8142 assert(vertexes.length); 8143 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 8144 foreach(ref vertex; vertexes) { 8145 if(vertex.x < minX) 8146 minX = vertex.x; 8147 if(vertex.y < minY) 8148 minY = vertex.y; 8149 if(vertex.x > maxX) 8150 maxX = vertex.x; 8151 if(vertex.y > maxY) 8152 maxY = vertex.y; 8153 transform(vertex); 8154 } 8155 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 8156 impl.drawPolygon(vertexes); 8157 } 8158 8159 /// ditto 8160 void drawPolygon(Point[] vertexes...) { 8161 if(impl is null) return; 8162 drawPolygon(vertexes); 8163 } 8164 8165 8166 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 8167 8168 //mixin NativeScreenPainterImplementation!() impl; 8169 8170 8171 // HACK: if I mixin the impl directly, it won't let me override the copy 8172 // constructor! The linker complains about there being multiple definitions. 8173 // I'll make the best of it and reference count it though. 8174 ScreenPainterImplementation* impl; 8175 } 8176 8177 // HACK: I need a pointer to the implementation so it's separate 8178 struct ScreenPainterImplementation { 8179 CapableOfBeingDrawnUpon window; 8180 int referenceCount; 8181 mixin NativeScreenPainterImplementation!(); 8182 } 8183 8184 // FIXME: i haven't actually tested the sprite class on MS Windows 8185 8186 /** 8187 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 8188 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 8189 8190 8191 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 8192 though I'm not sure that's ideal and the implementation might change. 8193 8194 You create one by giving a window and an image. It optimizes for that window, 8195 and copies the image into it to use as the initial picture. Creating a sprite 8196 can be quite slow (especially over a network connection) so you should do it 8197 as little as possible and just hold on to your sprite handles after making them. 8198 simpledisplay does try to do its best though, using the XSHM extension if available, 8199 but you should still write your code as if it will always be slow. 8200 8201 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 8202 a fast operation - much faster than drawing the Image itself every time. 8203 8204 `Sprite` represents a scarce resource which should be freed when you 8205 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 8206 after it has been disposed. If you are unsure about this, don't take chances, 8207 just let the garbage collector do it for you. But ideally, you can manage its 8208 lifetime more efficiently. 8209 8210 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 8211 support alpha blending in its drawing at this time. That might change in the 8212 future, but if you need alpha blending right now, use OpenGL instead. See 8213 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 8214 8215 FIXME: you are supposed to be able to draw on these similarly to on windows. 8216 ScreenPainter needs to be refactored to allow that though. So until that is 8217 done, consider a `Sprite` to have const contents. 8218 */ 8219 version(OSXCocoa) {} else // NotYetImplementedException 8220 class Sprite : CapableOfBeingDrawnUpon { 8221 8222 /// 8223 ScreenPainter draw() { 8224 return ScreenPainter(this, handle); 8225 } 8226 8227 /++ 8228 Copies the sprite's current state into a [TrueColorImage]. 8229 8230 Be warned: this can be a very slow operation 8231 8232 History: 8233 Actually implemented on March 14, 2021 8234 +/ 8235 TrueColorImage takeScreenshot() { 8236 return trueColorImageFromNativeHandle(handle, width, height); 8237 } 8238 8239 void delegate() paintingFinishedDg() { return null; } 8240 bool closed() { return false; } 8241 ScreenPainterImplementation* activeScreenPainter_; 8242 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 8243 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 8244 8245 version(Windows) 8246 private ubyte* rawData; 8247 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 8248 8249 this(SimpleWindow win, int width, int height) { 8250 this._width = width; 8251 this._height = height; 8252 8253 version(X11) { 8254 auto display = XDisplayConnection.get(); 8255 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 8256 } else version(Windows) { 8257 BITMAPINFO infoheader; 8258 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 8259 infoheader.bmiHeader.biWidth = width; 8260 infoheader.bmiHeader.biHeight = height; 8261 infoheader.bmiHeader.biPlanes = 1; 8262 infoheader.bmiHeader.biBitCount = 24; 8263 infoheader.bmiHeader.biCompression = BI_RGB; 8264 8265 // FIXME: this should prolly be a device dependent bitmap... 8266 handle = CreateDIBSection( 8267 null, 8268 &infoheader, 8269 DIB_RGB_COLORS, 8270 cast(void**) &rawData, 8271 null, 8272 0); 8273 8274 if(handle is null) 8275 throw new Exception("couldn't create pixmap"); 8276 } 8277 } 8278 8279 /// Makes a sprite based on the image with the initial contents from the Image 8280 this(SimpleWindow win, Image i) { 8281 this(win, i.width, i.height); 8282 8283 version(X11) { 8284 auto display = XDisplayConnection.get(); 8285 if(i.usingXshm) 8286 XShmPutImage(display, cast(Drawable) handle, DefaultGC(display, DefaultScreen(display)), i.handle, 0, 0, 0, 0, i.width, i.height, false); 8287 else 8288 XPutImage(display, cast(Drawable) handle, DefaultGC(display, DefaultScreen(display)), i.handle, 0, 0, 0, 0, i.width, i.height); 8289 } else version(Windows) { 8290 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 8291 auto arrLength = itemsPerLine * height; 8292 rawData[0..arrLength] = i.rawData[0..arrLength]; 8293 } else version(OSXCocoa) { 8294 // FIXME: I have no idea if this is even any good 8295 ubyte* rawData; 8296 8297 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 8298 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 8299 colorSpace, 8300 kCGImageAlphaPremultipliedLast 8301 |kCGBitmapByteOrder32Big); 8302 CGColorSpaceRelease(colorSpace); 8303 rawData = CGBitmapContextGetData(context); 8304 8305 auto rdl = (width * height * 4); 8306 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 8307 } else static assert(0); 8308 } 8309 8310 /++ 8311 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 8312 8313 Params: 8314 where = point on the window where the upper left corner of the image will be drawn 8315 imageUpperLeft = point on the image to start the slice to draw 8316 sliceSize = size of the slice of the image to draw on the window. If width or height is 0, it uses the entire width or height of the image. 8317 History: 8318 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 8319 +/ 8320 void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 8321 painter.drawPixmap(this, where, imageUpperLeft, sliceSize); 8322 } 8323 8324 8325 /// Call this when you're ready to get rid of it 8326 void dispose() { 8327 version(X11) { 8328 if(handle) 8329 XFreePixmap(XDisplayConnection.get(), handle); 8330 handle = None; 8331 } else version(Windows) { 8332 if(handle) 8333 DeleteObject(handle); 8334 handle = null; 8335 } else version(OSXCocoa) { 8336 if(context) 8337 CGContextRelease(context); 8338 context = null; 8339 } else static assert(0); 8340 8341 } 8342 8343 ~this() { 8344 dispose(); 8345 } 8346 8347 /// 8348 final @property int width() { return _width; } 8349 8350 /// 8351 final @property int height() { return _height; } 8352 8353 /// 8354 static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img) { 8355 return new Sprite(win, Image.fromMemoryImage(img)); 8356 } 8357 8358 private: 8359 8360 int _width; 8361 int _height; 8362 version(X11) 8363 Pixmap handle; 8364 else version(Windows) 8365 HBITMAP handle; 8366 else version(OSXCocoa) 8367 CGContextRef context; 8368 else static assert(0); 8369 } 8370 8371 /++ 8372 NOT IMPLEMENTED 8373 8374 A display-stored image optimized for relatively quick drawing, like 8375 [Sprite], but this one supports alpha channel blending and does NOT 8376 support direct drawing upon it with a [ScreenPainter]. 8377 8378 You can think of it as an [arsd.game.OpenGlTexture] for usage with a 8379 plain [ScreenPainter]... sort of. 8380 8381 On X11, it requires the Xrender extension and library. This is available 8382 almost everywhere though. 8383 8384 History: 8385 Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED 8386 +/ 8387 version(none) 8388 class AlphaSprite { 8389 /++ 8390 Copies the given image into it. 8391 +/ 8392 this(MemoryImage img) { 8393 8394 if(!XRenderLibrary.loadAttempted) { 8395 XRenderLibrary.loadDynamicLibrary(); 8396 8397 // FIXME: this needs to be reconstructed when the X server changes 8398 repopulateX(); 8399 } 8400 if(!XRenderLibrary.loadSuccessful) 8401 throw new Exception("XRender library load failure"); 8402 8403 // I probably need to put the alpha mask in a separate Picture 8404 // ugh 8405 // maybe the Sprite itself can have an alpha bitmask anyway 8406 8407 8408 auto display = XDisplayConnection.get(); 8409 pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 8410 8411 8412 XRenderPictureAttributes attrs; 8413 8414 handle = XRenderCreatePicture( 8415 XDisplayConnection.get, 8416 pixmap, 8417 RGBA, 8418 0, 8419 &attrs 8420 ); 8421 8422 } 8423 8424 // maybe i'll use the create gradient functions too with static factories.. 8425 8426 void drawAt(ScreenPainter painter, Point where) { 8427 //painter.drawPixmap(this, where); 8428 8429 XRenderPictureAttributes attrs; 8430 8431 auto pic = XRenderCreatePicture( 8432 XDisplayConnection.get, 8433 painter.impl.d, 8434 RGB, 8435 0, 8436 &attrs 8437 ); 8438 8439 XRenderComposite( 8440 XDisplayConnection.get, 8441 3, // PictOpOver 8442 handle, 8443 None, 8444 pic, 8445 0, // src 8446 0, 8447 0, // mask 8448 0, 8449 10, // dest 8450 10, 8451 100, // width 8452 100 8453 ); 8454 8455 /+ 8456 XRenderFreePicture( 8457 XDisplayConnection.get, 8458 pic 8459 ); 8460 8461 XRenderFreePicture( 8462 XDisplayConnection.get, 8463 fill 8464 ); 8465 +/ 8466 // on Windows you can stretch but Xrender still can't :( 8467 } 8468 8469 static XRenderPictFormat* RGB; 8470 static XRenderPictFormat* RGBA; 8471 static void repopulateX() { 8472 auto display = XDisplayConnection.get; 8473 RGB = XRenderFindStandardFormat(display, PictStandardRGB24); 8474 RGBA = XRenderFindStandardFormat(display, PictStandardARGB24); 8475 } 8476 8477 XPixmap pixmap; 8478 Picture handle; 8479 } 8480 8481 /// 8482 interface CapableOfBeingDrawnUpon { 8483 /// 8484 ScreenPainter draw(); 8485 /// 8486 int width(); 8487 /// 8488 int height(); 8489 protected ScreenPainterImplementation* activeScreenPainter(); 8490 protected void activeScreenPainter(ScreenPainterImplementation*); 8491 bool closed(); 8492 8493 void delegate() paintingFinishedDg(); 8494 8495 /// Be warned: this can be a very slow operation 8496 TrueColorImage takeScreenshot(); 8497 } 8498 8499 /// Flushes any pending gui buffers. Necessary if you are using with_eventloop with X - flush after you create your windows but before you call loop() 8500 void flushGui() { 8501 version(X11) { 8502 auto dpy = XDisplayConnection.get(); 8503 XLockDisplay(dpy); 8504 scope(exit) XUnlockDisplay(dpy); 8505 XFlush(dpy); 8506 } 8507 } 8508 8509 /++ 8510 Runs the given code in the GUI thread when its event loop 8511 is available, blocking until it completes. This allows you 8512 to create and manipulate windows from another thread without 8513 invoking undefined behavior. 8514 8515 If this is the gui thread, it runs the code immediately. 8516 8517 If no gui thread exists yet, the current thread is assumed 8518 to be it. Attempting to create windows or run the event loop 8519 in any other thread will cause an assertion failure. 8520 8521 8522 $(TIP 8523 Did you know you can use UFCS on delegate literals? 8524 8525 () { 8526 // code here 8527 }.runInGuiThread; 8528 ) 8529 8530 Returns: 8531 `true` if the function was called, `false` if it was not. 8532 The function may not be called because the gui thread had 8533 already terminated by the time you called this. 8534 8535 History: 8536 Added April 10, 2020 (v7.2.0) 8537 8538 Return value added and implementation tweaked to avoid locking 8539 at program termination on February 24, 2021 (v9.2.1). 8540 +/ 8541 bool runInGuiThread(scope void delegate() dg) @trusted { 8542 claimGuiThread(); 8543 8544 if(thisIsGuiThread) { 8545 dg(); 8546 return true; 8547 } 8548 8549 if(guiThreadTerminating) 8550 return false; 8551 8552 import core.sync.semaphore; 8553 static Semaphore sc; 8554 if(sc is null) 8555 sc = new Semaphore(); 8556 8557 static RunQueueMember* rqm; 8558 if(rqm is null) 8559 rqm = new RunQueueMember; 8560 rqm.dg = cast(typeof(rqm.dg)) dg; 8561 rqm.signal = sc; 8562 rqm.thrown = null; 8563 8564 synchronized(runInGuiThreadLock) { 8565 runInGuiThreadQueue ~= rqm; 8566 } 8567 8568 if(!SimpleWindow.eventWakeUp()) 8569 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 8570 8571 rqm.signal.wait(); 8572 auto t = rqm.thrown; 8573 8574 if(t) 8575 throw t; 8576 8577 return true; 8578 } 8579 8580 private void runPendingRunInGuiThreadDelegates() { 8581 more: 8582 RunQueueMember* next; 8583 synchronized(runInGuiThreadLock) { 8584 if(runInGuiThreadQueue.length) { 8585 next = runInGuiThreadQueue[0]; 8586 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 8587 } else { 8588 next = null; 8589 } 8590 } 8591 8592 if(next) { 8593 try { 8594 next.dg(); 8595 next.thrown = null; 8596 } catch(Throwable t) { 8597 next.thrown = t; 8598 } 8599 8600 next.signal.notify(); 8601 8602 goto more; 8603 } 8604 } 8605 8606 private void claimGuiThread() { 8607 import core.atomic; 8608 if(cas(&guiThreadExists, false, true)) 8609 thisIsGuiThread = true; 8610 } 8611 8612 private struct RunQueueMember { 8613 void delegate() dg; 8614 import core.sync.semaphore; 8615 Semaphore signal; 8616 Throwable thrown; 8617 } 8618 8619 private __gshared RunQueueMember*[] runInGuiThreadQueue; 8620 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE 8621 private bool thisIsGuiThread = false; 8622 private shared bool guiThreadExists = false; 8623 private shared bool guiThreadTerminating = false; 8624 8625 private void guiThreadFinalize() { 8626 assert(thisIsGuiThread); 8627 8628 guiThreadTerminating = true; // don't add any more from this point on 8629 runPendingRunInGuiThreadDelegates(); 8630 } 8631 8632 /+ 8633 interface IPromise { 8634 void reportProgress(int current, int max, string message); 8635 8636 /+ // not formally in cuz of templates but still 8637 IPromise Then(); 8638 IPromise Catch(); 8639 IPromise Finally(); 8640 +/ 8641 } 8642 8643 /+ 8644 auto promise = async({ ... }); 8645 promise.Then(whatever). 8646 Then(whateverelse). 8647 Catch((exception) { }); 8648 8649 8650 A promise is run inside a fiber and it looks something like: 8651 8652 try { 8653 auto res = whatever(); 8654 auto res2 = whateverelse(res); 8655 } catch(Exception e) { 8656 { }(e); 8657 } 8658 8659 When a thing succeeds, it is passed as an arg to the next 8660 +/ 8661 class Promise(T) : IPromise { 8662 auto Then() { return null; } 8663 auto Catch() { return null; } 8664 auto Finally() { return null; } 8665 8666 // wait for it to resolve and return the value, or rethrow the error if that occurred. 8667 // cannot be called from the gui thread, but this is caught at runtime instead of compile time. 8668 T await(); 8669 } 8670 8671 interface Task { 8672 } 8673 8674 interface Resolvable(T) : Task { 8675 void run(); 8676 8677 void resolve(T); 8678 8679 Resolvable!T then(void delegate(T)); // returns a new promise 8680 Resolvable!T error(Throwable); // js catch 8681 Resolvable!T completed(); // js finally 8682 8683 } 8684 8685 /++ 8686 Runs `work` in a helper thread and sends its return value back to the main gui 8687 thread as the argument to `uponCompletion`. If `work` throws, the exception is 8688 sent to the `uponThrown` if given, or if null, rethrown from the event loop to 8689 kill the program. 8690 8691 You can call reportProgress(position, max, message) to update your parent window 8692 on your progress. 8693 8694 I should also use `shared` methods. FIXME 8695 8696 History: 8697 Added March 6, 2021 (dub version 9.3). 8698 +/ 8699 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) { 8700 uponCompletion(work(null)); 8701 } 8702 8703 +/ 8704 8705 /// Used internal to dispatch events to various classes. 8706 interface CapableOfHandlingNativeEvent { 8707 NativeEventHandler getNativeEventHandler(); 8708 8709 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 8710 8711 version(X11) { 8712 // if this is impossible, you are allowed to just throw from it 8713 // Note: if you call it from another object, set a flag cuz the manger will call you again 8714 void recreateAfterDisconnect(); 8715 // discard any *connection specific* state, but keep enough that you 8716 // can be recreated if possible. discardConnectionState() is always called immediately 8717 // before recreateAfterDisconnect(), so you can set a flag there to decide if 8718 // you need initialization order 8719 void discardConnectionState(); 8720 } 8721 } 8722 8723 version(X11) 8724 /++ 8725 State of keys on mouse events, especially motion. 8726 8727 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 8728 +/ 8729 enum ModifierState : uint { 8730 shift = 1, /// 8731 capsLock = 2, /// 8732 ctrl = 4, /// 8733 alt = 8, /// Not always available on Windows 8734 windows = 64, /// ditto 8735 numLock = 16, /// 8736 8737 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 8738 middleButtonDown = 512, /// ditto 8739 rightButtonDown = 1024, /// ditto 8740 } 8741 else version(Windows) 8742 /// ditto 8743 enum ModifierState : uint { 8744 shift = 4, /// 8745 ctrl = 8, /// 8746 8747 // i'm not sure if the next two are available 8748 alt = 256, /// not always available on Windows 8749 windows = 512, /// ditto 8750 8751 capsLock = 1024, /// 8752 numLock = 2048, /// 8753 8754 leftButtonDown = 1, /// not available on key events 8755 middleButtonDown = 16, /// ditto 8756 rightButtonDown = 2, /// ditto 8757 8758 backButtonDown = 0x20, /// not available on X 8759 forwardButtonDown = 0x40, /// ditto 8760 } 8761 else version(OSXCocoa) 8762 // FIXME FIXME NotYetImplementedException 8763 enum ModifierState : uint { 8764 shift = 1, /// 8765 capsLock = 2, /// 8766 ctrl = 4, /// 8767 alt = 8, /// Not always available on Windows 8768 windows = 64, /// ditto 8769 numLock = 16, /// 8770 8771 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 8772 middleButtonDown = 512, /// ditto 8773 rightButtonDown = 1024, /// ditto 8774 } 8775 8776 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them 8777 enum MouseButton : int { 8778 none = 0, 8779 left = 1, /// 8780 right = 2, /// 8781 middle = 4, /// 8782 wheelUp = 8, /// 8783 wheelDown = 16, /// 8784 backButton = 32, /// often found on the thumb and used for back in browsers 8785 forwardButton = 64, /// often found on the thumb and used for forward in browsers 8786 } 8787 8788 version(X11) { 8789 // FIXME: match ASCII whenever we can. Most of it is already there, 8790 // but there's a few exceptions and mismatches with Windows 8791 8792 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 8793 enum Key { 8794 Escape = 0xff1b, /// 8795 F1 = 0xffbe, /// 8796 F2 = 0xffbf, /// 8797 F3 = 0xffc0, /// 8798 F4 = 0xffc1, /// 8799 F5 = 0xffc2, /// 8800 F6 = 0xffc3, /// 8801 F7 = 0xffc4, /// 8802 F8 = 0xffc5, /// 8803 F9 = 0xffc6, /// 8804 F10 = 0xffc7, /// 8805 F11 = 0xffc8, /// 8806 F12 = 0xffc9, /// 8807 PrintScreen = 0xff61, /// 8808 ScrollLock = 0xff14, /// 8809 Pause = 0xff13, /// 8810 Grave = 0x60, /// The $(BACKTICK) ~ key 8811 // number keys across the top of the keyboard 8812 N1 = 0x31, /// Number key atop the keyboard 8813 N2 = 0x32, /// 8814 N3 = 0x33, /// 8815 N4 = 0x34, /// 8816 N5 = 0x35, /// 8817 N6 = 0x36, /// 8818 N7 = 0x37, /// 8819 N8 = 0x38, /// 8820 N9 = 0x39, /// 8821 N0 = 0x30, /// 8822 Dash = 0x2d, /// 8823 Equals = 0x3d, /// 8824 Backslash = 0x5c, /// The \ | key 8825 Backspace = 0xff08, /// 8826 Insert = 0xff63, /// 8827 Home = 0xff50, /// 8828 PageUp = 0xff55, /// 8829 Delete = 0xffff, /// 8830 End = 0xff57, /// 8831 PageDown = 0xff56, /// 8832 Up = 0xff52, /// 8833 Down = 0xff54, /// 8834 Left = 0xff51, /// 8835 Right = 0xff53, /// 8836 8837 Tab = 0xff09, /// 8838 Q = 0x71, /// 8839 W = 0x77, /// 8840 E = 0x65, /// 8841 R = 0x72, /// 8842 T = 0x74, /// 8843 Y = 0x79, /// 8844 U = 0x75, /// 8845 I = 0x69, /// 8846 O = 0x6f, /// 8847 P = 0x70, /// 8848 LeftBracket = 0x5b, /// the [ { key 8849 RightBracket = 0x5d, /// the ] } key 8850 CapsLock = 0xffe5, /// 8851 A = 0x61, /// 8852 S = 0x73, /// 8853 D = 0x64, /// 8854 F = 0x66, /// 8855 G = 0x67, /// 8856 H = 0x68, /// 8857 J = 0x6a, /// 8858 K = 0x6b, /// 8859 L = 0x6c, /// 8860 Semicolon = 0x3b, /// 8861 Apostrophe = 0x27, /// 8862 Enter = 0xff0d, /// 8863 Shift = 0xffe1, /// 8864 Z = 0x7a, /// 8865 X = 0x78, /// 8866 C = 0x63, /// 8867 V = 0x76, /// 8868 B = 0x62, /// 8869 N = 0x6e, /// 8870 M = 0x6d, /// 8871 Comma = 0x2c, /// 8872 Period = 0x2e, /// 8873 Slash = 0x2f, /// the / ? key 8874 Shift_r = 0xffe2, /// Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it. If it is supported though, it is the right Shift key, as opposed to the left Shift key 8875 Ctrl = 0xffe3, /// 8876 Windows = 0xffeb, /// 8877 Alt = 0xffe9, /// 8878 Space = 0x20, /// 8879 Alt_r = 0xffea, /// ditto of shift_r 8880 Windows_r = 0xffec, /// 8881 Menu = 0xff67, /// 8882 Ctrl_r = 0xffe4, /// 8883 8884 NumLock = 0xff7f, /// 8885 Divide = 0xffaf, /// The / key on the number pad 8886 Multiply = 0xffaa, /// The * key on the number pad 8887 Minus = 0xffad, /// The - key on the number pad 8888 Plus = 0xffab, /// The + key on the number pad 8889 PadEnter = 0xff8d, /// Numberpad enter key 8890 Pad1 = 0xff9c, /// Numberpad keys 8891 Pad2 = 0xff99, /// 8892 Pad3 = 0xff9b, /// 8893 Pad4 = 0xff96, /// 8894 Pad5 = 0xff9d, /// 8895 Pad6 = 0xff98, /// 8896 Pad7 = 0xff95, /// 8897 Pad8 = 0xff97, /// 8898 Pad9 = 0xff9a, /// 8899 Pad0 = 0xff9e, /// 8900 PadDot = 0xff9f, /// 8901 } 8902 } else version(Windows) { 8903 // the character here is for en-us layouts and for illustration only 8904 // if you actually want to get characters, wait for character events 8905 // (the argument to your event handler is simply a dchar) 8906 // those will be converted by the OS for the right locale. 8907 8908 enum Key { 8909 Escape = 0x1b, 8910 F1 = 0x70, 8911 F2 = 0x71, 8912 F3 = 0x72, 8913 F4 = 0x73, 8914 F5 = 0x74, 8915 F6 = 0x75, 8916 F7 = 0x76, 8917 F8 = 0x77, 8918 F9 = 0x78, 8919 F10 = 0x79, 8920 F11 = 0x7a, 8921 F12 = 0x7b, 8922 PrintScreen = 0x2c, 8923 ScrollLock = 0x91, 8924 Pause = 0x13, 8925 Grave = 0xc0, 8926 // number keys across the top of the keyboard 8927 N1 = 0x31, 8928 N2 = 0x32, 8929 N3 = 0x33, 8930 N4 = 0x34, 8931 N5 = 0x35, 8932 N6 = 0x36, 8933 N7 = 0x37, 8934 N8 = 0x38, 8935 N9 = 0x39, 8936 N0 = 0x30, 8937 Dash = 0xbd, 8938 Equals = 0xbb, 8939 Backslash = 0xdc, 8940 Backspace = 0x08, 8941 Insert = 0x2d, 8942 Home = 0x24, 8943 PageUp = 0x21, 8944 Delete = 0x2e, 8945 End = 0x23, 8946 PageDown = 0x22, 8947 Up = 0x26, 8948 Down = 0x28, 8949 Left = 0x25, 8950 Right = 0x27, 8951 8952 Tab = 0x09, 8953 Q = 0x51, 8954 W = 0x57, 8955 E = 0x45, 8956 R = 0x52, 8957 T = 0x54, 8958 Y = 0x59, 8959 U = 0x55, 8960 I = 0x49, 8961 O = 0x4f, 8962 P = 0x50, 8963 LeftBracket = 0xdb, 8964 RightBracket = 0xdd, 8965 CapsLock = 0x14, 8966 A = 0x41, 8967 S = 0x53, 8968 D = 0x44, 8969 F = 0x46, 8970 G = 0x47, 8971 H = 0x48, 8972 J = 0x4a, 8973 K = 0x4b, 8974 L = 0x4c, 8975 Semicolon = 0xba, 8976 Apostrophe = 0xde, 8977 Enter = 0x0d, 8978 Shift = 0x10, 8979 Z = 0x5a, 8980 X = 0x58, 8981 C = 0x43, 8982 V = 0x56, 8983 B = 0x42, 8984 N = 0x4e, 8985 M = 0x4d, 8986 Comma = 0xbc, 8987 Period = 0xbe, 8988 Slash = 0xbf, 8989 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 8990 Ctrl = 0x11, 8991 Windows = 0x5b, 8992 Alt = -5, // FIXME 8993 Space = 0x20, 8994 Alt_r = 0xffea, // ditto of shift_r 8995 Windows_r = 0x5c, // ditto of shift_r 8996 Menu = 0x5d, 8997 Ctrl_r = 0xa3, // ditto of shift_r 8998 8999 NumLock = 0x90, 9000 Divide = 0x6f, 9001 Multiply = 0x6a, 9002 Minus = 0x6d, 9003 Plus = 0x6b, 9004 PadEnter = -8, // FIXME 9005 Pad1 = 0x61, 9006 Pad2 = 0x62, 9007 Pad3 = 0x63, 9008 Pad4 = 0x64, 9009 Pad5 = 0x65, 9010 Pad6 = 0x66, 9011 Pad7 = 0x67, 9012 Pad8 = 0x68, 9013 Pad9 = 0x69, 9014 Pad0 = 0x60, 9015 PadDot = 0x6e, 9016 } 9017 9018 // I'm keeping this around for reference purposes 9019 // ideally all these buttons will be listed for all platforms, 9020 // but now now I'm just focusing on my US keyboard 9021 version(none) 9022 enum Key { 9023 LBUTTON = 0x01, 9024 RBUTTON = 0x02, 9025 CANCEL = 0x03, 9026 MBUTTON = 0x04, 9027 //static if (_WIN32_WINNT > = 0x500) { 9028 XBUTTON1 = 0x05, 9029 XBUTTON2 = 0x06, 9030 //} 9031 BACK = 0x08, 9032 TAB = 0x09, 9033 CLEAR = 0x0C, 9034 RETURN = 0x0D, 9035 SHIFT = 0x10, 9036 CONTROL = 0x11, 9037 MENU = 0x12, 9038 PAUSE = 0x13, 9039 CAPITAL = 0x14, 9040 KANA = 0x15, 9041 HANGEUL = 0x15, 9042 HANGUL = 0x15, 9043 JUNJA = 0x17, 9044 FINAL = 0x18, 9045 HANJA = 0x19, 9046 KANJI = 0x19, 9047 ESCAPE = 0x1B, 9048 CONVERT = 0x1C, 9049 NONCONVERT = 0x1D, 9050 ACCEPT = 0x1E, 9051 MODECHANGE = 0x1F, 9052 SPACE = 0x20, 9053 PRIOR = 0x21, 9054 NEXT = 0x22, 9055 END = 0x23, 9056 HOME = 0x24, 9057 LEFT = 0x25, 9058 UP = 0x26, 9059 RIGHT = 0x27, 9060 DOWN = 0x28, 9061 SELECT = 0x29, 9062 PRINT = 0x2A, 9063 EXECUTE = 0x2B, 9064 SNAPSHOT = 0x2C, 9065 INSERT = 0x2D, 9066 DELETE = 0x2E, 9067 HELP = 0x2F, 9068 LWIN = 0x5B, 9069 RWIN = 0x5C, 9070 APPS = 0x5D, 9071 SLEEP = 0x5F, 9072 NUMPAD0 = 0x60, 9073 NUMPAD1 = 0x61, 9074 NUMPAD2 = 0x62, 9075 NUMPAD3 = 0x63, 9076 NUMPAD4 = 0x64, 9077 NUMPAD5 = 0x65, 9078 NUMPAD6 = 0x66, 9079 NUMPAD7 = 0x67, 9080 NUMPAD8 = 0x68, 9081 NUMPAD9 = 0x69, 9082 MULTIPLY = 0x6A, 9083 ADD = 0x6B, 9084 SEPARATOR = 0x6C, 9085 SUBTRACT = 0x6D, 9086 DECIMAL = 0x6E, 9087 DIVIDE = 0x6F, 9088 F1 = 0x70, 9089 F2 = 0x71, 9090 F3 = 0x72, 9091 F4 = 0x73, 9092 F5 = 0x74, 9093 F6 = 0x75, 9094 F7 = 0x76, 9095 F8 = 0x77, 9096 F9 = 0x78, 9097 F10 = 0x79, 9098 F11 = 0x7A, 9099 F12 = 0x7B, 9100 F13 = 0x7C, 9101 F14 = 0x7D, 9102 F15 = 0x7E, 9103 F16 = 0x7F, 9104 F17 = 0x80, 9105 F18 = 0x81, 9106 F19 = 0x82, 9107 F20 = 0x83, 9108 F21 = 0x84, 9109 F22 = 0x85, 9110 F23 = 0x86, 9111 F24 = 0x87, 9112 NUMLOCK = 0x90, 9113 SCROLL = 0x91, 9114 LSHIFT = 0xA0, 9115 RSHIFT = 0xA1, 9116 LCONTROL = 0xA2, 9117 RCONTROL = 0xA3, 9118 LMENU = 0xA4, 9119 RMENU = 0xA5, 9120 //static if (_WIN32_WINNT > = 0x500) { 9121 BROWSER_BACK = 0xA6, 9122 BROWSER_FORWARD = 0xA7, 9123 BROWSER_REFRESH = 0xA8, 9124 BROWSER_STOP = 0xA9, 9125 BROWSER_SEARCH = 0xAA, 9126 BROWSER_FAVORITES = 0xAB, 9127 BROWSER_HOME = 0xAC, 9128 VOLUME_MUTE = 0xAD, 9129 VOLUME_DOWN = 0xAE, 9130 VOLUME_UP = 0xAF, 9131 MEDIA_NEXT_TRACK = 0xB0, 9132 MEDIA_PREV_TRACK = 0xB1, 9133 MEDIA_STOP = 0xB2, 9134 MEDIA_PLAY_PAUSE = 0xB3, 9135 LAUNCH_MAIL = 0xB4, 9136 LAUNCH_MEDIA_SELECT = 0xB5, 9137 LAUNCH_APP1 = 0xB6, 9138 LAUNCH_APP2 = 0xB7, 9139 //} 9140 OEM_1 = 0xBA, 9141 //static if (_WIN32_WINNT > = 0x500) { 9142 OEM_PLUS = 0xBB, 9143 OEM_COMMA = 0xBC, 9144 OEM_MINUS = 0xBD, 9145 OEM_PERIOD = 0xBE, 9146 //} 9147 OEM_2 = 0xBF, 9148 OEM_3 = 0xC0, 9149 OEM_4 = 0xDB, 9150 OEM_5 = 0xDC, 9151 OEM_6 = 0xDD, 9152 OEM_7 = 0xDE, 9153 OEM_8 = 0xDF, 9154 //static if (_WIN32_WINNT > = 0x500) { 9155 OEM_102 = 0xE2, 9156 //} 9157 PROCESSKEY = 0xE5, 9158 //static if (_WIN32_WINNT > = 0x500) { 9159 PACKET = 0xE7, 9160 //} 9161 ATTN = 0xF6, 9162 CRSEL = 0xF7, 9163 EXSEL = 0xF8, 9164 EREOF = 0xF9, 9165 PLAY = 0xFA, 9166 ZOOM = 0xFB, 9167 NONAME = 0xFC, 9168 PA1 = 0xFD, 9169 OEM_CLEAR = 0xFE, 9170 } 9171 9172 } else version(OSXCocoa) { 9173 // FIXME 9174 enum Key { 9175 Escape = 0x1b, 9176 F1 = 0x70, 9177 F2 = 0x71, 9178 F3 = 0x72, 9179 F4 = 0x73, 9180 F5 = 0x74, 9181 F6 = 0x75, 9182 F7 = 0x76, 9183 F8 = 0x77, 9184 F9 = 0x78, 9185 F10 = 0x79, 9186 F11 = 0x7a, 9187 F12 = 0x7b, 9188 PrintScreen = 0x2c, 9189 ScrollLock = -2, // FIXME 9190 Pause = -3, // FIXME 9191 Grave = 0xc0, 9192 // number keys across the top of the keyboard 9193 N1 = 0x31, 9194 N2 = 0x32, 9195 N3 = 0x33, 9196 N4 = 0x34, 9197 N5 = 0x35, 9198 N6 = 0x36, 9199 N7 = 0x37, 9200 N8 = 0x38, 9201 N9 = 0x39, 9202 N0 = 0x30, 9203 Dash = 0xbd, 9204 Equals = 0xbb, 9205 Backslash = 0xdc, 9206 Backspace = 0x08, 9207 Insert = 0x2d, 9208 Home = 0x24, 9209 PageUp = 0x21, 9210 Delete = 0x2e, 9211 End = 0x23, 9212 PageDown = 0x22, 9213 Up = 0x26, 9214 Down = 0x28, 9215 Left = 0x25, 9216 Right = 0x27, 9217 9218 Tab = 0x09, 9219 Q = 0x51, 9220 W = 0x57, 9221 E = 0x45, 9222 R = 0x52, 9223 T = 0x54, 9224 Y = 0x59, 9225 U = 0x55, 9226 I = 0x49, 9227 O = 0x4f, 9228 P = 0x50, 9229 LeftBracket = 0xdb, 9230 RightBracket = 0xdd, 9231 CapsLock = 0x14, 9232 A = 0x41, 9233 S = 0x53, 9234 D = 0x44, 9235 F = 0x46, 9236 G = 0x47, 9237 H = 0x48, 9238 J = 0x4a, 9239 K = 0x4b, 9240 L = 0x4c, 9241 Semicolon = 0xba, 9242 Apostrophe = 0xde, 9243 Enter = 0x0d, 9244 Shift = 0x10, 9245 Z = 0x5a, 9246 X = 0x58, 9247 C = 0x43, 9248 V = 0x56, 9249 B = 0x42, 9250 N = 0x4e, 9251 M = 0x4d, 9252 Comma = 0xbc, 9253 Period = 0xbe, 9254 Slash = 0xbf, 9255 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 9256 Ctrl = 0x11, 9257 Windows = 0x5b, 9258 Alt = -5, // FIXME 9259 Space = 0x20, 9260 Alt_r = 0xffea, // ditto of shift_r 9261 Windows_r = -6, // FIXME 9262 Menu = 0x5d, 9263 Ctrl_r = -7, // FIXME 9264 9265 NumLock = 0x90, 9266 Divide = 0x6f, 9267 Multiply = 0x6a, 9268 Minus = 0x6d, 9269 Plus = 0x6b, 9270 PadEnter = -8, // FIXME 9271 // FIXME for the rest of these: 9272 Pad1 = 0xff9c, 9273 Pad2 = 0xff99, 9274 Pad3 = 0xff9b, 9275 Pad4 = 0xff96, 9276 Pad5 = 0xff9d, 9277 Pad6 = 0xff98, 9278 Pad7 = 0xff95, 9279 Pad8 = 0xff97, 9280 Pad9 = 0xff9a, 9281 Pad0 = 0xff9e, 9282 PadDot = 0xff9f, 9283 } 9284 9285 } 9286 9287 /* Additional utilities */ 9288 9289 9290 Color fromHsl(real h, real s, real l) { 9291 return arsd.color.fromHsl([h,s,l]); 9292 } 9293 9294 9295 9296 /* ********** What follows is the system-specific implementations *********/ 9297 version(Windows) { 9298 9299 9300 // helpers for making HICONs from MemoryImages 9301 class WindowsIcon { 9302 struct Win32Icon(int colorCount) { 9303 align(1): 9304 uint biSize; 9305 int biWidth; 9306 int biHeight; 9307 ushort biPlanes; 9308 ushort biBitCount; 9309 uint biCompression; 9310 uint biSizeImage; 9311 int biXPelsPerMeter; 9312 int biYPelsPerMeter; 9313 uint biClrUsed; 9314 uint biClrImportant; 9315 RGBQUAD[colorCount] biColors; 9316 /* Pixels: 9317 Uint8 pixels[] 9318 */ 9319 /* Mask: 9320 Uint8 mask[] 9321 */ 9322 9323 ubyte[4096] data; 9324 9325 void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 9326 width = mi.width; 9327 height = mi.height; 9328 9329 auto indexedImage = cast(IndexedImage) mi; 9330 if(indexedImage is null) 9331 indexedImage = quantize(mi.getAsTrueColorImage()); 9332 9333 assert(width %8 == 0); // i don't want padding nor do i want the and mask to get fancy 9334 assert(height %4 == 0); 9335 9336 int icon_plen = height*((width+3)&~3); 9337 int icon_mlen = height*((((width+7)/8)+3)&~3); 9338 icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 9339 9340 biSize = 40; 9341 biWidth = width; 9342 biHeight = height*2; 9343 biPlanes = 1; 9344 biBitCount = 8; 9345 biSizeImage = icon_plen+icon_mlen; 9346 9347 int offset = 0; 9348 int andOff = icon_plen * 8; // the and offset is in bits 9349 for(int y = height - 1; y >= 0; y--) { 9350 int off2 = y * width; 9351 foreach(x; 0 .. width) { 9352 const b = indexedImage.data[off2 + x]; 9353 data[offset] = b; 9354 offset++; 9355 9356 const andBit = andOff % 8; 9357 const andIdx = andOff / 8; 9358 assert(b < indexedImage.palette.length); 9359 // this is anded to the destination, since and 0 means erase, 9360 // we want that to be opaque, and 1 for transparent 9361 auto transparent = (indexedImage.palette[b].a <= 127); 9362 data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0); 9363 9364 andOff++; 9365 } 9366 9367 andOff += andOff % 32; 9368 } 9369 9370 foreach(idx, entry; indexedImage.palette) { 9371 if(entry.a > 127) { 9372 biColors[idx].rgbBlue = entry.b; 9373 biColors[idx].rgbGreen = entry.g; 9374 biColors[idx].rgbRed = entry.r; 9375 } else { 9376 biColors[idx].rgbBlue = 255; 9377 biColors[idx].rgbGreen = 255; 9378 biColors[idx].rgbRed = 255; 9379 } 9380 } 9381 9382 /* 9383 data[0..icon_plen] = getFlippedUnfilteredDatastream(png); 9384 data[icon_plen..icon_plen+icon_mlen] = getANDMask(png); 9385 //icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0); 9386 auto pngMap = fetchPaletteWin32(png); 9387 biColors[0..pngMap.length] = pngMap[]; 9388 */ 9389 } 9390 } 9391 9392 9393 Win32Icon!(256) icon_win32; 9394 9395 9396 this(MemoryImage mi) { 9397 int icon_len, width, height; 9398 9399 icon_win32.fromMemoryImage(mi, icon_len, width, height); 9400 9401 /* 9402 PNG* png = readPnpngData); 9403 PNGHeader pngh = getHeader(png); 9404 void* icon_win32; 9405 if(pngh.depth == 4) { 9406 auto i = new Win32Icon!(16); 9407 i.fromPNG(png, pngh, icon_len, width, height); 9408 icon_win32 = i; 9409 } 9410 else if(pngh.depth == 8) { 9411 auto i = new Win32Icon!(256); 9412 i.fromPNG(png, pngh, icon_len, width, height); 9413 icon_win32 = i; 9414 } else assert(0); 9415 */ 9416 9417 hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0); 9418 9419 if(hIcon is null) throw new Exception("CreateIconFromResourceEx"); 9420 } 9421 9422 ~this() { 9423 DestroyIcon(hIcon); 9424 } 9425 9426 HICON hIcon; 9427 } 9428 9429 9430 9431 9432 9433 9434 alias int delegate(HWND, UINT, WPARAM, LPARAM) NativeEventHandler; 9435 alias HWND NativeWindowHandle; 9436 9437 extern(Windows) 9438 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 9439 try { 9440 if(SimpleWindow.handleNativeGlobalEvent !is null) { 9441 // it returns zero if the message is handled, so we won't do anything more there 9442 // do I like that though? 9443 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam); 9444 if(ret == 0) 9445 return ret; 9446 } 9447 9448 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 9449 if(window.getNativeEventHandler !is null) { 9450 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam); 9451 if(ret == 0) 9452 return ret; 9453 } 9454 if(auto w = cast(SimpleWindow) (*window)) 9455 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 9456 else 9457 return DefWindowProc(hWnd, iMessage, wParam, lParam); 9458 } else { 9459 return DefWindowProc(hWnd, iMessage, wParam, lParam); 9460 } 9461 } catch (Exception e) { 9462 try { 9463 sdpy_abort(e); 9464 return 0; 9465 } catch(Exception e) { assert(0); } 9466 } 9467 } 9468 9469 void sdpy_abort(Throwable e) nothrow { 9470 try 9471 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 9472 catch(Exception e) 9473 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 9474 ExitProcess(1); 9475 } 9476 9477 mixin template NativeScreenPainterImplementation() { 9478 HDC hdc; 9479 HWND hwnd; 9480 //HDC windowHdc; 9481 HBITMAP oldBmp; 9482 9483 void create(NativeWindowHandle window) { 9484 hwnd = window; 9485 9486 if(auto sw = cast(SimpleWindow) this.window) { 9487 // drawing on a window, double buffer 9488 auto windowHdc = GetDC(hwnd); 9489 9490 auto buffer = sw.impl.buffer; 9491 if(buffer is null) { 9492 hdc = windowHdc; 9493 windowDc = true; 9494 } else { 9495 hdc = CreateCompatibleDC(windowHdc); 9496 9497 ReleaseDC(hwnd, windowHdc); 9498 9499 oldBmp = SelectObject(hdc, buffer); 9500 } 9501 } else { 9502 // drawing on something else, draw directly 9503 hdc = CreateCompatibleDC(null); 9504 SelectObject(hdc, window); 9505 9506 } 9507 9508 // X doesn't draw a text background, so neither should we 9509 SetBkMode(hdc, TRANSPARENT); 9510 9511 ensureDefaultFontLoaded(); 9512 9513 if(defaultGuiFont) { 9514 SelectObject(hdc, defaultGuiFont); 9515 // DeleteObject(defaultGuiFont); 9516 } 9517 } 9518 9519 static HFONT defaultGuiFont; 9520 static void ensureDefaultFontLoaded() { 9521 static bool triedDefaultGuiFont = false; 9522 if(!triedDefaultGuiFont) { 9523 NONCLIENTMETRICS params; 9524 params.cbSize = params.sizeof; 9525 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 9526 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 9527 } 9528 triedDefaultGuiFont = true; 9529 } 9530 } 9531 9532 void setFont(OperatingSystemFont font) { 9533 if(font && font.font) { 9534 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 9535 // error... how to handle tho? 9536 } 9537 } 9538 else if(defaultGuiFont) 9539 SelectObject(hdc, defaultGuiFont); 9540 } 9541 9542 arsd.color.Rectangle _clipRectangle; 9543 9544 void setClipRectangle(int x, int y, int width, int height) { 9545 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 9546 9547 if(width == 0 || height == 0) { 9548 SelectClipRgn(hdc, null); 9549 } else { 9550 auto region = CreateRectRgn(x, y, x + width, y + height); 9551 SelectClipRgn(hdc, region); 9552 DeleteObject(region); 9553 } 9554 } 9555 9556 9557 // just because we can on Windows... 9558 //void create(Image image); 9559 9560 void dispose() { 9561 // FIXME: this.window.width/height is probably wrong 9562 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 9563 // ReleaseDC(hwnd, windowHdc); 9564 9565 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 9566 if(cast(SimpleWindow) this.window) 9567 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 9568 9569 if(originalPen !is null) 9570 SelectObject(hdc, originalPen); 9571 if(currentPen !is null) 9572 DeleteObject(currentPen); 9573 if(originalBrush !is null) 9574 SelectObject(hdc, originalBrush); 9575 if(currentBrush !is null) 9576 DeleteObject(currentBrush); 9577 9578 SelectObject(hdc, oldBmp); 9579 9580 if(windowDc) 9581 ReleaseDC(hwnd, hdc); 9582 else 9583 DeleteDC(hdc); 9584 9585 if(window.paintingFinishedDg !is null) 9586 window.paintingFinishedDg()(); 9587 } 9588 9589 bool windowDc; 9590 HPEN originalPen; 9591 HPEN currentPen; 9592 9593 Pen _activePen; 9594 9595 @property void pen(Pen p) { 9596 _activePen = p; 9597 9598 HPEN pen; 9599 if(p.color.a == 0) { 9600 pen = GetStockObject(NULL_PEN); 9601 } else { 9602 int style = PS_SOLID; 9603 final switch(p.style) { 9604 case Pen.Style.Solid: 9605 style = PS_SOLID; 9606 break; 9607 case Pen.Style.Dashed: 9608 style = PS_DASH; 9609 break; 9610 case Pen.Style.Dotted: 9611 style = PS_DOT; 9612 break; 9613 } 9614 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 9615 } 9616 auto orig = SelectObject(hdc, pen); 9617 if(originalPen is null) 9618 originalPen = orig; 9619 9620 if(currentPen !is null) 9621 DeleteObject(currentPen); 9622 9623 currentPen = pen; 9624 9625 // the outline is like a foreground since it's done that way on X 9626 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 9627 9628 } 9629 9630 @property void rasterOp(RasterOp op) { 9631 int mode; 9632 final switch(op) { 9633 case RasterOp.normal: 9634 mode = R2_COPYPEN; 9635 break; 9636 case RasterOp.xor: 9637 mode = R2_XORPEN; 9638 break; 9639 } 9640 SetROP2(hdc, mode); 9641 } 9642 9643 HBRUSH originalBrush; 9644 HBRUSH currentBrush; 9645 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 9646 @property void fillColor(Color c) { 9647 if(c == _fillColor) 9648 return; 9649 _fillColor = c; 9650 HBRUSH brush; 9651 if(c.a == 0) { 9652 brush = GetStockObject(HOLLOW_BRUSH); 9653 } else { 9654 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 9655 } 9656 auto orig = SelectObject(hdc, brush); 9657 if(originalBrush is null) 9658 originalBrush = orig; 9659 9660 if(currentBrush !is null) 9661 DeleteObject(currentBrush); 9662 9663 currentBrush = brush; 9664 9665 // background color is NOT set because X doesn't draw text backgrounds 9666 // SetBkColor(hdc, RGB(255, 255, 255)); 9667 } 9668 9669 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 9670 BITMAP bm; 9671 9672 HDC hdcMem = CreateCompatibleDC(hdc); 9673 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 9674 9675 GetObject(i.handle, bm.sizeof, &bm); 9676 9677 // or should I AlphaBlend!??!?! 9678 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 9679 9680 SelectObject(hdcMem, hbmOld); 9681 DeleteDC(hdcMem); 9682 } 9683 9684 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 9685 BITMAP bm; 9686 9687 HDC hdcMem = CreateCompatibleDC(hdc); 9688 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 9689 9690 GetObject(s.handle, bm.sizeof, &bm); 9691 9692 // or should I AlphaBlend!??!?! note it is supposed to be premultiplied http://www.fengyuan.com/article/alphablend.html 9693 BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY); 9694 9695 SelectObject(hdcMem, hbmOld); 9696 DeleteDC(hdcMem); 9697 } 9698 9699 Size textSize(scope const(char)[] text) { 9700 bool dummyX; 9701 if(text.length == 0) { 9702 text = " "; 9703 dummyX = true; 9704 } 9705 RECT rect; 9706 WCharzBuffer buffer = WCharzBuffer(text); 9707 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT); 9708 return Size(dummyX ? 0 : rect.right, rect.bottom); 9709 } 9710 9711 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 9712 if(text.length && text[$-1] == '\n') 9713 text = text[0 .. $-1]; // tailing newlines are weird on windows... 9714 if(text.length && text[$-1] == '\r') 9715 text = text[0 .. $-1]; 9716 9717 WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines); 9718 if(x2 == 0 && y2 == 0) 9719 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 9720 else { 9721 RECT rect; 9722 rect.left = x; 9723 rect.top = y; 9724 rect.right = x2; 9725 rect.bottom = y2; 9726 9727 uint mode = DT_LEFT; 9728 if(alignment & TextAlignment.Right) 9729 mode = DT_RIGHT; 9730 else if(alignment & TextAlignment.Center) 9731 mode = DT_CENTER; 9732 9733 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 9734 if(alignment & TextAlignment.VerticalCenter) 9735 mode |= DT_VCENTER | DT_SINGLELINE; 9736 9737 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode); 9738 } 9739 9740 /* 9741 uint mode; 9742 9743 if(alignment & TextAlignment.Center) 9744 mode = TA_CENTER; 9745 9746 SetTextAlign(hdc, mode); 9747 */ 9748 } 9749 9750 int fontHeight() { 9751 TEXTMETRIC metric; 9752 if(GetTextMetricsW(hdc, &metric)) { 9753 return metric.tmHeight; 9754 } 9755 9756 return 16; // idk just guessing here, maybe we should throw 9757 } 9758 9759 void drawPixel(int x, int y) { 9760 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 9761 } 9762 9763 // The basic shapes, outlined 9764 9765 void drawLine(int x1, int y1, int x2, int y2) { 9766 MoveToEx(hdc, x1, y1, null); 9767 LineTo(hdc, x2, y2); 9768 } 9769 9770 void drawRectangle(int x, int y, int width, int height) { 9771 gdi.Rectangle(hdc, x, y, x + width, y + height); 9772 } 9773 9774 /// Arguments are the points of the bounding rectangle 9775 void drawEllipse(int x1, int y1, int x2, int y2) { 9776 Ellipse(hdc, x1, y1, x2, y2); 9777 } 9778 9779 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 9780 if((start % (360*64)) == (finish % (360*64))) 9781 drawEllipse(x1, y1, x1 + width, y1 + height); 9782 else { 9783 import core.stdc.math; 9784 float startAngle = start * 64 * 180 / 3.14159265; 9785 float endAngle = finish * 64 * 180 / 3.14159265; 9786 Arc(hdc, x1, y1, x1 + width, y1 + height, 9787 cast(int)(cos(startAngle) * width / 2 + x1), 9788 cast(int)(sin(startAngle) * height / 2 + y1), 9789 cast(int)(cos(endAngle) * width / 2 + x1), 9790 cast(int)(sin(endAngle) * height / 2 + y1), 9791 ); 9792 } 9793 } 9794 9795 void drawPolygon(Point[] vertexes) { 9796 POINT[] points; 9797 points.length = vertexes.length; 9798 9799 foreach(i, p; vertexes) { 9800 points[i].x = p.x; 9801 points[i].y = p.y; 9802 } 9803 9804 Polygon(hdc, points.ptr, cast(int) points.length); 9805 } 9806 } 9807 9808 9809 // Mix this into the SimpleWindow class 9810 mixin template NativeSimpleWindowImplementation() { 9811 int curHidden = 0; // counter 9812 __gshared static bool[string] knownWinClasses; 9813 static bool altPressed = false; 9814 9815 HANDLE oldCursor; 9816 9817 void hideCursor () { 9818 if(curHidden == 0) 9819 oldCursor = SetCursor(null); 9820 ++curHidden; 9821 } 9822 9823 void showCursor () { 9824 --curHidden; 9825 if(curHidden == 0) { 9826 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 9827 } 9828 } 9829 9830 9831 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 9832 9833 void setMinSize (int minwidth, int minheight) { 9834 minWidth = minwidth; 9835 minHeight = minheight; 9836 } 9837 void setMaxSize (int maxwidth, int maxheight) { 9838 maxWidth = maxwidth; 9839 maxHeight = maxheight; 9840 } 9841 9842 // FIXME i'm not sure that Windows has this functionality 9843 // though it is nonessential anyway. 9844 void setResizeGranularity (int granx, int grany) {} 9845 9846 ScreenPainter getPainter() { 9847 return ScreenPainter(this, hwnd); 9848 } 9849 9850 HBITMAP buffer; 9851 9852 void setTitle(string title) { 9853 WCharzBuffer bfr = WCharzBuffer(title); 9854 SetWindowTextW(hwnd, bfr.ptr); 9855 } 9856 9857 string getTitle() { 9858 auto len = GetWindowTextLengthW(hwnd); 9859 if (!len) 9860 return null; 9861 wchar[256] tmpBuffer; 9862 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 9863 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 9864 auto str = buffer[0 .. len2]; 9865 return makeUtf8StringFromWindowsString(str); 9866 } 9867 9868 void move(int x, int y) { 9869 RECT rect; 9870 GetWindowRect(hwnd, &rect); 9871 // move it while maintaining the same size... 9872 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 9873 } 9874 9875 void resize(int w, int h) { 9876 RECT rect; 9877 GetWindowRect(hwnd, &rect); 9878 9879 RECT client; 9880 GetClientRect(hwnd, &client); 9881 9882 rect.right = rect.right - client.right + w; 9883 rect.bottom = rect.bottom - client.bottom + h; 9884 9885 // same position, new size for the client rectangle 9886 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 9887 9888 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 9889 } 9890 9891 void moveResize (int x, int y, int w, int h) { 9892 // what's given is the client rectangle, we need to adjust 9893 9894 RECT rect; 9895 rect.left = x; 9896 rect.top = y; 9897 rect.right = w + x; 9898 rect.bottom = h + y; 9899 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 9900 throw new Exception("AdjustWindowRect"); 9901 9902 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 9903 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 9904 if (windowResized !is null) windowResized(w, h); 9905 } 9906 9907 version(without_opengl) {} else { 9908 HGLRC ghRC; 9909 HDC ghDC; 9910 } 9911 9912 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 9913 string cnamec; 9914 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 9915 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 9916 cnamec = "DSimpleWindow"; 9917 } else { 9918 cnamec = sdpyWindowClass; 9919 } 9920 9921 WCharzBuffer cn = WCharzBuffer(cnamec); 9922 9923 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 9924 9925 if(cnamec !in knownWinClasses) { 9926 WNDCLASSEX wc; 9927 9928 // FIXME: I might be able to use cbWndExtra to hold the pointer back 9929 // to the object. Maybe. 9930 wc.cbSize = wc.sizeof; 9931 wc.cbClsExtra = 0; 9932 wc.cbWndExtra = 0; 9933 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 9934 wc.hCursor = LoadCursorW(null, IDC_ARROW); 9935 wc.hIcon = LoadIcon(hInstance, null); 9936 wc.hInstance = hInstance; 9937 wc.lpfnWndProc = &WndProc; 9938 wc.lpszClassName = cn.ptr; 9939 wc.hIconSm = null; 9940 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 9941 if(!RegisterClassExW(&wc)) 9942 throw new WindowsApiException("RegisterClassExW"); 9943 knownWinClasses[cnamec] = true; 9944 } 9945 9946 int style; 9947 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 9948 9949 // FIXME: windowType and customizationFlags 9950 final switch(windowType) { 9951 case WindowTypes.normal: 9952 style = WS_OVERLAPPEDWINDOW; 9953 break; 9954 case WindowTypes.undecorated: 9955 style = WS_POPUP | WS_SYSMENU; 9956 break; 9957 case WindowTypes.eventOnly: 9958 _hidden = true; 9959 break; 9960 case WindowTypes.dropdownMenu: 9961 case WindowTypes.popupMenu: 9962 case WindowTypes.notification: 9963 style = WS_POPUP; 9964 flags |= WS_EX_NOACTIVATE; 9965 break; 9966 case WindowTypes.nestedChild: 9967 style = WS_CHILD; 9968 break; 9969 } 9970 9971 if ((customizationFlags & WindowFlags.extraComposite) != 0) 9972 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 9973 9974 hwnd = CreateWindowEx(flags, cn.ptr, toWStringz(title), style | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, // the clip children helps avoid flickering in minigui and doesn't seem to harm other use (mostly, sdpy is no child windows anyway) sooo i think it is ok 9975 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 9976 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 9977 9978 if ((customizationFlags & WindowFlags.extraComposite) != 0) 9979 setOpacity(255); 9980 9981 SimpleWindow.nativeMapping[hwnd] = this; 9982 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 9983 9984 if(windowType == WindowTypes.eventOnly) 9985 return; 9986 9987 HDC hdc = GetDC(hwnd); 9988 9989 9990 version(without_opengl) {} 9991 else { 9992 if(opengl == OpenGlOptions.yes) { 9993 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 9994 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 9995 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 9996 ghDC = hdc; 9997 PIXELFORMATDESCRIPTOR pfd; 9998 9999 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 10000 pfd.nVersion = 1; 10001 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 10002 pfd.dwLayerMask = PFD_MAIN_PLANE; 10003 pfd.iPixelType = PFD_TYPE_RGBA; 10004 pfd.cColorBits = 24; 10005 pfd.cDepthBits = 24; 10006 pfd.cAccumBits = 0; 10007 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 10008 10009 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 10010 10011 if (pixelformat == 0) 10012 throw new WindowsApiException("ChoosePixelFormat"); 10013 10014 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 10015 throw new WindowsApiException("SetPixelFormat"); 10016 10017 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 10018 // windoze is idiotic: we have to have OpenGL context to get function addresses 10019 // so we will create fake context to get that stupid address 10020 auto tmpcc = wglCreateContext(ghDC); 10021 if (tmpcc !is null) { 10022 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 10023 wglMakeCurrent(ghDC, tmpcc); 10024 wglInitOtherFunctions(); 10025 } 10026 } 10027 10028 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 10029 int[9] contextAttribs = [ 10030 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 10031 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 10032 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 10033 // for modern context, set "forward compatibility" flag too 10034 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 10035 0/*None*/, 10036 ]; 10037 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 10038 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 10039 // activate fallback mode 10040 // sdpyOpenGLContextVeto-type focus management policy leads to race conditions because the window becoming unviewable may coincide with the window manager deciding to move the focus elsrsion = 0; 10041 ghRC = wglCreateContext(ghDC); 10042 } 10043 if (ghRC is null) 10044 throw new WindowsApiException("wglCreateContextAttribsARB"); 10045 } else { 10046 // try to do at least something 10047 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 10048 sdpyOpenGLContextVersion = 0; 10049 ghRC = wglCreateContext(ghDC); 10050 } 10051 if (ghRC is null) 10052 throw new WindowsApiException("wglCreateContext"); 10053 } 10054 } 10055 } 10056 10057 if(opengl == OpenGlOptions.no) { 10058 buffer = CreateCompatibleBitmap(hdc, width, height); 10059 10060 auto hdcBmp = CreateCompatibleDC(hdc); 10061 // make sure it's filled with a blank slate 10062 auto oldBmp = SelectObject(hdcBmp, buffer); 10063 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 10064 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 10065 gdi.Rectangle(hdcBmp, 0, 0, width, height); 10066 SelectObject(hdcBmp, oldBmp); 10067 SelectObject(hdcBmp, oldBrush); 10068 SelectObject(hdcBmp, oldPen); 10069 DeleteDC(hdcBmp); 10070 10071 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 10072 } 10073 10074 // We want the window's client area to match the image size 10075 RECT rcClient, rcWindow; 10076 POINT ptDiff; 10077 GetClientRect(hwnd, &rcClient); 10078 GetWindowRect(hwnd, &rcWindow); 10079 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 10080 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 10081 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 10082 10083 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 10084 ShowWindow(hwnd, SW_SHOWNORMAL); 10085 } else { 10086 _hidden = true; 10087 } 10088 this._visibleForTheFirstTimeCalled = false; // hack! 10089 } 10090 10091 10092 void dispose() { 10093 if(buffer) 10094 DeleteObject(buffer); 10095 } 10096 10097 void closeWindow() { 10098 DestroyWindow(hwnd); 10099 } 10100 10101 bool setOpacity(ubyte alpha) { 10102 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 10103 } 10104 10105 HANDLE currentCursor; 10106 10107 // returns zero if it recognized the event 10108 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 10109 MouseEvent mouse; 10110 10111 void mouseEvent(bool isScreen, ulong mods) { 10112 auto x = LOWORD(lParam); 10113 auto y = HIWORD(lParam); 10114 if(isScreen) { 10115 POINT p; 10116 p.x = x; 10117 p.y = y; 10118 ScreenToClient(hwnd, &p); 10119 x = cast(ushort) p.x; 10120 y = cast(ushort) p.y; 10121 } 10122 mouse.x = x + offsetX; 10123 mouse.y = y + offsetY; 10124 10125 wind.mdx(mouse); 10126 mouse.modifierState = cast(int) mods; 10127 mouse.window = wind; 10128 10129 if(wind.handleMouseEvent) 10130 wind.handleMouseEvent(mouse); 10131 } 10132 10133 switch(msg) { 10134 case WM_GETMINMAXINFO: 10135 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 10136 10137 if(wind.minWidth > 0) { 10138 RECT rect; 10139 rect.left = 100; 10140 rect.top = 100; 10141 rect.right = wind.minWidth + 100; 10142 rect.bottom = wind.minHeight + 100; 10143 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 10144 throw new WindowsApiException("AdjustWindowRect"); 10145 10146 mmi.ptMinTrackSize.x = rect.right - rect.left; 10147 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 10148 } 10149 10150 if(wind.maxWidth < int.max) { 10151 RECT rect; 10152 rect.left = 100; 10153 rect.top = 100; 10154 rect.right = wind.maxWidth + 100; 10155 rect.bottom = wind.maxHeight + 100; 10156 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 10157 throw new WindowsApiException("AdjustWindowRect"); 10158 10159 mmi.ptMaxTrackSize.x = rect.right - rect.left; 10160 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 10161 } 10162 break; 10163 case WM_CHAR: 10164 wchar c = cast(wchar) wParam; 10165 if(wind.handleCharEvent) 10166 wind.handleCharEvent(cast(dchar) c); 10167 break; 10168 case WM_SETFOCUS: 10169 case WM_KILLFOCUS: 10170 wind._focused = (msg == WM_SETFOCUS); 10171 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 10172 if(wind.onFocusChange) 10173 wind.onFocusChange(msg == WM_SETFOCUS); 10174 break; 10175 case WM_SYSKEYDOWN: 10176 goto case; 10177 case WM_SYSKEYUP: 10178 if(lParam & (1 << 29)) { 10179 goto case; 10180 } else { 10181 // no window has keyboard focus 10182 goto default; 10183 } 10184 case WM_KEYDOWN: 10185 case WM_KEYUP: 10186 KeyEvent ev; 10187 ev.key = cast(Key) wParam; 10188 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 10189 if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way 10190 10191 ev.hardwareCode = (lParam & 0xff0000) >> 16; 10192 10193 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 10194 ev.modifierState |= ModifierState.shift; 10195 //k8: this doesn't work; thanks for nothing, windows 10196 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 10197 ev.modifierState |= ModifierState.alt;*/ 10198 if (wParam == 0x12) altPressed = (msg == WM_SYSKEYDOWN); 10199 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 10200 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 10201 ev.modifierState |= ModifierState.ctrl; 10202 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 10203 ev.modifierState |= ModifierState.windows; 10204 if(GetKeyState(Key.NumLock)) 10205 ev.modifierState |= ModifierState.numLock; 10206 if(GetKeyState(Key.CapsLock)) 10207 ev.modifierState |= ModifierState.capsLock; 10208 10209 /+ 10210 // we always want to send the character too, so let's convert it 10211 ubyte[256] state; 10212 wchar[16] buffer; 10213 GetKeyboardState(state.ptr); 10214 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 10215 10216 foreach(dchar d; buffer) { 10217 ev.character = d; 10218 break; 10219 } 10220 +/ 10221 10222 ev.window = wind; 10223 if(wind.handleKeyEvent) 10224 wind.handleKeyEvent(ev); 10225 break; 10226 case 0x020a /*WM_MOUSEWHEEL*/: 10227 // send click 10228 mouse.type = cast(MouseEventType) 1; 10229 mouse.button = ((HIWORD(wParam) > 120) ? MouseButton.wheelDown : MouseButton.wheelUp); 10230 mouseEvent(true, LOWORD(wParam)); 10231 10232 // also send release 10233 mouse.type = cast(MouseEventType) 2; 10234 mouse.button = ((HIWORD(wParam) > 120) ? MouseButton.wheelDown : MouseButton.wheelUp); 10235 mouseEvent(true, LOWORD(wParam)); 10236 break; 10237 case WM_MOUSEMOVE: 10238 mouse.type = cast(MouseEventType) 0; 10239 mouseEvent(false, wParam); 10240 break; 10241 case WM_LBUTTONDOWN: 10242 case WM_LBUTTONDBLCLK: 10243 mouse.type = cast(MouseEventType) 1; 10244 mouse.button = MouseButton.left; 10245 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 10246 mouseEvent(false, wParam); 10247 break; 10248 case WM_LBUTTONUP: 10249 mouse.type = cast(MouseEventType) 2; 10250 mouse.button = MouseButton.left; 10251 mouseEvent(false, wParam); 10252 break; 10253 case WM_RBUTTONDOWN: 10254 case WM_RBUTTONDBLCLK: 10255 mouse.type = cast(MouseEventType) 1; 10256 mouse.button = MouseButton.right; 10257 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 10258 mouseEvent(false, wParam); 10259 break; 10260 case WM_RBUTTONUP: 10261 mouse.type = cast(MouseEventType) 2; 10262 mouse.button = MouseButton.right; 10263 mouseEvent(false, wParam); 10264 break; 10265 case WM_MBUTTONDOWN: 10266 case WM_MBUTTONDBLCLK: 10267 mouse.type = cast(MouseEventType) 1; 10268 mouse.button = MouseButton.middle; 10269 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 10270 mouseEvent(false, wParam); 10271 break; 10272 case WM_MBUTTONUP: 10273 mouse.type = cast(MouseEventType) 2; 10274 mouse.button = MouseButton.middle; 10275 mouseEvent(false, wParam); 10276 break; 10277 case WM_XBUTTONDOWN: 10278 case WM_XBUTTONDBLCLK: 10279 mouse.type = cast(MouseEventType) 1; 10280 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 10281 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 10282 mouseEvent(false, wParam); 10283 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 10284 case WM_XBUTTONUP: 10285 mouse.type = cast(MouseEventType) 2; 10286 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 10287 mouseEvent(false, wParam); 10288 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 10289 10290 default: return 1; 10291 } 10292 return 0; 10293 } 10294 10295 HWND hwnd; 10296 int oldWidth; 10297 int oldHeight; 10298 bool inSizeMove; 10299 10300 // the extern(Windows) wndproc should just forward to this 10301 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 10302 assert(hwnd is this.hwnd); 10303 10304 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 10305 switch(msg) { 10306 case WM_SETCURSOR: 10307 if(cast(HWND) wParam !is hwnd) 10308 return 0; // further processing elsewhere 10309 10310 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 10311 SetCursor(this.curHidden > 0 ? null : currentCursor); 10312 return 1; 10313 } else { 10314 return DefWindowProc(hwnd, msg, wParam, lParam); 10315 } 10316 //break; 10317 10318 case WM_CLOSE: 10319 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 10320 break; 10321 case WM_DESTROY: 10322 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 10323 SimpleWindow.nativeMapping.remove(hwnd); 10324 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 10325 10326 bool anyImportant = false; 10327 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 10328 if(w.beingOpenKeepsAppOpen) { 10329 anyImportant = true; 10330 break; 10331 } 10332 if(!anyImportant) { 10333 PostQuitMessage(0); 10334 } 10335 break; 10336 case WM_SIZE: 10337 if(wParam == 1 /* SIZE_MINIMIZED */) 10338 break; 10339 _width = LOWORD(lParam); 10340 _height = HIWORD(lParam); 10341 10342 // I want to avoid tearing in the windows (my code is inefficient 10343 // so this is a hack around that) so while sizing, we don't trigger, 10344 // but we do want to trigger on events like mazimize. 10345 if(!inSizeMove) 10346 goto size_changed; 10347 break; 10348 // I don't like the tearing I get when redrawing on WM_SIZE 10349 // (I know there's other ways to fix that but I don't like that behavior anyway) 10350 // so instead it is going to redraw only at the end of a size. 10351 case 0x0231: /* WM_ENTERSIZEMOVE */ 10352 oldWidth = this.width; 10353 oldHeight = this.height; 10354 inSizeMove = true; 10355 break; 10356 case 0x0232: /* WM_EXITSIZEMOVE */ 10357 inSizeMove = false; 10358 // nothing relevant changed, don't bother redrawing 10359 if(oldWidth == width && oldHeight == height) 10360 break; 10361 10362 size_changed: 10363 10364 // note: OpenGL windows don't use a backing bmp, so no need to change them 10365 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 10366 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 10367 // gotta get the double buffer bmp to match the window 10368 // FIXME: could this be more efficient? It isn't really necessary to make 10369 // a new buffer if we're sizing down at least. 10370 auto hdc = GetDC(hwnd); 10371 auto oldBuffer = buffer; 10372 buffer = CreateCompatibleBitmap(hdc, width, height); 10373 10374 auto hdcBmp = CreateCompatibleDC(hdc); 10375 auto oldBmp = SelectObject(hdcBmp, buffer); 10376 10377 auto hdcOldBmp = CreateCompatibleDC(hdc); 10378 auto oldOldBmp = SelectObject(hdcOldBmp, oldBmp); 10379 10380 BitBlt(hdcBmp, 0, 0, width, height, hdcOldBmp, oldWidth, oldHeight, SRCCOPY); 10381 10382 SelectObject(hdcOldBmp, oldOldBmp); 10383 DeleteDC(hdcOldBmp); 10384 10385 SelectObject(hdcBmp, oldBmp); 10386 DeleteDC(hdcBmp); 10387 10388 ReleaseDC(hwnd, hdc); 10389 10390 DeleteObject(oldBuffer); 10391 } 10392 10393 version(without_opengl) {} else 10394 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 10395 glViewport(0, 0, width, height); 10396 } 10397 10398 if(windowResized !is null) 10399 windowResized(width, height); 10400 break; 10401 case WM_ERASEBKGND: 10402 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 10403 if (!this._visibleForTheFirstTimeCalled) { 10404 this._visibleForTheFirstTimeCalled = true; 10405 if (this.visibleForTheFirstTime !is null) { 10406 version(without_opengl) {} else { 10407 if(openglMode == OpenGlOptions.yes) { 10408 this.setAsCurrentOpenGlContextNT(); 10409 glViewport(0, 0, width, height); 10410 } 10411 } 10412 this.visibleForTheFirstTime(); 10413 } 10414 } 10415 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 10416 version(without_opengl) {} else { 10417 if (openglMode == OpenGlOptions.yes) return 1; 10418 } 10419 // call windows default handler, so it can paint standard controls 10420 goto default; 10421 case WM_CTLCOLORBTN: 10422 case WM_CTLCOLORSTATIC: 10423 SetBkMode(cast(HDC) wParam, TRANSPARENT); 10424 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 10425 GetSysColorBrush(COLOR_3DFACE); 10426 //break; 10427 case WM_SHOWWINDOW: 10428 this._visible = (wParam != 0); 10429 if (!this._visibleForTheFirstTimeCalled && this._visible) { 10430 this._visibleForTheFirstTimeCalled = true; 10431 if (this.visibleForTheFirstTime !is null) { 10432 version(without_opengl) {} else { 10433 if(openglMode == OpenGlOptions.yes) { 10434 this.setAsCurrentOpenGlContextNT(); 10435 glViewport(0, 0, width, height); 10436 } 10437 } 10438 this.visibleForTheFirstTime(); 10439 } 10440 } 10441 if (this.visibilityChanged !is null) this.visibilityChanged(this._visible); 10442 break; 10443 case WM_PAINT: { 10444 if (!this._visibleForTheFirstTimeCalled) { 10445 this._visibleForTheFirstTimeCalled = true; 10446 if (this.visibleForTheFirstTime !is null) { 10447 version(without_opengl) {} else { 10448 if(openglMode == OpenGlOptions.yes) { 10449 this.setAsCurrentOpenGlContextNT(); 10450 glViewport(0, 0, width, height); 10451 } 10452 } 10453 this.visibleForTheFirstTime(); 10454 } 10455 } 10456 10457 BITMAP bm; 10458 PAINTSTRUCT ps; 10459 10460 HDC hdc = BeginPaint(hwnd, &ps); 10461 10462 if(openglMode == OpenGlOptions.no) { 10463 10464 HDC hdcMem = CreateCompatibleDC(hdc); 10465 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 10466 10467 GetObject(buffer, bm.sizeof, &bm); 10468 10469 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 10470 if(resizability == Resizability.automaticallyScaleIfPossible) 10471 StretchBlt(hdc, 0, 0, this.width, this.height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 10472 else 10473 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 10474 10475 SelectObject(hdcMem, hbmOld); 10476 DeleteDC(hdcMem); 10477 EndPaint(hwnd, &ps); 10478 } else { 10479 EndPaint(hwnd, &ps); 10480 version(without_opengl) {} else 10481 redrawOpenGlSceneNow(); 10482 } 10483 } break; 10484 default: 10485 return DefWindowProc(hwnd, msg, wParam, lParam); 10486 } 10487 return 0; 10488 10489 } 10490 } 10491 10492 mixin template NativeImageImplementation() { 10493 HBITMAP handle; 10494 ubyte* rawData; 10495 10496 final: 10497 10498 Color getPixel(int x, int y) { 10499 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 10500 // remember, bmps are upside down 10501 auto offset = itemsPerLine * (height - y - 1) + x * 3; 10502 10503 Color c; 10504 c.a = 255; 10505 c.b = rawData[offset + 0]; 10506 c.g = rawData[offset + 1]; 10507 c.r = rawData[offset + 2]; 10508 return c; 10509 } 10510 10511 void setPixel(int x, int y, Color c) { 10512 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 10513 // remember, bmps are upside down 10514 auto offset = itemsPerLine * (height - y - 1) + x * 3; 10515 10516 rawData[offset + 0] = c.b; 10517 rawData[offset + 1] = c.g; 10518 rawData[offset + 2] = c.r; 10519 } 10520 10521 void convertToRgbaBytes(ubyte[] where) { 10522 assert(where.length == this.width * this.height * 4); 10523 10524 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 10525 int idx = 0; 10526 int offset = itemsPerLine * (height - 1); 10527 // remember, bmps are upside down 10528 for(int y = height - 1; y >= 0; y--) { 10529 auto offsetStart = offset; 10530 for(int x = 0; x < width; x++) { 10531 where[idx + 0] = rawData[offset + 2]; // r 10532 where[idx + 1] = rawData[offset + 1]; // g 10533 where[idx + 2] = rawData[offset + 0]; // b 10534 where[idx + 3] = 255; // a 10535 idx += 4; 10536 offset += 3; 10537 } 10538 10539 offset = offsetStart - itemsPerLine; 10540 } 10541 } 10542 10543 void setFromRgbaBytes(in ubyte[] what) { 10544 assert(what.length == this.width * this.height * 4); 10545 10546 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 10547 int idx = 0; 10548 int offset = itemsPerLine * (height - 1); 10549 // remember, bmps are upside down 10550 for(int y = height - 1; y >= 0; y--) { 10551 auto offsetStart = offset; 10552 for(int x = 0; x < width; x++) { 10553 rawData[offset + 2] = what[idx + 0]; // r 10554 rawData[offset + 1] = what[idx + 1]; // g 10555 rawData[offset + 0] = what[idx + 2]; // b 10556 //where[idx + 3] = 255; // a 10557 idx += 4; 10558 offset += 3; 10559 } 10560 10561 offset = offsetStart - itemsPerLine; 10562 } 10563 } 10564 10565 10566 void createImage(int width, int height, bool forcexshm=false) { 10567 BITMAPINFO infoheader; 10568 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 10569 infoheader.bmiHeader.biWidth = width; 10570 infoheader.bmiHeader.biHeight = height; 10571 infoheader.bmiHeader.biPlanes = 1; 10572 infoheader.bmiHeader.biBitCount = 24; 10573 infoheader.bmiHeader.biCompression = BI_RGB; 10574 10575 handle = CreateDIBSection( 10576 null, 10577 &infoheader, 10578 DIB_RGB_COLORS, 10579 cast(void**) &rawData, 10580 null, 10581 0); 10582 if(handle is null) 10583 throw new WindowsApiException("create image failed"); 10584 10585 } 10586 10587 void dispose() { 10588 DeleteObject(handle); 10589 } 10590 } 10591 10592 enum KEY_ESCAPE = 27; 10593 } 10594 version(X11) { 10595 /// This is the default font used. You might change this before doing anything else with 10596 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 10597 /// for cross-platform compatibility. 10598 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 10599 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 10600 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 10601 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 10602 10603 alias int delegate(XEvent) NativeEventHandler; 10604 alias Window NativeWindowHandle; 10605 10606 enum KEY_ESCAPE = 9; 10607 10608 mixin template NativeScreenPainterImplementation() { 10609 Display* display; 10610 Drawable d; 10611 Drawable destiny; 10612 10613 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 10614 GC gc; 10615 10616 __gshared bool fontAttempted; 10617 10618 __gshared XFontStruct* defaultfont; 10619 __gshared XFontSet defaultfontset; 10620 10621 XFontStruct* font; 10622 XFontSet fontset; 10623 10624 void create(NativeWindowHandle window) { 10625 this.display = XDisplayConnection.get(); 10626 10627 Drawable buffer = None; 10628 if(auto sw = cast(SimpleWindow) this.window) { 10629 buffer = sw.impl.buffer; 10630 this.destiny = cast(Drawable) window; 10631 } else { 10632 buffer = cast(Drawable) window; 10633 this.destiny = None; 10634 } 10635 10636 this.d = cast(Drawable) buffer; 10637 10638 auto dgc = DefaultGC(display, DefaultScreen(display)); 10639 10640 this.gc = XCreateGC(display, d, 0, null); 10641 10642 XCopyGC(display, dgc, 0xffffffff, this.gc); 10643 10644 ensureDefaultFontLoaded(); 10645 10646 font = defaultfont; 10647 fontset = defaultfontset; 10648 10649 if(font) { 10650 XSetFont(display, gc, font.fid); 10651 } 10652 } 10653 10654 static void ensureDefaultFontLoaded() { 10655 if(!fontAttempted) { 10656 auto display = XDisplayConnection.get; 10657 auto font = XLoadQueryFont(display, xfontstr.ptr); 10658 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 10659 if(font is null) 10660 font = XLoadQueryFont(display, "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*".ptr); 10661 10662 char** lol; 10663 int lol2; 10664 char* lol3; 10665 auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 10666 10667 fontAttempted = true; 10668 10669 defaultfont = font; 10670 defaultfontset = fontset; 10671 } 10672 } 10673 10674 arsd.color.Rectangle _clipRectangle; 10675 void setClipRectangle(int x, int y, int width, int height) { 10676 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 10677 if(width == 0 || height == 0) 10678 XSetClipMask(display, gc, None); 10679 else { 10680 XRectangle[1] rects; 10681 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 10682 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 10683 } 10684 } 10685 10686 version(with_xft) { 10687 XftFont* xftFont; 10688 XftDraw* xftDraw; 10689 10690 XftColor xftColor; 10691 10692 void updateXftColor() { 10693 if(xftFont is null) 10694 return; 10695 10696 // not bothering with XftColorFree since p sure i don't need it on 24 bit displays.... 10697 XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255); 10698 10699 XftColorAllocValue( 10700 display, 10701 DefaultVisual(display, DefaultScreen(display)), 10702 DefaultColormap(display, 0), 10703 &colorIn, 10704 &xftColor 10705 ); 10706 } 10707 } 10708 10709 void setFont(OperatingSystemFont font) { 10710 version(with_xft) { 10711 if(font && font.isXft && font.xftFont) 10712 this.xftFont = font.xftFont; 10713 else 10714 this.xftFont = null; 10715 10716 if(this.xftFont) { 10717 if(xftDraw is null) { 10718 xftDraw = XftDrawCreate( 10719 display, 10720 d, 10721 DefaultVisual(display, DefaultScreen(display)), 10722 DefaultColormap(display, 0) 10723 ); 10724 10725 updateXftColor(); 10726 } 10727 10728 return; 10729 } 10730 } 10731 10732 if(font && font.font) { 10733 this.font = font.font; 10734 this.fontset = font.fontset; 10735 XSetFont(display, gc, font.font.fid); 10736 } else { 10737 this.font = defaultfont; 10738 this.fontset = defaultfontset; 10739 } 10740 10741 } 10742 10743 void dispose() { 10744 this.rasterOp = RasterOp.normal; 10745 10746 // FIXME: this.window.width/height is probably wrong 10747 10748 // src x,y then dest x, y 10749 if(destiny != None) { 10750 XSetClipMask(display, gc, None); 10751 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 10752 } 10753 10754 XFreeGC(display, gc); 10755 10756 version(with_xft) 10757 if(xftDraw) { 10758 XftDrawDestroy(xftDraw); 10759 xftDraw = null; 10760 } 10761 10762 version(none) // we don't want to free it because we can use it later 10763 if(font) 10764 XFreeFont(display, font); 10765 version(none) // we don't want to free it because we can use it later 10766 if(fontset) 10767 XFreeFontSet(display, fontset); 10768 XFlush(display); 10769 10770 if(window.paintingFinishedDg !is null) 10771 window.paintingFinishedDg()(); 10772 } 10773 10774 bool backgroundIsNotTransparent = true; 10775 bool foregroundIsNotTransparent = true; 10776 10777 bool _penInitialized = false; 10778 Pen _activePen; 10779 10780 Color _outlineColor; 10781 Color _fillColor; 10782 10783 @property void pen(Pen p) { 10784 if(_penInitialized && p == _activePen) { 10785 return; 10786 } 10787 _penInitialized = true; 10788 _activePen = p; 10789 _outlineColor = p.color; 10790 10791 int style; 10792 10793 byte dashLength; 10794 10795 final switch(p.style) { 10796 case Pen.Style.Solid: 10797 style = 0 /*LineSolid*/; 10798 break; 10799 case Pen.Style.Dashed: 10800 style = 1 /*LineOnOffDash*/; 10801 dashLength = 4; 10802 break; 10803 case Pen.Style.Dotted: 10804 style = 1 /*LineOnOffDash*/; 10805 dashLength = 1; 10806 break; 10807 } 10808 10809 XSetLineAttributes(display, gc, p.width, style, 0, 0); 10810 if(dashLength) 10811 XSetDashes(display, gc, 0, &dashLength, 1); 10812 10813 if(p.color.a == 0) { 10814 foregroundIsNotTransparent = false; 10815 return; 10816 } 10817 10818 foregroundIsNotTransparent = true; 10819 10820 XSetForeground(display, gc, colorToX(p.color, display)); 10821 10822 version(with_xft) 10823 updateXftColor(); 10824 } 10825 10826 RasterOp _currentRasterOp; 10827 bool _currentRasterOpInitialized = false; 10828 @property void rasterOp(RasterOp op) { 10829 if(_currentRasterOpInitialized && _currentRasterOp == op) 10830 return; 10831 _currentRasterOp = op; 10832 _currentRasterOpInitialized = true; 10833 int mode; 10834 final switch(op) { 10835 case RasterOp.normal: 10836 mode = GXcopy; 10837 break; 10838 case RasterOp.xor: 10839 mode = GXxor; 10840 break; 10841 } 10842 XSetFunction(display, gc, mode); 10843 } 10844 10845 10846 bool _fillColorInitialized = false; 10847 10848 @property void fillColor(Color c) { 10849 if(_fillColorInitialized && _fillColor == c) 10850 return; // already good, no need to waste time calling it 10851 _fillColor = c; 10852 _fillColorInitialized = true; 10853 if(c.a == 0) { 10854 backgroundIsNotTransparent = false; 10855 return; 10856 } 10857 10858 backgroundIsNotTransparent = true; 10859 10860 XSetBackground(display, gc, colorToX(c, display)); 10861 10862 } 10863 10864 void swapColors() { 10865 auto tmp = _fillColor; 10866 fillColor = _outlineColor; 10867 auto newPen = _activePen; 10868 newPen.color = tmp; 10869 pen(newPen); 10870 } 10871 10872 uint colorToX(Color c, Display* display) { 10873 auto visual = DefaultVisual(display, DefaultScreen(display)); 10874 import core.bitop; 10875 uint color = 0; 10876 { 10877 auto startBit = bsf(visual.red_mask); 10878 auto lastBit = bsr(visual.red_mask); 10879 auto r = cast(uint) c.r; 10880 r >>= 7 - (lastBit - startBit); 10881 r <<= startBit; 10882 color |= r; 10883 } 10884 { 10885 auto startBit = bsf(visual.green_mask); 10886 auto lastBit = bsr(visual.green_mask); 10887 auto g = cast(uint) c.g; 10888 g >>= 7 - (lastBit - startBit); 10889 g <<= startBit; 10890 color |= g; 10891 } 10892 { 10893 auto startBit = bsf(visual.blue_mask); 10894 auto lastBit = bsr(visual.blue_mask); 10895 auto b = cast(uint) c.b; 10896 b >>= 7 - (lastBit - startBit); 10897 b <<= startBit; 10898 color |= b; 10899 } 10900 10901 10902 10903 return color; 10904 } 10905 10906 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 10907 // source x, source y 10908 if(ix >= i.width) return; 10909 if(iy >= i.height) return; 10910 if(ix + w > i.width) w = i.width - ix; 10911 if(iy + h > i.height) h = i.height - iy; 10912 if(i.usingXshm) 10913 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 10914 else 10915 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 10916 } 10917 10918 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 10919 XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y); 10920 } 10921 10922 int fontHeight() { 10923 version(with_xft) 10924 if(xftFont !is null) 10925 return xftFont.height; 10926 if(font) 10927 return font.max_bounds.ascent + font.max_bounds.descent; 10928 return 12; // pretty common default... 10929 } 10930 10931 int textWidth(in char[] line) { 10932 version(with_xft) 10933 if(xftFont) { 10934 if(line.length == 0) 10935 return 0; 10936 XGlyphInfo extents; 10937 XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents); 10938 return extents.width; 10939 } 10940 10941 if(fontset) { 10942 if(line.length == 0) 10943 return 0; 10944 XRectangle rect; 10945 Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect); 10946 10947 return rect.width; 10948 } 10949 10950 if(font) 10951 // FIXME: unicode 10952 return XTextWidth( font, line.ptr, cast(int) line.length); 10953 else 10954 return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 10955 } 10956 10957 Size textSize(in char[] text) { 10958 auto maxWidth = 0; 10959 auto lineHeight = fontHeight; 10960 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 10961 foreach(line; text.split('\n')) { 10962 int textWidth = this.textWidth(line); 10963 if(textWidth > maxWidth) 10964 maxWidth = textWidth; 10965 h += lineHeight + 4; 10966 } 10967 return Size(maxWidth, h); 10968 } 10969 10970 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 10971 const(char)[] text; 10972 version(with_xft) 10973 if(xftFont) { 10974 text = originalText; 10975 goto loaded; 10976 } 10977 10978 if(fontset) 10979 text = originalText; 10980 else { 10981 text.reserve(originalText.length); 10982 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 10983 // then strip the rest so there isn't garbage 10984 foreach(dchar ch; originalText) 10985 if(ch < 256) 10986 text ~= cast(ubyte) ch; 10987 else 10988 text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space 10989 } 10990 loaded: 10991 if(text.length == 0) 10992 return; 10993 10994 // FIXME: should we clip it to the bounding box? 10995 int textHeight = fontHeight; 10996 10997 auto lines = text.split('\n'); 10998 10999 const lineHeight = textHeight; 11000 textHeight *= lines.length; 11001 11002 int cy = y; 11003 11004 if(alignment & TextAlignment.VerticalBottom) { 11005 assert(y2); 11006 auto h = y2 - y; 11007 if(h > textHeight) { 11008 cy += h - textHeight; 11009 cy -= lineHeight / 2; 11010 } 11011 } else if(alignment & TextAlignment.VerticalCenter) { 11012 assert(y2); 11013 auto h = y2 - y; 11014 if(textHeight < h) { 11015 cy += (h - textHeight) / 2; 11016 //cy -= lineHeight / 4; 11017 } 11018 } 11019 11020 foreach(line; text.split('\n')) { 11021 int textWidth = this.textWidth(line); 11022 11023 int px = x, py = cy; 11024 11025 if(alignment & TextAlignment.Center) { 11026 assert(x2); 11027 auto w = x2 - x; 11028 if(w > textWidth) 11029 px += (w - textWidth) / 2; 11030 } else if(alignment & TextAlignment.Right) { 11031 assert(x2); 11032 auto pos = x2 - textWidth; 11033 if(pos > x) 11034 px = pos; 11035 } 11036 11037 version(with_xft) 11038 if(xftFont) { 11039 XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length); 11040 11041 goto carry_on; 11042 } 11043 11044 if(fontset) 11045 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 11046 11047 else 11048 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 11049 carry_on: 11050 cy += lineHeight + 4; 11051 } 11052 } 11053 11054 void drawPixel(int x, int y) { 11055 XDrawPoint(display, d, gc, x, y); 11056 } 11057 11058 // The basic shapes, outlined 11059 11060 void drawLine(int x1, int y1, int x2, int y2) { 11061 if(foregroundIsNotTransparent) 11062 XDrawLine(display, d, gc, x1, y1, x2, y2); 11063 } 11064 11065 void drawRectangle(int x, int y, int width, int height) { 11066 if(backgroundIsNotTransparent) { 11067 swapColors(); 11068 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 11069 swapColors(); 11070 } 11071 if(foregroundIsNotTransparent) 11072 XDrawRectangle(display, d, gc, x, y, width - 1, height - 1); 11073 } 11074 11075 /// Arguments are the points of the bounding rectangle 11076 void drawEllipse(int x1, int y1, int x2, int y2) { 11077 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 11078 } 11079 11080 // NOTE: start and finish are in units of degrees * 64 11081 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 11082 if(backgroundIsNotTransparent) { 11083 swapColors(); 11084 XFillArc(display, d, gc, x1, y1, width, height, start, finish); 11085 swapColors(); 11086 } 11087 if(foregroundIsNotTransparent) 11088 XDrawArc(display, d, gc, x1, y1, width, height, start, finish); 11089 } 11090 11091 void drawPolygon(Point[] vertexes) { 11092 XPoint[16] pointsBuffer; 11093 XPoint[] points; 11094 if(vertexes.length <= pointsBuffer.length) 11095 points = pointsBuffer[0 .. vertexes.length]; 11096 else 11097 points.length = vertexes.length; 11098 11099 foreach(i, p; vertexes) { 11100 points[i].x = cast(short) p.x; 11101 points[i].y = cast(short) p.y; 11102 } 11103 11104 if(backgroundIsNotTransparent) { 11105 swapColors(); 11106 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 11107 swapColors(); 11108 } 11109 if(foregroundIsNotTransparent) { 11110 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 11111 } 11112 } 11113 } 11114 11115 /* XRender { */ 11116 11117 struct XRenderColor { 11118 ushort red; 11119 ushort green; 11120 ushort blue; 11121 ushort alpha; 11122 } 11123 11124 alias Picture = XID; 11125 alias PictFormat = XID; 11126 11127 struct XGlyphInfo { 11128 ushort width; 11129 ushort height; 11130 short x; 11131 short y; 11132 short xOff; 11133 short yOff; 11134 } 11135 11136 struct XRenderDirectFormat { 11137 short red; 11138 short redMask; 11139 short green; 11140 short greenMask; 11141 short blue; 11142 short blueMask; 11143 short alpha; 11144 short alphaMask; 11145 } 11146 11147 struct XRenderPictFormat { 11148 PictFormat id; 11149 int type; 11150 int depth; 11151 XRenderDirectFormat direct; 11152 Colormap colormap; 11153 } 11154 11155 enum PictFormatID = (1 << 0); 11156 enum PictFormatType = (1 << 1); 11157 enum PictFormatDepth = (1 << 2); 11158 enum PictFormatRed = (1 << 3); 11159 enum PictFormatRedMask =(1 << 4); 11160 enum PictFormatGreen = (1 << 5); 11161 enum PictFormatGreenMask=(1 << 6); 11162 enum PictFormatBlue = (1 << 7); 11163 enum PictFormatBlueMask =(1 << 8); 11164 enum PictFormatAlpha = (1 << 9); 11165 enum PictFormatAlphaMask=(1 << 10); 11166 enum PictFormatColormap =(1 << 11); 11167 11168 struct XRenderPictureAttributes { 11169 int repeat; 11170 Picture alpha_map; 11171 int alpha_x_origin; 11172 int alpha_y_origin; 11173 int clip_x_origin; 11174 int clip_y_origin; 11175 Pixmap clip_mask; 11176 Bool graphics_exposures; 11177 int subwindow_mode; 11178 int poly_edge; 11179 int poly_mode; 11180 Atom dither; 11181 Bool component_alpha; 11182 } 11183 11184 alias int XFixed; 11185 11186 struct XPointFixed { 11187 XFixed x, y; 11188 } 11189 11190 struct XCircle { 11191 XFixed x; 11192 XFixed y; 11193 XFixed radius; 11194 } 11195 11196 struct XTransform { 11197 XFixed[3][3] matrix; 11198 } 11199 11200 struct XFilters { 11201 int nfilter; 11202 char **filter; 11203 int nalias; 11204 short *alias_; 11205 } 11206 11207 struct XIndexValue { 11208 c_ulong pixel; 11209 ushort red, green, blue, alpha; 11210 } 11211 11212 struct XAnimCursor { 11213 Cursor cursor; 11214 c_ulong delay; 11215 } 11216 11217 struct XLinearGradient { 11218 XPointFixed p1; 11219 XPointFixed p2; 11220 } 11221 11222 struct XRadialGradient { 11223 XCircle inner; 11224 XCircle outer; 11225 } 11226 11227 struct XConicalGradient { 11228 XPointFixed center; 11229 XFixed angle; /* in degrees */ 11230 } 11231 11232 enum PictStandardARGB32 = 0; 11233 enum PictStandardRGB24 = 1; 11234 enum PictStandardA8 = 2; 11235 enum PictStandardA4 = 3; 11236 enum PictStandardA1 = 4; 11237 enum PictStandardNUM = 5; 11238 11239 interface XRender { 11240 extern(C) @nogc: 11241 11242 Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep); 11243 11244 Status XRenderQueryVersion (Display *dpy, 11245 int *major_versionp, 11246 int *minor_versionp); 11247 11248 Status XRenderQueryFormats (Display *dpy); 11249 11250 int XRenderQuerySubpixelOrder (Display *dpy, int screen); 11251 11252 Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel); 11253 11254 XRenderPictFormat * 11255 XRenderFindVisualFormat (Display *dpy, const Visual *visual); 11256 11257 XRenderPictFormat * 11258 XRenderFindFormat (Display *dpy, 11259 c_ulong mask, 11260 const XRenderPictFormat *templ, 11261 int count); 11262 XRenderPictFormat * 11263 XRenderFindStandardFormat (Display *dpy, 11264 int format); 11265 11266 XIndexValue * 11267 XRenderQueryPictIndexValues(Display *dpy, 11268 const XRenderPictFormat *format, 11269 int *num); 11270 11271 Picture XRenderCreatePicture( 11272 Display *dpy, 11273 Drawable drawable, 11274 const XRenderPictFormat *format, 11275 c_ulong valuemask, 11276 const XRenderPictureAttributes *attributes); 11277 11278 void XRenderChangePicture (Display *dpy, 11279 Picture picture, 11280 c_ulong valuemask, 11281 const XRenderPictureAttributes *attributes); 11282 11283 void 11284 XRenderSetPictureClipRectangles (Display *dpy, 11285 Picture picture, 11286 int xOrigin, 11287 int yOrigin, 11288 const XRectangle *rects, 11289 int n); 11290 11291 void 11292 XRenderSetPictureClipRegion (Display *dpy, 11293 Picture picture, 11294 Region r); 11295 11296 void 11297 XRenderSetPictureTransform (Display *dpy, 11298 Picture picture, 11299 XTransform *transform); 11300 11301 void 11302 XRenderFreePicture (Display *dpy, 11303 Picture picture); 11304 11305 void 11306 XRenderComposite (Display *dpy, 11307 int op, 11308 Picture src, 11309 Picture mask, 11310 Picture dst, 11311 int src_x, 11312 int src_y, 11313 int mask_x, 11314 int mask_y, 11315 int dst_x, 11316 int dst_y, 11317 uint width, 11318 uint height); 11319 11320 11321 Picture XRenderCreateSolidFill (Display *dpy, 11322 const XRenderColor *color); 11323 11324 Picture XRenderCreateLinearGradient (Display *dpy, 11325 const XLinearGradient *gradient, 11326 const XFixed *stops, 11327 const XRenderColor *colors, 11328 int nstops); 11329 11330 Picture XRenderCreateRadialGradient (Display *dpy, 11331 const XRadialGradient *gradient, 11332 const XFixed *stops, 11333 const XRenderColor *colors, 11334 int nstops); 11335 11336 Picture XRenderCreateConicalGradient (Display *dpy, 11337 const XConicalGradient *gradient, 11338 const XFixed *stops, 11339 const XRenderColor *colors, 11340 int nstops); 11341 11342 11343 11344 Cursor 11345 XRenderCreateCursor (Display *dpy, 11346 Picture source, 11347 uint x, 11348 uint y); 11349 11350 XFilters * 11351 XRenderQueryFilters (Display *dpy, Drawable drawable); 11352 11353 void 11354 XRenderSetPictureFilter (Display *dpy, 11355 Picture picture, 11356 const char *filter, 11357 XFixed *params, 11358 int nparams); 11359 11360 Cursor 11361 XRenderCreateAnimCursor (Display *dpy, 11362 int ncursor, 11363 XAnimCursor *cursors); 11364 } 11365 11366 mixin DynamicLoad!(XRender, "Xrender", 1, false, true) XRenderLibrary; 11367 11368 11369 11370 /* XRender } */ 11371 11372 /* Xft { */ 11373 11374 // actually freetype 11375 alias void FT_Face; 11376 11377 // actually fontconfig 11378 private alias FcBool = int; 11379 alias void FcCharSet; 11380 alias void FcPattern; 11381 alias void FcResult; 11382 enum FcEndian { FcEndianBig, FcEndianLittle } 11383 struct FcFontSet { 11384 int nfont; 11385 int sfont; 11386 FcPattern** fonts; 11387 } 11388 11389 // actually XRegion 11390 struct BOX { 11391 short x1, x2, y1, y2; 11392 } 11393 struct Region { 11394 c_long size; 11395 c_long numRects; 11396 BOX* rects; 11397 BOX extents; 11398 } 11399 11400 // ok actually Xft 11401 11402 struct XftFontInfo; 11403 11404 struct XftFont { 11405 int ascent; 11406 int descent; 11407 int height; 11408 int max_advance_width; 11409 FcCharSet* charset; 11410 FcPattern* pattern; 11411 } 11412 11413 struct XftDraw; 11414 11415 struct XftColor { 11416 c_ulong pixel; 11417 XRenderColor color; 11418 } 11419 11420 struct XftCharSpec { 11421 dchar ucs4; 11422 short x; 11423 short y; 11424 } 11425 11426 struct XftCharFontSpec { 11427 XftFont *font; 11428 dchar ucs4; 11429 short x; 11430 short y; 11431 } 11432 11433 struct XftGlyphSpec { 11434 uint glyph; 11435 short x; 11436 short y; 11437 } 11438 11439 struct XftGlyphFontSpec { 11440 XftFont *font; 11441 uint glyph; 11442 short x; 11443 short y; 11444 } 11445 11446 interface Xft { 11447 extern(C) @nogc pure: 11448 11449 Bool XftColorAllocName (Display *dpy, 11450 const Visual *visual, 11451 Colormap cmap, 11452 const char *name, 11453 XftColor *result); 11454 11455 Bool XftColorAllocValue (Display *dpy, 11456 Visual *visual, 11457 Colormap cmap, 11458 const XRenderColor *color, 11459 XftColor *result); 11460 11461 void XftColorFree (Display *dpy, 11462 Visual *visual, 11463 Colormap cmap, 11464 XftColor *color); 11465 11466 Bool XftDefaultHasRender (Display *dpy); 11467 11468 Bool XftDefaultSet (Display *dpy, FcPattern *defaults); 11469 11470 void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern); 11471 11472 XftDraw * XftDrawCreate (Display *dpy, 11473 Drawable drawable, 11474 Visual *visual, 11475 Colormap colormap); 11476 11477 XftDraw * XftDrawCreateBitmap (Display *dpy, 11478 Pixmap bitmap); 11479 11480 XftDraw * XftDrawCreateAlpha (Display *dpy, 11481 Pixmap pixmap, 11482 int depth); 11483 11484 void XftDrawChange (XftDraw *draw, 11485 Drawable drawable); 11486 11487 Display * XftDrawDisplay (XftDraw *draw); 11488 11489 Drawable XftDrawDrawable (XftDraw *draw); 11490 11491 Colormap XftDrawColormap (XftDraw *draw); 11492 11493 Visual * XftDrawVisual (XftDraw *draw); 11494 11495 void XftDrawDestroy (XftDraw *draw); 11496 11497 Picture XftDrawPicture (XftDraw *draw); 11498 11499 Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color); 11500 11501 void XftDrawGlyphs (XftDraw *draw, 11502 const XftColor *color, 11503 XftFont *pub, 11504 int x, 11505 int y, 11506 const uint *glyphs, 11507 int nglyphs); 11508 11509 void XftDrawString8 (XftDraw *draw, 11510 const XftColor *color, 11511 XftFont *pub, 11512 int x, 11513 int y, 11514 const char *string, 11515 int len); 11516 11517 void XftDrawString16 (XftDraw *draw, 11518 const XftColor *color, 11519 XftFont *pub, 11520 int x, 11521 int y, 11522 const wchar *string, 11523 int len); 11524 11525 void XftDrawString32 (XftDraw *draw, 11526 const XftColor *color, 11527 XftFont *pub, 11528 int x, 11529 int y, 11530 const dchar *string, 11531 int len); 11532 11533 void XftDrawStringUtf8 (XftDraw *draw, 11534 const XftColor *color, 11535 XftFont *pub, 11536 int x, 11537 int y, 11538 const char *string, 11539 int len); 11540 void XftDrawStringUtf16 (XftDraw *draw, 11541 const XftColor *color, 11542 XftFont *pub, 11543 int x, 11544 int y, 11545 const char *string, 11546 FcEndian endian, 11547 int len); 11548 11549 void XftDrawCharSpec (XftDraw *draw, 11550 const XftColor *color, 11551 XftFont *pub, 11552 const XftCharSpec *chars, 11553 int len); 11554 11555 void XftDrawCharFontSpec (XftDraw *draw, 11556 const XftColor *color, 11557 const XftCharFontSpec *chars, 11558 int len); 11559 11560 void XftDrawGlyphSpec (XftDraw *draw, 11561 const XftColor *color, 11562 XftFont *pub, 11563 const XftGlyphSpec *glyphs, 11564 int len); 11565 11566 void XftDrawGlyphFontSpec (XftDraw *draw, 11567 const XftColor *color, 11568 const XftGlyphFontSpec *glyphs, 11569 int len); 11570 11571 void XftDrawRect (XftDraw *draw, 11572 const XftColor *color, 11573 int x, 11574 int y, 11575 uint width, 11576 uint height); 11577 11578 Bool XftDrawSetClip (XftDraw *draw, 11579 Region r); 11580 11581 11582 Bool XftDrawSetClipRectangles (XftDraw *draw, 11583 int xOrigin, 11584 int yOrigin, 11585 const XRectangle *rects, 11586 int n); 11587 11588 void XftDrawSetSubwindowMode (XftDraw *draw, 11589 int mode); 11590 11591 void XftGlyphExtents (Display *dpy, 11592 XftFont *pub, 11593 const uint *glyphs, 11594 int nglyphs, 11595 XGlyphInfo *extents); 11596 11597 void XftTextExtents8 (Display *dpy, 11598 XftFont *pub, 11599 const char *string, 11600 int len, 11601 XGlyphInfo *extents); 11602 11603 void XftTextExtents16 (Display *dpy, 11604 XftFont *pub, 11605 const wchar *string, 11606 int len, 11607 XGlyphInfo *extents); 11608 11609 void XftTextExtents32 (Display *dpy, 11610 XftFont *pub, 11611 const dchar *string, 11612 int len, 11613 XGlyphInfo *extents); 11614 11615 void XftTextExtentsUtf8 (Display *dpy, 11616 XftFont *pub, 11617 const char *string, 11618 int len, 11619 XGlyphInfo *extents); 11620 11621 void XftTextExtentsUtf16 (Display *dpy, 11622 XftFont *pub, 11623 const char *string, 11624 FcEndian endian, 11625 int len, 11626 XGlyphInfo *extents); 11627 11628 FcPattern * XftFontMatch (Display *dpy, 11629 int screen, 11630 const FcPattern *pattern, 11631 FcResult *result); 11632 11633 XftFont * XftFontOpen (Display *dpy, int screen, ...); 11634 11635 XftFont * XftFontOpenName (Display *dpy, int screen, const char *name); 11636 11637 XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd); 11638 11639 FT_Face XftLockFace (XftFont *pub); 11640 11641 void XftUnlockFace (XftFont *pub); 11642 11643 XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern); 11644 11645 void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi); 11646 11647 dchar XftFontInfoHash (const XftFontInfo *fi); 11648 11649 FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b); 11650 11651 XftFont * XftFontOpenInfo (Display *dpy, 11652 FcPattern *pattern, 11653 XftFontInfo *fi); 11654 11655 XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern); 11656 11657 XftFont * XftFontCopy (Display *dpy, XftFont *pub); 11658 11659 void XftFontClose (Display *dpy, XftFont *pub); 11660 11661 FcBool XftInitFtLibrary(); 11662 void XftFontLoadGlyphs (Display *dpy, 11663 XftFont *pub, 11664 FcBool need_bitmaps, 11665 const uint *glyphs, 11666 int nglyph); 11667 11668 void XftFontUnloadGlyphs (Display *dpy, 11669 XftFont *pub, 11670 const uint *glyphs, 11671 int nglyph); 11672 11673 FcBool XftFontCheckGlyph (Display *dpy, 11674 XftFont *pub, 11675 FcBool need_bitmaps, 11676 uint glyph, 11677 uint *missing, 11678 int *nmissing); 11679 11680 FcBool XftCharExists (Display *dpy, 11681 XftFont *pub, 11682 dchar ucs4); 11683 11684 uint XftCharIndex (Display *dpy, 11685 XftFont *pub, 11686 dchar ucs4); 11687 FcBool XftInit (const char *config); 11688 11689 int XftGetVersion (); 11690 11691 11692 FcFontSet * XftListFonts (Display *dpy, 11693 int screen, 11694 ...); 11695 11696 FcPattern *XftNameParse (const char *name); 11697 11698 void XftGlyphRender (Display *dpy, 11699 int op, 11700 Picture src, 11701 XftFont *pub, 11702 Picture dst, 11703 int srcx, 11704 int srcy, 11705 int x, 11706 int y, 11707 const uint *glyphs, 11708 int nglyphs); 11709 11710 void XftGlyphSpecRender (Display *dpy, 11711 int op, 11712 Picture src, 11713 XftFont *pub, 11714 Picture dst, 11715 int srcx, 11716 int srcy, 11717 const XftGlyphSpec *glyphs, 11718 int nglyphs); 11719 11720 void XftCharSpecRender (Display *dpy, 11721 int op, 11722 Picture src, 11723 XftFont *pub, 11724 Picture dst, 11725 int srcx, 11726 int srcy, 11727 const XftCharSpec *chars, 11728 int len); 11729 void XftGlyphFontSpecRender (Display *dpy, 11730 int op, 11731 Picture src, 11732 Picture dst, 11733 int srcx, 11734 int srcy, 11735 const XftGlyphFontSpec *glyphs, 11736 int nglyphs); 11737 11738 void XftCharFontSpecRender (Display *dpy, 11739 int op, 11740 Picture src, 11741 Picture dst, 11742 int srcx, 11743 int srcy, 11744 const XftCharFontSpec *chars, 11745 int len); 11746 11747 void XftTextRender8 (Display *dpy, 11748 int op, 11749 Picture src, 11750 XftFont *pub, 11751 Picture dst, 11752 int srcx, 11753 int srcy, 11754 int x, 11755 int y, 11756 const char *string, 11757 int len); 11758 void XftTextRender16 (Display *dpy, 11759 int op, 11760 Picture src, 11761 XftFont *pub, 11762 Picture dst, 11763 int srcx, 11764 int srcy, 11765 int x, 11766 int y, 11767 const wchar *string, 11768 int len); 11769 11770 void XftTextRender16BE (Display *dpy, 11771 int op, 11772 Picture src, 11773 XftFont *pub, 11774 Picture dst, 11775 int srcx, 11776 int srcy, 11777 int x, 11778 int y, 11779 const char *string, 11780 int len); 11781 11782 void XftTextRender16LE (Display *dpy, 11783 int op, 11784 Picture src, 11785 XftFont *pub, 11786 Picture dst, 11787 int srcx, 11788 int srcy, 11789 int x, 11790 int y, 11791 const char *string, 11792 int len); 11793 11794 void XftTextRender32 (Display *dpy, 11795 int op, 11796 Picture src, 11797 XftFont *pub, 11798 Picture dst, 11799 int srcx, 11800 int srcy, 11801 int x, 11802 int y, 11803 const dchar *string, 11804 int len); 11805 11806 void XftTextRender32BE (Display *dpy, 11807 int op, 11808 Picture src, 11809 XftFont *pub, 11810 Picture dst, 11811 int srcx, 11812 int srcy, 11813 int x, 11814 int y, 11815 const char *string, 11816 int len); 11817 11818 void XftTextRender32LE (Display *dpy, 11819 int op, 11820 Picture src, 11821 XftFont *pub, 11822 Picture dst, 11823 int srcx, 11824 int srcy, 11825 int x, 11826 int y, 11827 const char *string, 11828 int len); 11829 11830 void XftTextRenderUtf8 (Display *dpy, 11831 int op, 11832 Picture src, 11833 XftFont *pub, 11834 Picture dst, 11835 int srcx, 11836 int srcy, 11837 int x, 11838 int y, 11839 const char *string, 11840 int len); 11841 11842 void XftTextRenderUtf16 (Display *dpy, 11843 int op, 11844 Picture src, 11845 XftFont *pub, 11846 Picture dst, 11847 int srcx, 11848 int srcy, 11849 int x, 11850 int y, 11851 const char *string, 11852 FcEndian endian, 11853 int len); 11854 FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete); 11855 11856 } 11857 11858 mixin DynamicLoad!(Xft, "Xft", 2) XftLibrary; 11859 11860 11861 /* Xft } */ 11862 11863 class XDisconnectException : Exception { 11864 bool userRequested; 11865 this(bool userRequested = true) { 11866 this.userRequested = userRequested; 11867 super("X disconnected"); 11868 } 11869 } 11870 11871 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a Display* 11872 class XDisplayConnection { 11873 private __gshared Display* display; 11874 private __gshared XIM xim; 11875 private __gshared char* displayName; 11876 11877 private __gshared int connectionSequence_; 11878 11879 /// use this for lazy caching when reconnection 11880 static int connectionSequenceNumber() { return connectionSequence_; } 11881 11882 /// Attempts recreation of state, may require application assistance 11883 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 11884 /// then call this, and if successful, reenter the loop. 11885 static void discardAndRecreate(string newDisplayString = null) { 11886 if(insideXEventLoop) 11887 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 11888 11889 // auto swnm = SimpleWindow.nativeMapping.dup; // this SHOULD be unnecessary because all simple windows are capable of handling native events, so the latter ought to do it all 11890 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 11891 11892 foreach(handle; chnenhm) { 11893 handle.discardConnectionState(); 11894 } 11895 11896 discardState(); 11897 11898 if(newDisplayString !is null) 11899 setDisplayName(newDisplayString); 11900 11901 auto display = get(); 11902 11903 foreach(handle; chnenhm) { 11904 handle.recreateAfterDisconnect(); 11905 } 11906 } 11907 11908 private __gshared EventMask rootEventMask; 11909 11910 /++ 11911 Requests the specified input from the root window on the connection, in addition to any other request. 11912 11913 11914 Since plain XSelectInput will replace the existing mask, adding input from multiple locations is tricky. This central function will combine all the masks for you. 11915 11916 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 11917 +/ 11918 static void addRootInput(EventMask mask) { 11919 auto old = rootEventMask; 11920 rootEventMask |= mask; 11921 get(); // to ensure display connected 11922 if(display !is null && rootEventMask != old) 11923 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 11924 } 11925 11926 static void discardState() { 11927 freeImages(); 11928 11929 foreach(atomPtr; interredAtoms) 11930 *atomPtr = 0; 11931 interredAtoms = null; 11932 interredAtoms.assumeSafeAppend(); 11933 11934 ScreenPainterImplementation.fontAttempted = false; 11935 ScreenPainterImplementation.defaultfont = null; 11936 ScreenPainterImplementation.defaultfontset = null; 11937 11938 Image.impl.xshmQueryCompleted = false; 11939 Image.impl._xshmAvailable = false; 11940 11941 SimpleWindow.nativeMapping = null; 11942 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 11943 // GlobalHotkeyManager 11944 11945 display = null; 11946 xim = null; 11947 } 11948 11949 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 11950 private static void createXIM () { 11951 import core.stdc.locale : setlocale, LC_ALL; 11952 import core.stdc.stdio : stderr, fprintf; 11953 import core.stdc.stdlib : free; 11954 import core.stdc..string : strdup; 11955 11956 static immutable string[3] mtry = [ null, "@im=local", "@im=" ]; 11957 11958 auto olocale = strdup(setlocale(LC_ALL, null)); 11959 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 11960 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 11961 11962 //fprintf(stderr, "opening IM...\n"); 11963 foreach (string s; mtry) { 11964 if (s.length) XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 11965 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 11966 } 11967 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 11968 } 11969 11970 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 11971 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 11972 static struct ImgList { 11973 size_t img; // class; hide it from GC 11974 ImgList* next; 11975 } 11976 11977 static __gshared ImgList* imglist = null; 11978 static __gshared bool imglistLocked = false; // true: don't register and unregister images 11979 11980 static void registerImage (Image img) { 11981 if (!imglistLocked && img !is null) { 11982 import core.stdc.stdlib : malloc; 11983 auto it = cast(ImgList*)malloc(ImgList.sizeof); 11984 assert(it !is null); // do proper checks 11985 it.img = cast(size_t)cast(void*)img; 11986 it.next = imglist; 11987 imglist = it; 11988 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 11989 } 11990 } 11991 11992 static void unregisterImage (Image img) { 11993 if (!imglistLocked && img !is null) { 11994 import core.stdc.stdlib : free; 11995 ImgList* prev = null; 11996 ImgList* cur = imglist; 11997 while (cur !is null) { 11998 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 11999 prev = cur; 12000 cur = cur.next; 12001 } 12002 if (cur !is null) { 12003 if (prev is null) imglist = cur.next; else prev.next = cur.next; 12004 free(cur); 12005 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 12006 } else { 12007 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 12008 } 12009 } 12010 } 12011 12012 static void freeImages () { // needed for discardAndRecreate 12013 imglistLocked = true; 12014 scope(exit) imglistLocked = false; 12015 ImgList* cur = imglist; 12016 ImgList* next = null; 12017 while (cur !is null) { 12018 import core.stdc.stdlib : free; 12019 next = cur.next; 12020 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 12021 (cast(Image)cast(void*)cur.img).dispose(); 12022 free(cur); 12023 cur = next; 12024 } 12025 imglist = null; 12026 } 12027 12028 /// can be used to override normal handling of display name 12029 /// from environment and/or command line 12030 static setDisplayName(string newDisplayName) { 12031 displayName = cast(char*) (newDisplayName ~ '\0'); 12032 } 12033 12034 /// resets to the default display string 12035 static resetDisplayName() { 12036 displayName = null; 12037 } 12038 12039 /// 12040 static Display* get() { 12041 if(display is null) { 12042 if(!librariesSuccessfullyLoaded) 12043 throw new Exception("Unable to load X11 client libraries"); 12044 display = XOpenDisplay(displayName); 12045 connectionSequence_++; 12046 if(display is null) 12047 throw new Exception("Unable to open X display"); 12048 XSetIOErrorHandler(&x11ioerrCB); 12049 Bool sup; 12050 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 12051 createXIM(); 12052 version(with_eventloop) { 12053 import arsd.eventloop; 12054 addFileEventListeners(display.fd, &eventListener, null, null); 12055 } 12056 } 12057 12058 return display; 12059 } 12060 12061 extern(C) 12062 static int x11ioerrCB(Display* dpy) { 12063 throw new XDisconnectException(false); 12064 } 12065 12066 version(with_eventloop) { 12067 import arsd.eventloop; 12068 static void eventListener(OsFileHandle fd) { 12069 //this.mtLock(); 12070 //scope(exit) this.mtUnlock(); 12071 while(XPending(display)) 12072 doXNextEvent(display); 12073 } 12074 } 12075 12076 // close connection on program exit -- we need this to properly free all images 12077 static ~this () { 12078 // the gui thread must clean up after itself or else Xlib might deadlock 12079 // using this flag on any thread destruction is the easiest way i know of 12080 // (shared static this is run by the LAST thread to exit, which may not be 12081 // the gui thread, and normal static this run by ALL threads, so we gotta check.) 12082 if(thisIsGuiThread) 12083 close(); 12084 } 12085 12086 /// 12087 static void close() { 12088 if(display is null) 12089 return; 12090 12091 version(with_eventloop) { 12092 import arsd.eventloop; 12093 removeFileEventListeners(display.fd); 12094 } 12095 12096 // now remove all registered images to prevent shared memory leaks 12097 freeImages(); 12098 12099 // tbh I don't know why it is doing this but like if this happens to run 12100 // from the other thread there's frequent hanging inside here. 12101 if(thisIsGuiThread) 12102 XCloseDisplay(display); 12103 display = null; 12104 } 12105 } 12106 12107 mixin template NativeImageImplementation() { 12108 XImage* handle; 12109 ubyte* rawData; 12110 12111 XShmSegmentInfo shminfo; 12112 12113 __gshared bool xshmQueryCompleted; 12114 __gshared bool _xshmAvailable; 12115 public static @property bool xshmAvailable() { 12116 if(!xshmQueryCompleted) { 12117 int i1, i2, i3; 12118 xshmQueryCompleted = true; 12119 12120 auto str = XDisplayConnection.get().display_name; 12121 // only if we are actually on the same machine does this 12122 // have any hope, and the query extension only asks if 12123 // the server can in theory, not in practice. 12124 if(str is null || (str[0] != ':' && str[0] != '/')) 12125 _xshmAvailable = false; 12126 else 12127 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 12128 } 12129 return _xshmAvailable; 12130 } 12131 12132 bool usingXshm; 12133 final: 12134 12135 void createImage(int width, int height, bool forcexshm=false) { 12136 auto display = XDisplayConnection.get(); 12137 assert(display !is null); 12138 auto screen = DefaultScreen(display); 12139 12140 // it will only use shared memory for somewhat largish images, 12141 // since otherwise we risk wasting shared memory handles on a lot of little ones 12142 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 12143 usingXshm = true; 12144 handle = XShmCreateImage( 12145 display, 12146 DefaultVisual(display, screen), 12147 24, 12148 ImageFormat.ZPixmap, 12149 null, 12150 &shminfo, 12151 width, height); 12152 assert(handle !is null); 12153 12154 assert(handle.bytes_per_line == 4 * width); 12155 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 12156 //import std.conv; import core.stdc.errno; 12157 assert(shminfo.shmid >= 0);//, to!string(errno)); 12158 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 12159 assert(rawData != cast(ubyte*) -1); 12160 shminfo.readOnly = 0; 12161 XShmAttach(display, &shminfo); 12162 XDisplayConnection.registerImage(this); 12163 // if I don't flush here there's a chance the dtor will run before the 12164 // ctor and lead to a bad value X error. While this hurts the efficiency 12165 // it is local anyway so prolly better to keep it simple 12166 XFlush(display); 12167 } else { 12168 if (forcexshm) throw new Exception("can't create XShm Image"); 12169 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 12170 import core.stdc.stdlib : malloc; 12171 rawData = cast(ubyte*) malloc(width * height * 4); 12172 12173 handle = XCreateImage( 12174 display, 12175 DefaultVisual(display, screen), 12176 24, // bpp 12177 ImageFormat.ZPixmap, 12178 0, // offset 12179 rawData, 12180 width, height, 12181 8 /* FIXME */, 4 * width); // padding, bytes per line 12182 } 12183 } 12184 12185 void dispose() { 12186 // note: this calls free(rawData) for us 12187 if(handle) { 12188 if (usingXshm) { 12189 XDisplayConnection.unregisterImage(this); 12190 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 12191 } 12192 XDestroyImage(handle); 12193 if(usingXshm) { 12194 shmdt(shminfo.shmaddr); 12195 shmctl(shminfo.shmid, IPC_RMID, null); 12196 } 12197 handle = null; 12198 } 12199 } 12200 12201 Color getPixel(int x, int y) { 12202 auto offset = (y * width + x) * 4; 12203 Color c; 12204 c.a = 255; 12205 c.b = rawData[offset + 0]; 12206 c.g = rawData[offset + 1]; 12207 c.r = rawData[offset + 2]; 12208 return c; 12209 } 12210 12211 void setPixel(int x, int y, Color c) { 12212 auto offset = (y * width + x) * 4; 12213 rawData[offset + 0] = c.b; 12214 rawData[offset + 1] = c.g; 12215 rawData[offset + 2] = c.r; 12216 } 12217 12218 void convertToRgbaBytes(ubyte[] where) { 12219 assert(where.length == this.width * this.height * 4); 12220 12221 // if rawData had a length.... 12222 //assert(rawData.length == where.length); 12223 for(int idx = 0; idx < where.length; idx += 4) { 12224 where[idx + 0] = rawData[idx + 2]; // r 12225 where[idx + 1] = rawData[idx + 1]; // g 12226 where[idx + 2] = rawData[idx + 0]; // b 12227 where[idx + 3] = 255; // a 12228 } 12229 } 12230 12231 void setFromRgbaBytes(in ubyte[] where) { 12232 assert(where.length == this.width * this.height * 4); 12233 12234 // if rawData had a length.... 12235 //assert(rawData.length == where.length); 12236 for(int idx = 0; idx < where.length; idx += 4) { 12237 rawData[idx + 2] = where[idx + 0]; // r 12238 rawData[idx + 1] = where[idx + 1]; // g 12239 rawData[idx + 0] = where[idx + 2]; // b 12240 //rawData[idx + 3] = 255; // a 12241 } 12242 } 12243 12244 } 12245 12246 mixin template NativeSimpleWindowImplementation() { 12247 GC gc; 12248 Window window; 12249 Display* display; 12250 12251 Pixmap buffer; 12252 int bufferw, bufferh; // size of the buffer; can be bigger than window 12253 XIC xic; // input context 12254 int curHidden = 0; // counter 12255 Cursor blankCurPtr = 0; 12256 int cursorSequenceNumber = 0; 12257 int warpEventCount = 0; // number of mouse movement events to eat 12258 12259 __gshared X11SetSelectionHandler[Atom] setSelectionHandlers; 12260 X11GetSelectionHandler[Atom] getSelectionHandlers; 12261 12262 version(without_opengl) {} else 12263 GLXContext glc; 12264 12265 private void fixFixedSize(bool forced=false) (int width, int height) { 12266 if (forced || this.resizability == Resizability.fixedSize) { 12267 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 12268 XSizeHints sh; 12269 static if (!forced) { 12270 c_long spr; 12271 XGetWMNormalHints(display, window, &sh, &spr); 12272 sh.flags |= PMaxSize | PMinSize; 12273 } else { 12274 sh.flags = PMaxSize | PMinSize; 12275 } 12276 sh.min_width = width; 12277 sh.min_height = height; 12278 sh.max_width = width; 12279 sh.max_height = height; 12280 XSetWMNormalHints(display, window, &sh); 12281 //XFlush(display); 12282 } 12283 } 12284 12285 ScreenPainter getPainter() { 12286 return ScreenPainter(this, window); 12287 } 12288 12289 void move(int x, int y) { 12290 XMoveWindow(display, window, x, y); 12291 } 12292 12293 void resize(int w, int h) { 12294 if (w < 1) w = 1; 12295 if (h < 1) h = 1; 12296 XResizeWindow(display, window, w, h); 12297 12298 // calling this now to avoid waiting for the server to 12299 // acknowledge the resize; draws without returning to the 12300 // event loop will thus actually work. the server's event 12301 // btw might overrule this and resize it again 12302 recordX11Resize(display, this, w, h); 12303 12304 // FIXME: do we need to set this as the opengl context to do the glViewport change? 12305 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 12306 } 12307 12308 void moveResize (int x, int y, int w, int h) { 12309 if (w < 1) w = 1; 12310 if (h < 1) h = 1; 12311 XMoveResizeWindow(display, window, x, y, w, h); 12312 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 12313 } 12314 12315 void hideCursor () { 12316 if (curHidden++ == 0) { 12317 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 12318 static const(char)[1] cmbmp = 0; 12319 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 12320 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 12321 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 12322 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 12323 XFreePixmap(display, pm); 12324 } 12325 XDefineCursor(display, window, blankCurPtr); 12326 } 12327 } 12328 12329 void showCursor () { 12330 if (--curHidden == 0) XUndefineCursor(display, window); 12331 } 12332 12333 void warpMouse (int x, int y) { 12334 // here i will send dummy "ignore next mouse motion" event, 12335 // 'cause `XWarpPointer()` sends synthesised mouse motion, 12336 // and we don't need to report it to the user (as warping is 12337 // used when the user needs movement deltas). 12338 //XClientMessageEvent xclient; 12339 XEvent e; 12340 e.xclient.type = EventType.ClientMessage; 12341 e.xclient.window = window; 12342 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 12343 e.xclient.format = 32; 12344 e.xclient.data.l[0] = 0; 12345 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 12346 //{ import core.stdc.stdio : printf; printf("*X11 CLIENT: w=%u; type=%u; [0]=%u\n", cast(uint)e.xclient.window, cast(uint)e.xclient.message_type, cast(uint)e.xclient.data.l[0]); } 12347 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 12348 // now warp pointer... 12349 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 12350 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 12351 // ...and flush 12352 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 12353 XFlush(display); 12354 } 12355 12356 void sendDummyEvent () { 12357 // here i will send dummy event to ping event queue 12358 XEvent e; 12359 e.xclient.type = EventType.ClientMessage; 12360 e.xclient.window = window; 12361 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 12362 e.xclient.format = 32; 12363 e.xclient.data.l[0] = 0; 12364 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 12365 XFlush(display); 12366 } 12367 12368 void setTitle(string title) { 12369 if (title.ptr is null) title = ""; 12370 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 12371 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 12372 XTextProperty windowName; 12373 windowName.value = title.ptr; 12374 windowName.encoding = XA_UTF8; //XA_STRING; 12375 windowName.format = 8; 12376 windowName.nitems = cast(uint)title.length; 12377 XSetWMName(display, window, &windowName); 12378 char[1024] namebuf = 0; 12379 auto maxlen = namebuf.length-1; 12380 if (maxlen > title.length) maxlen = title.length; 12381 namebuf[0..maxlen] = title[0..maxlen]; 12382 XStoreName(display, window, namebuf.ptr); 12383 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 12384 flushGui(); // without this OpenGL windows has a LONG delay before changing title 12385 } 12386 12387 string[] getTitles() { 12388 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 12389 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 12390 XTextProperty textProp; 12391 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 12392 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 12393 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 12394 } else 12395 return []; 12396 } else 12397 return null; 12398 } 12399 12400 string getTitle() { 12401 auto titles = getTitles(); 12402 return titles.length ? titles[0] : null; 12403 } 12404 12405 void setMinSize (int minwidth, int minheight) { 12406 import core.stdc.config : c_long; 12407 if (minwidth < 1) minwidth = 1; 12408 if (minheight < 1) minheight = 1; 12409 XSizeHints sh; 12410 c_long spr; 12411 XGetWMNormalHints(display, window, &sh, &spr); 12412 sh.min_width = minwidth; 12413 sh.min_height = minheight; 12414 sh.flags |= PMinSize; 12415 XSetWMNormalHints(display, window, &sh); 12416 flushGui(); 12417 } 12418 12419 void setMaxSize (int maxwidth, int maxheight) { 12420 import core.stdc.config : c_long; 12421 if (maxwidth < 1) maxwidth = 1; 12422 if (maxheight < 1) maxheight = 1; 12423 XSizeHints sh; 12424 c_long spr; 12425 XGetWMNormalHints(display, window, &sh, &spr); 12426 sh.max_width = maxwidth; 12427 sh.max_height = maxheight; 12428 sh.flags |= PMaxSize; 12429 XSetWMNormalHints(display, window, &sh); 12430 flushGui(); 12431 } 12432 12433 void setResizeGranularity (int granx, int grany) { 12434 import core.stdc.config : c_long; 12435 if (granx < 1) granx = 1; 12436 if (grany < 1) grany = 1; 12437 XSizeHints sh; 12438 c_long spr; 12439 XGetWMNormalHints(display, window, &sh, &spr); 12440 sh.width_inc = granx; 12441 sh.height_inc = grany; 12442 sh.flags |= PResizeInc; 12443 XSetWMNormalHints(display, window, &sh); 12444 flushGui(); 12445 } 12446 12447 void setOpacity (uint opacity) { 12448 arch_ulong o = opacity; 12449 if (opacity == uint.max) 12450 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 12451 else 12452 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 12453 XA_CARDINAL, 32, PropModeReplace, &o, 1); 12454 } 12455 12456 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) { 12457 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 12458 display = XDisplayConnection.get(); 12459 auto screen = DefaultScreen(display); 12460 12461 version(without_opengl) {} 12462 else { 12463 if(opengl == OpenGlOptions.yes) { 12464 GLXFBConfig fbconf = null; 12465 XVisualInfo* vi = null; 12466 bool useLegacy = false; 12467 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 12468 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 12469 int[23] visualAttribs = [ 12470 GLX_X_RENDERABLE , 1/*True*/, 12471 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 12472 GLX_RENDER_TYPE , GLX_RGBA_BIT, 12473 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 12474 GLX_RED_SIZE , 8, 12475 GLX_GREEN_SIZE , 8, 12476 GLX_BLUE_SIZE , 8, 12477 GLX_ALPHA_SIZE , 8, 12478 GLX_DEPTH_SIZE , 24, 12479 GLX_STENCIL_SIZE , 8, 12480 GLX_DOUBLEBUFFER , 1/*True*/, 12481 0/*None*/, 12482 ]; 12483 int fbcount; 12484 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 12485 if (fbcount == 0) { 12486 useLegacy = true; // try to do at least something 12487 } else { 12488 // pick the FB config/visual with the most samples per pixel 12489 int bestidx = -1, bestns = -1; 12490 foreach (int fbi; 0..fbcount) { 12491 int sb, samples; 12492 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 12493 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 12494 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 12495 } 12496 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 12497 fbconf = fbc[bestidx]; 12498 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 12499 XFree(fbc); 12500 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 12501 } 12502 } 12503 if (vi is null || useLegacy) { 12504 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 12505 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 12506 useLegacy = true; 12507 } 12508 if (vi is null) throw new Exception("no open gl visual found"); 12509 12510 XSetWindowAttributes swa; 12511 auto root = RootWindow(display, screen); 12512 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 12513 12514 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 12515 0, 0, width, height, 12516 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap, &swa); 12517 12518 // now try to use `glXCreateContextAttribsARB()` if it's here 12519 if (!useLegacy) { 12520 // request fairly advanced context, even with stencil buffer! 12521 int[9] contextAttribs = [ 12522 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 12523 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 12524 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 12525 // for modern context, set "forward compatibility" flag too 12526 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 12527 0/*None*/, 12528 ]; 12529 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 12530 if (glc is null && sdpyOpenGLContextAllowFallback) { 12531 sdpyOpenGLContextVersion = 0; 12532 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 12533 } 12534 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 12535 } else { 12536 // fallback to old GLX call 12537 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 12538 sdpyOpenGLContextVersion = 0; 12539 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 12540 } 12541 } 12542 // sync to ensure any errors generated are processed 12543 XSync(display, 0/*False*/); 12544 //{ import core.stdc.stdio; printf("ogl is here\n"); } 12545 if(glc is null) 12546 throw new Exception("glc"); 12547 } 12548 } 12549 12550 if(opengl == OpenGlOptions.no) { 12551 12552 bool overrideRedirect = false; 12553 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification) 12554 overrideRedirect = true; 12555 12556 XSetWindowAttributes swa; 12557 swa.background_pixel = WhitePixel(display, screen); 12558 swa.border_pixel = BlackPixel(display, screen); 12559 swa.override_redirect = overrideRedirect; 12560 auto root = RootWindow(display, screen); 12561 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 12562 12563 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 12564 0, 0, width, height, 12565 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap | CWBackPixel | CWBorderPixel | CWOverrideRedirect, &swa); 12566 12567 12568 12569 /* 12570 window = XCreateSimpleWindow( 12571 display, 12572 parent is null ? RootWindow(display, screen) : parent.impl.window, 12573 0, 0, // x, y 12574 width, height, 12575 1, // border width 12576 BlackPixel(display, screen), // border 12577 WhitePixel(display, screen)); // background 12578 */ 12579 12580 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 12581 bufferw = width; 12582 bufferh = height; 12583 12584 gc = DefaultGC(display, screen); 12585 12586 // clear out the buffer to get us started... 12587 XSetForeground(display, gc, WhitePixel(display, screen)); 12588 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 12589 XSetForeground(display, gc, BlackPixel(display, screen)); 12590 } 12591 12592 // input context 12593 //TODO: create this only for top-level windows, and reuse that? 12594 if (XDisplayConnection.xim !is null) { 12595 xic = XCreateIC(XDisplayConnection.xim, 12596 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 12597 /*XNClientWindow*/"clientWindow".ptr, window, 12598 /*XNFocusWindow*/"focusWindow".ptr, window, 12599 null); 12600 if (xic is null) { 12601 import core.stdc.stdio : stderr, fprintf; 12602 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 12603 } 12604 } 12605 12606 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 12607 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 12608 // window class 12609 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 12610 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 12611 XClassHint klass; 12612 XWMHints wh; 12613 XSizeHints size; 12614 klass.res_name = sdpyWindowClassStr; 12615 klass.res_class = sdpyWindowClassStr; 12616 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 12617 } 12618 12619 setTitle(title); 12620 SimpleWindow.nativeMapping[window] = this; 12621 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 12622 12623 // This gives our window a close button 12624 if (windowType != WindowTypes.eventOnly) { 12625 // FIXME: actually implement the WM_TAKE_FOCUS correctly 12626 //Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)]; 12627 Atom[1] atoms = [GetAtom!"WM_DELETE_WINDOW"(display)]; 12628 XSetWMProtocols(display, window, atoms.ptr, cast(int) atoms.length); 12629 } 12630 12631 // FIXME: windowType and customizationFlags 12632 Atom[8] wsatoms; // here, due to goto 12633 int wmsacount = 0; // here, due to goto 12634 12635 try 12636 final switch(windowType) { 12637 case WindowTypes.normal: 12638 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 12639 break; 12640 case WindowTypes.undecorated: 12641 motifHideDecorations(); 12642 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 12643 break; 12644 case WindowTypes.eventOnly: 12645 _hidden = true; 12646 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 12647 goto hiddenWindow; 12648 //break; 12649 case WindowTypes.nestedChild: 12650 // handled in XCreateWindow calls 12651 break; 12652 12653 case WindowTypes.dropdownMenu: 12654 motifHideDecorations(); 12655 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 12656 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 12657 break; 12658 case WindowTypes.popupMenu: 12659 motifHideDecorations(); 12660 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 12661 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 12662 break; 12663 case WindowTypes.notification: 12664 motifHideDecorations(); 12665 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 12666 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 12667 break; 12668 /+ 12669 case WindowTypes.menu: 12670 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 12671 motifHideDecorations(); 12672 break; 12673 case WindowTypes.desktop: 12674 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 12675 break; 12676 case WindowTypes.dock: 12677 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 12678 break; 12679 case WindowTypes.toolbar: 12680 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 12681 break; 12682 case WindowTypes.menu: 12683 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 12684 break; 12685 case WindowTypes.utility: 12686 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 12687 break; 12688 case WindowTypes.splash: 12689 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 12690 break; 12691 case WindowTypes.dialog: 12692 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display); 12693 break; 12694 case WindowTypes.tooltip: 12695 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display); 12696 break; 12697 case WindowTypes.notification: 12698 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 12699 break; 12700 case WindowTypes.combo: 12701 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display); 12702 break; 12703 case WindowTypes.dnd: 12704 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display); 12705 break; 12706 +/ 12707 } 12708 catch(Exception e) { 12709 // XInternAtom failed, prolly a WM 12710 // that doesn't support these things 12711 } 12712 12713 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 12714 // the two following flags may be ignored by WM 12715 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 12716 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 12717 12718 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 12719 12720 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 12721 12722 // What would be ideal here is if they only were 12723 // selected if there was actually an event handler 12724 // for them... 12725 12726 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 12727 12728 hiddenWindow: 12729 12730 // set the pid property for lookup later by window managers 12731 // a standard convenience 12732 import core.sys.posix.unistd; 12733 arch_ulong pid = getpid(); 12734 12735 XChangeProperty( 12736 display, 12737 impl.window, 12738 GetAtom!("_NET_WM_PID", true)(display), 12739 XA_CARDINAL, 12740 32 /* bits */, 12741 0 /*PropModeReplace*/, 12742 &pid, 12743 1); 12744 12745 if(isTransient && parent) { // customizationFlags & WindowFlags.transient) { 12746 if(parent is null) assert(0); 12747 XChangeProperty( 12748 display, 12749 impl.window, 12750 GetAtom!("WM_TRANSIENT_FOR", true)(display), 12751 XA_WINDOW, 12752 32 /* bits */, 12753 0 /*PropModeReplace*/, 12754 &parent.impl.window, 12755 1); 12756 12757 } 12758 12759 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 12760 XMapWindow(display, window); 12761 } else { 12762 _hidden = true; 12763 } 12764 } 12765 12766 void selectDefaultInput(bool forceIncludeMouseMotion) { 12767 auto mask = EventMask.ExposureMask | 12768 EventMask.KeyPressMask | 12769 EventMask.KeyReleaseMask | 12770 EventMask.PropertyChangeMask | 12771 EventMask.FocusChangeMask | 12772 EventMask.StructureNotifyMask | 12773 EventMask.VisibilityChangeMask 12774 | EventMask.ButtonPressMask 12775 | EventMask.ButtonReleaseMask 12776 ; 12777 12778 // xshm is our shortcut for local connections 12779 if(Image.impl.xshmAvailable || forceIncludeMouseMotion) 12780 mask |= EventMask.PointerMotionMask; 12781 else 12782 mask |= EventMask.ButtonMotionMask; 12783 12784 XSelectInput(display, window, mask); 12785 } 12786 12787 12788 void setNetWMWindowType(Atom type) { 12789 Atom[2] atoms; 12790 12791 atoms[0] = type; 12792 // generic fallback 12793 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 12794 12795 XChangeProperty( 12796 display, 12797 impl.window, 12798 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 12799 XA_ATOM, 12800 32 /* bits */, 12801 0 /*PropModeReplace*/, 12802 atoms.ptr, 12803 cast(int) atoms.length); 12804 } 12805 12806 void motifHideDecorations() { 12807 MwmHints hints; 12808 hints.flags = MWM_HINTS_DECORATIONS; 12809 12810 XChangeProperty( 12811 display, 12812 impl.window, 12813 GetAtom!"_MOTIF_WM_HINTS"(display), 12814 GetAtom!"_MOTIF_WM_HINTS"(display), 12815 32 /* bits */, 12816 0 /*PropModeReplace*/, 12817 &hints, 12818 hints.sizeof / 4); 12819 } 12820 12821 /*k8: unused 12822 void createOpenGlContext() { 12823 12824 } 12825 */ 12826 12827 void closeWindow() { 12828 // I can't close this or a child window closing will 12829 // break events for everyone. So I'm just leaking it right 12830 // now and that is probably perfectly fine... 12831 version(none) 12832 if (customEventFDRead != -1) { 12833 import core.sys.posix.unistd : close; 12834 auto same = customEventFDRead == customEventFDWrite; 12835 12836 close(customEventFDRead); 12837 if(!same) 12838 close(customEventFDWrite); 12839 customEventFDRead = -1; 12840 customEventFDWrite = -1; 12841 } 12842 if(buffer) 12843 XFreePixmap(display, buffer); 12844 bufferw = bufferh = 0; 12845 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 12846 XDestroyWindow(display, window); 12847 XFlush(display); 12848 } 12849 12850 void dispose() { 12851 } 12852 12853 bool destroyed = false; 12854 } 12855 12856 bool insideXEventLoop; 12857 } 12858 12859 version(X11) { 12860 12861 int mouseDoubleClickTimeout = 350; /// double click timeout. X only, you probably shouldn't change this. 12862 12863 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 12864 if(width != win.width || height != win.height) { 12865 win._width = width; 12866 win._height = height; 12867 12868 if(win.openglMode == OpenGlOptions.no) { 12869 // FIXME: could this be more efficient? 12870 12871 if (win.bufferw < width || win.bufferh < height) { 12872 //{ import core.stdc.stdio; printf("new buffer; old size: %dx%d; new size: %dx%d\n", win.bufferw, win.bufferh, cast(int)width, cast(int)height); } 12873 // grow the internal buffer to match the window... 12874 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 12875 { 12876 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 12877 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 12878 scope(exit) XFreeGC(win.display, xgc); 12879 XSetClipMask(win.display, xgc, None); 12880 XSetForeground(win.display, xgc, 0); 12881 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 12882 } 12883 XCopyArea(display, 12884 cast(Drawable) win.buffer, 12885 cast(Drawable) newPixmap, 12886 win.gc, 0, 0, 12887 win.bufferw < width ? win.bufferw : win.width, 12888 win.bufferh < height ? win.bufferh : win.height, 12889 0, 0); 12890 12891 XFreePixmap(display, win.buffer); 12892 win.buffer = newPixmap; 12893 win.bufferw = width; 12894 win.bufferh = height; 12895 } 12896 12897 // clear unused parts of the buffer 12898 if (win.bufferw > width || win.bufferh > height) { 12899 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 12900 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 12901 scope(exit) XFreeGC(win.display, xgc); 12902 XSetClipMask(win.display, xgc, None); 12903 XSetForeground(win.display, xgc, 0); 12904 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 12905 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 12906 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 12907 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 12908 } 12909 12910 } 12911 12912 version(without_opengl) {} else 12913 if(win.openglMode == OpenGlOptions.yes && win.resizability == Resizability.automaticallyScaleIfPossible) { 12914 glViewport(0, 0, width, height); 12915 } 12916 12917 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 12918 12919 if(win.windowResized !is null) { 12920 XUnlockDisplay(display); 12921 scope(exit) XLockDisplay(display); 12922 win.windowResized(width, height); 12923 } 12924 } 12925 } 12926 12927 12928 /// Platform-specific, you might use it when doing a custom event loop 12929 bool doXNextEvent(Display* display) { 12930 bool done; 12931 XEvent e; 12932 XNextEvent(display, &e); 12933 version(sddddd) { 12934 import std.stdio, std.conv : to; 12935 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 12936 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 12937 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 12938 } 12939 } 12940 12941 // filter out compose events 12942 if (XFilterEvent(&e, None)) { 12943 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 12944 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 12945 return false; 12946 } 12947 // process keyboard mapping changes 12948 if (e.type == EventType.KeymapNotify) { 12949 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 12950 XRefreshKeyboardMapping(&e.xmapping); 12951 return false; 12952 } 12953 12954 version(with_eventloop) 12955 import arsd.eventloop; 12956 12957 if(SimpleWindow.handleNativeGlobalEvent !is null) { 12958 // see windows impl's comments 12959 XUnlockDisplay(display); 12960 scope(exit) XLockDisplay(display); 12961 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 12962 if(ret == 0) 12963 return done; 12964 } 12965 12966 12967 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 12968 if(win.getNativeEventHandler !is null) { 12969 XUnlockDisplay(display); 12970 scope(exit) XLockDisplay(display); 12971 auto ret = win.getNativeEventHandler()(e); 12972 if(ret == 0) 12973 return done; 12974 } 12975 } 12976 12977 switch(e.type) { 12978 case EventType.SelectionClear: 12979 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) { 12980 // FIXME so it is supposed to finish any in progress transfers... but idk... 12981 //import std.stdio; writeln("SelectionClear"); 12982 SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection); 12983 } 12984 break; 12985 case EventType.SelectionRequest: 12986 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 12987 if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) { 12988 // import std.stdio; printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target)); 12989 XUnlockDisplay(display); 12990 scope(exit) XLockDisplay(display); 12991 (*ssh).handleRequest(e); 12992 } 12993 break; 12994 case EventType.PropertyNotify: 12995 // import std.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state); 12996 12997 foreach(ssh; SimpleWindow.impl.setSelectionHandlers) { 12998 if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete) 12999 ssh.sendMoreIncr(&e.xproperty); 13000 } 13001 13002 13003 if(auto win = e.xproperty.window in SimpleWindow.nativeMapping) 13004 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 13005 if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) { 13006 Atom target; 13007 int format; 13008 arch_ulong bytesafter, length; 13009 void* value; 13010 13011 ubyte[] s; 13012 Atom targetToKeep; 13013 13014 XGetWindowProperty( 13015 e.xproperty.display, 13016 e.xproperty.window, 13017 e.xproperty.atom, 13018 0, 13019 100000 /* length */, 13020 true, /* erase it to signal we got it and want more */ 13021 0 /*AnyPropertyType*/, 13022 &target, &format, &length, &bytesafter, &value); 13023 13024 if(!targetToKeep) 13025 targetToKeep = target; 13026 13027 auto id = (cast(ubyte*) value)[0 .. length]; 13028 13029 handler.handleIncrData(targetToKeep, id); 13030 13031 XFree(value); 13032 } 13033 } 13034 break; 13035 case EventType.SelectionNotify: 13036 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 13037 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 13038 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 13039 XUnlockDisplay(display); 13040 scope(exit) XLockDisplay(display); 13041 handler.handleData(None, null); 13042 } else { 13043 Atom target; 13044 int format; 13045 arch_ulong bytesafter, length; 13046 void* value; 13047 XGetWindowProperty( 13048 e.xselection.display, 13049 e.xselection.requestor, 13050 e.xselection.property, 13051 0, 13052 100000 /* length */, 13053 //false, /* don't erase it */ 13054 true, /* do erase it lol */ 13055 0 /*AnyPropertyType*/, 13056 &target, &format, &length, &bytesafter, &value); 13057 13058 // FIXME: I don't have to copy it now since it is in char[] instead of string 13059 13060 { 13061 XUnlockDisplay(display); 13062 scope(exit) XLockDisplay(display); 13063 13064 if(target == XA_ATOM) { 13065 // initial request, see what they are able to work with and request the best one 13066 // we can handle, if available 13067 13068 Atom[] answer = (cast(Atom*) value)[0 .. length]; 13069 Atom best = handler.findBestFormat(answer); 13070 13071 /+ 13072 writeln("got ", answer); 13073 foreach(a; answer) 13074 printf("%s\n", XGetAtomName(display, a)); 13075 writeln("best ", best); 13076 +/ 13077 13078 if(best != None) { 13079 // actually request the best format 13080 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 13081 } 13082 } else if(target == GetAtom!"INCR"(display)) { 13083 // incremental 13084 13085 handler.prepareIncremental(e.xselection.requestor, e.xselection.property); 13086 13087 // signal the sending program that we see 13088 // the incr and are ready to receive more. 13089 XDeleteProperty( 13090 e.xselection.display, 13091 e.xselection.requestor, 13092 e.xselection.property); 13093 } else { 13094 // unsupported type... maybe, forward 13095 handler.handleData(target, cast(ubyte[]) value[0 .. length]); 13096 } 13097 } 13098 XFree(value); 13099 /* 13100 XDeleteProperty( 13101 e.xselection.display, 13102 e.xselection.requestor, 13103 e.xselection.property); 13104 */ 13105 } 13106 } 13107 break; 13108 case EventType.ConfigureNotify: 13109 auto event = e.xconfigure; 13110 if(auto win = event.window in SimpleWindow.nativeMapping) { 13111 //version(sdddd) { import std.stdio; writeln(" w=", event.width, "; h=", event.height); } 13112 13113 recordX11Resize(display, *win, event.width, event.height); 13114 } 13115 break; 13116 case EventType.Expose: 13117 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 13118 // if it is closing from a popup menu, it can get 13119 // an Expose event right by the end and trigger a 13120 // BadDrawable error ... we'll just check 13121 // closed to handle that. 13122 if((*win).closed) break; 13123 if((*win).openglMode == OpenGlOptions.no) { 13124 bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh 13125 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 13126 if (doCopy) XCopyArea(display, cast(Drawable) (*win).buffer, cast(Drawable) (*win).window, (*win).gc, e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.x, e.xexpose.y); 13127 } else { 13128 // need to redraw the scene somehow 13129 if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all 13130 XUnlockDisplay(display); 13131 scope(exit) XLockDisplay(display); 13132 version(without_opengl) {} else 13133 win.redrawOpenGlSceneSoon(); 13134 } 13135 } 13136 } 13137 break; 13138 case EventType.FocusIn: 13139 case EventType.FocusOut: 13140 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 13141 if (win.xic !is null) { 13142 //{ import core.stdc.stdio : printf; printf("XIC focus change!\n"); } 13143 if (e.type == EventType.FocusIn) XSetICFocus(win.xic); else XUnsetICFocus(win.xic); 13144 } 13145 13146 win._focused = e.type == EventType.FocusIn; 13147 13148 if(win.demandingAttention) 13149 demandAttention(*win, false); 13150 13151 if(win.onFocusChange) { 13152 XUnlockDisplay(display); 13153 scope(exit) XLockDisplay(display); 13154 win.onFocusChange(e.type == EventType.FocusIn); 13155 } 13156 } 13157 break; 13158 case EventType.VisibilityNotify: 13159 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 13160 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 13161 if (win.visibilityChanged !is null) { 13162 XUnlockDisplay(display); 13163 scope(exit) XLockDisplay(display); 13164 win.visibilityChanged(false); 13165 } 13166 } else { 13167 if (win.visibilityChanged !is null) { 13168 XUnlockDisplay(display); 13169 scope(exit) XLockDisplay(display); 13170 win.visibilityChanged(true); 13171 } 13172 } 13173 } 13174 break; 13175 case EventType.ClientMessage: 13176 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 13177 // "ignore next mouse motion" event, increment ignore counter for teh window 13178 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 13179 ++(*win).warpEventCount; 13180 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 13181 } else { 13182 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 13183 } 13184 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 13185 // user clicked the close button on the window manager 13186 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 13187 XUnlockDisplay(display); 13188 scope(exit) XLockDisplay(display); 13189 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 13190 } 13191 13192 } else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) { 13193 import std.stdio; writeln("HAPPENED"); 13194 // user clicked the close button on the window manager 13195 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 13196 XUnlockDisplay(display); 13197 scope(exit) XLockDisplay(display); 13198 13199 // FIXME: so this is actually supposed to focus to a relevant child window if appropriate 13200 XSetInputFocus(display, e.xclient.window, RevertToParent, e.xclient.data.l[1]); 13201 } 13202 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 13203 foreach(nai; NotificationAreaIcon.activeIcons) 13204 nai.newManager(); 13205 } else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 13206 13207 bool xDragWindow = true; 13208 if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) { 13209 //XDefineCursor(display, xDragWindow.impl.window, 13210 //import std.stdio; writeln("XdndStatus ", e.xclient.data.l); 13211 } 13212 if(auto dh = win.dropHandler) { 13213 13214 static Atom[3] xFormatsBuffer; 13215 static Atom[] xFormats; 13216 13217 void resetXFormats() { 13218 xFormatsBuffer[] = 0; 13219 xFormats = xFormatsBuffer[]; 13220 } 13221 13222 if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) { 13223 // on Windows it is supposed to return the effect you actually do FIXME 13224 13225 auto sourceWindow = e.xclient.data.l[0]; 13226 13227 xFormatsBuffer[0] = e.xclient.data.l[2]; 13228 xFormatsBuffer[1] = e.xclient.data.l[3]; 13229 xFormatsBuffer[2] = e.xclient.data.l[4]; 13230 13231 if(e.xclient.data.l[1] & 1) { 13232 // can just grab it all but like we don't necessarily need them... 13233 xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM); 13234 } else { 13235 int len; 13236 foreach(fmt; xFormatsBuffer) 13237 if(fmt) len++; 13238 xFormats = xFormatsBuffer[0 .. len]; 13239 } 13240 13241 auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats); 13242 13243 dh.dragEnter(&pkg); 13244 } else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) { 13245 13246 auto pack = e.xclient.data.l[2]; 13247 13248 auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords 13249 13250 13251 XClientMessageEvent xclient; 13252 13253 xclient.type = EventType.ClientMessage; 13254 xclient.window = e.xclient.data.l[0]; 13255 xclient.message_type = GetAtom!"XdndStatus"(display); 13256 xclient.format = 32; 13257 xclient.data.l[0] = win.impl.window; 13258 xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept 13259 auto r = result.consistentWithin; 13260 xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top); 13261 xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height); 13262 xclient.data.l[4] = dndActionAtom(e.xany.display, result.action); 13263 13264 XSendEvent( 13265 display, 13266 e.xclient.data.l[0], 13267 false, 13268 EventMask.NoEventMask, 13269 cast(XEvent*) &xclient 13270 ); 13271 13272 13273 } else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) { 13274 //import std.stdio; writeln("XdndLeave"); 13275 // drop cancelled. 13276 // data.l[0] is the source window 13277 dh.dragLeave(); 13278 13279 resetXFormats(); 13280 } else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) { 13281 // drop happening, should fetch data, then send finished 13282 //import std.stdio; writeln("XdndDrop"); 13283 13284 auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats); 13285 13286 dh.drop(&pkg); 13287 13288 resetXFormats(); 13289 } else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) { 13290 // import std.stdio; writeln("XdndFinished"); 13291 13292 dh.finish(); 13293 } 13294 13295 } 13296 } 13297 break; 13298 case EventType.MapNotify: 13299 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 13300 (*win)._visible = true; 13301 if (!(*win)._visibleForTheFirstTimeCalled) { 13302 (*win)._visibleForTheFirstTimeCalled = true; 13303 if ((*win).visibleForTheFirstTime !is null) { 13304 XUnlockDisplay(display); 13305 scope(exit) XLockDisplay(display); 13306 version(without_opengl) {} else { 13307 if((*win).openglMode == OpenGlOptions.yes) { 13308 (*win).setAsCurrentOpenGlContextNT(); 13309 glViewport(0, 0, (*win).width, (*win).height); 13310 } 13311 } 13312 (*win).visibleForTheFirstTime(); 13313 } 13314 } 13315 if ((*win).visibilityChanged !is null) { 13316 XUnlockDisplay(display); 13317 scope(exit) XLockDisplay(display); 13318 (*win).visibilityChanged(true); 13319 } 13320 } 13321 break; 13322 case EventType.UnmapNotify: 13323 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 13324 win._visible = false; 13325 if (win.visibilityChanged !is null) { 13326 XUnlockDisplay(display); 13327 scope(exit) XLockDisplay(display); 13328 win.visibilityChanged(false); 13329 } 13330 } 13331 break; 13332 case EventType.DestroyNotify: 13333 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 13334 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 13335 win._closed = true; // just in case 13336 win.destroyed = true; 13337 if (win.xic !is null) { 13338 XDestroyIC(win.xic); 13339 win.xic = null; // just in calse 13340 } 13341 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 13342 bool anyImportant = false; 13343 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 13344 if(w.beingOpenKeepsAppOpen) { 13345 anyImportant = true; 13346 break; 13347 } 13348 if(!anyImportant) 13349 done = true; 13350 } 13351 auto window = e.xdestroywindow.window; 13352 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 13353 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 13354 13355 version(with_eventloop) { 13356 if(done) exit(); 13357 } 13358 break; 13359 13360 case EventType.MotionNotify: 13361 MouseEvent mouse; 13362 auto event = e.xmotion; 13363 13364 mouse.type = MouseEventType.motion; 13365 mouse.x = event.x; 13366 mouse.y = event.y; 13367 mouse.modifierState = event.state; 13368 13369 mouse.timestamp = event.time; 13370 13371 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 13372 mouse.window = *win; 13373 if (win.warpEventCount > 0) { 13374 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 13375 --(*win).warpEventCount; 13376 (*win).mdx(mouse); // so deltas will be correctly updated 13377 } else { 13378 win.warpEventCount = 0; // just in case 13379 (*win).mdx(mouse); 13380 if((*win).handleMouseEvent) { 13381 XUnlockDisplay(display); 13382 scope(exit) XLockDisplay(display); 13383 (*win).handleMouseEvent(mouse); 13384 } 13385 } 13386 } 13387 13388 version(with_eventloop) 13389 send(mouse); 13390 break; 13391 case EventType.ButtonPress: 13392 case EventType.ButtonRelease: 13393 MouseEvent mouse; 13394 auto event = e.xbutton; 13395 13396 mouse.timestamp = event.time; 13397 13398 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 13399 mouse.x = event.x; 13400 mouse.y = event.y; 13401 13402 static Time lastMouseDownTime = 0; 13403 13404 mouse.doubleClick = e.type == EventType.ButtonPress && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 13405 if(e.type == EventType.ButtonPress) lastMouseDownTime = event.time; 13406 13407 switch(event.button) { 13408 case 1: mouse.button = MouseButton.left; break; // left 13409 case 2: mouse.button = MouseButton.middle; break; // middle 13410 case 3: mouse.button = MouseButton.right; break; // right 13411 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 13412 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 13413 case 6: break; // idk 13414 case 7: break; // idk 13415 case 8: mouse.button = MouseButton.backButton; break; 13416 case 9: mouse.button = MouseButton.forwardButton; break; 13417 default: 13418 } 13419 13420 // FIXME: double check this 13421 mouse.modifierState = event.state; 13422 13423 //mouse.modifierState = event.detail; 13424 13425 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 13426 mouse.window = *win; 13427 (*win).mdx(mouse); 13428 if((*win).handleMouseEvent) { 13429 XUnlockDisplay(display); 13430 scope(exit) XLockDisplay(display); 13431 (*win).handleMouseEvent(mouse); 13432 } 13433 } 13434 version(with_eventloop) 13435 send(mouse); 13436 break; 13437 13438 case EventType.KeyPress: 13439 case EventType.KeyRelease: 13440 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 13441 KeyEvent ke; 13442 ke.pressed = e.type == EventType.KeyPress; 13443 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 13444 13445 auto sym = XKeycodeToKeysym( 13446 XDisplayConnection.get(), 13447 e.xkey.keycode, 13448 0); 13449 13450 ke.key = cast(Key) sym;//e.xkey.keycode; 13451 13452 ke.modifierState = e.xkey.state; 13453 13454 // import std.stdio; writefln("%x", sym); 13455 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 13456 int charbuflen = 0; // return value of XwcLookupString 13457 if (ke.pressed) { 13458 auto win = e.xkey.window in SimpleWindow.nativeMapping; 13459 if (win !is null && win.xic !is null) { 13460 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 13461 Status status; 13462 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 13463 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 13464 } else { 13465 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 13466 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 13467 char[16] buffer; 13468 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 13469 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 13470 } 13471 } 13472 13473 // if there's no char, subst one 13474 if (charbuflen == 0) { 13475 switch (sym) { 13476 case 0xff09: charbuf[charbuflen++] = '\t'; break; 13477 case 0xff8d: // keypad enter 13478 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 13479 default : // ignore 13480 } 13481 } 13482 13483 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 13484 ke.window = *win; 13485 13486 13487 if(win.inputProxy) 13488 win = &win.inputProxy; 13489 13490 if (win.handleKeyEvent) { 13491 XUnlockDisplay(display); 13492 scope(exit) XLockDisplay(display); 13493 win.handleKeyEvent(ke); 13494 } 13495 13496 // char events are separate since they are on Windows too 13497 // also, xcompose can generate long char sequences 13498 // don't send char events if Meta and/or Hyper is pressed 13499 // TODO: ctrl+char should only send control chars; not yet 13500 if ((e.xkey.state&ModifierState.ctrl) != 0) { 13501 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 13502 } 13503 if (ke.pressed && charbuflen > 0 && (e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0) { 13504 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 13505 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 13506 if (win.handleCharEvent) { 13507 XUnlockDisplay(display); 13508 scope(exit) XLockDisplay(display); 13509 win.handleCharEvent(ch); 13510 } 13511 } 13512 } 13513 } 13514 13515 version(with_eventloop) 13516 send(ke); 13517 break; 13518 default: 13519 } 13520 13521 return done; 13522 } 13523 } 13524 13525 /* *************************************** */ 13526 /* Done with simpledisplay stuff */ 13527 /* *************************************** */ 13528 13529 // Necessary C library bindings follow 13530 version(Windows) {} else 13531 version(X11) { 13532 13533 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 13534 13535 // X11 bindings needed here 13536 /* 13537 A little of this is from the bindings project on 13538 D Source and some of it is copy/paste from the C 13539 header. 13540 13541 The DSource listing consistently used D's long 13542 where C used long. That's wrong - C long is 32 bit, so 13543 it should be int in D. I changed that here. 13544 13545 Note: 13546 This isn't complete, just took what I needed for myself. 13547 */ 13548 13549 import core.stdc.stddef : wchar_t; 13550 13551 interface XLib { 13552 extern(C) nothrow @nogc { 13553 char* XResourceManagerString(Display*); 13554 void XrmInitialize(); 13555 XrmDatabase XrmGetStringDatabase(char* data); 13556 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 13557 13558 Cursor XCreateFontCursor(Display*, uint shape); 13559 int XDefineCursor(Display* display, Window w, Cursor cursor); 13560 int XUndefineCursor(Display* display, Window w); 13561 13562 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 13563 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 13564 int XFreeCursor(Display* display, Cursor cursor); 13565 13566 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 13567 13568 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 13569 13570 char *XKeysymToString(KeySym keysym); 13571 KeySym XKeycodeToKeysym( 13572 Display* /* display */, 13573 KeyCode /* keycode */, 13574 int /* index */ 13575 ); 13576 13577 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 13578 13579 int XFree(void*); 13580 int XDeleteProperty(Display *display, Window w, Atom property); 13581 13582 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, in void *data, int nelements); 13583 13584 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 13585 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 13586 *actual_type_return, int *actual_format_return, arch_ulong 13587 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 13588 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 13589 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 13590 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 13591 13592 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 13593 13594 Window XGetSelectionOwner(Display *display, Atom selection); 13595 13596 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 13597 13598 Display* XOpenDisplay(const char*); 13599 int XCloseDisplay(Display*); 13600 13601 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 13602 13603 Bool XSupportsLocale(); 13604 char* XSetLocaleModifiers(const(char)* modifier_list); 13605 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 13606 Status XCloseOM(XOM om); 13607 13608 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 13609 Status XCloseIM(XIM im); 13610 13611 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 13612 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 13613 Display* XDisplayOfIM(XIM im); 13614 char* XLocaleOfIM(XIM im); 13615 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 13616 void XDestroyIC(XIC ic); 13617 void XSetICFocus(XIC ic); 13618 void XUnsetICFocus(XIC ic); 13619 //wchar_t* XwcResetIC(XIC ic); 13620 char* XmbResetIC(XIC ic); 13621 char* Xutf8ResetIC(XIC ic); 13622 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 13623 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 13624 XIM XIMOfIC(XIC ic); 13625 13626 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 13627 13628 13629 XFontStruct *XLoadQueryFont(Display *display, in char *name); 13630 int XFreeFont(Display *display, XFontStruct *font_struct); 13631 int XSetFont(Display* display, GC gc, Font font); 13632 int XTextWidth(XFontStruct*, in char*, int); 13633 13634 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 13635 int XSetDashes(Display *display, GC gc, int dash_offset, in byte* dash_list, int n); 13636 13637 Window XCreateSimpleWindow( 13638 Display* /* display */, 13639 Window /* parent */, 13640 int /* x */, 13641 int /* y */, 13642 uint /* width */, 13643 uint /* height */, 13644 uint /* border_width */, 13645 uint /* border */, 13646 uint /* background */ 13647 ); 13648 Window XCreateWindow(Display *display, Window parent, int x, int y, uint width, uint height, uint border_width, int depth, uint class_, Visual *visual, arch_ulong valuemask, XSetWindowAttributes *attributes); 13649 13650 int XReparentWindow(Display*, Window, Window, int, int); 13651 int XClearWindow(Display*, Window); 13652 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 13653 int XMoveWindow(Display*, Window, int, int); 13654 int XResizeWindow(Display *display, Window w, uint width, uint height); 13655 13656 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 13657 13658 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 13659 13660 XImage *XCreateImage( 13661 Display* /* display */, 13662 Visual* /* visual */, 13663 uint /* depth */, 13664 int /* format */, 13665 int /* offset */, 13666 ubyte* /* data */, 13667 uint /* width */, 13668 uint /* height */, 13669 int /* bitmap_pad */, 13670 int /* bytes_per_line */ 13671 ); 13672 13673 Status XInitImage (XImage* image); 13674 13675 Atom XInternAtom( 13676 Display* /* display */, 13677 const char* /* atom_name */, 13678 Bool /* only_if_exists */ 13679 ); 13680 13681 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 13682 char* XGetAtomName(Display*, Atom); 13683 Status XGetAtomNames(Display*, Atom*, int count, char**); 13684 13685 int XPutImage( 13686 Display* /* display */, 13687 Drawable /* d */, 13688 GC /* gc */, 13689 XImage* /* image */, 13690 int /* src_x */, 13691 int /* src_y */, 13692 int /* dest_x */, 13693 int /* dest_y */, 13694 uint /* width */, 13695 uint /* height */ 13696 ); 13697 13698 XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format); 13699 13700 13701 int XDestroyWindow( 13702 Display* /* display */, 13703 Window /* w */ 13704 ); 13705 13706 int XDestroyImage(XImage*); 13707 13708 int XSelectInput( 13709 Display* /* display */, 13710 Window /* w */, 13711 EventMask /* event_mask */ 13712 ); 13713 13714 int XMapWindow( 13715 Display* /* display */, 13716 Window /* w */ 13717 ); 13718 13719 Status XIconifyWindow(Display*, Window, int); 13720 int XMapRaised(Display*, Window); 13721 int XMapSubwindows(Display*, Window); 13722 13723 int XNextEvent( 13724 Display* /* display */, 13725 XEvent* /* event_return */ 13726 ); 13727 13728 int XMaskEvent(Display*, arch_long, XEvent*); 13729 13730 Bool XFilterEvent(XEvent *event, Window window); 13731 int XRefreshKeyboardMapping(XMappingEvent *event_map); 13732 13733 Status XSetWMProtocols( 13734 Display* /* display */, 13735 Window /* w */, 13736 Atom* /* protocols */, 13737 int /* count */ 13738 ); 13739 13740 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 13741 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 13742 13743 13744 Status XInitThreads(); 13745 void XLockDisplay (Display* display); 13746 void XUnlockDisplay (Display* display); 13747 13748 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 13749 13750 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 13751 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 13752 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 13753 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 13754 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 13755 13756 13757 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 13758 int XDrawString(Display*, Drawable, GC, int, int, in char*, int); 13759 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 13760 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 13761 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 13762 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 13763 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 13764 int XDrawPoint(Display*, Drawable, GC, int, int); 13765 int XSetForeground(Display*, GC, uint); 13766 int XSetBackground(Display*, GC, uint); 13767 13768 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 13769 void XFreeFontSet(Display*, XFontSet); 13770 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, in char*, int); 13771 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 13772 13773 int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 13774 13775 13776 //Status Xutf8TextPerCharExtents(XFontSet font_set, char *string, int num_bytes, XRectangle *ink_array_return, XRectangle *logical_array_return, int array_size, int *num_chars_return, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 13777 13778 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 13779 int XSetFunction(Display*, GC, int); 13780 13781 GC XCreateGC(Display*, Drawable, uint, void*); 13782 int XCopyGC(Display*, GC, uint, GC); 13783 int XFreeGC(Display*, GC); 13784 13785 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 13786 bool XCheckMaskEvent(Display*, int, XEvent*); 13787 13788 int XPending(Display*); 13789 int XEventsQueued(Display* display, int mode); 13790 13791 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 13792 int XFreePixmap(Display*, Pixmap); 13793 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 13794 int XFlush(Display*); 13795 int XBell(Display*, int); 13796 int XSync(Display*, bool); 13797 13798 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 13799 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 13800 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 13801 13802 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 13803 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 13804 13805 Status XAllocColor(Display*, Colormap, XColor*); 13806 13807 int XWithdrawWindow(Display*, Window, int); 13808 int XUnmapWindow(Display*, Window); 13809 int XLowerWindow(Display*, Window); 13810 int XRaiseWindow(Display*, Window); 13811 13812 int XWarpPointer(Display *display, Window src_w, Window dest_w, int src_x, int src_y, uint src_width, uint src_height, int dest_x, int dest_y); 13813 Bool XTranslateCoordinates(Display *display, Window src_w, Window dest_w, int src_x, int src_y, int *dest_x_return, int *dest_y_return, Window *child_return); 13814 13815 int XGetInputFocus(Display*, Window*, int*); 13816 int XSetInputFocus(Display*, Window, int, Time); 13817 13818 XErrorHandler XSetErrorHandler(XErrorHandler); 13819 13820 int XGetErrorText(Display*, int, char*, int); 13821 13822 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 13823 13824 13825 int XGrabPointer(Display *display, Window grab_window, Bool owner_events, uint event_mask, int pointer_mode, int keyboard_mode, Window confine_to, Cursor cursor, Time time); 13826 int XUngrabPointer(Display *display, Time time); 13827 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 13828 13829 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 13830 13831 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 13832 int XSetClipMask(Display*, GC, Pixmap); 13833 int XSetClipOrigin(Display*, GC, int, int); 13834 13835 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 13836 13837 void XSetWMName(Display*, Window, XTextProperty*); 13838 Status XGetWMName(Display*, Window, XTextProperty*); 13839 int XStoreName(Display* display, Window w, const(char)* window_name); 13840 13841 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 13842 13843 } 13844 } 13845 13846 interface Xext { 13847 extern(C) nothrow @nogc { 13848 Status XShmAttach(Display*, XShmSegmentInfo*); 13849 Status XShmDetach(Display*, XShmSegmentInfo*); 13850 Status XShmPutImage( 13851 Display* /* dpy */, 13852 Drawable /* d */, 13853 GC /* gc */, 13854 XImage* /* image */, 13855 int /* src_x */, 13856 int /* src_y */, 13857 int /* dst_x */, 13858 int /* dst_y */, 13859 uint /* src_width */, 13860 uint /* src_height */, 13861 Bool /* send_event */ 13862 ); 13863 13864 Status XShmQueryExtension(Display*); 13865 13866 XImage *XShmCreateImage( 13867 Display* /* dpy */, 13868 Visual* /* visual */, 13869 uint /* depth */, 13870 int /* format */, 13871 char* /* data */, 13872 XShmSegmentInfo* /* shminfo */, 13873 uint /* width */, 13874 uint /* height */ 13875 ); 13876 13877 Pixmap XShmCreatePixmap( 13878 Display* /* dpy */, 13879 Drawable /* d */, 13880 char* /* data */, 13881 XShmSegmentInfo* /* shminfo */, 13882 uint /* width */, 13883 uint /* height */, 13884 uint /* depth */ 13885 ); 13886 13887 } 13888 } 13889 13890 // this requires -lXpm 13891 //int XpmCreatePixmapFromData(Display*, Drawable, in char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 13892 13893 13894 mixin DynamicLoad!(XLib, "X11", 6) xlib; 13895 mixin DynamicLoad!(Xext, "Xext", 6) xext; 13896 shared static this() { 13897 xlib.loadDynamicLibrary(); 13898 xext.loadDynamicLibrary(); 13899 } 13900 13901 13902 extern(C) nothrow @nogc { 13903 13904 alias XrmDatabase = void*; 13905 struct XrmValue { 13906 uint size; 13907 void* addr; 13908 } 13909 13910 struct XVisualInfo { 13911 Visual* visual; 13912 VisualID visualid; 13913 int screen; 13914 uint depth; 13915 int c_class; 13916 c_ulong red_mask; 13917 c_ulong green_mask; 13918 c_ulong blue_mask; 13919 int colormap_size; 13920 int bits_per_rgb; 13921 } 13922 13923 enum VisualNoMask= 0x0; 13924 enum VisualIDMask= 0x1; 13925 enum VisualScreenMask=0x2; 13926 enum VisualDepthMask= 0x4; 13927 enum VisualClassMask= 0x8; 13928 enum VisualRedMaskMask=0x10; 13929 enum VisualGreenMaskMask=0x20; 13930 enum VisualBlueMaskMask=0x40; 13931 enum VisualColormapSizeMask=0x80; 13932 enum VisualBitsPerRGBMask=0x100; 13933 enum VisualAllMask= 0x1FF; 13934 13935 13936 // XIM and other crap 13937 struct _XOM {} 13938 struct _XIM {} 13939 struct _XIC {} 13940 alias XOM = _XOM*; 13941 alias XIM = _XIM*; 13942 alias XIC = _XIC*; 13943 13944 alias XIMStyle = arch_ulong; 13945 enum : arch_ulong { 13946 XIMPreeditArea = 0x0001, 13947 XIMPreeditCallbacks = 0x0002, 13948 XIMPreeditPosition = 0x0004, 13949 XIMPreeditNothing = 0x0008, 13950 XIMPreeditNone = 0x0010, 13951 XIMStatusArea = 0x0100, 13952 XIMStatusCallbacks = 0x0200, 13953 XIMStatusNothing = 0x0400, 13954 XIMStatusNone = 0x0800, 13955 } 13956 13957 13958 /* X Shared Memory Extension functions */ 13959 //pragma(lib, "Xshm"); 13960 alias arch_ulong ShmSeg; 13961 struct XShmSegmentInfo { 13962 ShmSeg shmseg; 13963 int shmid; 13964 ubyte* shmaddr; 13965 Bool readOnly; 13966 } 13967 13968 // and the necessary OS functions 13969 int shmget(int, size_t, int); 13970 void* shmat(int, in void*, int); 13971 int shmdt(in void*); 13972 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 13973 13974 enum IPC_PRIVATE = 0; 13975 enum IPC_CREAT = 512; 13976 enum IPC_RMID = 0; 13977 13978 /* MIT-SHM end */ 13979 13980 13981 enum MappingType:int { 13982 MappingModifier =0, 13983 MappingKeyboard =1, 13984 MappingPointer =2 13985 } 13986 13987 /* ImageFormat -- PutImage, GetImage */ 13988 enum ImageFormat:int { 13989 XYBitmap =0, /* depth 1, XYFormat */ 13990 XYPixmap =1, /* depth == drawable depth */ 13991 ZPixmap =2 /* depth == drawable depth */ 13992 } 13993 13994 enum ModifierName:int { 13995 ShiftMapIndex =0, 13996 LockMapIndex =1, 13997 ControlMapIndex =2, 13998 Mod1MapIndex =3, 13999 Mod2MapIndex =4, 14000 Mod3MapIndex =5, 14001 Mod4MapIndex =6, 14002 Mod5MapIndex =7 14003 } 14004 14005 enum ButtonMask:int { 14006 Button1Mask =1<<8, 14007 Button2Mask =1<<9, 14008 Button3Mask =1<<10, 14009 Button4Mask =1<<11, 14010 Button5Mask =1<<12, 14011 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 14012 } 14013 14014 enum KeyOrButtonMask:uint { 14015 ShiftMask =1<<0, 14016 LockMask =1<<1, 14017 ControlMask =1<<2, 14018 Mod1Mask =1<<3, 14019 Mod2Mask =1<<4, 14020 Mod3Mask =1<<5, 14021 Mod4Mask =1<<6, 14022 Mod5Mask =1<<7, 14023 Button1Mask =1<<8, 14024 Button2Mask =1<<9, 14025 Button3Mask =1<<10, 14026 Button4Mask =1<<11, 14027 Button5Mask =1<<12, 14028 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 14029 } 14030 14031 enum ButtonName:int { 14032 Button1 =1, 14033 Button2 =2, 14034 Button3 =3, 14035 Button4 =4, 14036 Button5 =5 14037 } 14038 14039 /* Notify modes */ 14040 enum NotifyModes:int 14041 { 14042 NotifyNormal =0, 14043 NotifyGrab =1, 14044 NotifyUngrab =2, 14045 NotifyWhileGrabbed =3 14046 } 14047 enum NotifyHint = 1; /* for MotionNotify events */ 14048 14049 /* Notify detail */ 14050 enum NotifyDetail:int 14051 { 14052 NotifyAncestor =0, 14053 NotifyVirtual =1, 14054 NotifyInferior =2, 14055 NotifyNonlinear =3, 14056 NotifyNonlinearVirtual =4, 14057 NotifyPointer =5, 14058 NotifyPointerRoot =6, 14059 NotifyDetailNone =7 14060 } 14061 14062 /* Visibility notify */ 14063 14064 enum VisibilityNotify:int 14065 { 14066 VisibilityUnobscured =0, 14067 VisibilityPartiallyObscured =1, 14068 VisibilityFullyObscured =2 14069 } 14070 14071 14072 enum WindowStackingMethod:int 14073 { 14074 Above =0, 14075 Below =1, 14076 TopIf =2, 14077 BottomIf =3, 14078 Opposite =4 14079 } 14080 14081 /* Circulation request */ 14082 enum CirculationRequest:int 14083 { 14084 PlaceOnTop =0, 14085 PlaceOnBottom =1 14086 } 14087 14088 enum PropertyNotification:int 14089 { 14090 PropertyNewValue =0, 14091 PropertyDelete =1 14092 } 14093 14094 enum ColorMapNotification:int 14095 { 14096 ColormapUninstalled =0, 14097 ColormapInstalled =1 14098 } 14099 14100 14101 struct _XPrivate {} 14102 struct _XrmHashBucketRec {} 14103 14104 alias void* XPointer; 14105 alias void* XExtData; 14106 14107 version( X86_64 ) { 14108 alias ulong XID; 14109 alias ulong arch_ulong; 14110 alias long arch_long; 14111 } else { 14112 alias uint XID; 14113 alias uint arch_ulong; 14114 alias int arch_long; 14115 } 14116 14117 alias XID Window; 14118 alias XID Drawable; 14119 alias XID Pixmap; 14120 14121 alias arch_ulong Atom; 14122 alias int Bool; 14123 alias Display XDisplay; 14124 14125 alias int ByteOrder; 14126 alias arch_ulong Time; 14127 alias void ScreenFormat; 14128 14129 struct XImage { 14130 int width, height; /* size of image */ 14131 int xoffset; /* number of pixels offset in X direction */ 14132 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 14133 void *data; /* pointer to image data */ 14134 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 14135 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 14136 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 14137 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 14138 int depth; /* depth of image */ 14139 int bytes_per_line; /* accelarator to next line */ 14140 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 14141 arch_ulong red_mask; /* bits in z arrangment */ 14142 arch_ulong green_mask; 14143 arch_ulong blue_mask; 14144 XPointer obdata; /* hook for the object routines to hang on */ 14145 static struct F { /* image manipulation routines */ 14146 XImage* function( 14147 XDisplay* /* display */, 14148 Visual* /* visual */, 14149 uint /* depth */, 14150 int /* format */, 14151 int /* offset */, 14152 ubyte* /* data */, 14153 uint /* width */, 14154 uint /* height */, 14155 int /* bitmap_pad */, 14156 int /* bytes_per_line */) create_image; 14157 int function(XImage *) destroy_image; 14158 arch_ulong function(XImage *, int, int) get_pixel; 14159 int function(XImage *, int, int, arch_ulong) put_pixel; 14160 XImage* function(XImage *, int, int, uint, uint) sub_image; 14161 int function(XImage *, arch_long) add_pixel; 14162 } 14163 F f; 14164 } 14165 version(X86_64) static assert(XImage.sizeof == 136); 14166 else version(X86) static assert(XImage.sizeof == 88); 14167 14168 struct XCharStruct { 14169 short lbearing; /* origin to left edge of raster */ 14170 short rbearing; /* origin to right edge of raster */ 14171 short width; /* advance to next char's origin */ 14172 short ascent; /* baseline to top edge of raster */ 14173 short descent; /* baseline to bottom edge of raster */ 14174 ushort attributes; /* per char flags (not predefined) */ 14175 } 14176 14177 /* 14178 * To allow arbitrary information with fonts, there are additional properties 14179 * returned. 14180 */ 14181 struct XFontProp { 14182 Atom name; 14183 arch_ulong card32; 14184 } 14185 14186 alias Atom Font; 14187 14188 struct XFontStruct { 14189 XExtData *ext_data; /* Hook for extension to hang data */ 14190 Font fid; /* Font ID for this font */ 14191 uint direction; /* Direction the font is painted */ 14192 uint min_char_or_byte2; /* First character */ 14193 uint max_char_or_byte2; /* Last character */ 14194 uint min_byte1; /* First row that exists (for two-byte fonts) */ 14195 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 14196 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 14197 uint default_char; /* Char to print for undefined character */ 14198 int n_properties; /* How many properties there are */ 14199 XFontProp *properties; /* Pointer to array of additional properties*/ 14200 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 14201 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 14202 XCharStruct *per_char; /* first_char to last_char information */ 14203 int ascent; /* Max extent above baseline for spacing */ 14204 int descent; /* Max descent below baseline for spacing */ 14205 } 14206 14207 14208 /* 14209 * Definitions of specific events. 14210 */ 14211 struct XKeyEvent 14212 { 14213 int type; /* of event */ 14214 arch_ulong serial; /* # of last request processed by server */ 14215 Bool send_event; /* true if this came from a SendEvent request */ 14216 Display *display; /* Display the event was read from */ 14217 Window window; /* "event" window it is reported relative to */ 14218 Window root; /* root window that the event occurred on */ 14219 Window subwindow; /* child window */ 14220 Time time; /* milliseconds */ 14221 int x, y; /* pointer x, y coordinates in event window */ 14222 int x_root, y_root; /* coordinates relative to root */ 14223 KeyOrButtonMask state; /* key or button mask */ 14224 uint keycode; /* detail */ 14225 Bool same_screen; /* same screen flag */ 14226 } 14227 version(X86_64) static assert(XKeyEvent.sizeof == 96); 14228 alias XKeyEvent XKeyPressedEvent; 14229 alias XKeyEvent XKeyReleasedEvent; 14230 14231 struct XButtonEvent 14232 { 14233 int type; /* of event */ 14234 arch_ulong serial; /* # of last request processed by server */ 14235 Bool send_event; /* true if this came from a SendEvent request */ 14236 Display *display; /* Display the event was read from */ 14237 Window window; /* "event" window it is reported relative to */ 14238 Window root; /* root window that the event occurred on */ 14239 Window subwindow; /* child window */ 14240 Time time; /* milliseconds */ 14241 int x, y; /* pointer x, y coordinates in event window */ 14242 int x_root, y_root; /* coordinates relative to root */ 14243 KeyOrButtonMask state; /* key or button mask */ 14244 uint button; /* detail */ 14245 Bool same_screen; /* same screen flag */ 14246 } 14247 alias XButtonEvent XButtonPressedEvent; 14248 alias XButtonEvent XButtonReleasedEvent; 14249 14250 struct XMotionEvent{ 14251 int type; /* of event */ 14252 arch_ulong serial; /* # of last request processed by server */ 14253 Bool send_event; /* true if this came from a SendEvent request */ 14254 Display *display; /* Display the event was read from */ 14255 Window window; /* "event" window reported relative to */ 14256 Window root; /* root window that the event occurred on */ 14257 Window subwindow; /* child window */ 14258 Time time; /* milliseconds */ 14259 int x, y; /* pointer x, y coordinates in event window */ 14260 int x_root, y_root; /* coordinates relative to root */ 14261 KeyOrButtonMask state; /* key or button mask */ 14262 byte is_hint; /* detail */ 14263 Bool same_screen; /* same screen flag */ 14264 } 14265 alias XMotionEvent XPointerMovedEvent; 14266 14267 struct XCrossingEvent{ 14268 int type; /* of event */ 14269 arch_ulong serial; /* # of last request processed by server */ 14270 Bool send_event; /* true if this came from a SendEvent request */ 14271 Display *display; /* Display the event was read from */ 14272 Window window; /* "event" window reported relative to */ 14273 Window root; /* root window that the event occurred on */ 14274 Window subwindow; /* child window */ 14275 Time time; /* milliseconds */ 14276 int x, y; /* pointer x, y coordinates in event window */ 14277 int x_root, y_root; /* coordinates relative to root */ 14278 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 14279 NotifyDetail detail; 14280 /* 14281 * NotifyAncestor, NotifyVirtual, NotifyInferior, 14282 * NotifyNonlinear,NotifyNonlinearVirtual 14283 */ 14284 Bool same_screen; /* same screen flag */ 14285 Bool focus; /* Boolean focus */ 14286 KeyOrButtonMask state; /* key or button mask */ 14287 } 14288 alias XCrossingEvent XEnterWindowEvent; 14289 alias XCrossingEvent XLeaveWindowEvent; 14290 14291 struct XFocusChangeEvent{ 14292 int type; /* FocusIn or FocusOut */ 14293 arch_ulong serial; /* # of last request processed by server */ 14294 Bool send_event; /* true if this came from a SendEvent request */ 14295 Display *display; /* Display the event was read from */ 14296 Window window; /* window of event */ 14297 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 14298 NotifyGrab, NotifyUngrab */ 14299 NotifyDetail detail; 14300 /* 14301 * NotifyAncestor, NotifyVirtual, NotifyInferior, 14302 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 14303 * NotifyPointerRoot, NotifyDetailNone 14304 */ 14305 } 14306 alias XFocusChangeEvent XFocusInEvent; 14307 alias XFocusChangeEvent XFocusOutEvent; 14308 14309 enum CWBackPixmap = (1L<<0); 14310 enum CWBackPixel = (1L<<1); 14311 enum CWBorderPixmap = (1L<<2); 14312 enum CWBorderPixel = (1L<<3); 14313 enum CWBitGravity = (1L<<4); 14314 enum CWWinGravity = (1L<<5); 14315 enum CWBackingStore = (1L<<6); 14316 enum CWBackingPlanes = (1L<<7); 14317 enum CWBackingPixel = (1L<<8); 14318 enum CWOverrideRedirect = (1L<<9); 14319 enum CWSaveUnder = (1L<<10); 14320 enum CWEventMask = (1L<<11); 14321 enum CWDontPropagate = (1L<<12); 14322 enum CWColormap = (1L<<13); 14323 enum CWCursor = (1L<<14); 14324 14325 struct XWindowAttributes { 14326 int x, y; /* location of window */ 14327 int width, height; /* width and height of window */ 14328 int border_width; /* border width of window */ 14329 int depth; /* depth of window */ 14330 Visual *visual; /* the associated visual structure */ 14331 Window root; /* root of screen containing window */ 14332 int class_; /* InputOutput, InputOnly*/ 14333 int bit_gravity; /* one of the bit gravity values */ 14334 int win_gravity; /* one of the window gravity values */ 14335 int backing_store; /* NotUseful, WhenMapped, Always */ 14336 arch_ulong backing_planes; /* planes to be preserved if possible */ 14337 arch_ulong backing_pixel; /* value to be used when restoring planes */ 14338 Bool save_under; /* boolean, should bits under be saved? */ 14339 Colormap colormap; /* color map to be associated with window */ 14340 Bool map_installed; /* boolean, is color map currently installed*/ 14341 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 14342 arch_long all_event_masks; /* set of events all people have interest in*/ 14343 arch_long your_event_mask; /* my event mask */ 14344 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 14345 Bool override_redirect; /* boolean value for override-redirect */ 14346 Screen *screen; /* back pointer to correct screen */ 14347 } 14348 14349 enum IsUnmapped = 0; 14350 enum IsUnviewable = 1; 14351 enum IsViewable = 2; 14352 14353 struct XSetWindowAttributes { 14354 Pixmap background_pixmap;/* background, None, or ParentRelative */ 14355 arch_ulong background_pixel;/* background pixel */ 14356 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 14357 arch_ulong border_pixel;/* border pixel value */ 14358 int bit_gravity; /* one of bit gravity values */ 14359 int win_gravity; /* one of the window gravity values */ 14360 int backing_store; /* NotUseful, WhenMapped, Always */ 14361 arch_ulong backing_planes;/* planes to be preserved if possible */ 14362 arch_ulong backing_pixel;/* value to use in restoring planes */ 14363 Bool save_under; /* should bits under be saved? (popups) */ 14364 arch_long event_mask; /* set of events that should be saved */ 14365 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 14366 Bool override_redirect; /* boolean value for override_redirect */ 14367 Colormap colormap; /* color map to be associated with window */ 14368 Cursor cursor; /* cursor to be displayed (or None) */ 14369 } 14370 14371 14372 alias int Status; 14373 14374 14375 enum EventMask:int 14376 { 14377 NoEventMask =0, 14378 KeyPressMask =1<<0, 14379 KeyReleaseMask =1<<1, 14380 ButtonPressMask =1<<2, 14381 ButtonReleaseMask =1<<3, 14382 EnterWindowMask =1<<4, 14383 LeaveWindowMask =1<<5, 14384 PointerMotionMask =1<<6, 14385 PointerMotionHintMask =1<<7, 14386 Button1MotionMask =1<<8, 14387 Button2MotionMask =1<<9, 14388 Button3MotionMask =1<<10, 14389 Button4MotionMask =1<<11, 14390 Button5MotionMask =1<<12, 14391 ButtonMotionMask =1<<13, 14392 KeymapStateMask =1<<14, 14393 ExposureMask =1<<15, 14394 VisibilityChangeMask =1<<16, 14395 StructureNotifyMask =1<<17, 14396 ResizeRedirectMask =1<<18, 14397 SubstructureNotifyMask =1<<19, 14398 SubstructureRedirectMask=1<<20, 14399 FocusChangeMask =1<<21, 14400 PropertyChangeMask =1<<22, 14401 ColormapChangeMask =1<<23, 14402 OwnerGrabButtonMask =1<<24 14403 } 14404 14405 struct MwmHints { 14406 int flags; 14407 int functions; 14408 int decorations; 14409 int input_mode; 14410 int status; 14411 } 14412 14413 enum { 14414 MWM_HINTS_FUNCTIONS = (1L << 0), 14415 MWM_HINTS_DECORATIONS = (1L << 1), 14416 14417 MWM_FUNC_ALL = (1L << 0), 14418 MWM_FUNC_RESIZE = (1L << 1), 14419 MWM_FUNC_MOVE = (1L << 2), 14420 MWM_FUNC_MINIMIZE = (1L << 3), 14421 MWM_FUNC_MAXIMIZE = (1L << 4), 14422 MWM_FUNC_CLOSE = (1L << 5) 14423 } 14424 14425 import core.stdc.config : c_long, c_ulong; 14426 14427 /* Size hints mask bits */ 14428 14429 enum USPosition = (1L << 0) /* user specified x, y */; 14430 enum USSize = (1L << 1) /* user specified width, height */; 14431 enum PPosition = (1L << 2) /* program specified position */; 14432 enum PSize = (1L << 3) /* program specified size */; 14433 enum PMinSize = (1L << 4) /* program specified minimum size */; 14434 enum PMaxSize = (1L << 5) /* program specified maximum size */; 14435 enum PResizeInc = (1L << 6) /* program specified resize increments */; 14436 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 14437 enum PBaseSize = (1L << 8); 14438 enum PWinGravity = (1L << 9); 14439 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 14440 struct XSizeHints { 14441 arch_long flags; /* marks which fields in this structure are defined */ 14442 int x, y; /* Obsolete */ 14443 int width, height; /* Obsolete */ 14444 int min_width, min_height; 14445 int max_width, max_height; 14446 int width_inc, height_inc; 14447 struct Aspect { 14448 int x; /* numerator */ 14449 int y; /* denominator */ 14450 } 14451 14452 Aspect min_aspect; 14453 Aspect max_aspect; 14454 int base_width, base_height; 14455 int win_gravity; 14456 /* this structure may be extended in the future */ 14457 } 14458 14459 14460 14461 enum EventType:int 14462 { 14463 KeyPress =2, 14464 KeyRelease =3, 14465 ButtonPress =4, 14466 ButtonRelease =5, 14467 MotionNotify =6, 14468 EnterNotify =7, 14469 LeaveNotify =8, 14470 FocusIn =9, 14471 FocusOut =10, 14472 KeymapNotify =11, 14473 Expose =12, 14474 GraphicsExpose =13, 14475 NoExpose =14, 14476 VisibilityNotify =15, 14477 CreateNotify =16, 14478 DestroyNotify =17, 14479 UnmapNotify =18, 14480 MapNotify =19, 14481 MapRequest =20, 14482 ReparentNotify =21, 14483 ConfigureNotify =22, 14484 ConfigureRequest =23, 14485 GravityNotify =24, 14486 ResizeRequest =25, 14487 CirculateNotify =26, 14488 CirculateRequest =27, 14489 PropertyNotify =28, 14490 SelectionClear =29, 14491 SelectionRequest =30, 14492 SelectionNotify =31, 14493 ColormapNotify =32, 14494 ClientMessage =33, 14495 MappingNotify =34, 14496 LASTEvent =35 /* must be bigger than any event # */ 14497 } 14498 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 14499 struct XKeymapEvent 14500 { 14501 int type; 14502 arch_ulong serial; /* # of last request processed by server */ 14503 Bool send_event; /* true if this came from a SendEvent request */ 14504 Display *display; /* Display the event was read from */ 14505 Window window; 14506 byte[32] key_vector; 14507 } 14508 14509 struct XExposeEvent 14510 { 14511 int type; 14512 arch_ulong serial; /* # of last request processed by server */ 14513 Bool send_event; /* true if this came from a SendEvent request */ 14514 Display *display; /* Display the event was read from */ 14515 Window window; 14516 int x, y; 14517 int width, height; 14518 int count; /* if non-zero, at least this many more */ 14519 } 14520 14521 struct XGraphicsExposeEvent{ 14522 int type; 14523 arch_ulong serial; /* # of last request processed by server */ 14524 Bool send_event; /* true if this came from a SendEvent request */ 14525 Display *display; /* Display the event was read from */ 14526 Drawable drawable; 14527 int x, y; 14528 int width, height; 14529 int count; /* if non-zero, at least this many more */ 14530 int major_code; /* core is CopyArea or CopyPlane */ 14531 int minor_code; /* not defined in the core */ 14532 } 14533 14534 struct XNoExposeEvent{ 14535 int type; 14536 arch_ulong serial; /* # of last request processed by server */ 14537 Bool send_event; /* true if this came from a SendEvent request */ 14538 Display *display; /* Display the event was read from */ 14539 Drawable drawable; 14540 int major_code; /* core is CopyArea or CopyPlane */ 14541 int minor_code; /* not defined in the core */ 14542 } 14543 14544 struct XVisibilityEvent{ 14545 int type; 14546 arch_ulong serial; /* # of last request processed by server */ 14547 Bool send_event; /* true if this came from a SendEvent request */ 14548 Display *display; /* Display the event was read from */ 14549 Window window; 14550 VisibilityNotify state; /* Visibility state */ 14551 } 14552 14553 struct XCreateWindowEvent{ 14554 int type; 14555 arch_ulong serial; /* # of last request processed by server */ 14556 Bool send_event; /* true if this came from a SendEvent request */ 14557 Display *display; /* Display the event was read from */ 14558 Window parent; /* parent of the window */ 14559 Window window; /* window id of window created */ 14560 int x, y; /* window location */ 14561 int width, height; /* size of window */ 14562 int border_width; /* border width */ 14563 Bool override_redirect; /* creation should be overridden */ 14564 } 14565 14566 struct XDestroyWindowEvent 14567 { 14568 int type; 14569 arch_ulong serial; /* # of last request processed by server */ 14570 Bool send_event; /* true if this came from a SendEvent request */ 14571 Display *display; /* Display the event was read from */ 14572 Window event; 14573 Window window; 14574 } 14575 14576 struct XUnmapEvent 14577 { 14578 int type; 14579 arch_ulong serial; /* # of last request processed by server */ 14580 Bool send_event; /* true if this came from a SendEvent request */ 14581 Display *display; /* Display the event was read from */ 14582 Window event; 14583 Window window; 14584 Bool from_configure; 14585 } 14586 14587 struct XMapEvent 14588 { 14589 int type; 14590 arch_ulong serial; /* # of last request processed by server */ 14591 Bool send_event; /* true if this came from a SendEvent request */ 14592 Display *display; /* Display the event was read from */ 14593 Window event; 14594 Window window; 14595 Bool override_redirect; /* Boolean, is override set... */ 14596 } 14597 14598 struct XMapRequestEvent 14599 { 14600 int type; 14601 arch_ulong serial; /* # of last request processed by server */ 14602 Bool send_event; /* true if this came from a SendEvent request */ 14603 Display *display; /* Display the event was read from */ 14604 Window parent; 14605 Window window; 14606 } 14607 14608 struct XReparentEvent 14609 { 14610 int type; 14611 arch_ulong serial; /* # of last request processed by server */ 14612 Bool send_event; /* true if this came from a SendEvent request */ 14613 Display *display; /* Display the event was read from */ 14614 Window event; 14615 Window window; 14616 Window parent; 14617 int x, y; 14618 Bool override_redirect; 14619 } 14620 14621 struct XConfigureEvent 14622 { 14623 int type; 14624 arch_ulong serial; /* # of last request processed by server */ 14625 Bool send_event; /* true if this came from a SendEvent request */ 14626 Display *display; /* Display the event was read from */ 14627 Window event; 14628 Window window; 14629 int x, y; 14630 int width, height; 14631 int border_width; 14632 Window above; 14633 Bool override_redirect; 14634 } 14635 14636 struct XGravityEvent 14637 { 14638 int type; 14639 arch_ulong serial; /* # of last request processed by server */ 14640 Bool send_event; /* true if this came from a SendEvent request */ 14641 Display *display; /* Display the event was read from */ 14642 Window event; 14643 Window window; 14644 int x, y; 14645 } 14646 14647 struct XResizeRequestEvent 14648 { 14649 int type; 14650 arch_ulong serial; /* # of last request processed by server */ 14651 Bool send_event; /* true if this came from a SendEvent request */ 14652 Display *display; /* Display the event was read from */ 14653 Window window; 14654 int width, height; 14655 } 14656 14657 struct XConfigureRequestEvent 14658 { 14659 int type; 14660 arch_ulong serial; /* # of last request processed by server */ 14661 Bool send_event; /* true if this came from a SendEvent request */ 14662 Display *display; /* Display the event was read from */ 14663 Window parent; 14664 Window window; 14665 int x, y; 14666 int width, height; 14667 int border_width; 14668 Window above; 14669 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 14670 arch_ulong value_mask; 14671 } 14672 14673 struct XCirculateEvent 14674 { 14675 int type; 14676 arch_ulong serial; /* # of last request processed by server */ 14677 Bool send_event; /* true if this came from a SendEvent request */ 14678 Display *display; /* Display the event was read from */ 14679 Window event; 14680 Window window; 14681 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 14682 } 14683 14684 struct XCirculateRequestEvent 14685 { 14686 int type; 14687 arch_ulong serial; /* # of last request processed by server */ 14688 Bool send_event; /* true if this came from a SendEvent request */ 14689 Display *display; /* Display the event was read from */ 14690 Window parent; 14691 Window window; 14692 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 14693 } 14694 14695 struct XPropertyEvent 14696 { 14697 int type; 14698 arch_ulong serial; /* # of last request processed by server */ 14699 Bool send_event; /* true if this came from a SendEvent request */ 14700 Display *display; /* Display the event was read from */ 14701 Window window; 14702 Atom atom; 14703 Time time; 14704 PropertyNotification state; /* NewValue, Deleted */ 14705 } 14706 14707 struct XSelectionClearEvent 14708 { 14709 int type; 14710 arch_ulong serial; /* # of last request processed by server */ 14711 Bool send_event; /* true if this came from a SendEvent request */ 14712 Display *display; /* Display the event was read from */ 14713 Window window; 14714 Atom selection; 14715 Time time; 14716 } 14717 14718 struct XSelectionRequestEvent 14719 { 14720 int type; 14721 arch_ulong serial; /* # of last request processed by server */ 14722 Bool send_event; /* true if this came from a SendEvent request */ 14723 Display *display; /* Display the event was read from */ 14724 Window owner; 14725 Window requestor; 14726 Atom selection; 14727 Atom target; 14728 Atom property; 14729 Time time; 14730 } 14731 14732 struct XSelectionEvent 14733 { 14734 int type; 14735 arch_ulong serial; /* # of last request processed by server */ 14736 Bool send_event; /* true if this came from a SendEvent request */ 14737 Display *display; /* Display the event was read from */ 14738 Window requestor; 14739 Atom selection; 14740 Atom target; 14741 Atom property; /* ATOM or None */ 14742 Time time; 14743 } 14744 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 14745 14746 struct XColormapEvent 14747 { 14748 int type; 14749 arch_ulong serial; /* # of last request processed by server */ 14750 Bool send_event; /* true if this came from a SendEvent request */ 14751 Display *display; /* Display the event was read from */ 14752 Window window; 14753 Colormap colormap; /* COLORMAP or None */ 14754 Bool new_; /* C++ */ 14755 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 14756 } 14757 version(X86_64) static assert(XColormapEvent.sizeof == 56); 14758 14759 struct XClientMessageEvent 14760 { 14761 int type; 14762 arch_ulong serial; /* # of last request processed by server */ 14763 Bool send_event; /* true if this came from a SendEvent request */ 14764 Display *display; /* Display the event was read from */ 14765 Window window; 14766 Atom message_type; 14767 int format; 14768 union Data{ 14769 byte[20] b; 14770 short[10] s; 14771 arch_ulong[5] l; 14772 } 14773 Data data; 14774 14775 } 14776 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 14777 14778 struct XMappingEvent 14779 { 14780 int type; 14781 arch_ulong serial; /* # of last request processed by server */ 14782 Bool send_event; /* true if this came from a SendEvent request */ 14783 Display *display; /* Display the event was read from */ 14784 Window window; /* unused */ 14785 MappingType request; /* one of MappingModifier, MappingKeyboard, 14786 MappingPointer */ 14787 int first_keycode; /* first keycode */ 14788 int count; /* defines range of change w. first_keycode*/ 14789 } 14790 14791 struct XErrorEvent 14792 { 14793 int type; 14794 Display *display; /* Display the event was read from */ 14795 XID resourceid; /* resource id */ 14796 arch_ulong serial; /* serial number of failed request */ 14797 ubyte error_code; /* error code of failed request */ 14798 ubyte request_code; /* Major op-code of failed request */ 14799 ubyte minor_code; /* Minor op-code of failed request */ 14800 } 14801 14802 struct XAnyEvent 14803 { 14804 int type; 14805 arch_ulong serial; /* # of last request processed by server */ 14806 Bool send_event; /* true if this came from a SendEvent request */ 14807 Display *display;/* Display the event was read from */ 14808 Window window; /* window on which event was requested in event mask */ 14809 } 14810 14811 union XEvent{ 14812 int type; /* must not be changed; first element */ 14813 XAnyEvent xany; 14814 XKeyEvent xkey; 14815 XButtonEvent xbutton; 14816 XMotionEvent xmotion; 14817 XCrossingEvent xcrossing; 14818 XFocusChangeEvent xfocus; 14819 XExposeEvent xexpose; 14820 XGraphicsExposeEvent xgraphicsexpose; 14821 XNoExposeEvent xnoexpose; 14822 XVisibilityEvent xvisibility; 14823 XCreateWindowEvent xcreatewindow; 14824 XDestroyWindowEvent xdestroywindow; 14825 XUnmapEvent xunmap; 14826 XMapEvent xmap; 14827 XMapRequestEvent xmaprequest; 14828 XReparentEvent xreparent; 14829 XConfigureEvent xconfigure; 14830 XGravityEvent xgravity; 14831 XResizeRequestEvent xresizerequest; 14832 XConfigureRequestEvent xconfigurerequest; 14833 XCirculateEvent xcirculate; 14834 XCirculateRequestEvent xcirculaterequest; 14835 XPropertyEvent xproperty; 14836 XSelectionClearEvent xselectionclear; 14837 XSelectionRequestEvent xselectionrequest; 14838 XSelectionEvent xselection; 14839 XColormapEvent xcolormap; 14840 XClientMessageEvent xclient; 14841 XMappingEvent xmapping; 14842 XErrorEvent xerror; 14843 XKeymapEvent xkeymap; 14844 arch_ulong[24] pad; 14845 } 14846 14847 14848 struct Display { 14849 XExtData *ext_data; /* hook for extension to hang data */ 14850 _XPrivate *private1; 14851 int fd; /* Network socket. */ 14852 int private2; 14853 int proto_major_version;/* major version of server's X protocol */ 14854 int proto_minor_version;/* minor version of servers X protocol */ 14855 char *vendor; /* vendor of the server hardware */ 14856 XID private3; 14857 XID private4; 14858 XID private5; 14859 int private6; 14860 XID function(Display*)resource_alloc;/* allocator function */ 14861 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 14862 int bitmap_unit; /* padding and data requirements */ 14863 int bitmap_pad; /* padding requirements on bitmaps */ 14864 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 14865 int nformats; /* number of pixmap formats in list */ 14866 ScreenFormat *pixmap_format; /* pixmap format list */ 14867 int private8; 14868 int release; /* release of the server */ 14869 _XPrivate *private9; 14870 _XPrivate *private10; 14871 int qlen; /* Length of input event queue */ 14872 arch_ulong last_request_read; /* seq number of last event read */ 14873 arch_ulong request; /* sequence number of last request. */ 14874 XPointer private11; 14875 XPointer private12; 14876 XPointer private13; 14877 XPointer private14; 14878 uint max_request_size; /* maximum number 32 bit words in request*/ 14879 _XrmHashBucketRec *db; 14880 int function (Display*)private15; 14881 char *display_name; /* "host:display" string used on this connect*/ 14882 int default_screen; /* default screen for operations */ 14883 int nscreens; /* number of screens on this server*/ 14884 Screen *screens; /* pointer to list of screens */ 14885 arch_ulong motion_buffer; /* size of motion buffer */ 14886 arch_ulong private16; 14887 int min_keycode; /* minimum defined keycode */ 14888 int max_keycode; /* maximum defined keycode */ 14889 XPointer private17; 14890 XPointer private18; 14891 int private19; 14892 byte *xdefaults; /* contents of defaults from server */ 14893 /* there is more to this structure, but it is private to Xlib */ 14894 } 14895 14896 // I got these numbers from a C program as a sanity test 14897 version(X86_64) { 14898 static assert(Display.sizeof == 296); 14899 static assert(XPointer.sizeof == 8); 14900 static assert(XErrorEvent.sizeof == 40); 14901 static assert(XAnyEvent.sizeof == 40); 14902 static assert(XMappingEvent.sizeof == 56); 14903 static assert(XEvent.sizeof == 192); 14904 } else { 14905 static assert(Display.sizeof == 176); 14906 static assert(XPointer.sizeof == 4); 14907 static assert(XEvent.sizeof == 96); 14908 } 14909 14910 struct Depth 14911 { 14912 int depth; /* this depth (Z) of the depth */ 14913 int nvisuals; /* number of Visual types at this depth */ 14914 Visual *visuals; /* list of visuals possible at this depth */ 14915 } 14916 14917 alias void* GC; 14918 alias c_ulong VisualID; 14919 alias XID Colormap; 14920 alias XID Cursor; 14921 alias XID KeySym; 14922 alias uint KeyCode; 14923 enum None = 0; 14924 } 14925 14926 version(without_opengl) {} 14927 else { 14928 extern(C) nothrow @nogc { 14929 14930 14931 static if(!SdpyIsUsingIVGLBinds) { 14932 enum GLX_USE_GL= 1; /* support GLX rendering */ 14933 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 14934 enum GLX_LEVEL= 3; /* level in plane stacking */ 14935 enum GLX_RGBA= 4; /* true if RGBA mode */ 14936 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 14937 enum GLX_STEREO= 6; /* stereo buffering supported */ 14938 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 14939 enum GLX_RED_SIZE= 8; /* number of red component bits */ 14940 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 14941 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 14942 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 14943 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 14944 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 14945 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 14946 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 14947 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 14948 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 14949 14950 14951 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 14952 14953 14954 14955 enum GL_TRUE = 1; 14956 enum GL_FALSE = 0; 14957 alias int GLint; 14958 } 14959 14960 alias XID GLXContextID; 14961 alias XID GLXPixmap; 14962 alias XID GLXDrawable; 14963 alias XID GLXPbuffer; 14964 alias XID GLXWindow; 14965 alias XID GLXFBConfigID; 14966 alias void* GLXContext; 14967 14968 } 14969 } 14970 14971 enum AllocNone = 0; 14972 14973 extern(C) { 14974 /* WARNING, this type not in Xlib spec */ 14975 extern(C) alias XIOErrorHandler = int function (Display* display); 14976 } 14977 14978 extern(C) nothrow @nogc { 14979 struct Screen{ 14980 XExtData *ext_data; /* hook for extension to hang data */ 14981 Display *display; /* back pointer to display structure */ 14982 Window root; /* Root window id. */ 14983 int width, height; /* width and height of screen */ 14984 int mwidth, mheight; /* width and height of in millimeters */ 14985 int ndepths; /* number of depths possible */ 14986 Depth *depths; /* list of allowable depths on the screen */ 14987 int root_depth; /* bits per pixel */ 14988 Visual *root_visual; /* root visual */ 14989 GC default_gc; /* GC for the root root visual */ 14990 Colormap cmap; /* default color map */ 14991 uint white_pixel; 14992 uint black_pixel; /* White and Black pixel values */ 14993 int max_maps, min_maps; /* max and min color maps */ 14994 int backing_store; /* Never, WhenMapped, Always */ 14995 bool save_unders; 14996 int root_input_mask; /* initial root input mask */ 14997 } 14998 14999 struct Visual 15000 { 15001 XExtData *ext_data; /* hook for extension to hang data */ 15002 VisualID visualid; /* visual id of this visual */ 15003 int class_; /* class of screen (monochrome, etc.) */ 15004 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 15005 int bits_per_rgb; /* log base 2 of distinct color values */ 15006 int map_entries; /* color map entries */ 15007 } 15008 15009 alias Display* _XPrivDisplay; 15010 15011 Screen* ScreenOfDisplay(Display* dpy, int scr) { 15012 assert(dpy !is null); 15013 return &dpy.screens[scr]; 15014 } 15015 15016 Window RootWindow(Display *dpy,int scr) { 15017 return ScreenOfDisplay(dpy,scr).root; 15018 } 15019 15020 struct XWMHints { 15021 arch_long flags; 15022 Bool input; 15023 int initial_state; 15024 Pixmap icon_pixmap; 15025 Window icon_window; 15026 int icon_x, icon_y; 15027 Pixmap icon_mask; 15028 XID window_group; 15029 } 15030 15031 struct XClassHint { 15032 char* res_name; 15033 char* res_class; 15034 } 15035 15036 int DefaultScreen(Display *dpy) { 15037 return dpy.default_screen; 15038 } 15039 15040 int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 15041 int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 15042 int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 15043 int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 15044 int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 15045 auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 15046 15047 int ConnectionNumber(Display* dpy) { return dpy.fd; } 15048 15049 enum int AnyPropertyType = 0; 15050 enum int Success = 0; 15051 15052 enum int RevertToNone = None; 15053 enum int PointerRoot = 1; 15054 enum Time CurrentTime = 0; 15055 enum int RevertToPointerRoot = PointerRoot; 15056 enum int RevertToParent = 2; 15057 15058 int DefaultDepthOfDisplay(Display* dpy) { 15059 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 15060 } 15061 15062 Visual* DefaultVisual(Display *dpy,int scr) { 15063 return ScreenOfDisplay(dpy,scr).root_visual; 15064 } 15065 15066 GC DefaultGC(Display *dpy,int scr) { 15067 return ScreenOfDisplay(dpy,scr).default_gc; 15068 } 15069 15070 uint BlackPixel(Display *dpy,int scr) { 15071 return ScreenOfDisplay(dpy,scr).black_pixel; 15072 } 15073 15074 uint WhitePixel(Display *dpy,int scr) { 15075 return ScreenOfDisplay(dpy,scr).white_pixel; 15076 } 15077 15078 alias void* XFontSet; // i think 15079 struct XmbTextItem { 15080 char* chars; 15081 int nchars; 15082 int delta; 15083 XFontSet font_set; 15084 } 15085 15086 struct XTextItem { 15087 char* chars; 15088 int nchars; 15089 int delta; 15090 Font font; 15091 } 15092 15093 enum { 15094 GXclear = 0x0, /* 0 */ 15095 GXand = 0x1, /* src AND dst */ 15096 GXandReverse = 0x2, /* src AND NOT dst */ 15097 GXcopy = 0x3, /* src */ 15098 GXandInverted = 0x4, /* NOT src AND dst */ 15099 GXnoop = 0x5, /* dst */ 15100 GXxor = 0x6, /* src XOR dst */ 15101 GXor = 0x7, /* src OR dst */ 15102 GXnor = 0x8, /* NOT src AND NOT dst */ 15103 GXequiv = 0x9, /* NOT src XOR dst */ 15104 GXinvert = 0xa, /* NOT dst */ 15105 GXorReverse = 0xb, /* src OR NOT dst */ 15106 GXcopyInverted = 0xc, /* NOT src */ 15107 GXorInverted = 0xd, /* NOT src OR dst */ 15108 GXnand = 0xe, /* NOT src OR NOT dst */ 15109 GXset = 0xf, /* 1 */ 15110 } 15111 enum QueueMode : int { 15112 QueuedAlready, 15113 QueuedAfterReading, 15114 QueuedAfterFlush 15115 } 15116 15117 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 15118 15119 struct XPoint { 15120 short x; 15121 short y; 15122 } 15123 15124 enum CoordMode:int { 15125 CoordModeOrigin = 0, 15126 CoordModePrevious = 1 15127 } 15128 15129 enum PolygonShape:int { 15130 Complex = 0, 15131 Nonconvex = 1, 15132 Convex = 2 15133 } 15134 15135 struct XTextProperty { 15136 const(char)* value; /* same as Property routines */ 15137 Atom encoding; /* prop type */ 15138 int format; /* prop data format: 8, 16, or 32 */ 15139 arch_ulong nitems; /* number of data items in value */ 15140 } 15141 15142 version( X86_64 ) { 15143 static assert(XTextProperty.sizeof == 32); 15144 } 15145 15146 15147 struct XGCValues { 15148 int function_; /* logical operation */ 15149 arch_ulong plane_mask;/* plane mask */ 15150 arch_ulong foreground;/* foreground pixel */ 15151 arch_ulong background;/* background pixel */ 15152 int line_width; /* line width */ 15153 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 15154 int cap_style; /* CapNotLast, CapButt, 15155 CapRound, CapProjecting */ 15156 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 15157 int fill_style; /* FillSolid, FillTiled, 15158 FillStippled, FillOpaeueStippled */ 15159 int fill_rule; /* EvenOddRule, WindingRule */ 15160 int arc_mode; /* ArcChord, ArcPieSlice */ 15161 Pixmap tile; /* tile pixmap for tiling operations */ 15162 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 15163 int ts_x_origin; /* offset for tile or stipple operations */ 15164 int ts_y_origin; 15165 Font font; /* default text font for text operations */ 15166 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 15167 Bool graphics_exposures;/* boolean, should exposures be generated */ 15168 int clip_x_origin; /* origin for clipping */ 15169 int clip_y_origin; 15170 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 15171 int dash_offset; /* patterned/dashed line information */ 15172 char dashes; 15173 } 15174 15175 struct XColor { 15176 arch_ulong pixel; 15177 ushort red, green, blue; 15178 byte flags; 15179 byte pad; 15180 } 15181 15182 alias XErrorHandler = int function(Display*, XErrorEvent*); 15183 15184 struct XRectangle { 15185 short x; 15186 short y; 15187 ushort width; 15188 ushort height; 15189 } 15190 15191 enum ClipByChildren = 0; 15192 enum IncludeInferiors = 1; 15193 15194 enum Atom XA_PRIMARY = 1; 15195 enum Atom XA_SECONDARY = 2; 15196 enum Atom XA_STRING = 31; 15197 enum Atom XA_CARDINAL = 6; 15198 enum Atom XA_WM_NAME = 39; 15199 enum Atom XA_ATOM = 4; 15200 enum Atom XA_WINDOW = 33; 15201 enum Atom XA_WM_HINTS = 35; 15202 enum int PropModeAppend = 2; 15203 enum int PropModeReplace = 0; 15204 enum int PropModePrepend = 1; 15205 15206 enum int CopyFromParent = 0; 15207 enum int InputOutput = 1; 15208 15209 // XWMHints 15210 enum InputHint = 1 << 0; 15211 enum StateHint = 1 << 1; 15212 enum IconPixmapHint = (1L << 2); 15213 enum IconWindowHint = (1L << 3); 15214 enum IconPositionHint = (1L << 4); 15215 enum IconMaskHint = (1L << 5); 15216 enum WindowGroupHint = (1L << 6); 15217 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 15218 enum XUrgencyHint = (1L << 8); 15219 15220 // GC Components 15221 enum GCFunction = (1L<<0); 15222 enum GCPlaneMask = (1L<<1); 15223 enum GCForeground = (1L<<2); 15224 enum GCBackground = (1L<<3); 15225 enum GCLineWidth = (1L<<4); 15226 enum GCLineStyle = (1L<<5); 15227 enum GCCapStyle = (1L<<6); 15228 enum GCJoinStyle = (1L<<7); 15229 enum GCFillStyle = (1L<<8); 15230 enum GCFillRule = (1L<<9); 15231 enum GCTile = (1L<<10); 15232 enum GCStipple = (1L<<11); 15233 enum GCTileStipXOrigin = (1L<<12); 15234 enum GCTileStipYOrigin = (1L<<13); 15235 enum GCFont = (1L<<14); 15236 enum GCSubwindowMode = (1L<<15); 15237 enum GCGraphicsExposures= (1L<<16); 15238 enum GCClipXOrigin = (1L<<17); 15239 enum GCClipYOrigin = (1L<<18); 15240 enum GCClipMask = (1L<<19); 15241 enum GCDashOffset = (1L<<20); 15242 enum GCDashList = (1L<<21); 15243 enum GCArcMode = (1L<<22); 15244 enum GCLastBit = 22; 15245 15246 15247 enum int WithdrawnState = 0; 15248 enum int NormalState = 1; 15249 enum int IconicState = 3; 15250 15251 } 15252 } else version (OSXCocoa) { 15253 private: 15254 alias void* id; 15255 alias void* Class; 15256 alias void* SEL; 15257 alias void* IMP; 15258 alias void* Ivar; 15259 alias byte BOOL; 15260 alias const(void)* CFStringRef; 15261 alias const(void)* CFAllocatorRef; 15262 alias const(void)* CFTypeRef; 15263 alias const(void)* CGContextRef; 15264 alias const(void)* CGColorSpaceRef; 15265 alias const(void)* CGImageRef; 15266 alias ulong CGBitmapInfo; 15267 15268 struct objc_super { 15269 id self; 15270 Class superclass; 15271 } 15272 15273 struct CFRange { 15274 long location, length; 15275 } 15276 15277 struct NSPoint { 15278 double x, y; 15279 15280 static fromTuple(T)(T tupl) { 15281 return NSPoint(tupl.tupleof); 15282 } 15283 } 15284 struct NSSize { 15285 double width, height; 15286 } 15287 struct NSRect { 15288 NSPoint origin; 15289 NSSize size; 15290 } 15291 alias NSPoint CGPoint; 15292 alias NSSize CGSize; 15293 alias NSRect CGRect; 15294 15295 struct CGAffineTransform { 15296 double a, b, c, d, tx, ty; 15297 } 15298 15299 enum NSApplicationActivationPolicyRegular = 0; 15300 enum NSBackingStoreBuffered = 2; 15301 enum kCFStringEncodingUTF8 = 0x08000100; 15302 15303 enum : size_t { 15304 NSBorderlessWindowMask = 0, 15305 NSTitledWindowMask = 1 << 0, 15306 NSClosableWindowMask = 1 << 1, 15307 NSMiniaturizableWindowMask = 1 << 2, 15308 NSResizableWindowMask = 1 << 3, 15309 NSTexturedBackgroundWindowMask = 1 << 8 15310 } 15311 15312 enum : ulong { 15313 kCGImageAlphaNone, 15314 kCGImageAlphaPremultipliedLast, 15315 kCGImageAlphaPremultipliedFirst, 15316 kCGImageAlphaLast, 15317 kCGImageAlphaFirst, 15318 kCGImageAlphaNoneSkipLast, 15319 kCGImageAlphaNoneSkipFirst 15320 } 15321 enum : ulong { 15322 kCGBitmapAlphaInfoMask = 0x1F, 15323 kCGBitmapFloatComponents = (1 << 8), 15324 kCGBitmapByteOrderMask = 0x7000, 15325 kCGBitmapByteOrderDefault = (0 << 12), 15326 kCGBitmapByteOrder16Little = (1 << 12), 15327 kCGBitmapByteOrder32Little = (2 << 12), 15328 kCGBitmapByteOrder16Big = (3 << 12), 15329 kCGBitmapByteOrder32Big = (4 << 12) 15330 } 15331 enum CGPathDrawingMode { 15332 kCGPathFill, 15333 kCGPathEOFill, 15334 kCGPathStroke, 15335 kCGPathFillStroke, 15336 kCGPathEOFillStroke 15337 } 15338 enum objc_AssociationPolicy : size_t { 15339 OBJC_ASSOCIATION_ASSIGN = 0, 15340 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 15341 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 15342 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 15343 OBJC_ASSOCIATION_COPY = 0x303 //01403 15344 } 15345 15346 extern(C) { 15347 id objc_msgSend(id receiver, SEL selector, ...); 15348 id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...); 15349 id objc_getClass(const(char)* name); 15350 SEL sel_registerName(const(char)* str); 15351 Class objc_allocateClassPair(Class superclass, const(char)* name, 15352 size_t extra_bytes); 15353 void objc_registerClassPair(Class cls); 15354 BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types); 15355 id objc_getAssociatedObject(id object, void* key); 15356 void objc_setAssociatedObject(id object, void* key, id value, 15357 objc_AssociationPolicy policy); 15358 Ivar class_getInstanceVariable(Class cls, const(char)* name); 15359 id object_getIvar(id object, Ivar ivar); 15360 void object_setIvar(id object, Ivar ivar, id value); 15361 BOOL class_addIvar(Class cls, const(char)* name, 15362 size_t size, ubyte alignment, const(char)* types); 15363 15364 extern __gshared id NSApp; 15365 15366 void CFRelease(CFTypeRef obj); 15367 15368 CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator, 15369 const(char)* bytes, long numBytes, 15370 long encoding, 15371 BOOL isExternalRepresentation); 15372 long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding, 15373 char lossByte, bool isExternalRepresentation, 15374 char* buffer, long maxBufLen, long* usedBufLen); 15375 long CFStringGetLength(CFStringRef theString); 15376 15377 CGContextRef CGBitmapContextCreate(void* data, 15378 size_t width, size_t height, 15379 size_t bitsPerComponent, 15380 size_t bytesPerRow, 15381 CGColorSpaceRef colorspace, 15382 CGBitmapInfo bitmapInfo); 15383 void CGContextRelease(CGContextRef c); 15384 ubyte* CGBitmapContextGetData(CGContextRef c); 15385 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 15386 size_t CGBitmapContextGetWidth(CGContextRef c); 15387 size_t CGBitmapContextGetHeight(CGContextRef c); 15388 15389 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 15390 void CGColorSpaceRelease(CGColorSpaceRef cs); 15391 15392 void CGContextSetRGBStrokeColor(CGContextRef c, 15393 double red, double green, double blue, 15394 double alpha); 15395 void CGContextSetRGBFillColor(CGContextRef c, 15396 double red, double green, double blue, 15397 double alpha); 15398 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 15399 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, 15400 const(char)* str, size_t length); 15401 void CGContextStrokeLineSegments(CGContextRef c, 15402 const(CGPoint)* points, size_t count); 15403 15404 void CGContextBeginPath(CGContextRef c); 15405 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 15406 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 15407 void CGContextAddArc(CGContextRef c, double x, double y, double radius, 15408 double startAngle, double endAngle, long clockwise); 15409 void CGContextAddRect(CGContextRef c, CGRect rect); 15410 void CGContextAddLines(CGContextRef c, 15411 const(CGPoint)* points, size_t count); 15412 void CGContextSaveGState(CGContextRef c); 15413 void CGContextRestoreGState(CGContextRef c); 15414 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, 15415 ulong textEncoding); 15416 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 15417 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 15418 15419 void CGImageRelease(CGImageRef image); 15420 } 15421 15422 private: 15423 // A convenient method to create a CFString (=NSString) from a D string. 15424 CFStringRef createCFString(string str) { 15425 return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length, 15426 kCFStringEncodingUTF8, false); 15427 } 15428 15429 // Objective-C calls. 15430 RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) { 15431 auto _cmd = sel_registerName(selector.ptr); 15432 alias extern(C) RetType function(id, SEL, T) ExpectedType; 15433 return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args); 15434 } 15435 RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) { 15436 auto _cmd = sel_registerName(selector.ptr); 15437 auto cls = objc_getClass(className); 15438 alias extern(C) RetType function(id, SEL, T) ExpectedType; 15439 return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args); 15440 } 15441 RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) { 15442 return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args); 15443 } 15444 15445 alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay; 15446 alias objc_msgSend_classMethod!("alloc", id) alloc; 15447 alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:", 15448 id, NSRect, size_t, size_t, BOOL) initWithContentRect; 15449 alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle; 15450 alias objc_msgSend_specialized!("center", void) center; 15451 alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame; 15452 alias objc_msgSend_specialized!("setContentView:", void, id) setContentView; 15453 alias objc_msgSend_specialized!("release", void) release; 15454 alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor; 15455 alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor; 15456 alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront; 15457 alias objc_msgSend_specialized!("invalidate", void) invalidate; 15458 alias objc_msgSend_specialized!("close", void) close; 15459 alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:", 15460 id, double, id, SEL, id, BOOL) scheduledTimer; 15461 alias objc_msgSend_specialized!("run", void) run; 15462 alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext", 15463 id) currentNSGraphicsContext; 15464 alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort; 15465 alias objc_msgSend_specialized!("characters", CFStringRef) characters; 15466 alias objc_msgSend_specialized!("superclass", Class) superclass; 15467 alias objc_msgSend_specialized!("init", id) init; 15468 alias objc_msgSend_specialized!("addItem:", void, id) addItem; 15469 alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu; 15470 alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:", 15471 id, CFStringRef, SEL, CFStringRef) initWithTitle; 15472 alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu; 15473 alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate; 15474 alias objc_msgSend_specialized!("activateIgnoringOtherApps:", 15475 void, BOOL) activateIgnoringOtherApps; 15476 alias objc_msgSend_classMethod!("NSApplication", "sharedApplication", 15477 id) sharedNSApplication; 15478 alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy; 15479 } else static assert(0, "Unsupported operating system"); 15480 15481 15482 version(OSXCocoa) { 15483 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 15484 // 15485 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 15486 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 15487 // 15488 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 15489 // Probably won't even fully compile right now 15490 15491 import std.math : PI; 15492 import std.algorithm : map; 15493 import std.array : array; 15494 15495 alias SimpleWindow NativeWindowHandle; 15496 alias void delegate(id) NativeEventHandler; 15497 15498 __gshared Ivar simpleWindowIvar; 15499 15500 enum KEY_ESCAPE = 27; 15501 15502 mixin template NativeImageImplementation() { 15503 CGContextRef context; 15504 ubyte* rawData; 15505 final: 15506 15507 void convertToRgbaBytes(ubyte[] where) { 15508 assert(where.length == this.width * this.height * 4); 15509 15510 // if rawData had a length.... 15511 //assert(rawData.length == where.length); 15512 for(long idx = 0; idx < where.length; idx += 4) { 15513 auto alpha = rawData[idx + 3]; 15514 if(alpha == 255) { 15515 where[idx + 0] = rawData[idx + 0]; // r 15516 where[idx + 1] = rawData[idx + 1]; // g 15517 where[idx + 2] = rawData[idx + 2]; // b 15518 where[idx + 3] = rawData[idx + 3]; // a 15519 } else { 15520 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 15521 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 15522 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 15523 where[idx + 3] = rawData[idx + 3]; // a 15524 15525 } 15526 } 15527 } 15528 15529 void setFromRgbaBytes(in ubyte[] where) { 15530 // FIXME: this is probably wrong 15531 assert(where.length == this.width * this.height * 4); 15532 15533 // if rawData had a length.... 15534 //assert(rawData.length == where.length); 15535 for(long idx = 0; idx < where.length; idx += 4) { 15536 auto alpha = rawData[idx + 3]; 15537 if(alpha == 255) { 15538 rawData[idx + 0] = where[idx + 0]; // r 15539 rawData[idx + 1] = where[idx + 1]; // g 15540 rawData[idx + 2] = where[idx + 2]; // b 15541 rawData[idx + 3] = where[idx + 3]; // a 15542 } else { 15543 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 15544 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 15545 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 15546 rawData[idx + 3] = where[idx + 3]; // a 15547 15548 } 15549 } 15550 } 15551 15552 15553 void createImage(int width, int height, bool forcexshm=false) { 15554 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 15555 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 15556 colorSpace, 15557 kCGImageAlphaPremultipliedLast 15558 |kCGBitmapByteOrder32Big); 15559 CGColorSpaceRelease(colorSpace); 15560 rawData = CGBitmapContextGetData(context); 15561 } 15562 void dispose() { 15563 CGContextRelease(context); 15564 } 15565 15566 void setPixel(int x, int y, Color c) { 15567 auto offset = (y * width + x) * 4; 15568 if (c.a == 255) { 15569 rawData[offset + 0] = c.r; 15570 rawData[offset + 1] = c.g; 15571 rawData[offset + 2] = c.b; 15572 rawData[offset + 3] = c.a; 15573 } else { 15574 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 15575 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 15576 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 15577 rawData[offset + 3] = c.a; 15578 } 15579 } 15580 } 15581 15582 mixin template NativeScreenPainterImplementation() { 15583 CGContextRef context; 15584 ubyte[4] _outlineComponents; 15585 id view; 15586 15587 void create(NativeWindowHandle window) { 15588 context = window.drawingContext; 15589 view = window.view; 15590 } 15591 15592 void dispose() { 15593 setNeedsDisplay(view, true); 15594 } 15595 15596 // NotYetImplementedException 15597 Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); } 15598 void rasterOp(RasterOp op) {} 15599 Pen _activePen; 15600 Color _fillColor; 15601 Rectangle _clipRectangle; 15602 void setClipRectangle(int, int, int, int) {} 15603 void setFont(OperatingSystemFont) {} 15604 int fontHeight() { return 14; } 15605 15606 // end 15607 15608 void pen(Pen pen) { 15609 _activePen = pen; 15610 auto color = pen.color; // FIXME 15611 double alphaComponent = color.a/255.0f; 15612 CGContextSetRGBStrokeColor(context, 15613 color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 15614 15615 if (color.a != 255) { 15616 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 15617 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 15618 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 15619 _outlineComponents[3] = color.a; 15620 } else { 15621 _outlineComponents[0] = color.r; 15622 _outlineComponents[1] = color.g; 15623 _outlineComponents[2] = color.b; 15624 _outlineComponents[3] = color.a; 15625 } 15626 } 15627 15628 @property void fillColor(Color color) { 15629 CGContextSetRGBFillColor(context, 15630 color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 15631 } 15632 15633 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 15634 // NotYetImplementedException for upper left/width/height 15635 auto cgImage = CGBitmapContextCreateImage(image.context); 15636 auto size = CGSize(CGBitmapContextGetWidth(image.context), 15637 CGBitmapContextGetHeight(image.context)); 15638 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 15639 CGImageRelease(cgImage); 15640 } 15641 15642 version(OSXCocoa) {} else // NotYetImplementedException 15643 void drawPixmap(Sprite image, int x, int y) { 15644 // FIXME: is this efficient? 15645 auto cgImage = CGBitmapContextCreateImage(image.context); 15646 auto size = CGSize(CGBitmapContextGetWidth(image.context), 15647 CGBitmapContextGetHeight(image.context)); 15648 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 15649 CGImageRelease(cgImage); 15650 } 15651 15652 15653 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 15654 // FIXME: alignment 15655 if (_outlineComponents[3] != 0) { 15656 CGContextSaveGState(context); 15657 auto invAlpha = 1.0f/_outlineComponents[3]; 15658 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 15659 _outlineComponents[1]*invAlpha, 15660 _outlineComponents[2]*invAlpha, 15661 _outlineComponents[3]/255.0f); 15662 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length); 15663 // auto cfstr = cast(id)createCFString(text); 15664 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 15665 // NSPoint(x, y), null); 15666 // CFRelease(cfstr); 15667 CGContextRestoreGState(context); 15668 } 15669 } 15670 15671 void drawPixel(int x, int y) { 15672 auto rawData = CGBitmapContextGetData(context); 15673 auto width = CGBitmapContextGetWidth(context); 15674 auto height = CGBitmapContextGetHeight(context); 15675 auto offset = ((height - y - 1) * width + x) * 4; 15676 rawData[offset .. offset+4] = _outlineComponents; 15677 } 15678 15679 void drawLine(int x1, int y1, int x2, int y2) { 15680 CGPoint[2] linePoints; 15681 linePoints[0] = CGPoint(x1, y1); 15682 linePoints[1] = CGPoint(x2, y2); 15683 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 15684 } 15685 15686 void drawRectangle(int x, int y, int width, int height) { 15687 CGContextBeginPath(context); 15688 auto rect = CGRect(CGPoint(x, y), CGSize(width, height)); 15689 CGContextAddRect(context, rect); 15690 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 15691 } 15692 15693 void drawEllipse(int x1, int y1, int x2, int y2) { 15694 CGContextBeginPath(context); 15695 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 15696 CGContextAddEllipseInRect(context, rect); 15697 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 15698 } 15699 15700 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 15701 // @@@BUG@@@ Does not support elliptic arc (width != height). 15702 CGContextBeginPath(context); 15703 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 15704 start*PI/(180*64), finish*PI/(180*64), 0); 15705 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 15706 } 15707 15708 void drawPolygon(Point[] intPoints) { 15709 CGContextBeginPath(context); 15710 auto points = array(map!(CGPoint.fromTuple)(intPoints)); 15711 CGContextAddLines(context, points.ptr, points.length); 15712 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 15713 } 15714 } 15715 15716 mixin template NativeSimpleWindowImplementation() { 15717 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 15718 synchronized { 15719 if (NSApp == null) initializeApp(); 15720 } 15721 15722 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 15723 15724 // create the window. 15725 window = initWithContentRect(alloc("NSWindow"), 15726 contentRect, 15727 NSTitledWindowMask 15728 |NSClosableWindowMask 15729 |NSMiniaturizableWindowMask 15730 |NSResizableWindowMask, 15731 NSBackingStoreBuffered, 15732 true); 15733 15734 // set the title & move the window to center. 15735 auto windowTitle = createCFString(title); 15736 setTitle(window, windowTitle); 15737 CFRelease(windowTitle); 15738 center(window); 15739 15740 // create area to draw on. 15741 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 15742 drawingContext = CGBitmapContextCreate(null, width, height, 15743 8, 4*width, colorSpace, 15744 kCGImageAlphaPremultipliedLast 15745 |kCGBitmapByteOrder32Big); 15746 CGColorSpaceRelease(colorSpace); 15747 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 15748 auto matrix = CGContextGetTextMatrix(drawingContext); 15749 matrix.c = -matrix.c; 15750 matrix.d = -matrix.d; 15751 CGContextSetTextMatrix(drawingContext, matrix); 15752 15753 // create the subview that things will be drawn on. 15754 view = initWithFrame(alloc("SDGraphicsView"), contentRect); 15755 setContentView(window, view); 15756 object_setIvar(view, simpleWindowIvar, cast(id)this); 15757 release(view); 15758 15759 setBackgroundColor(window, whiteNSColor); 15760 makeKeyAndOrderFront(window, null); 15761 } 15762 void dispose() { 15763 closeWindow(); 15764 release(window); 15765 } 15766 void closeWindow() { 15767 invalidate(timer); 15768 .close(window); 15769 } 15770 15771 ScreenPainter getPainter() { 15772 return ScreenPainter(this, this); 15773 } 15774 15775 id window; 15776 id timer; 15777 id view; 15778 CGContextRef drawingContext; 15779 } 15780 15781 extern(C) { 15782 private: 15783 BOOL returnTrue3(id self, SEL _cmd, id app) { 15784 return true; 15785 } 15786 BOOL returnTrue2(id self, SEL _cmd) { 15787 return true; 15788 } 15789 15790 void pulse(id self, SEL _cmd) { 15791 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 15792 simpleWindow.handlePulse(); 15793 setNeedsDisplay(self, true); 15794 } 15795 void drawRect(id self, SEL _cmd, NSRect rect) { 15796 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 15797 auto curCtx = graphicsPort(currentNSGraphicsContext); 15798 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 15799 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), 15800 CGBitmapContextGetHeight(simpleWindow.drawingContext)); 15801 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 15802 CGImageRelease(cgImage); 15803 } 15804 void keyDown(id self, SEL _cmd, id event) { 15805 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 15806 15807 // the event may have multiple characters, and we send them all at 15808 // once. 15809 if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) { 15810 auto chars = characters(event); 15811 auto range = CFRange(0, CFStringGetLength(chars)); 15812 auto buffer = new char[range.length*3]; 15813 long actualLength; 15814 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false, 15815 buffer.ptr, cast(int) buffer.length, &actualLength); 15816 foreach (dchar dc; buffer[0..actualLength]) { 15817 if (simpleWindow.handleCharEvent) 15818 simpleWindow.handleCharEvent(dc); 15819 // NotYetImplementedException 15820 //if (simpleWindow.handleKeyEvent) 15821 //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp? 15822 } 15823 } 15824 15825 // the event's 'keyCode' is hardware-dependent. I don't think people 15826 // will like it. Let's leave it to the native handler. 15827 15828 // perform the default action. 15829 15830 // so the default action is to make a bomp sound and i dont want that 15831 // sooooooooo yeah not gonna do that. 15832 15833 //auto superData = objc_super(self, superclass(self)); 15834 //alias extern(C) void function(objc_super*, SEL, id) T; 15835 //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event); 15836 } 15837 } 15838 15839 // initialize the app so that it can be interacted with the user. 15840 // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html 15841 private void initializeApp() { 15842 // push an autorelease pool to avoid leaking. 15843 init(alloc("NSAutoreleasePool")); 15844 15845 // create a new NSApp instance 15846 sharedNSApplication; 15847 setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular); 15848 15849 // create the "Quit" menu. 15850 auto menuBar = init(alloc("NSMenu")); 15851 auto appMenuItem = init(alloc("NSMenuItem")); 15852 addItem(menuBar, appMenuItem); 15853 setMainMenu(NSApp, menuBar); 15854 release(appMenuItem); 15855 release(menuBar); 15856 15857 auto appMenu = init(alloc("NSMenu")); 15858 auto quitTitle = createCFString("Quit"); 15859 auto q = createCFString("q"); 15860 auto quitItem = initWithTitle(alloc("NSMenuItem"), 15861 quitTitle, sel_registerName("terminate:"), q); 15862 addItem(appMenu, quitItem); 15863 setSubmenu(appMenuItem, appMenu); 15864 release(quitItem); 15865 release(appMenu); 15866 CFRelease(q); 15867 CFRelease(quitTitle); 15868 15869 // assign a delegate for the application, allow it to quit when the last 15870 // window is closed. 15871 auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"), 15872 "SDWindowCloseDelegate", 0); 15873 class_addMethod(delegateClass, 15874 sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"), 15875 &returnTrue3, "c@:@"); 15876 objc_registerClassPair(delegateClass); 15877 15878 auto appDelegate = init(alloc("SDWindowCloseDelegate")); 15879 setDelegate(NSApp, appDelegate); 15880 activateIgnoringOtherApps(NSApp, true); 15881 15882 // create a new view that draws the graphics and respond to keyDown 15883 // events. 15884 auto viewClass = objc_allocateClassPair(objc_getClass("NSView"), 15885 "SDGraphicsView", (void*).sizeof); 15886 class_addIvar(viewClass, "simpledisplay_simpleWindow", 15887 (void*).sizeof, (void*).alignof, "^v"); 15888 class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"), 15889 &pulse, "v@:"); 15890 class_addMethod(viewClass, sel_registerName("drawRect:"), 15891 &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}"); 15892 class_addMethod(viewClass, sel_registerName("isFlipped"), 15893 &returnTrue2, "c@:"); 15894 class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"), 15895 &returnTrue2, "c@:"); 15896 class_addMethod(viewClass, sel_registerName("keyDown:"), 15897 &keyDown, "v@:@"); 15898 objc_registerClassPair(viewClass); 15899 simpleWindowIvar = class_getInstanceVariable(viewClass, 15900 "simpledisplay_simpleWindow"); 15901 } 15902 } 15903 15904 version(without_opengl) {} else 15905 extern(System) nothrow @nogc { 15906 //enum uint GL_VERSION = 0x1F02; 15907 //const(char)* glGetString (/*GLenum*/uint); 15908 version(X11) { 15909 static if (!SdpyIsUsingIVGLBinds) { 15910 15911 enum GLX_X_RENDERABLE = 0x8012; 15912 enum GLX_DRAWABLE_TYPE = 0x8010; 15913 enum GLX_RENDER_TYPE = 0x8011; 15914 enum GLX_X_VISUAL_TYPE = 0x22; 15915 enum GLX_TRUE_COLOR = 0x8002; 15916 enum GLX_WINDOW_BIT = 0x00000001; 15917 enum GLX_RGBA_BIT = 0x00000001; 15918 enum GLX_COLOR_INDEX_BIT = 0x00000002; 15919 enum GLX_SAMPLE_BUFFERS = 0x186a0; 15920 enum GLX_SAMPLES = 0x186a1; 15921 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 15922 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 15923 } 15924 15925 // GLX_EXT_swap_control 15926 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 15927 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 15928 15929 //k8: ugly code to prevent warnings when sdpy is compiled into .a 15930 extern(System) { 15931 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 15932 } 15933 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 15934 15935 // this made public so we don't have to get it again and again 15936 public bool glXCreateContextAttribsARB_present () { 15937 if (glXCreateContextAttribsARBFn is cast(void*)1) { 15938 // get it 15939 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 15940 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 15941 } 15942 return (glXCreateContextAttribsARBFn !is null); 15943 } 15944 15945 // this made public so we don't have to get it again and again 15946 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) { 15947 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 15948 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 15949 } 15950 15951 // extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers 15952 extern(C) private __gshared int function(int) glXSwapIntervalMESA; 15953 15954 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 15955 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 15956 if (_glx_swapInterval_fn is null) { 15957 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 15958 if (_glx_swapInterval_fn is null) { 15959 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 15960 return; 15961 } 15962 version(sdddd) { import std.stdio; debug writeln("glXSwapIntervalEXT found!"); } 15963 } 15964 15965 if(glXSwapIntervalMESA is null) { 15966 // it seems to require both to actually take effect on many computers 15967 // idk why 15968 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA"); 15969 if(glXSwapIntervalMESA is null) 15970 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1; 15971 } 15972 15973 if(cast(void*) glXSwapIntervalMESA > cast(void*) 1) 15974 glXSwapIntervalMESA(wait ? 1 : 0); 15975 15976 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 15977 } 15978 } else version(Windows) { 15979 static if (!SdpyIsUsingIVGLBinds) { 15980 enum GL_TRUE = 1; 15981 enum GL_FALSE = 0; 15982 alias int GLint; 15983 15984 public void* glbindGetProcAddress (const(char)* name) { 15985 void* res = wglGetProcAddress(name); 15986 if (res is null) { 15987 /+ 15988 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 15989 import core.sys.windows.windef, core.sys.windows.winbase; 15990 __gshared HINSTANCE dll = null; 15991 if (dll is null) { 15992 dll = LoadLibraryA("opengl32.dll"); 15993 if (dll is null) return null; // <32, but idc 15994 } 15995 res = GetProcAddress(dll, name); 15996 +/ 15997 res = GetProcAddress(gl.libHandle, name); 15998 } 15999 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 16000 return res; 16001 } 16002 } 16003 16004 16005 private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT; 16006 void wglSetVSync(bool wait) { 16007 if(wglSwapIntervalEXT is null) { 16008 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT"); 16009 if(wglSwapIntervalEXT is null) 16010 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1; 16011 } 16012 if(cast(void*) wglSwapIntervalEXT is cast(void*) 1) 16013 return; 16014 16015 wglSwapIntervalEXT(wait ? 1 : 0); 16016 } 16017 16018 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 16019 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 16020 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 16021 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 16022 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 16023 16024 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 16025 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 16026 16027 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 16028 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 16029 16030 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 16031 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 16032 16033 void wglInitOtherFunctions () { 16034 if (wglCreateContextAttribsARB is null) { 16035 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 16036 } 16037 } 16038 } 16039 16040 static if (!SdpyIsUsingIVGLBinds) { 16041 16042 interface GL { 16043 extern(System) @nogc nothrow: 16044 16045 void glGetIntegerv(int, void*); 16046 void glMatrixMode(int); 16047 void glPushMatrix(); 16048 void glLoadIdentity(); 16049 void glOrtho(double, double, double, double, double, double); 16050 void glFrustum(double, double, double, double, double, double); 16051 16052 void glPopMatrix(); 16053 void glEnable(int); 16054 void glDisable(int); 16055 void glClear(int); 16056 void glBegin(int); 16057 void glVertex2f(float, float); 16058 void glVertex3f(float, float, float); 16059 void glEnd(); 16060 void glColor3b(byte, byte, byte); 16061 void glColor3ub(ubyte, ubyte, ubyte); 16062 void glColor4b(byte, byte, byte, byte); 16063 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 16064 void glColor3i(int, int, int); 16065 void glColor3ui(uint, uint, uint); 16066 void glColor4i(int, int, int, int); 16067 void glColor4ui(uint, uint, uint, uint); 16068 void glColor3f(float, float, float); 16069 void glColor4f(float, float, float, float); 16070 void glTranslatef(float, float, float); 16071 void glScalef(float, float, float); 16072 version(X11) { 16073 void glSecondaryColor3b(byte, byte, byte); 16074 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 16075 void glSecondaryColor3i(int, int, int); 16076 void glSecondaryColor3ui(uint, uint, uint); 16077 void glSecondaryColor3f(float, float, float); 16078 } 16079 16080 void glDrawElements(int, int, int, void*); 16081 16082 void glRotatef(float, float, float, float); 16083 16084 uint glGetError(); 16085 16086 void glDeleteTextures(int, uint*); 16087 16088 16089 void glRasterPos2i(int, int); 16090 void glDrawPixels(int, int, uint, uint, void*); 16091 void glClearColor(float, float, float, float); 16092 16093 16094 void glPixelStorei(uint, int); 16095 16096 void glGenTextures(uint, uint*); 16097 void glBindTexture(int, int); 16098 void glTexParameteri(uint, uint, int); 16099 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 16100 void glTexImage2D(int, int, int, int, int, int, int, int, in void*); 16101 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 16102 /*GLsizei*/int width, /*GLsizei*/int height, 16103 uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels); 16104 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 16105 16106 void glLineWidth(int); 16107 16108 16109 void glTexCoord2f(float, float); 16110 void glVertex2i(int, int); 16111 void glBlendFunc (int, int); 16112 void glDepthFunc (int); 16113 void glViewport(int, int, int, int); 16114 16115 void glClearDepth(double); 16116 16117 void glReadBuffer(uint); 16118 void glReadPixels(int, int, int, int, int, int, void*); 16119 16120 void glFlush(); 16121 void glFinish(); 16122 16123 version(Windows) { 16124 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 16125 HGLRC wglCreateContext(HDC); 16126 HGLRC wglCreateLayerContext(HDC, int); 16127 BOOL wglDeleteContext(HGLRC); 16128 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 16129 HGLRC wglGetCurrentContext(); 16130 HDC wglGetCurrentDC(); 16131 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 16132 PROC wglGetProcAddress(LPCSTR); 16133 BOOL wglMakeCurrent(HDC, HGLRC); 16134 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 16135 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 16136 BOOL wglShareLists(HGLRC, HGLRC); 16137 BOOL wglSwapLayerBuffers(HDC, UINT); 16138 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 16139 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 16140 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 16141 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 16142 } 16143 16144 } 16145 16146 interface GL3 { 16147 extern(System) @nogc nothrow: 16148 16149 void glGenVertexArrays(GLsizei, GLuint*); 16150 void glBindVertexArray(GLuint); 16151 void glDeleteVertexArrays(GLsizei, const(GLuint)*); 16152 void glGenerateMipmap(GLenum); 16153 void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 16154 void glStencilMask(GLuint); 16155 void glStencilFunc(GLenum, GLint, GLuint); 16156 void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 16157 void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 16158 GLuint glCreateProgram(); 16159 GLuint glCreateShader(GLenum); 16160 void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 16161 void glCompileShader(GLuint); 16162 void glGetShaderiv(GLuint, GLenum, GLint*); 16163 void glAttachShader(GLuint, GLuint); 16164 void glBindAttribLocation(GLuint, GLuint, const(GLchar)*); 16165 void glLinkProgram(GLuint); 16166 void glGetProgramiv(GLuint, GLenum, GLint*); 16167 void glDeleteProgram(GLuint); 16168 void glDeleteShader(GLuint); 16169 GLint glGetUniformLocation(GLuint, const(GLchar)*); 16170 void glGenBuffers(GLsizei, GLuint*); 16171 void glUniform4fv(GLint, GLsizei, const(GLfloat)*); 16172 void glUniform4f(GLint, float, float, float, float); 16173 void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); 16174 void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); 16175 void glDrawArrays(GLenum, GLint, GLsizei); 16176 void glStencilOp(GLenum, GLenum, GLenum); 16177 void glUseProgram(GLuint); 16178 void glCullFace(GLenum); 16179 void glFrontFace(GLenum); 16180 void glActiveTexture(GLenum); 16181 void glBindBuffer(GLenum, GLuint); 16182 void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum); 16183 void glEnableVertexAttribArray(GLuint); 16184 void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 16185 void glUniform1i(GLint, GLint); 16186 void glUniform2fv(GLint, GLsizei, const(GLfloat)*); 16187 void glDisableVertexAttribArray(GLuint); 16188 void glDeleteBuffers(GLsizei, const(GLuint)*); 16189 void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); 16190 void glLogicOp (GLenum opcode); 16191 void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 16192 void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers); 16193 void glGenFramebuffers (GLsizei n, GLuint* framebuffers); 16194 GLenum glCheckFramebufferStatus (GLenum target); 16195 void glBindFramebuffer (GLenum target, GLuint framebuffer); 16196 } 16197 16198 interface GL4 { 16199 extern(System) @nogc nothrow: 16200 16201 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 16202 /*GLsizei*/int width, /*GLsizei*/int height, 16203 uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels); 16204 } 16205 16206 interface GLU { 16207 extern(System) @nogc nothrow: 16208 16209 void gluLookAt(double, double, double, double, double, double, double, double, double); 16210 void gluPerspective(double, double, double, double); 16211 16212 char* gluErrorString(uint); 16213 } 16214 16215 16216 enum GL_RED = 0x1903; 16217 enum GL_ALPHA = 0x1906; 16218 16219 enum uint GL_FRONT = 0x0404; 16220 16221 enum uint GL_BLEND = 0x0be2; 16222 enum uint GL_LEQUAL = 0x0203; 16223 16224 16225 enum uint GL_RGB = 0x1907; 16226 enum uint GL_BGRA = 0x80e1; 16227 enum uint GL_RGBA = 0x1908; 16228 enum uint GL_TEXTURE_2D = 0x0DE1; 16229 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 16230 enum uint GL_NEAREST = 0x2600; 16231 enum uint GL_LINEAR = 0x2601; 16232 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 16233 enum uint GL_TEXTURE_WRAP_S = 0x2802; 16234 enum uint GL_TEXTURE_WRAP_T = 0x2803; 16235 enum uint GL_REPEAT = 0x2901; 16236 enum uint GL_CLAMP = 0x2900; 16237 enum uint GL_CLAMP_TO_EDGE = 0x812F; 16238 enum uint GL_CLAMP_TO_BORDER = 0x812D; 16239 enum uint GL_DECAL = 0x2101; 16240 enum uint GL_MODULATE = 0x2100; 16241 enum uint GL_TEXTURE_ENV = 0x2300; 16242 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 16243 enum uint GL_REPLACE = 0x1E01; 16244 enum uint GL_LIGHTING = 0x0B50; 16245 enum uint GL_DITHER = 0x0BD0; 16246 16247 enum uint GL_NO_ERROR = 0; 16248 16249 16250 16251 enum int GL_VIEWPORT = 0x0BA2; 16252 enum int GL_MODELVIEW = 0x1700; 16253 enum int GL_TEXTURE = 0x1702; 16254 enum int GL_PROJECTION = 0x1701; 16255 enum int GL_DEPTH_TEST = 0x0B71; 16256 16257 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 16258 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 16259 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 16260 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 16261 16262 enum int GL_POINTS = 0x0000; 16263 enum int GL_LINES = 0x0001; 16264 enum int GL_LINE_LOOP = 0x0002; 16265 enum int GL_LINE_STRIP = 0x0003; 16266 enum int GL_TRIANGLES = 0x0004; 16267 enum int GL_TRIANGLE_STRIP = 5; 16268 enum int GL_TRIANGLE_FAN = 6; 16269 enum int GL_QUADS = 7; 16270 enum int GL_QUAD_STRIP = 8; 16271 enum int GL_POLYGON = 9; 16272 16273 alias GLvoid = void; 16274 alias GLboolean = ubyte; 16275 alias GLuint = uint; 16276 alias GLenum = uint; 16277 alias GLchar = char; 16278 alias GLsizei = int; 16279 alias GLfloat = float; 16280 alias GLintptr = size_t; 16281 alias GLsizeiptr = ptrdiff_t; 16282 16283 16284 enum uint GL_INVALID_ENUM = 0x0500; 16285 16286 enum uint GL_ZERO = 0; 16287 enum uint GL_ONE = 1; 16288 16289 enum uint GL_BYTE = 0x1400; 16290 enum uint GL_UNSIGNED_BYTE = 0x1401; 16291 enum uint GL_SHORT = 0x1402; 16292 enum uint GL_UNSIGNED_SHORT = 0x1403; 16293 enum uint GL_INT = 0x1404; 16294 enum uint GL_UNSIGNED_INT = 0x1405; 16295 enum uint GL_FLOAT = 0x1406; 16296 enum uint GL_2_BYTES = 0x1407; 16297 enum uint GL_3_BYTES = 0x1408; 16298 enum uint GL_4_BYTES = 0x1409; 16299 enum uint GL_DOUBLE = 0x140A; 16300 16301 enum uint GL_STREAM_DRAW = 0x88E0; 16302 16303 enum uint GL_CCW = 0x0901; 16304 16305 enum uint GL_STENCIL_TEST = 0x0B90; 16306 enum uint GL_SCISSOR_TEST = 0x0C11; 16307 16308 enum uint GL_EQUAL = 0x0202; 16309 enum uint GL_NOTEQUAL = 0x0205; 16310 16311 enum uint GL_ALWAYS = 0x0207; 16312 enum uint GL_KEEP = 0x1E00; 16313 16314 enum uint GL_INCR = 0x1E02; 16315 16316 enum uint GL_INCR_WRAP = 0x8507; 16317 enum uint GL_DECR_WRAP = 0x8508; 16318 16319 enum uint GL_CULL_FACE = 0x0B44; 16320 enum uint GL_BACK = 0x0405; 16321 16322 enum uint GL_FRAGMENT_SHADER = 0x8B30; 16323 enum uint GL_VERTEX_SHADER = 0x8B31; 16324 16325 enum uint GL_COMPILE_STATUS = 0x8B81; 16326 enum uint GL_LINK_STATUS = 0x8B82; 16327 16328 enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893; 16329 16330 enum uint GL_STATIC_DRAW = 0x88E4; 16331 16332 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 16333 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 16334 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 16335 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 16336 16337 enum uint GL_GENERATE_MIPMAP = 0x8191; 16338 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 16339 16340 enum uint GL_TEXTURE0 = 0x84C0U; 16341 enum uint GL_TEXTURE1 = 0x84C1U; 16342 16343 enum uint GL_ARRAY_BUFFER = 0x8892; 16344 16345 enum uint GL_SRC_COLOR = 0x0300; 16346 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 16347 enum uint GL_SRC_ALPHA = 0x0302; 16348 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 16349 enum uint GL_DST_ALPHA = 0x0304; 16350 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 16351 enum uint GL_DST_COLOR = 0x0306; 16352 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 16353 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 16354 16355 enum uint GL_INVERT = 0x150AU; 16356 16357 enum uint GL_DEPTH_STENCIL = 0x84F9U; 16358 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 16359 16360 enum uint GL_FRAMEBUFFER = 0x8D40U; 16361 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 16362 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 16363 16364 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 16365 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 16366 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 16367 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 16368 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 16369 16370 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 16371 enum uint GL_CLEAR = 0x1500U; 16372 enum uint GL_COPY = 0x1503U; 16373 enum uint GL_XOR = 0x1506U; 16374 16375 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 16376 16377 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 16378 16379 } 16380 } 16381 16382 version(without_opengl) {} else { 16383 static if(!SdpyIsUsingIVGLBinds) { 16384 version(Windows) { 16385 mixin DynamicLoad!(GL, "opengl32", 1, true) gl; 16386 mixin DynamicLoad!(GLU, "glu32", 1, true) glu; 16387 } else { 16388 mixin DynamicLoad!(GL, "GL", 1, true) gl; 16389 mixin DynamicLoad!(GLU, "GLU", 3, true) glu; 16390 } 16391 mixin DynamicLoadSupplementalOpenGL!(GL3) gl3; 16392 16393 16394 shared static this() { 16395 gl.loadDynamicLibrary(); 16396 glu.loadDynamicLibrary(); 16397 } 16398 } 16399 } 16400 16401 /++ 16402 Convenience method for converting D arrays to opengl buffer data 16403 16404 I would LOVE to overload it with the original glBufferData, but D won't 16405 let me since glBufferData is a function pointer :( 16406 16407 Added: August 25, 2020 (version 8.5) 16408 +/ 16409 version(without_opengl) {} else 16410 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) { 16411 glBufferData(target, data.length, data.ptr, usage); 16412 } 16413 16414 /+ 16415 /++ 16416 A matrix for simple uses that easily integrates with [OpenGlShader]. 16417 16418 Might not be useful to you since it only as some simple functions and 16419 probably isn't that fast. 16420 16421 Note it uses an inline static array for its storage, so copying it 16422 may be expensive. 16423 +/ 16424 struct BasicMatrix(int columns, int rows, T = float) { 16425 import core.stdc.math; 16426 16427 T[columns * rows] data = 0.0; 16428 16429 /++ 16430 Basic operations that operate *in place*. 16431 +/ 16432 void translate() { 16433 16434 } 16435 16436 /// ditto 16437 void scale() { 16438 16439 } 16440 16441 /// ditto 16442 void rotate() { 16443 16444 } 16445 16446 /++ 16447 16448 +/ 16449 static if(columns == rows) 16450 static BasicMatrix identity() { 16451 BasicMatrix m; 16452 foreach(i; 0 .. columns) 16453 data[0 + i + i * columns] = 1.0; 16454 return m; 16455 } 16456 16457 static BasicMatrix ortho() { 16458 return BasicMatrix.init; 16459 } 16460 } 16461 +/ 16462 16463 /++ 16464 Convenience class for using opengl shaders. 16465 16466 Ensure that you've loaded opengl 3+ and set your active 16467 context before trying to use this. 16468 16469 Added: August 25, 2020 (version 8.5) 16470 +/ 16471 version(without_opengl) {} else 16472 final class OpenGlShader { 16473 private int shaderProgram_; 16474 private @property void shaderProgram(int a) { 16475 shaderProgram_ = a; 16476 } 16477 /// Get the program ID for use in OpenGL functions. 16478 public @property int shaderProgram() { 16479 return shaderProgram_; 16480 } 16481 16482 /++ 16483 16484 +/ 16485 static struct Source { 16486 uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc. 16487 string code; /// 16488 } 16489 16490 /++ 16491 Helper method to just compile some shader code and check for errors 16492 while you do glCreateShader, etc. on the outside yourself. 16493 16494 This just does `glShaderSource` and `glCompileShader` for the given code. 16495 16496 If you the OpenGlShader class constructor, you never need to call this yourself. 16497 +/ 16498 static void compile(int sid, Source code) { 16499 const(char)*[1] buffer; 16500 int[1] lengthBuffer; 16501 16502 buffer[0] = code.code.ptr; 16503 lengthBuffer[0] = cast(int) code.code.length; 16504 16505 glShaderSource(sid, 1, buffer.ptr, lengthBuffer.ptr); 16506 glCompileShader(sid); 16507 16508 int success; 16509 glGetShaderiv(sid, GL_COMPILE_STATUS, &success); 16510 if(!success) { 16511 char[512] info; 16512 int len; 16513 glGetShaderInfoLog(sid, info.length, &len, info.ptr); 16514 16515 throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]); 16516 } 16517 } 16518 16519 /++ 16520 Calls `glLinkProgram` and throws if error a occurs. 16521 16522 If you the OpenGlShader class constructor, you never need to call this yourself. 16523 +/ 16524 static void link(int shaderProgram) { 16525 glLinkProgram(shaderProgram); 16526 int success; 16527 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 16528 if(!success) { 16529 char[512] info; 16530 int len; 16531 glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr); 16532 16533 throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]); 16534 } 16535 } 16536 16537 /++ 16538 Constructs the shader object by calling `glCreateProgram`, then 16539 compiling each given [Source], and finally, linking them together. 16540 16541 Throws: on compile or link failure. 16542 +/ 16543 this(Source[] codes...) { 16544 shaderProgram = glCreateProgram(); 16545 16546 int[16] shadersBufferStack; 16547 16548 int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 16549 shadersBufferStack[0 .. codes.length] : 16550 new int[](codes.length); 16551 16552 foreach(idx, code; codes) { 16553 shadersBuffer[idx] = glCreateShader(code.type); 16554 16555 compile(shadersBuffer[idx], code); 16556 16557 glAttachShader(shaderProgram, shadersBuffer[idx]); 16558 } 16559 16560 link(shaderProgram); 16561 16562 foreach(s; shadersBuffer) 16563 glDeleteShader(s); 16564 } 16565 16566 /// Calls `glUseProgram(this.shaderProgram)` 16567 void use() { 16568 glUseProgram(this.shaderProgram); 16569 } 16570 16571 /// Deletes the program. 16572 void delete_() { 16573 glDeleteProgram(shaderProgram); 16574 shaderProgram = 0; 16575 } 16576 16577 /++ 16578 [OpenGlShader.uniforms].name gives you one of these. 16579 16580 You can get the id out of it or just assign 16581 +/ 16582 static struct Uniform { 16583 /// the id passed to glUniform* 16584 int id; 16585 16586 /// Assigns the 4 floats. You will probably have to call this via the .opAssign name 16587 void opAssign(float x, float y, float z, float w) { 16588 glUniform4f(id, x, y, z, w); 16589 } 16590 } 16591 16592 static struct UniformsHelper { 16593 OpenGlShader _shader; 16594 16595 @property Uniform opDispatch(string name)() { 16596 auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); 16597 if(i == -1) 16598 throw new Exception("Could not find uniform " ~ name); 16599 return Uniform(i); 16600 } 16601 } 16602 16603 /++ 16604 Gives access to the uniforms through dot access. 16605 `OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo"); 16606 +/ 16607 @property UniformsHelper uniforms() { return UniformsHelper(this); } 16608 } 16609 16610 version(linux) { 16611 version(with_eventloop) {} else { 16612 private int epollFd = -1; 16613 void prepareEventLoop() { 16614 if(epollFd != -1) 16615 return; // already initialized, no need to do it again 16616 import ep = core.sys.linux.epoll; 16617 16618 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 16619 if(epollFd == -1) 16620 throw new Exception("epoll create failure"); 16621 } 16622 } 16623 } else version(Posix) { 16624 void prepareEventLoop() {} 16625 } 16626 16627 version(X11) { 16628 import core.stdc.locale : LC_ALL; // rdmd fix 16629 __gshared bool sdx_isUTF8Locale; 16630 16631 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 16632 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 16633 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 16634 // anal magic is here. I (Ketmar) hope you like it. 16635 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 16636 // always return correct unicode symbols. The detection is here 'cause user can change locale 16637 // later. 16638 16639 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 16640 shared static this () { 16641 if(!librariesSuccessfullyLoaded) 16642 return; 16643 16644 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 16645 16646 // this doesn't hurt; it may add some locking, but the speed is still 16647 // allows doing 60 FPS videogames; also, ignore the result, as most 16648 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 16649 // never seen this failing). 16650 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 16651 16652 setlocale(LC_ALL, ""); 16653 // check if out locale is UTF-8 16654 auto lct = setlocale(LC_CTYPE, null); 16655 if (lct is null) { 16656 sdx_isUTF8Locale = false; 16657 } else { 16658 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 16659 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 16660 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 16661 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 16662 { 16663 sdx_isUTF8Locale = true; 16664 break; 16665 } 16666 } 16667 } 16668 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 16669 } 16670 } 16671 16672 class ExperimentalTextComponent2 { 16673 /+ 16674 Stage 1: get it working monospace 16675 Stage 2: use proportional font 16676 Stage 3: allow changes in inline style 16677 Stage 4: allow new fonts and sizes in the middle 16678 Stage 5: optimize gap buffer 16679 Stage 6: optimize layout 16680 Stage 7: word wrap 16681 Stage 8: justification 16682 Stage 9: editing, selection, etc. 16683 +/ 16684 16685 /++ 16686 It asks for a window so it can translate abstract font sizes to actual on-screen values depending on the window's current dpi, scaling settings, etc. 16687 +/ 16688 this(SimpleWindow window) { 16689 this.window = window; 16690 } 16691 16692 private SimpleWindow window; 16693 16694 16695 /++ 16696 When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces 16697 representing the internal parts. The first pass is focused on the x parameter, then the 16698 renderer is responsible for going back to the parts in the current line and calling 16699 adjustDownForAscent to change the y params. 16700 +/ 16701 static interface ComponentRenderHelper { 16702 void adjustDownForAscent(int amount); // at the end of the line it needs to do these 16703 16704 int ascent() const; 16705 int descent() const; 16706 16707 int advance() const; 16708 16709 bool endsWithExplititLineBreak() const; 16710 } 16711 16712 static interface RenderResult { 16713 /++ 16714 This is responsible for using what space is left (your object is responsible for keeping its own state after getting it updated from [repositionForNextLine]) and not going over if at all possible. If you can word wrap, you should when space is out. Otherwise, you can keep going if it means overflow hidden or scroll. 16715 +/ 16716 void popFront(); 16717 @property bool empty() const; 16718 @property ComponentRenderHelper front() const; 16719 16720 void repositionForNextLine(Point baseline, int availableWidth); 16721 } 16722 16723 static interface ComponentInFlow { 16724 void draw(ScreenPainter painter); 16725 //RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different" 16726 16727 bool startsWithExplicitLineBreak() const; 16728 } 16729 16730 static class TextFlowComponent : ComponentInFlow { 16731 bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true 16732 16733 Color foreground; 16734 Color background; 16735 16736 OperatingSystemFont font; // should NEVER be null 16737 16738 ubyte attributes; // underline, strike through, display on new block 16739 16740 version(Windows) 16741 const(wchar)[] content; 16742 else 16743 const(char)[] content; // this should NEVER have a newline, except at the end 16744 16745 RenderedComponent[] rendered; // entirely controlled by [rerender] 16746 16747 // could prolly put some spacing around it too like margin / padding 16748 16749 this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) 16750 in { assert(font !is null); 16751 assert(!font.isNull); } 16752 do 16753 { 16754 this.foreground = f; 16755 this.background = b; 16756 this.font = font; 16757 16758 this.attributes = attr; 16759 version(Windows) { 16760 auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines; 16761 auto sz = sizeOfConvertedWstring(c, conversionFlags); 16762 auto buffer = new wchar[](sz); 16763 this.content = makeWindowsString(c, buffer, conversionFlags); 16764 } else { 16765 this.content = c.dup; 16766 } 16767 } 16768 16769 void draw(ScreenPainter painter) { 16770 painter.setFont(this.font); 16771 painter.outlineColor = this.foreground; 16772 painter.fillColor = Color.transparent; 16773 foreach(rendered; this.rendered) { 16774 // the component works in term of baseline, 16775 // but the painter works in term of upper left bounding box 16776 // so need to translate that 16777 16778 if(this.background.a) { 16779 painter.fillColor = this.background; 16780 painter.outlineColor = this.background; 16781 16782 painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height)); 16783 16784 painter.outlineColor = this.foreground; 16785 painter.fillColor = Color.transparent; 16786 } 16787 16788 painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice); 16789 16790 // FIXME: strike through, underline, highlight selection, etc. 16791 } 16792 } 16793 } 16794 16795 // I could split the parts into words on render 16796 // for easier word-wrap, each one being an unbreakable "inline-block" 16797 private TextFlowComponent[] parts; 16798 private int needsRerenderFrom; 16799 16800 void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) { 16801 // FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop. 16802 parts ~= new TextFlowComponent(f, b, font, attr, c); 16803 } 16804 16805 static struct RenderedComponent { 16806 int startX; 16807 int startY; 16808 short width; 16809 // height is always from the containing part's font. This saves some space and means recalculations need not continue past the current line, unless a new part is added with a different font! 16810 // for individual chars in here you've gotta process on demand 16811 version(Windows) 16812 const(wchar)[] slice; 16813 else 16814 const(char)[] slice; 16815 } 16816 16817 16818 void rerender(Rectangle boundingBox) { 16819 Point baseline = boundingBox.upperLeft; 16820 16821 this.boundingBox.left = boundingBox.left; 16822 this.boundingBox.top = boundingBox.top; 16823 16824 auto remainingParts = parts; 16825 16826 int largestX; 16827 16828 16829 foreach(part; parts) 16830 part.font.prepareContext(window); 16831 scope(exit) 16832 foreach(part; parts) 16833 part.font.releaseContext(); 16834 16835 calculateNextLine: 16836 16837 int nextLineHeight = 0; 16838 int nextBiggestDescent = 0; 16839 16840 foreach(part; remainingParts) { 16841 auto height = part.font.ascent; 16842 if(height > nextLineHeight) 16843 nextLineHeight = height; 16844 if(part.font.descent > nextBiggestDescent) 16845 nextBiggestDescent = part.font.descent; 16846 if(part.content.length && part.content[$-1] == '\n') 16847 break; 16848 } 16849 16850 baseline.y += nextLineHeight; 16851 auto lineStart = baseline; 16852 16853 while(remainingParts.length) { 16854 remainingParts[0].rendered = null; 16855 16856 bool eol; 16857 if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n') 16858 eol = true; 16859 16860 // FIXME: word wrap 16861 auto font = remainingParts[0].font; 16862 auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)]; 16863 auto width = font.stringWidth(slice, window); 16864 remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice); 16865 16866 remainingParts = remainingParts[1 .. $]; 16867 baseline.x += width; 16868 16869 if(eol) { 16870 baseline.y += nextBiggestDescent; 16871 if(baseline.x > largestX) 16872 largestX = baseline.x; 16873 baseline.x = lineStart.x; 16874 goto calculateNextLine; 16875 } 16876 } 16877 16878 if(baseline.x > largestX) 16879 largestX = baseline.x; 16880 16881 this.boundingBox.right = largestX; 16882 this.boundingBox.bottom = baseline.y; 16883 } 16884 16885 // you must call rerender first! 16886 void draw(ScreenPainter painter) { 16887 foreach(part; parts) { 16888 part.draw(painter); 16889 } 16890 } 16891 16892 struct IdentifyResult { 16893 TextFlowComponent part; 16894 int charIndexInPart; 16895 int totalCharIndex = -1; // if this is -1, it just means the end 16896 16897 Rectangle boundingBox; 16898 } 16899 16900 IdentifyResult identify(Point pt, bool exact = false) { 16901 if(parts.length == 0) 16902 return IdentifyResult(null, 0); 16903 16904 if(pt.y < boundingBox.top) { 16905 if(exact) 16906 return IdentifyResult(null, 1); 16907 return IdentifyResult(parts[0], 0); 16908 } 16909 if(pt.y > boundingBox.bottom) { 16910 if(exact) 16911 return IdentifyResult(null, 2); 16912 return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length); 16913 } 16914 16915 int tci = 0; 16916 16917 // I should probably like binary search this or something... 16918 foreach(ref part; parts) { 16919 foreach(rendered; part.rendered) { 16920 auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent); 16921 if(rect.contains(pt)) { 16922 auto x = pt.x - rendered.startX; 16923 auto estimatedIdx = x / part.font.averageWidth; 16924 16925 if(estimatedIdx < 0) 16926 estimatedIdx = 0; 16927 16928 if(estimatedIdx > rendered.slice.length) 16929 estimatedIdx = cast(int) rendered.slice.length; 16930 16931 int idx; 16932 int x1, x2; 16933 if(part.font.isMonospace) { 16934 auto w = part.font.averageWidth; 16935 if(!exact && x > (estimatedIdx + 1) * w) 16936 return IdentifyResult(null, 4); 16937 idx = estimatedIdx; 16938 x1 = idx * w; 16939 x2 = (idx + 1) * w; 16940 } else { 16941 idx = estimatedIdx; 16942 16943 part.font.prepareContext(window); 16944 scope(exit) part.font.releaseContext(); 16945 16946 // int iterations; 16947 16948 while(true) { 16949 // iterations++; 16950 x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0; 16951 x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies. 16952 16953 x1 += rendered.startX; 16954 x2 += rendered.startX; 16955 16956 if(pt.x < x1) { 16957 if(idx == 0) { 16958 if(exact) 16959 return IdentifyResult(null, 6); 16960 else 16961 break; 16962 } 16963 idx--; 16964 } else if(pt.x > x2) { 16965 idx++; 16966 if(idx > rendered.slice.length) { 16967 if(exact) 16968 return IdentifyResult(null, 5); 16969 else 16970 break; 16971 } 16972 } else if(pt.x >= x1 && pt.x <= x2) { 16973 if(idx) 16974 idx--; // point it at the original index 16975 break; // we fit 16976 } 16977 } 16978 16979 // import std.stdio; writeln(iterations) 16980 } 16981 16982 16983 return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8? 16984 } 16985 } 16986 tci += cast(int) part.content.length; // FIXME: utf-8? 16987 } 16988 return IdentifyResult(null, 3); 16989 } 16990 16991 Rectangle boundingBox; // only set after [rerender] 16992 16993 // text will be positioned around the exclusion zone 16994 static struct ExclusionZone { 16995 16996 } 16997 16998 ExclusionZone[] exclusionZones; 16999 } 17000 17001 17002 // Don't use this yet. When I'm happy with it, I will move it to the 17003 // regular module namespace. 17004 mixin template ExperimentalTextComponent() { 17005 17006 alias Rectangle = arsd.color.Rectangle; 17007 17008 struct ForegroundColor { 17009 Color color; 17010 alias color this; 17011 17012 this(Color c) { 17013 color = c; 17014 } 17015 17016 this(int r, int g, int b, int a = 255) { 17017 color = Color(r, g, b, a); 17018 } 17019 17020 static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) { 17021 return ForegroundColor(mixin("Color." ~ s)); 17022 } 17023 } 17024 17025 struct BackgroundColor { 17026 Color color; 17027 alias color this; 17028 17029 this(Color c) { 17030 color = c; 17031 } 17032 17033 this(int r, int g, int b, int a = 255) { 17034 color = Color(r, g, b, a); 17035 } 17036 17037 static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) { 17038 return BackgroundColor(mixin("Color." ~ s)); 17039 } 17040 } 17041 17042 static class InlineElement { 17043 string text; 17044 17045 BlockElement containingBlock; 17046 17047 Color color = Color.black; 17048 Color backgroundColor = Color.transparent; 17049 ushort styles; 17050 17051 string font; 17052 int fontSize; 17053 17054 int lineHeight; 17055 17056 void* identifier; 17057 17058 Rectangle boundingBox; 17059 int[] letterXs; // FIXME: maybe i should do bounding boxes for every character 17060 17061 bool isMergeCompatible(InlineElement other) { 17062 return 17063 containingBlock is other.containingBlock && 17064 color == other.color && 17065 backgroundColor == other.backgroundColor && 17066 styles == other.styles && 17067 font == other.font && 17068 fontSize == other.fontSize && 17069 lineHeight == other.lineHeight && 17070 true; 17071 } 17072 17073 int xOfIndex(size_t index) { 17074 if(index < letterXs.length) 17075 return letterXs[index]; 17076 else 17077 return boundingBox.right; 17078 } 17079 17080 InlineElement clone() { 17081 auto ie = new InlineElement(); 17082 ie.tupleof = this.tupleof; 17083 return ie; 17084 } 17085 17086 InlineElement getPreviousInlineElement() { 17087 InlineElement prev = null; 17088 foreach(ie; this.containingBlock.parts) { 17089 if(ie is this) 17090 break; 17091 prev = ie; 17092 } 17093 if(prev is null) { 17094 BlockElement pb; 17095 BlockElement cb = this.containingBlock; 17096 moar: 17097 foreach(ie; this.containingBlock.containingLayout.blocks) { 17098 if(ie is cb) 17099 break; 17100 pb = ie; 17101 } 17102 if(pb is null) 17103 return null; 17104 if(pb.parts.length == 0) { 17105 cb = pb; 17106 goto moar; 17107 } 17108 17109 prev = pb.parts[$-1]; 17110 17111 } 17112 return prev; 17113 } 17114 17115 InlineElement getNextInlineElement() { 17116 InlineElement next = null; 17117 foreach(idx, ie; this.containingBlock.parts) { 17118 if(ie is this) { 17119 if(idx + 1 < this.containingBlock.parts.length) 17120 next = this.containingBlock.parts[idx + 1]; 17121 break; 17122 } 17123 } 17124 if(next is null) { 17125 BlockElement n; 17126 foreach(idx, ie; this.containingBlock.containingLayout.blocks) { 17127 if(ie is this.containingBlock) { 17128 if(idx + 1 < this.containingBlock.containingLayout.blocks.length) 17129 n = this.containingBlock.containingLayout.blocks[idx + 1]; 17130 break; 17131 } 17132 } 17133 if(n is null) 17134 return null; 17135 17136 if(n.parts.length) 17137 next = n.parts[0]; 17138 else {} // FIXME 17139 17140 } 17141 return next; 17142 } 17143 17144 } 17145 17146 // Block elements are used entirely for positioning inline elements, 17147 // which are the things that are actually drawn. 17148 class BlockElement { 17149 InlineElement[] parts; 17150 uint alignment; 17151 17152 int whiteSpace; // pre, pre-wrap, wrap 17153 17154 TextLayout containingLayout; 17155 17156 // inputs 17157 Point where; 17158 Size minimumSize; 17159 Size maximumSize; 17160 Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box. 17161 void* identifier; 17162 17163 Rectangle margin; 17164 Rectangle padding; 17165 17166 // outputs 17167 Rectangle[] boundingBoxes; 17168 } 17169 17170 struct TextIdentifyResult { 17171 InlineElement element; 17172 int offset; 17173 17174 private TextIdentifyResult fixupNewline() { 17175 if(element !is null && offset < element.text.length && element.text[offset] == '\n') { 17176 offset--; 17177 } else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') { 17178 offset--; 17179 } 17180 return this; 17181 } 17182 } 17183 17184 class TextLayout { 17185 BlockElement[] blocks; 17186 Rectangle boundingBox_; 17187 Rectangle boundingBox() { return boundingBox_; } 17188 void boundingBox(Rectangle r) { 17189 if(r != boundingBox_) { 17190 boundingBox_ = r; 17191 layoutInvalidated = true; 17192 } 17193 } 17194 17195 Rectangle contentBoundingBox() { 17196 Rectangle r; 17197 foreach(block; blocks) 17198 foreach(ie; block.parts) { 17199 if(ie.boundingBox.right > r.right) 17200 r.right = ie.boundingBox.right; 17201 if(ie.boundingBox.bottom > r.bottom) 17202 r.bottom = ie.boundingBox.bottom; 17203 } 17204 return r; 17205 } 17206 17207 BlockElement[] getBlocks() { 17208 return blocks; 17209 } 17210 17211 InlineElement[] getTexts() { 17212 InlineElement[] elements; 17213 foreach(block; blocks) 17214 elements ~= block.parts; 17215 return elements; 17216 } 17217 17218 string getPlainText() { 17219 string text; 17220 foreach(block; blocks) 17221 foreach(part; block.parts) 17222 text ~= part.text; 17223 return text; 17224 } 17225 17226 string getHtml() { 17227 return null; // FIXME 17228 } 17229 17230 this(Rectangle boundingBox) { 17231 this.boundingBox = boundingBox; 17232 } 17233 17234 BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) { 17235 auto be = new BlockElement(); 17236 be.containingLayout = this; 17237 if(after is null) 17238 blocks ~= be; 17239 else { 17240 foreach(idx, b; blocks) { 17241 if(b is after.containingBlock) { 17242 blocks = blocks[0 .. idx + 1] ~ be ~ blocks[idx + 1 .. $]; 17243 break; 17244 } 17245 } 17246 } 17247 return be; 17248 } 17249 17250 void clear() { 17251 blocks = null; 17252 selectionStart = selectionEnd = caret = Caret.init; 17253 } 17254 17255 void addText(Args...)(Args args) { 17256 if(blocks.length == 0) 17257 addBlock(); 17258 17259 InlineElement ie = new InlineElement(); 17260 foreach(idx, arg; args) { 17261 static if(is(typeof(arg) == ForegroundColor)) 17262 ie.color = arg; 17263 else static if(is(typeof(arg) == TextFormat)) { 17264 if(arg & 0x8000) // ~TextFormat.something turns it off 17265 ie.styles &= arg; 17266 else 17267 ie.styles |= arg; 17268 } else static if(is(typeof(arg) == string)) { 17269 static if(idx == 0 && args.length > 1) 17270 static assert(0, "Put styles before the string."); 17271 size_t lastLineIndex; 17272 foreach(cidx, char a; arg) { 17273 if(a == '\n') { 17274 ie.text = arg[lastLineIndex .. cidx + 1]; 17275 lastLineIndex = cidx + 1; 17276 ie.containingBlock = blocks[$-1]; 17277 blocks[$-1].parts ~= ie.clone; 17278 ie.text = null; 17279 } else { 17280 17281 } 17282 } 17283 17284 ie.text = arg[lastLineIndex .. $]; 17285 ie.containingBlock = blocks[$-1]; 17286 blocks[$-1].parts ~= ie.clone; 17287 caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length); 17288 } 17289 } 17290 17291 invalidateLayout(); 17292 } 17293 17294 void tryMerge(InlineElement into, InlineElement what) { 17295 if(!into.isMergeCompatible(what)) { 17296 return; // cannot merge, different configs 17297 } 17298 17299 // cool, can merge, bring text together... 17300 into.text ~= what.text; 17301 17302 // and remove what 17303 for(size_t a = 0; a < what.containingBlock.parts.length; a++) { 17304 if(what.containingBlock.parts[a] is what) { 17305 for(size_t i = a; i < what.containingBlock.parts.length - 1; i++) 17306 what.containingBlock.parts[i] = what.containingBlock.parts[i + 1]; 17307 what.containingBlock.parts = what.containingBlock.parts[0 .. $-1]; 17308 17309 } 17310 } 17311 17312 // FIXME: ensure no other carets have a reference to it 17313 } 17314 17315 /// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click. 17316 TextIdentifyResult identify(int x, int y, bool exact = false) { 17317 TextIdentifyResult inexactMatch; 17318 foreach(block; blocks) { 17319 foreach(part; block.parts) { 17320 if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { 17321 17322 // FIXME binary search 17323 int tidx; 17324 int lastX; 17325 foreach_reverse(idxo, lx; part.letterXs) { 17326 int idx = cast(int) idxo; 17327 if(lx <= x) { 17328 if(lastX && lastX - x < x - lx) 17329 tidx = idx + 1; 17330 else 17331 tidx = idx; 17332 break; 17333 } 17334 lastX = lx; 17335 } 17336 17337 return TextIdentifyResult(part, tidx).fixupNewline; 17338 } else if(!exact) { 17339 // we're not in the box, but are we on the same line? 17340 if(y >= part.boundingBox.top && y < part.boundingBox.bottom) 17341 inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length); 17342 } 17343 } 17344 } 17345 17346 if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) 17347 return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline; 17348 17349 return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; 17350 } 17351 17352 void moveCaretToPixelCoordinates(int x, int y) { 17353 auto result = identify(x, y); 17354 caret.inlineElement = result.element; 17355 caret.offset = result.offset; 17356 } 17357 17358 void selectToPixelCoordinates(int x, int y) { 17359 auto result = identify(x, y); 17360 17361 if(y < caretLastDrawnY1) { 17362 // on a previous line, carat is selectionEnd 17363 selectionEnd = caret; 17364 17365 selectionStart = Caret(this, result.element, result.offset); 17366 } else if(y > caretLastDrawnY2) { 17367 // on a later line 17368 selectionStart = caret; 17369 17370 selectionEnd = Caret(this, result.element, result.offset); 17371 } else { 17372 // on the same line... 17373 if(x <= caretLastDrawnX) { 17374 selectionEnd = caret; 17375 selectionStart = Caret(this, result.element, result.offset); 17376 } else { 17377 selectionStart = caret; 17378 selectionEnd = Caret(this, result.element, result.offset); 17379 } 17380 17381 } 17382 } 17383 17384 17385 /// Call this if the inputs change. It will reflow everything 17386 void redoLayout(ScreenPainter painter) { 17387 //painter.setClipRectangle(boundingBox); 17388 auto pos = Point(boundingBox.left, boundingBox.top); 17389 17390 int lastHeight; 17391 void nl() { 17392 pos.x = boundingBox.left; 17393 pos.y += lastHeight; 17394 } 17395 foreach(block; blocks) { 17396 nl(); 17397 foreach(part; block.parts) { 17398 part.letterXs = null; 17399 17400 auto size = painter.textSize(part.text); 17401 version(Windows) 17402 if(part.text.length && part.text[$-1] == '\n') 17403 size.height /= 2; // windows counts the new line at the end, but we don't want that 17404 17405 part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height); 17406 17407 foreach(idx, char c; part.text) { 17408 // FIXME: unicode 17409 part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x; 17410 } 17411 17412 pos.x += size.width; 17413 if(pos.x >= boundingBox.right) { 17414 pos.y += size.height; 17415 pos.x = boundingBox.left; 17416 lastHeight = 0; 17417 } else { 17418 lastHeight = size.height; 17419 } 17420 17421 if(part.text.length && part.text[$-1] == '\n') 17422 nl(); 17423 } 17424 } 17425 17426 layoutInvalidated = false; 17427 } 17428 17429 bool layoutInvalidated = true; 17430 void invalidateLayout() { 17431 layoutInvalidated = true; 17432 } 17433 17434 // FIXME: caret can remain sometimes when inserting 17435 // FIXME: inserting at the beginning once you already have something can eff it up. 17436 void drawInto(ScreenPainter painter, bool focused = false) { 17437 if(layoutInvalidated) 17438 redoLayout(painter); 17439 foreach(block; blocks) { 17440 foreach(part; block.parts) { 17441 painter.outlineColor = part.color; 17442 painter.fillColor = part.backgroundColor; 17443 17444 auto pos = part.boundingBox.upperLeft; 17445 auto size = part.boundingBox.size; 17446 17447 painter.drawText(pos, part.text); 17448 if(part.styles & TextFormat.underline) 17449 painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4)); 17450 if(part.styles & TextFormat.strikethrough) 17451 painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2)); 17452 } 17453 } 17454 17455 // on every redraw, I will force the caret to be 17456 // redrawn too, in order to eliminate perceived lag 17457 // when moving around with the mouse. 17458 eraseCaret(painter); 17459 17460 if(focused) { 17461 highlightSelection(painter); 17462 drawCaret(painter); 17463 } 17464 } 17465 17466 void highlightSelection(ScreenPainter painter) { 17467 if(selectionStart is selectionEnd) 17468 return; // no selection 17469 17470 if(selectionStart.inlineElement is null) return; 17471 if(selectionEnd.inlineElement is null) return; 17472 17473 assert(selectionStart.inlineElement !is null); 17474 assert(selectionEnd.inlineElement !is null); 17475 17476 painter.rasterOp = RasterOp.xor; 17477 painter.outlineColor = Color.transparent; 17478 painter.fillColor = Color(255, 255, 127); 17479 17480 auto at = selectionStart.inlineElement; 17481 auto atOffset = selectionStart.offset; 17482 bool done; 17483 while(at) { 17484 auto box = at.boundingBox; 17485 if(atOffset < at.letterXs.length) 17486 box.left = at.letterXs[atOffset]; 17487 17488 if(at is selectionEnd.inlineElement) { 17489 if(selectionEnd.offset < at.letterXs.length) 17490 box.right = at.letterXs[selectionEnd.offset]; 17491 done = true; 17492 } 17493 17494 painter.drawRectangle(box.upperLeft, box.width, box.height); 17495 17496 if(done) 17497 break; 17498 17499 at = at.getNextInlineElement(); 17500 atOffset = 0; 17501 } 17502 } 17503 17504 int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2; 17505 bool caretShowingOnScreen = false; 17506 void drawCaret(ScreenPainter painter) { 17507 //painter.setClipRectangle(boundingBox); 17508 int x, y1, y2; 17509 if(caret.inlineElement is null) { 17510 x = boundingBox.left; 17511 y1 = boundingBox.top + 2; 17512 y2 = boundingBox.top + painter.fontHeight; 17513 } else { 17514 x = caret.inlineElement.xOfIndex(caret.offset); 17515 y1 = caret.inlineElement.boundingBox.top + 2; 17516 y2 = caret.inlineElement.boundingBox.bottom - 2; 17517 } 17518 17519 if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2)) 17520 eraseCaret(painter); 17521 17522 painter.pen = Pen(Color.white, 1); 17523 painter.rasterOp = RasterOp.xor; 17524 painter.drawLine( 17525 Point(x, y1), 17526 Point(x, y2) 17527 ); 17528 painter.rasterOp = RasterOp.normal; 17529 caretShowingOnScreen = !caretShowingOnScreen; 17530 17531 if(caretShowingOnScreen) { 17532 caretLastDrawnX = x; 17533 caretLastDrawnY1 = y1; 17534 caretLastDrawnY2 = y2; 17535 } 17536 } 17537 17538 Rectangle caretBoundingBox() { 17539 int x, y1, y2; 17540 if(caret.inlineElement is null) { 17541 x = boundingBox.left; 17542 y1 = boundingBox.top + 2; 17543 y2 = boundingBox.top + 16; 17544 } else { 17545 x = caret.inlineElement.xOfIndex(caret.offset); 17546 y1 = caret.inlineElement.boundingBox.top + 2; 17547 y2 = caret.inlineElement.boundingBox.bottom - 2; 17548 } 17549 17550 return Rectangle(x, y1, x + 1, y2); 17551 } 17552 17553 void eraseCaret(ScreenPainter painter) { 17554 //painter.setClipRectangle(boundingBox); 17555 if(!caretShowingOnScreen) return; 17556 painter.pen = Pen(Color.white, 1); 17557 painter.rasterOp = RasterOp.xor; 17558 painter.drawLine( 17559 Point(caretLastDrawnX, caretLastDrawnY1), 17560 Point(caretLastDrawnX, caretLastDrawnY2) 17561 ); 17562 17563 caretShowingOnScreen = false; 17564 painter.rasterOp = RasterOp.normal; 17565 } 17566 17567 /// Caret movement api 17568 /// These should give the user a logical result based on what they see on screen... 17569 /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) 17570 void moveUp() { 17571 if(caret.inlineElement is null) return; 17572 auto x = caret.inlineElement.xOfIndex(caret.offset); 17573 auto y = caret.inlineElement.boundingBox.top + 2; 17574 17575 y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 17576 if(y < 0) 17577 return; 17578 17579 auto i = identify(x, y); 17580 17581 if(i.element) { 17582 caret.inlineElement = i.element; 17583 caret.offset = i.offset; 17584 } 17585 } 17586 void moveDown() { 17587 if(caret.inlineElement is null) return; 17588 auto x = caret.inlineElement.xOfIndex(caret.offset); 17589 auto y = caret.inlineElement.boundingBox.bottom - 2; 17590 17591 y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 17592 17593 auto i = identify(x, y); 17594 if(i.element) { 17595 caret.inlineElement = i.element; 17596 caret.offset = i.offset; 17597 } 17598 } 17599 void moveLeft() { 17600 if(caret.inlineElement is null) return; 17601 if(caret.offset) 17602 caret.offset--; 17603 else { 17604 auto p = caret.inlineElement.getPreviousInlineElement(); 17605 if(p) { 17606 caret.inlineElement = p; 17607 if(p.text.length && p.text[$-1] == '\n') 17608 caret.offset = cast(int) p.text.length - 1; 17609 else 17610 caret.offset = cast(int) p.text.length; 17611 } 17612 } 17613 } 17614 void moveRight() { 17615 if(caret.inlineElement is null) return; 17616 if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { 17617 caret.offset++; 17618 } else { 17619 auto p = caret.inlineElement.getNextInlineElement(); 17620 if(p) { 17621 caret.inlineElement = p; 17622 caret.offset = 0; 17623 } 17624 } 17625 } 17626 void moveHome() { 17627 if(caret.inlineElement is null) return; 17628 auto x = 0; 17629 auto y = caret.inlineElement.boundingBox.top + 2; 17630 17631 auto i = identify(x, y); 17632 17633 if(i.element) { 17634 caret.inlineElement = i.element; 17635 caret.offset = i.offset; 17636 } 17637 } 17638 void moveEnd() { 17639 if(caret.inlineElement is null) return; 17640 auto x = int.max; 17641 auto y = caret.inlineElement.boundingBox.top + 2; 17642 17643 auto i = identify(x, y); 17644 17645 if(i.element) { 17646 caret.inlineElement = i.element; 17647 caret.offset = i.offset; 17648 } 17649 17650 } 17651 void movePageUp(ref Caret caret) {} 17652 void movePageDown(ref Caret caret) {} 17653 17654 void moveDocumentStart(ref Caret caret) { 17655 if(blocks.length && blocks[0].parts.length) 17656 caret = Caret(this, blocks[0].parts[0], 0); 17657 else 17658 caret = Caret.init; 17659 } 17660 17661 void moveDocumentEnd(ref Caret caret) { 17662 if(blocks.length) { 17663 auto parts = blocks[$-1].parts; 17664 if(parts.length) { 17665 caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length); 17666 } else { 17667 caret = Caret.init; 17668 } 17669 } else 17670 caret = Caret.init; 17671 } 17672 17673 void deleteSelection() { 17674 if(selectionStart is selectionEnd) 17675 return; 17676 17677 if(selectionStart.inlineElement is null) return; 17678 if(selectionEnd.inlineElement is null) return; 17679 17680 assert(selectionStart.inlineElement !is null); 17681 assert(selectionEnd.inlineElement !is null); 17682 17683 auto at = selectionStart.inlineElement; 17684 17685 if(selectionEnd.inlineElement is at) { 17686 // same element, need to chop out 17687 at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $]; 17688 at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $]; 17689 selectionEnd.offset -= selectionEnd.offset - selectionStart.offset; 17690 } else { 17691 // different elements, we can do it with slicing 17692 at.text = at.text[0 .. selectionStart.offset]; 17693 if(selectionStart.offset < at.letterXs.length) 17694 at.letterXs = at.letterXs[0 .. selectionStart.offset]; 17695 17696 at = at.getNextInlineElement(); 17697 17698 while(at) { 17699 if(at is selectionEnd.inlineElement) { 17700 at.text = at.text[selectionEnd.offset .. $]; 17701 if(selectionEnd.offset < at.letterXs.length) 17702 at.letterXs = at.letterXs[selectionEnd.offset .. $]; 17703 selectionEnd.offset = 0; 17704 break; 17705 } else { 17706 auto cfd = at; 17707 cfd.text = null; // delete the whole thing 17708 17709 at = at.getNextInlineElement(); 17710 17711 if(cfd.text.length == 0) { 17712 // and remove cfd 17713 for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) { 17714 if(cfd.containingBlock.parts[a] is cfd) { 17715 for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++) 17716 cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1]; 17717 cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1]; 17718 17719 } 17720 } 17721 } 17722 } 17723 } 17724 } 17725 17726 caret = selectionEnd; 17727 selectNone(); 17728 17729 invalidateLayout(); 17730 17731 } 17732 17733 /// Plain text editing api. These work at the current caret inside the selected inline element. 17734 void insert(in char[] text) { 17735 foreach(dchar ch; text) 17736 insert(ch); 17737 } 17738 /// ditto 17739 void insert(dchar ch) { 17740 17741 bool selectionDeleted = false; 17742 if(selectionStart !is selectionEnd) { 17743 deleteSelection(); 17744 selectionDeleted = true; 17745 } 17746 17747 if(ch == 127) { 17748 delete_(); 17749 return; 17750 } 17751 if(ch == 8) { 17752 if(!selectionDeleted) 17753 backspace(); 17754 return; 17755 } 17756 17757 invalidateLayout(); 17758 17759 if(ch == 13) ch = 10; 17760 auto e = caret.inlineElement; 17761 if(e is null) { 17762 addText("" ~ cast(char) ch) ; // FIXME 17763 return; 17764 } 17765 17766 if(caret.offset == e.text.length) { 17767 e.text ~= cast(char) ch; // FIXME 17768 caret.offset++; 17769 if(ch == 10) { 17770 auto c = caret.inlineElement.clone; 17771 c.text = null; 17772 c.letterXs = null; 17773 insertPartAfter(c,e); 17774 caret = Caret(this, c, 0); 17775 } 17776 } else { 17777 // FIXME cast char sucks 17778 if(ch == 10) { 17779 auto c = caret.inlineElement.clone; 17780 c.text = e.text[caret.offset .. $]; 17781 if(caret.offset < c.letterXs.length) 17782 c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox 17783 e.text = e.text[0 .. caret.offset] ~ cast(char) ch; 17784 if(caret.offset <= e.letterXs.length) { 17785 e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box 17786 } 17787 insertPartAfter(c,e); 17788 caret = Caret(this, c, 0); 17789 } else { 17790 e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $]; 17791 caret.offset++; 17792 } 17793 } 17794 } 17795 17796 void insertPartAfter(InlineElement what, InlineElement where) { 17797 foreach(idx, p; where.containingBlock.parts) { 17798 if(p is where) { 17799 if(idx + 1 == where.containingBlock.parts.length) 17800 where.containingBlock.parts ~= what; 17801 else 17802 where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $]; 17803 return; 17804 } 17805 } 17806 } 17807 17808 void cleanupStructures() { 17809 for(size_t i = 0; i < blocks.length; i++) { 17810 auto block = blocks[i]; 17811 for(size_t a = 0; a < block.parts.length; a++) { 17812 auto part = block.parts[a]; 17813 if(part.text.length == 0) { 17814 for(size_t b = a; b < block.parts.length - 1; b++) 17815 block.parts[b] = block.parts[b+1]; 17816 block.parts = block.parts[0 .. $-1]; 17817 } 17818 } 17819 if(block.parts.length == 0) { 17820 for(size_t a = i; a < blocks.length - 1; a++) 17821 blocks[a] = blocks[a+1]; 17822 blocks = blocks[0 .. $-1]; 17823 } 17824 } 17825 } 17826 17827 void backspace() { 17828 try_again: 17829 auto e = caret.inlineElement; 17830 if(e is null) 17831 return; 17832 if(caret.offset == 0) { 17833 auto prev = e.getPreviousInlineElement(); 17834 if(prev is null) 17835 return; 17836 auto newOffset = cast(int) prev.text.length; 17837 tryMerge(prev, e); 17838 caret.inlineElement = prev; 17839 caret.offset = prev is null ? 0 : newOffset; 17840 17841 goto try_again; 17842 } else if(caret.offset == e.text.length) { 17843 e.text = e.text[0 .. $-1]; 17844 caret.offset--; 17845 } else { 17846 e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $]; 17847 caret.offset--; 17848 } 17849 //cleanupStructures(); 17850 17851 invalidateLayout(); 17852 } 17853 void delete_() { 17854 if(selectionStart !is selectionEnd) 17855 deleteSelection(); 17856 else { 17857 auto before = caret; 17858 moveRight(); 17859 if(caret != before) { 17860 backspace(); 17861 } 17862 } 17863 17864 invalidateLayout(); 17865 } 17866 void overstrike() {} 17867 17868 /// Selection API. See also: caret movement. 17869 void selectAll() { 17870 moveDocumentStart(selectionStart); 17871 moveDocumentEnd(selectionEnd); 17872 } 17873 bool selectNone() { 17874 if(selectionStart != selectionEnd) { 17875 selectionStart = selectionEnd = Caret.init; 17876 return true; 17877 } 17878 return false; 17879 } 17880 17881 /// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements. 17882 /// They will modify the current selection if there is one and will splice one in if needed. 17883 void changeAttributes() {} 17884 17885 17886 /// Text search api. They manipulate the selection and/or caret. 17887 void findText(string text) {} 17888 void findIndex(size_t textIndex) {} 17889 17890 // sample event handlers 17891 17892 void handleEvent(KeyEvent event) { 17893 //if(event.type == KeyEvent.Type.KeyPressed) { 17894 17895 //} 17896 } 17897 17898 void handleEvent(dchar ch) { 17899 17900 } 17901 17902 void handleEvent(MouseEvent event) { 17903 17904 } 17905 17906 bool contentEditable; // can it be edited? 17907 bool contentCaretable; // is there a caret/cursor that moves around in there? 17908 bool contentSelectable; // selectable? 17909 17910 Caret caret; 17911 Caret selectionStart; 17912 Caret selectionEnd; 17913 17914 bool insertMode; 17915 } 17916 17917 struct Caret { 17918 TextLayout layout; 17919 InlineElement inlineElement; 17920 int offset; 17921 } 17922 17923 enum TextFormat : ushort { 17924 // decorations 17925 underline = 1, 17926 strikethrough = 2, 17927 17928 // font selectors 17929 17930 bold = 0x4000 | 1, // weight 700 17931 light = 0x4000 | 2, // weight 300 17932 veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold 17933 // bold | light is really invalid but should give weight 500 17934 // veryBoldOrLight without one of the others should just give the default for the font; it should be ignored. 17935 17936 italic = 0x4000 | 8, 17937 smallcaps = 0x4000 | 16, 17938 } 17939 17940 void* findFont(string family, int weight, TextFormat formats) { 17941 return null; 17942 } 17943 17944 } 17945 17946 /++ 17947 $(PITFALL this is not yet stable and may break in future versions without notice.) 17948 17949 History: 17950 Added February 19, 2021 17951 +/ 17952 /// Group: drag_and_drop 17953 interface DropHandler { 17954 /++ 17955 Called when the drag enters the handler's area. 17956 +/ 17957 DragAndDropAction dragEnter(DropPackage*); 17958 /++ 17959 Called when the drag leaves the handler's area or is 17960 cancelled. You should free your resources when this is called. 17961 +/ 17962 void dragLeave(); 17963 /++ 17964 Called continually as the drag moves over the handler's area. 17965 17966 Returns: feedback to the dragger 17967 +/ 17968 DropParameters dragOver(Point pt); 17969 /++ 17970 The user dropped the data and you should process it now. You can 17971 access the data through the given [DropPackage]. 17972 +/ 17973 void drop(scope DropPackage*); 17974 /++ 17975 Called when the drop is complete. You should free whatever temporary 17976 resources you were using. It is often reasonable to simply forward 17977 this call to [dragLeave]. 17978 +/ 17979 void finish(); 17980 17981 /++ 17982 Parameters returned by [DropHandler.drop]. 17983 +/ 17984 static struct DropParameters { 17985 /++ 17986 Acceptable action over this area. 17987 +/ 17988 DragAndDropAction action; 17989 /++ 17990 Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again. 17991 17992 If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources. 17993 +/ 17994 Rectangle consistentWithin; 17995 } 17996 } 17997 17998 /++ 17999 History: 18000 Added February 19, 2021 18001 +/ 18002 /// Group: drag_and_drop 18003 enum DragAndDropAction { 18004 none = 0, 18005 copy, 18006 move, 18007 link, 18008 ask, 18009 custom 18010 } 18011 18012 /++ 18013 An opaque structure representing dropped data. It contains 18014 private, platform-specific data that your `drop` function 18015 should simply forward. 18016 18017 $(PITFALL this is not yet stable and may break in future versions without notice.) 18018 18019 History: 18020 Added February 19, 2021 18021 +/ 18022 /// Group: drag_and_drop 18023 struct DropPackage { 18024 /++ 18025 Lists the available formats as magic numbers. You should compare these 18026 against looked-up formats (see [DraggableData.getFormatId]) you know you support and can 18027 understand the passed data. 18028 +/ 18029 DraggableData.FormatId[] availableFormats() { 18030 version(X11) { 18031 return xFormats; 18032 } else version(Windows) { 18033 if(pDataObj is null) 18034 return null; 18035 18036 typeof(return) ret; 18037 18038 IEnumFORMATETC ef; 18039 if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) { 18040 FORMATETC fmt; 18041 ULONG fetched; 18042 while(ef.Next(1, &fmt, &fetched) == S_OK) { 18043 if(fetched == 0) 18044 break; 18045 18046 if(fmt.lindex != -1) 18047 continue; 18048 if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT) 18049 continue; 18050 if(!(fmt.tymed & TYMED.TYMED_HGLOBAL)) 18051 continue; 18052 18053 ret ~= fmt.cfFormat; 18054 } 18055 } 18056 18057 return ret; 18058 } 18059 } 18060 18061 /++ 18062 Gets data from the drop and optionally accepts it. 18063 18064 Returns: 18065 void because the data is fed asynchronously through the `dg` parameter. 18066 18067 Params: 18068 acceptedAction = the action to report back to the ender. If it is [DragAndDropAction.none], you are just inspecting the data, but not accepting the drop. 18069 18070 This is useful to tell the sender that you accepted a move, for example, so they can update their data source as well. For other cases, accepting a drop also indicates that any memory associated with the transfer can be freed. 18071 18072 Calling `getData` again after accepting a drop is not permitted. 18073 18074 format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format. 18075 18076 dg = delegate to receive the data asynchronously. Please note this delegate may be called immediately, never be called, or be called somewhere in between during event loop processing depending on the platform, requested format, and other conditions beyond your control. 18077 18078 Throws: 18079 if `format` was not compatible with the [availableFormats] or if the drop has already been accepted. 18080 18081 History: 18082 Included in first release of [DropPackage]. 18083 +/ 18084 void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) { 18085 version(X11) { 18086 18087 auto display = XDisplayConnection.get(); 18088 auto selectionAtom = GetAtom!"XdndSelection"(display); 18089 auto best = format; 18090 18091 static class X11GetSelectionHandler_Drop : X11GetSelectionHandler { 18092 18093 XDisplay* display; 18094 Atom selectionAtom; 18095 DraggableData.FormatId best; 18096 DraggableData.FormatId format; 18097 void delegate(scope ubyte[] data) dg; 18098 DragAndDropAction acceptedAction; 18099 Window sourceWindow; 18100 SimpleWindow win; 18101 this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) { 18102 this.display = display; 18103 this.win = win; 18104 this.sourceWindow = sourceWindow; 18105 this.format = format; 18106 this.selectionAtom = selectionAtom; 18107 this.best = best; 18108 this.dg = dg; 18109 this.acceptedAction = acceptedAction; 18110 } 18111 18112 18113 mixin X11GetSelectionHandler_Basics; 18114 18115 void handleData(Atom target, in ubyte[] data) { 18116 //if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 18117 18118 dg(cast(ubyte[]) data); 18119 18120 if(acceptedAction != DragAndDropAction.none) { 18121 auto display = XDisplayConnection.get; 18122 18123 XClientMessageEvent xclient; 18124 18125 xclient.type = EventType.ClientMessage; 18126 xclient.window = sourceWindow; 18127 xclient.message_type = GetAtom!"XdndFinished"(display); 18128 xclient.format = 32; 18129 xclient.data.l[0] = win.impl.window; 18130 xclient.data.l[1] = 1; // drop successful 18131 xclient.data.l[2] = dndActionAtom(display, acceptedAction); 18132 18133 XSendEvent( 18134 display, 18135 sourceWindow, 18136 false, 18137 EventMask.NoEventMask, 18138 cast(XEvent*) &xclient 18139 ); 18140 18141 XFlush(display); 18142 } 18143 } 18144 18145 Atom findBestFormat(Atom[] answer) { 18146 Atom best = None; 18147 foreach(option; answer) { 18148 if(option == format) { 18149 best = option; 18150 break; 18151 } 18152 /* 18153 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 18154 best = option; 18155 break; 18156 } else if(option == XA_STRING) { 18157 best = option; 18158 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 18159 best = option; 18160 } 18161 */ 18162 } 18163 return best; 18164 } 18165 } 18166 18167 win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction); 18168 18169 XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp); 18170 18171 } else version(Windows) { 18172 18173 // clean up like DragLeave 18174 // pass effect back up 18175 18176 FORMATETC t; 18177 assert(format >= 0 && format <= ushort.max); 18178 t.cfFormat = cast(ushort) format; 18179 t.lindex = -1; 18180 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 18181 t.tymed = TYMED.TYMED_HGLOBAL; 18182 18183 STGMEDIUM m; 18184 18185 if(pDataObj.GetData(&t, &m) != S_OK) { 18186 // fail 18187 } else { 18188 // succeed, take the data and clean up 18189 18190 // FIXME: ensure it is legit HGLOBAL 18191 auto handle = m.hGlobal; 18192 18193 if(handle) { 18194 auto sz = GlobalSize(handle); 18195 if(auto ptr = cast(ubyte*) GlobalLock(handle)) { 18196 scope(exit) GlobalUnlock(handle); 18197 scope(exit) GlobalFree(handle); 18198 18199 auto data = ptr[0 .. sz]; 18200 18201 dg(data); 18202 } 18203 } 18204 } 18205 } 18206 } 18207 18208 private: 18209 18210 version(X11) { 18211 SimpleWindow win; 18212 Window sourceWindow; 18213 Time dataTimestamp; 18214 18215 Atom[] xFormats; 18216 } 18217 version(Windows) { 18218 IDataObject pDataObj; 18219 } 18220 } 18221 18222 /++ 18223 A generic helper base class for making a drop handler with a preference list of custom types. 18224 This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own 18225 droppers too. 18226 18227 It assumes the whole window it used, but you can subclass to change that. 18228 18229 $(PITFALL this is not yet stable and may break in future versions without notice.) 18230 18231 History: 18232 Added February 19, 2021 18233 +/ 18234 /// Group: drag_and_drop 18235 class GenericDropHandlerBase : DropHandler { 18236 // no fancy state here so no need to do anything here 18237 void finish() { } 18238 void dragLeave() { } 18239 18240 private DragAndDropAction acceptedAction; 18241 private DraggableData.FormatId acceptedFormat; 18242 private void delegate(scope ubyte[]) acceptedHandler; 18243 18244 struct FormatHandler { 18245 DraggableData.FormatId format; 18246 void delegate(scope ubyte[]) handler; 18247 } 18248 18249 protected abstract FormatHandler[] formatHandlers(); 18250 18251 DragAndDropAction dragEnter(DropPackage* pkg) { 18252 debug(sdpy_dnd) { import std.stdio; foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); } 18253 foreach(fmt; formatHandlers()) 18254 foreach(f; pkg.availableFormats()) 18255 if(f == fmt.format) { 18256 acceptedFormat = f; 18257 acceptedHandler = fmt.handler; 18258 return acceptedAction = DragAndDropAction.copy; 18259 } 18260 return acceptedAction = DragAndDropAction.none; 18261 } 18262 DropParameters dragOver(Point pt) { 18263 return DropParameters(acceptedAction); 18264 } 18265 18266 void drop(scope DropPackage* dropPackage) { 18267 if(!acceptedFormat || acceptedHandler is null) { 18268 debug(sdpy_dnd) { import std.stdio; writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); } 18269 return; // prolly shouldn't happen anyway... 18270 } 18271 18272 dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler); 18273 } 18274 } 18275 18276 /++ 18277 A simple handler for making your window accept drops of plain text. 18278 18279 $(PITFALL this is not yet stable and may break in future versions without notice.) 18280 18281 History: 18282 Added February 22, 2021 18283 +/ 18284 /// Group: drag_and_drop 18285 class TextDropHandler : GenericDropHandlerBase { 18286 private void delegate(in char[] text) dg; 18287 18288 /++ 18289 18290 +/ 18291 this(void delegate(in char[] text) dg) { 18292 this.dg = dg; 18293 } 18294 18295 protected override FormatHandler[] formatHandlers() { 18296 version(X11) 18297 return [ 18298 FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator), 18299 FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator), 18300 ]; 18301 else version(Windows) 18302 return [ 18303 FormatHandler(CF_UNICODETEXT, &translator), 18304 ]; 18305 } 18306 18307 private void translator(scope ubyte[] data) { 18308 version(X11) 18309 dg(cast(char[]) data); 18310 else version(Windows) 18311 dg(makeUtf8StringFromWindowsString(cast(wchar[]) data)); 18312 } 18313 } 18314 18315 /++ 18316 A simple handler for making your window accept drops of files, issued to you as file names. 18317 18318 $(PITFALL this is not yet stable and may break in future versions without notice.) 18319 18320 History: 18321 Added February 22, 2021 18322 +/ 18323 /// Group: drag_and_drop 18324 18325 class FilesDropHandler : GenericDropHandlerBase { 18326 private void delegate(in char[][]) dg; 18327 18328 /++ 18329 18330 +/ 18331 this(void delegate(in char[][] fileNames) dg) { 18332 this.dg = dg; 18333 } 18334 18335 protected override FormatHandler[] formatHandlers() { 18336 version(X11) 18337 return [ 18338 FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator), 18339 ]; 18340 else version(Windows) 18341 return [ 18342 FormatHandler(CF_HDROP, &translator), 18343 ]; 18344 } 18345 18346 private void translator(scope ubyte[] data) { 18347 version(X11) { 18348 char[] listString = cast(char[]) data; 18349 char[][16] buffer; 18350 int count; 18351 char[][] result = buffer[]; 18352 18353 void commit(char[] s) { 18354 if(count == result.length) 18355 result.length += 16; 18356 if(s.length > 7 && s[0 ..7] == "file://") 18357 s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding 18358 result[count++] = s; 18359 } 18360 18361 size_t last; 18362 foreach(idx, char c; listString) { 18363 if(c == '\n') { 18364 commit(listString[last .. idx - 1]); // a \r 18365 last = idx + 1; // a \n 18366 } 18367 } 18368 18369 if(last < listString.length) { 18370 commit(listString[last .. $]); 18371 } 18372 18373 // FIXME: they are uris now, should I translate it to local file names? 18374 // of course the host name is supposed to be there cuz of X rokking... 18375 18376 dg(result[0 .. count]); 18377 } else version(Windows) { 18378 18379 static struct DROPFILES { 18380 DWORD pFiles; 18381 POINT pt; 18382 BOOL fNC; 18383 BOOL fWide; 18384 } 18385 18386 18387 const(char)[][16] buffer; 18388 int count; 18389 const(char)[][] result = buffer[]; 18390 size_t last; 18391 18392 void commitA(in char[] stuff) { 18393 if(count == result.length) 18394 result.length += 16; 18395 result[count++] = stuff; 18396 } 18397 18398 void commitW(in wchar[] stuff) { 18399 commitA(makeUtf8StringFromWindowsString(stuff)); 18400 } 18401 18402 void magic(T)(T chars) { 18403 size_t idx; 18404 while(chars[idx]) { 18405 last = idx; 18406 while(chars[idx]) { 18407 idx++; 18408 } 18409 static if(is(T == char*)) 18410 commitA(chars[last .. idx]); 18411 else 18412 commitW(chars[last .. idx]); 18413 idx++; 18414 } 18415 } 18416 18417 auto df = cast(DROPFILES*) data.ptr; 18418 if(df.fWide) { 18419 wchar* chars = cast(wchar*) (data.ptr + df.pFiles); 18420 magic(chars); 18421 } else { 18422 char* chars = cast(char*) (data.ptr + df.pFiles); 18423 magic(chars); 18424 } 18425 dg(result[0 .. count]); 18426 } 18427 } 18428 } 18429 18430 /++ 18431 Interface to describe data being dragged. See also [draggable] helper function. 18432 18433 $(PITFALL this is not yet stable and may break in future versions without notice.) 18434 18435 History: 18436 Added February 19, 2021 18437 +/ 18438 interface DraggableData { 18439 version(X11) 18440 alias FormatId = Atom; 18441 else 18442 alias FormatId = uint; 18443 /++ 18444 Gets the platform-specific FormatId associated with the given named format. 18445 18446 This may be a MIME type, but may also be other various strings defined by the 18447 programs you want to interoperate with. 18448 18449 FIXME: sdpy needs to offer data adapter things that look for compatible formats 18450 and convert it to some particular type for you. 18451 +/ 18452 static FormatId getFormatId(string name)() { 18453 version(X11) 18454 return GetAtom!name(XDisplayConnection.get); 18455 else version(Windows) { 18456 static UINT cache; 18457 if(!cache) 18458 cache = RegisterClipboardFormatA(name); 18459 return cache; 18460 } else 18461 throw new NotYetImplementedException(); 18462 } 18463 18464 /++ 18465 Looks up a string to represent the name for the given format, if there is one. 18466 18467 You should avoid using this function because it is slow. It is provided more for 18468 debugging than for primary use. 18469 +/ 18470 static string getFormatName(FormatId format) { 18471 version(X11) { 18472 if(format == 0) 18473 return "None"; 18474 else 18475 return getAtomName(format, XDisplayConnection.get); 18476 } else version(Windows) { 18477 switch(format) { 18478 case CF_UNICODETEXT: return "CF_UNICODETEXT"; 18479 case CF_DIBV5: return "CF_DIBV5"; 18480 case CF_RIFF: return "CF_RIFF"; 18481 case CF_WAVE: return "CF_WAVE"; 18482 case CF_HDROP: return "CF_HDROP"; 18483 default: 18484 char[1024] name; 18485 auto count = GetClipboardFormatNameA(format, name.ptr, name.length); 18486 return name[0 .. count].idup; 18487 } 18488 } 18489 } 18490 18491 FormatId[] availableFormats(); 18492 // Return the slice of data you filled, empty slice if done. 18493 // this is to support the incremental thing 18494 ubyte[] getData(FormatId format, return scope ubyte[] data); 18495 18496 size_t dataLength(FormatId format); 18497 } 18498 18499 /++ 18500 $(PITFALL this is not yet stable and may break in future versions without notice.) 18501 18502 History: 18503 Added February 19, 2021 18504 +/ 18505 DraggableData draggable(string s) { 18506 version(X11) 18507 return new class X11SetSelectionHandler_Text, DraggableData { 18508 this() { 18509 super(s); 18510 } 18511 18512 override FormatId[] availableFormats() { 18513 return X11SetSelectionHandler_Text.availableFormats(); 18514 } 18515 18516 override ubyte[] getData(FormatId format, return scope ubyte[] data) { 18517 return X11SetSelectionHandler_Text.getData(format, data); 18518 } 18519 18520 size_t dataLength(FormatId format) { 18521 return s.length; 18522 } 18523 }; 18524 version(Windows) 18525 return new class DraggableData { 18526 FormatId[] availableFormats() { 18527 return [CF_UNICODETEXT]; 18528 } 18529 18530 ubyte[] getData(FormatId format, return scope ubyte[] data) { 18531 return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 18532 } 18533 18534 size_t dataLength(FormatId format) { 18535 return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof; 18536 } 18537 }; 18538 } 18539 18540 /++ 18541 $(PITFALL this is not yet stable and may break in future versions without notice.) 18542 18543 History: 18544 Added February 19, 2021 18545 +/ 18546 /// Group: drag_and_drop 18547 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) 18548 in { 18549 assert(window !is null); 18550 assert(handler !is null); 18551 } 18552 do 18553 { 18554 version(X11) { 18555 auto sh = cast(X11SetSelectionHandler) handler; 18556 if(sh is null) { 18557 // gotta make my own adapter. 18558 sh = new class X11SetSelectionHandler { 18559 mixin X11SetSelectionHandler_Basics; 18560 18561 Atom[] availableFormats() { return handler.availableFormats(); } 18562 ubyte[] getData(Atom format, return scope ubyte[] data) { 18563 return handler.getData(format, data); 18564 } 18565 18566 // since the drop selection is only ever used once it isn't important 18567 // to reset it. 18568 void done() {} 18569 }; 18570 } 18571 return doDragDropX11(window, sh, action); 18572 } else version(Windows) { 18573 return doDragDropWindows(window, handler, action); 18574 } else throw new NotYetImplementedException(); 18575 } 18576 18577 version(Windows) 18578 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) { 18579 IDataObject obj = new class IDataObject { 18580 ULONG refCount; 18581 ULONG AddRef() { 18582 return ++refCount; 18583 } 18584 ULONG Release() { 18585 return --refCount; 18586 } 18587 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 18588 if (IID_IUnknown == *riid) { 18589 *ppv = cast(void*) cast(IUnknown) this; 18590 } 18591 else if (IID_IDataObject == *riid) { 18592 *ppv = cast(void*) cast(IDataObject) this; 18593 } 18594 else { 18595 *ppv = null; 18596 return E_NOINTERFACE; 18597 } 18598 18599 AddRef(); 18600 return NOERROR; 18601 } 18602 18603 HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) { 18604 // import std.stdio; writeln("Advise"); 18605 return E_NOTIMPL; 18606 } 18607 HRESULT DUnadvise(DWORD dwConnection) { 18608 return E_NOTIMPL; 18609 } 18610 HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) { 18611 // import std.stdio; writeln("EnumDAdvise"); 18612 return OLE_E_ADVISENOTSUPPORTED; 18613 } 18614 // tell what formats it supports 18615 18616 FORMATETC[] types; 18617 this() { 18618 FORMATETC t; 18619 foreach(ty; handler.availableFormats()) { 18620 assert(ty <= ushort.max && ty >= 0); 18621 t.cfFormat = cast(ushort) ty; 18622 t.lindex = -1; 18623 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 18624 t.tymed = TYMED.TYMED_HGLOBAL; 18625 } 18626 types ~= t; 18627 } 18628 HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) { 18629 if(dwDirection == DATADIR.DATADIR_GET) { 18630 *ppenumFormatEtc = new class IEnumFORMATETC { 18631 ULONG refCount; 18632 ULONG AddRef() { 18633 return ++refCount; 18634 } 18635 ULONG Release() { 18636 return --refCount; 18637 } 18638 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 18639 if (IID_IUnknown == *riid) { 18640 *ppv = cast(void*) cast(IUnknown) this; 18641 } 18642 else if (IID_IEnumFORMATETC == *riid) { 18643 *ppv = cast(void*) cast(IEnumFORMATETC) this; 18644 } 18645 else { 18646 *ppv = null; 18647 return E_NOINTERFACE; 18648 } 18649 18650 AddRef(); 18651 return NOERROR; 18652 } 18653 18654 18655 int pos; 18656 this() { 18657 pos = 0; 18658 } 18659 18660 HRESULT Clone(IEnumFORMATETC* ppenum) { 18661 // import std.stdio; writeln("clone"); 18662 return E_NOTIMPL; // FIXME 18663 } 18664 18665 // Caller is responsible for freeing memory 18666 HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) { 18667 // fetched may be null if celt is one 18668 if(celt != 1) 18669 return E_NOTIMPL; // FIXME 18670 18671 if(celt + pos > types.length) 18672 return S_FALSE; 18673 18674 *rgelt = types[pos++]; 18675 18676 if(pceltFetched !is null) 18677 *pceltFetched = 1; 18678 18679 // import std.stdio; writeln("ok celt ", celt); 18680 return S_OK; 18681 } 18682 18683 HRESULT Reset() { 18684 pos = 0; 18685 return S_OK; 18686 } 18687 18688 HRESULT Skip(ULONG celt) { 18689 if(celt + pos <= types.length) { 18690 pos += celt; 18691 return S_OK; 18692 } 18693 return S_FALSE; 18694 } 18695 }; 18696 18697 return S_OK; 18698 } else 18699 return E_NOTIMPL; 18700 } 18701 // given a format, return the format you'd prefer to use cuz it is identical 18702 HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) { 18703 // FIXME: prolly could be better but meh 18704 // import std.stdio; writeln("gcf: ", *pformatectIn); 18705 *pformatetcOut = *pformatectIn; 18706 return S_OK; 18707 } 18708 HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 18709 foreach(ty; types) { 18710 if(ty == *pformatetcIn) { 18711 auto format = ty.cfFormat; 18712 // import std.stdio; writeln("A: ", *pformatetcIn, "\nB: ", ty); 18713 STGMEDIUM medium; 18714 medium.tymed = TYMED.TYMED_HGLOBAL; 18715 18716 auto sz = handler.dataLength(format); 18717 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz); 18718 if(handle is null) throw new Exception("GlobalAlloc"); 18719 if(auto data = cast(wchar*) GlobalLock(handle)) { 18720 auto slice = data[0 .. sz]; 18721 scope(exit) 18722 GlobalUnlock(handle); 18723 18724 handler.getData(format, cast(ubyte[]) slice[]); 18725 } 18726 18727 18728 medium.hGlobal = handle; // FIXME 18729 *pmedium = medium; 18730 return S_OK; 18731 } 18732 } 18733 return DV_E_FORMATETC; 18734 } 18735 HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 18736 // import std.stdio; writeln("GDH: ", *pformatetcIn); 18737 return E_NOTIMPL; // FIXME 18738 } 18739 HRESULT QueryGetData(FORMATETC* pformatetc) { 18740 auto search = *pformatetc; 18741 search.tymed &= TYMED.TYMED_HGLOBAL; 18742 foreach(ty; types) 18743 if(ty == search) { 18744 // import std.stdio; writeln("QueryGetData ", search, " ", types[0]); 18745 return S_OK; 18746 } 18747 if(pformatetc.cfFormat==CF_UNICODETEXT) { 18748 //import std.stdio; writeln("QueryGetData FALSE ", search, " ", types[0]); 18749 } 18750 return S_FALSE; 18751 } 18752 HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) { 18753 // import std.stdio; writeln("SetData: "); 18754 return E_NOTIMPL; 18755 } 18756 }; 18757 18758 18759 IDropSource src = new class IDropSource { 18760 ULONG refCount; 18761 ULONG AddRef() { 18762 return ++refCount; 18763 } 18764 ULONG Release() { 18765 return --refCount; 18766 } 18767 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 18768 if (IID_IUnknown == *riid) { 18769 *ppv = cast(void*) cast(IUnknown) this; 18770 } 18771 else if (IID_IDropSource == *riid) { 18772 *ppv = cast(void*) cast(IDropSource) this; 18773 } 18774 else { 18775 *ppv = null; 18776 return E_NOINTERFACE; 18777 } 18778 18779 AddRef(); 18780 return NOERROR; 18781 } 18782 18783 int QueryContinueDrag(int fEscapePressed, uint grfKeyState) { 18784 if(fEscapePressed) 18785 return DRAGDROP_S_CANCEL; 18786 if(!(grfKeyState & MK_LBUTTON)) 18787 return DRAGDROP_S_DROP; 18788 return S_OK; 18789 } 18790 18791 int GiveFeedback(uint dwEffect) { 18792 return DRAGDROP_S_USEDEFAULTCURSORS; 18793 } 18794 }; 18795 18796 DWORD effect; 18797 18798 if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect."); 18799 18800 DROPEFFECT de = win32DragAndDropAction(action); 18801 18802 // I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time 18803 // but still prolly a FIXME 18804 18805 auto ret = DoDragDrop(obj, src, de, &effect); 18806 /+ 18807 import std.stdio; 18808 if(ret == DRAGDROP_S_DROP) 18809 writeln("drop ", effect); 18810 else if(ret == DRAGDROP_S_CANCEL) 18811 writeln("cancel"); 18812 else if(ret == S_OK) 18813 writeln("ok"); 18814 else writeln(ret); 18815 +/ 18816 18817 return ret; 18818 } 18819 18820 version(Windows) 18821 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) { 18822 DROPEFFECT de; 18823 18824 with(DragAndDropAction) 18825 with(DROPEFFECT) 18826 final switch(action) { 18827 case none: de = DROPEFFECT_NONE; break; 18828 case copy: de = DROPEFFECT_COPY; break; 18829 case move: de = DROPEFFECT_MOVE; break; 18830 case link: de = DROPEFFECT_LINK; break; 18831 case ask: throw new Exception("ask not implemented yet"); 18832 case custom: throw new Exception("custom not implemented yet"); 18833 } 18834 18835 return de; 18836 } 18837 18838 18839 /++ 18840 History: 18841 Added February 19, 2021 18842 +/ 18843 /// Group: drag_and_drop 18844 void enableDragAndDrop(SimpleWindow window, DropHandler handler) { 18845 version(X11) { 18846 auto display = XDisplayConnection.get; 18847 18848 Atom atom = 5; // right??? 18849 18850 XChangeProperty( 18851 display, 18852 window.impl.window, 18853 GetAtom!"XdndAware"(display), 18854 XA_ATOM, 18855 32 /* bits */, 18856 PropModeReplace, 18857 &atom, 18858 1); 18859 18860 window.dropHandler = handler; 18861 } else version(Windows) { 18862 18863 initDnd(); 18864 18865 auto dropTarget = new class (handler) IDropTarget { 18866 DropHandler handler; 18867 this(DropHandler handler) { 18868 this.handler = handler; 18869 } 18870 ULONG refCount; 18871 ULONG AddRef() { 18872 return ++refCount; 18873 } 18874 ULONG Release() { 18875 return --refCount; 18876 } 18877 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 18878 if (IID_IUnknown == *riid) { 18879 *ppv = cast(void*) cast(IUnknown) this; 18880 } 18881 else if (IID_IDropTarget == *riid) { 18882 *ppv = cast(void*) cast(IDropTarget) this; 18883 } 18884 else { 18885 *ppv = null; 18886 return E_NOINTERFACE; 18887 } 18888 18889 AddRef(); 18890 return NOERROR; 18891 } 18892 18893 18894 // /////////////////// 18895 18896 HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 18897 DropPackage dropPackage = DropPackage(pDataObj); 18898 *pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage)); 18899 return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter 18900 } 18901 18902 HRESULT DragLeave() { 18903 handler.dragLeave(); 18904 // release the IDataObject if needed 18905 return S_OK; 18906 } 18907 18908 HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 18909 auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates 18910 18911 *pdwEffect = win32DragAndDropAction(res.action); 18912 // same as DragEnter basically 18913 return S_OK; 18914 } 18915 18916 HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 18917 DropPackage pkg = DropPackage(pDataObj); 18918 handler.drop(&pkg); 18919 18920 return S_OK; 18921 } 18922 }; 18923 // Windows can hold on to the handler and try to call it 18924 // during which time the GC can't see it. so important to 18925 // manually manage this. At some point i'll FIXME and make 18926 // all my com instances manually managed since they supposed 18927 // to respect the refcount. 18928 import core.memory; 18929 GC.addRoot(cast(void*) dropTarget); 18930 18931 if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK) 18932 throw new Exception("register"); 18933 18934 window.dropHandler = handler; 18935 } else throw new NotYetImplementedException(); 18936 } 18937 18938 18939 18940 static if(UsingSimpledisplayX11) { 18941 18942 enum _NET_WM_STATE_ADD = 1; 18943 enum _NET_WM_STATE_REMOVE = 0; 18944 enum _NET_WM_STATE_TOGGLE = 2; 18945 18946 /// X-specific. Use [SimpleWindow.requestAttention] instead for most casesl 18947 void demandAttention(SimpleWindow window, bool needs = true) { 18948 demandAttention(window.impl.window, needs); 18949 } 18950 18951 /// ditto 18952 void demandAttention(Window window, bool needs = true) { 18953 setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs); 18954 } 18955 18956 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) { 18957 auto display = XDisplayConnection.get(); 18958 if(atom == None) 18959 return; // non-failure error 18960 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 18961 18962 XClientMessageEvent xclient; 18963 18964 xclient.type = EventType.ClientMessage; 18965 xclient.window = window; 18966 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 18967 xclient.format = 32; 18968 xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 18969 xclient.data.l[1] = atom; 18970 xclient.data.l[2] = atom2; 18971 xclient.data.l[3] = 1; 18972 // [3] == source. 0 == unknown, 1 == app, 2 == else 18973 18974 XSendEvent( 18975 display, 18976 RootWindow(display, DefaultScreen(display)), 18977 false, 18978 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 18979 cast(XEvent*) &xclient 18980 ); 18981 18982 /+ 18983 XChangeProperty( 18984 display, 18985 window.impl.window, 18986 GetAtom!"_NET_WM_STATE"(display), 18987 XA_ATOM, 18988 32 /* bits */, 18989 PropModeAppend, 18990 &atom, 18991 1); 18992 +/ 18993 } 18994 18995 private Atom dndActionAtom(Display* display, DragAndDropAction action) { 18996 Atom actionAtom; 18997 with(DragAndDropAction) 18998 final switch(action) { 18999 case none: actionAtom = None; break; 19000 case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break; 19001 case move: actionAtom = GetAtom!"XdndActionMove"(display); break; 19002 case link: actionAtom = GetAtom!"XdndActionLink"(display); break; 19003 case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break; 19004 case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break; 19005 } 19006 19007 return actionAtom; 19008 } 19009 19010 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) { 19011 // FIXME: I need to show user feedback somehow. 19012 auto display = XDisplayConnection.get; 19013 19014 auto actionAtom = dndActionAtom(display, action); 19015 assert(actionAtom, "Don't use action none to accept a drop"); 19016 19017 setX11Selection!"XdndSelection"(window, handler, null); 19018 19019 auto oldKeyHandler = window.handleKeyEvent; 19020 scope(exit) window.handleKeyEvent = oldKeyHandler; 19021 19022 auto oldCharHandler = window.handleCharEvent; 19023 scope(exit) window.handleCharEvent = oldCharHandler; 19024 19025 auto oldMouseHandler = window.handleMouseEvent; 19026 scope(exit) window.handleMouseEvent = oldMouseHandler; 19027 19028 Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child 19029 19030 import core.sys.posix.sys.time; 19031 timeval tv; 19032 gettimeofday(&tv, null); 19033 19034 Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; 19035 19036 Time lastMouseTimestamp; 19037 19038 bool dnding = true; 19039 Window lastIn = None; 19040 19041 void leave() { 19042 if(lastIn == None) 19043 return; 19044 19045 XEvent ev; 19046 ev.xclient.type = EventType.ClientMessage; 19047 ev.xclient.window = lastIn; 19048 ev.xclient.message_type = GetAtom!("XdndLeave", true)(display); 19049 ev.xclient.format = 32; 19050 ev.xclient.data.l[0] = window.impl.window; 19051 19052 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 19053 XFlush(display); 19054 19055 lastIn = None; 19056 } 19057 19058 void enter(Window w) { 19059 assert(lastIn == None); 19060 19061 lastIn = w; 19062 19063 XEvent ev; 19064 ev.xclient.type = EventType.ClientMessage; 19065 ev.xclient.window = lastIn; 19066 ev.xclient.message_type = GetAtom!("XdndEnter", true)(display); 19067 ev.xclient.format = 32; 19068 ev.xclient.data.l[0] = window.impl.window; 19069 ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types 19070 19071 auto types = handler.availableFormats(); 19072 assert(types.length > 0); 19073 19074 ev.xclient.data.l[2] = types[0]; 19075 if(types.length > 1) 19076 ev.xclient.data.l[3] = types[1]; 19077 if(types.length > 2) 19078 ev.xclient.data.l[4] = types[2]; 19079 19080 // FIXME: other types?!?!? and make sure we skip TARGETS 19081 19082 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 19083 XFlush(display); 19084 } 19085 19086 void position(int rootX, int rootY) { 19087 assert(lastIn != None); 19088 19089 XEvent ev; 19090 ev.xclient.type = EventType.ClientMessage; 19091 ev.xclient.window = lastIn; 19092 ev.xclient.message_type = GetAtom!("XdndPosition", true)(display); 19093 ev.xclient.format = 32; 19094 ev.xclient.data.l[0] = window.impl.window; 19095 ev.xclient.data.l[1] = 0; // reserved 19096 ev.xclient.data.l[2] = (rootX << 16) | rootY; 19097 ev.xclient.data.l[3] = dataTimestamp; 19098 ev.xclient.data.l[4] = actionAtom; 19099 19100 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 19101 XFlush(display); 19102 19103 } 19104 19105 void drop() { 19106 XEvent ev; 19107 ev.xclient.type = EventType.ClientMessage; 19108 ev.xclient.window = lastIn; 19109 ev.xclient.message_type = GetAtom!("XdndDrop", true)(display); 19110 ev.xclient.format = 32; 19111 ev.xclient.data.l[0] = window.impl.window; 19112 ev.xclient.data.l[1] = 0; // reserved 19113 ev.xclient.data.l[2] = dataTimestamp; 19114 19115 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 19116 XFlush(display); 19117 19118 lastIn = None; 19119 dnding = false; 19120 } 19121 19122 // fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler 19123 // but idk if i should... 19124 19125 window.setEventHandlers( 19126 delegate(KeyEvent ev) { 19127 if(ev.pressed == true && ev.key == Key.Escape) { 19128 // cancel 19129 dnding = false; 19130 } 19131 }, 19132 delegate(MouseEvent ev) { 19133 if(ev.timestamp < lastMouseTimestamp) 19134 return; 19135 19136 lastMouseTimestamp = ev.timestamp; 19137 19138 if(ev.type == MouseEventType.motion) { 19139 auto display = XDisplayConnection.get; 19140 auto root = RootWindow(display, DefaultScreen(display)); 19141 19142 Window topWindow; 19143 int rootX, rootY; 19144 19145 XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow); 19146 19147 if(topWindow == None) 19148 return; 19149 19150 top: 19151 if(auto result = topWindow in eligibility) { 19152 auto dropWindow = *result; 19153 if(dropWindow == None) { 19154 leave(); 19155 return; 19156 } 19157 19158 if(dropWindow != lastIn) { 19159 leave(); 19160 enter(dropWindow); 19161 position(rootX, rootY); 19162 } else { 19163 position(rootX, rootY); 19164 } 19165 } else { 19166 // determine eligibility 19167 auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM); 19168 if(data.length == 1) { 19169 // in case there is no WM or it isn't reparenting 19170 eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh 19171 } else { 19172 19173 Window tryScanChildren(Window search, int maxRecurse) { 19174 // could be reparenting window manager, so gotta check the next few children too 19175 Window child; 19176 int x; 19177 int y; 19178 XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child); 19179 19180 if(child == None) 19181 return None; 19182 auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM); 19183 if(data.length == 1) { 19184 return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh 19185 } else { 19186 if(maxRecurse) 19187 return tryScanChildren(child, maxRecurse - 1); 19188 else 19189 return None; 19190 } 19191 19192 } 19193 19194 // if a WM puts more than 3 layers on it, like wtf is it doing, screw that. 19195 auto topResult = tryScanChildren(topWindow, 3); 19196 // it is easy to have a false negative due to the mouse going over a WM 19197 // child window like the close button if separate from the frame... so I 19198 // can't really cache negatives, :( 19199 if(topResult != None) { 19200 eligibility[topWindow] = topResult; 19201 goto top; // reload to do the positioning iff eligibility changed lest we endless loop 19202 } 19203 } 19204 19205 } 19206 19207 } else if(ev.type == MouseEventType.buttonReleased) { 19208 drop(); 19209 dnding = false; 19210 } 19211 } 19212 ); 19213 19214 window.grabInput(); 19215 scope(exit) 19216 window.releaseInputGrab(); 19217 19218 19219 EventLoop.get.run(() => dnding); 19220 19221 return 0; 19222 } 19223 19224 /// X-specific 19225 TrueColorImage getWindowNetWmIcon(Window window) { 19226 try { 19227 auto display = XDisplayConnection.get; 19228 19229 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 19230 19231 if (data.length > arch_ulong.sizeof * 2) { 19232 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 19233 // these are an array of rgba images that we have to convert into pixmaps ourself 19234 19235 int width = cast(int) meta[0]; 19236 int height = cast(int) meta[1]; 19237 19238 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 19239 19240 static if(arch_ulong.sizeof == 4) { 19241 bytes = bytes[0 .. width * height * 4]; 19242 alias imageData = bytes; 19243 } else static if(arch_ulong.sizeof == 8) { 19244 bytes = bytes[0 .. width * height * 8]; 19245 auto imageData = new ubyte[](4 * width * height); 19246 } else static assert(0); 19247 19248 19249 19250 // this returns ARGB. Remember it is little-endian so 19251 // we have BGRA 19252 // our thing uses RGBA, which in little endian, is ABGR 19253 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 19254 auto r = bytes[idx + 2]; 19255 auto g = bytes[idx + 1]; 19256 auto b = bytes[idx + 0]; 19257 auto a = bytes[idx + 3]; 19258 19259 imageData[idx2 + 0] = r; 19260 imageData[idx2 + 1] = g; 19261 imageData[idx2 + 2] = b; 19262 imageData[idx2 + 3] = a; 19263 } 19264 19265 return new TrueColorImage(width, height, imageData); 19266 } 19267 19268 return null; 19269 } catch(Exception e) { 19270 return null; 19271 } 19272 } 19273 19274 } /* UsingSimpledisplayX11 */ 19275 19276 19277 void loadBinNameToWindowClassName () { 19278 import core.stdc.stdlib : realloc; 19279 version(linux) { 19280 // args[0] MAY be empty, so we'll just use this 19281 import core.sys.posix.unistd : readlink; 19282 char[1024] ebuf = void; // 1KB should be enough for everyone! 19283 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 19284 if (len < 1) return; 19285 } else /*version(Windows)*/ { 19286 import core.runtime : Runtime; 19287 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 19288 auto ebuf = Runtime.args[0]; 19289 auto len = ebuf.length; 19290 } 19291 auto pos = len; 19292 while (pos > 0 && ebuf[pos-1] != '/') --pos; 19293 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 19294 if (sdpyWindowClassStr is null) return; // oops 19295 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 19296 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 19297 } 19298 19299 /++ 19300 An interface representing a font. 19301 19302 This is still MAJOR work in progress. 19303 +/ 19304 interface DrawableFont { 19305 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 19306 } 19307 19308 /++ 19309 Loads a true type font using [arsd.ttf]. That module must be compiled 19310 in if you choose to use this function. 19311 19312 Be warned: this can be slow and memory hungry, especially on remote connections 19313 to the X server. 19314 19315 This is still MAJOR work in progress. 19316 +/ 19317 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 19318 import arsd.ttf; 19319 static class ArsdTtfFont : DrawableFont { 19320 TtfFont font; 19321 int size; 19322 this(in ubyte[] data, int size) { 19323 font = TtfFont(data); 19324 this.size = size; 19325 } 19326 19327 Sprite[string] cache; 19328 19329 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 19330 Sprite sprite = (text in cache) ? *(text in cache) : null; 19331 19332 auto fg = painter.impl._outlineColor; 19333 auto bg = painter.impl._fillColor; 19334 19335 if(sprite is null) { 19336 int width, height; 19337 auto data = font.renderString(text, size, width, height); 19338 auto image = new TrueColorImage(width, height); 19339 int pos = 0; 19340 foreach(y; 0 .. height) 19341 foreach(x; 0 .. width) { 19342 fg.a = data[0]; 19343 bg.a = 255; 19344 auto color = alphaBlend(fg, bg); 19345 image.imageData.bytes[pos++] = color.r; 19346 image.imageData.bytes[pos++] = color.g; 19347 image.imageData.bytes[pos++] = color.b; 19348 image.imageData.bytes[pos++] = data[0]; 19349 data = data[1 .. $]; 19350 } 19351 assert(data.length == 0); 19352 19353 sprite = new Sprite(painter.window, Image.fromMemoryImage(image)); 19354 cache[text.idup] = sprite; 19355 } 19356 19357 sprite.drawAt(painter, upperLeft); 19358 } 19359 } 19360 19361 return new ArsdTtfFont(data, size); 19362 } 19363 19364 class NotYetImplementedException : Exception { 19365 this(string file = __FILE__, size_t line = __LINE__) { 19366 super("Not yet implemented", file, line); 19367 } 19368 } 19369 19370 /// 19371 __gshared bool librariesSuccessfullyLoaded = true; 19372 /// 19373 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 19374 19375 private mixin template DynamicLoadSupplementalOpenGL(Iface) { 19376 mixin(staticForeachReplacement!Iface); 19377 19378 void loadDynamicLibrary() @nogc { 19379 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 19380 } 19381 19382 void loadDynamicLibraryForReal() { 19383 foreach(name; __traits(derivedMembers, Iface)) { 19384 mixin("alias tmp = " ~ name ~ ";"); 19385 tmp = cast(typeof(tmp)) glbindGetProcAddress(name); 19386 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL"); 19387 } 19388 } 19389 } 19390 19391 private const(char)[] staticForeachReplacement(Iface)() pure { 19392 /* 19393 // just this for gdc 9.... 19394 // when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease 19395 19396 static foreach(name; __traits(derivedMembers, Iface)) 19397 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 19398 */ 19399 19400 char[] code = new char[](__traits(derivedMembers, Iface).length * 64); 19401 size_t pos; 19402 19403 void append(in char[] what) { 19404 if(pos + what.length > code.length) 19405 code.length = (code.length * 3) / 2; 19406 code[pos .. pos + what.length] = what[]; 19407 pos += what.length; 19408 } 19409 19410 foreach(name; __traits(derivedMembers, Iface)) { 19411 append(`__gshared typeof(&__traits(getMember, Iface, "`); 19412 append(name); 19413 append(`")) `); 19414 append(name); 19415 append(";"); 19416 } 19417 19418 return code[0 .. pos]; 19419 } 19420 19421 private mixin template DynamicLoad(Iface, string library, int majorVersion, bool openGLRelated = false, bool optional = false) { 19422 mixin(staticForeachReplacement!Iface); 19423 19424 private void* libHandle; 19425 private bool attempted; 19426 19427 void loadDynamicLibrary() @nogc { 19428 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 19429 } 19430 19431 bool loadAttempted() { 19432 return attempted; 19433 } 19434 bool loadSuccessful() { 19435 return libHandle !is null; 19436 } 19437 19438 void loadDynamicLibraryForReal() { 19439 attempted = true; 19440 version(Posix) { 19441 import core.sys.posix.dlfcn; 19442 version(OSX) { 19443 version(X11) 19444 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 19445 else 19446 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 19447 } else { 19448 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 19449 if(libHandle is null) 19450 libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW); 19451 } 19452 19453 static void* loadsym(void* l, const char* name) { 19454 import core.stdc.stdlib; 19455 if(l is null) 19456 return &abort; 19457 return dlsym(l, name); 19458 } 19459 } else version(Windows) { 19460 import core.sys.windows.windows; 19461 libHandle = LoadLibrary(library ~ ".dll"); 19462 static void* loadsym(void* l, const char* name) { 19463 import core.stdc.stdlib; 19464 if(l is null) 19465 return &abort; 19466 return GetProcAddress(l, name); 19467 } 19468 } 19469 if(libHandle is null && !optional) { 19470 if(openGLRelated) 19471 openGlLibrariesSuccessfullyLoaded = false; 19472 else 19473 librariesSuccessfullyLoaded = false; 19474 //throw new Exception("load failure of library " ~ library); 19475 } 19476 foreach(name; __traits(derivedMembers, Iface)) { 19477 mixin("alias tmp = " ~ name ~ ";"); 19478 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 19479 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 19480 } 19481 } 19482 19483 void unloadDynamicLibrary() { 19484 version(Posix) { 19485 import core.sys.posix.dlfcn; 19486 dlclose(libHandle); 19487 } else version(Windows) { 19488 import core.sys.windows.windows; 19489 FreeLibrary(libHandle); 19490 } 19491 foreach(name; __traits(derivedMembers, Iface)) 19492 mixin(name ~ " = null;"); 19493 } 19494 } 19495 19496 void guiAbortProcess(string msg) { 19497 import core.stdc.stdlib; 19498 version(Windows) { 19499 WCharzBuffer t = WCharzBuffer(msg); 19500 MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0); 19501 } else { 19502 import std.stdio; 19503 stderr.writeln(msg); 19504 stderr.flush(); 19505 } 19506 19507 abort(); 19508 } 19509 19510 private alias scriptable = arsd_jsvar_compatible;