1 // https://dpaste.dzfl.pl/7a77355acaec 2 3 /+ 4 To share some stuff between two opengl threads: 5 windows 6 https://www.khronos.org/opengl/wiki/OpenGL_and_multithreading 7 linux 8 https://stackoverflow.com/questions/18879520/sharing-opengl-objects-between-contexts-on-linux 9 +/ 10 11 12 // Search for: FIXME: leaks if multithreaded gc 13 14 // https://freedesktop.org/wiki/Specifications/XDND/ 15 16 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format 17 18 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html 19 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html 20 21 22 // on Mac with X11: -L-L/usr/X11/lib 23 24 /+ 25 26 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works 27 28 Progress bar in taskbar 29 - i can probably just set a property on the window... 30 it sets that prop to an integer 0 .. 100. Taskbar 31 deletes it or window deletes it when it is handled. 32 - prolly display it as a nice little line at the bottom. 33 34 35 from gtk: 36 37 #define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS" 38 #define PROGRESS_PULSE_HINT "_NET_WM_XAPP_PROGRESS_PULSE" 39 40 >+ if (cardinal > 0) 41 >+ { 42 >+ XChangeProperty (GDK_DISPLAY_XDISPLAY (display), 43 >+ xid, 44 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name), 45 >+ XA_CARDINAL, 32, 46 >+ PropModeReplace, 47 >+ (guchar *) &cardinal, 1); 48 >+ } 49 >+ else 50 >+ { 51 >+ XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), 52 >+ xid, 53 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name)); 54 >+ } 55 56 from Windows: 57 58 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3 59 60 interface 61 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 ); 62 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”)); 63 listen for msg, return TRUE 64 interface->SetProgressState(hwnd, TBPF_NORMAL); 65 interface->SetProgressValue(hwnd, 40, 100); 66 67 68 My new notification system. 69 - use a unix socket? or a x property? or a udp port? 70 - could of course also get on the dbus train but ugh. 71 - it could also reply with the info as a string for easy remote examination. 72 73 +/ 74 75 /* 76 Event Loop would be nices: 77 78 * add on idle - runs when nothing else happens 79 * which can specify how long to yield for 80 * send messages without a recipient window 81 * setTimeout 82 * setInterval 83 */ 84 85 /* 86 Classic games I want to add: 87 * my tetris clone 88 * pac man 89 */ 90 91 /* 92 Text layout needs a lot of work. Plain drawText is useful but too 93 limited. It will need some kind of text context thing which it will 94 update and you can pass it on and get more details out of it. 95 96 It will need a bounding box, a current cursor location that is updated 97 as drawing continues, and various changable facts (which can also be 98 changed on the painter i guess) like font, color, size, background, 99 etc. 100 101 We can also fetch the caret location from it somehow. 102 103 Should prolly be an overload of drawText 104 105 blink taskbar / demand attention cross platform. FlashWindow and demandAttention 106 107 WS_EX_NOACTIVATE 108 WS_CHILD - owner and owned vs parent and child. Does X have something similar? 109 full screen windows. Can just set the atom on X. Windows will be harder. 110 111 moving windows. resizing windows. 112 113 hide cursor, capture cursor, change cursor. 114 115 REMEMBER: simpledisplay does NOT have to do everything! It just needs to make 116 sure the pieces are there to do its job easily and make other jobs possible. 117 */ 118 119 /++ 120 simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality, 121 including creating windows, drawing on them, working with the clipboard, 122 timers, OpenGL, and more. However, it does NOT provide high level GUI 123 widgets. See my minigui.d, an extension to this module, for that 124 functionality. 125 126 simpledisplay provides cross-platform wrapping for Windows and Linux 127 (and perhaps other OSes that use X11), but also does not prevent you 128 from using the underlying facilities if you need them. It has a goal 129 of working efficiently over a remote X link (at least as far as Xlib 130 reasonably allows.) 131 132 simpledisplay depends on [arsd.color|color.d], which should be available from the 133 same place where you got this file. Other than that, however, it has 134 very few dependencies and ones that don't come with the OS and/or the 135 compiler are all opt-in. 136 137 simpledisplay.d's home base is on my arsd repo on Github. The file is: 138 https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d 139 140 simpledisplay is basically stable. I plan to refactor the internals, 141 and may add new features and fix bugs, but It do not expect to 142 significantly change the API. It has been stable a few years already now. 143 144 Installation_instructions: 145 146 `simpledisplay.d` does not have any dependencies outside the 147 operating system and `color.d`, so it should just work most the 148 time, but there are a few caveats on some systems: 149 150 On Win32, you can pass `-L/subsystem:windows` if you don't want a 151 console to be automatically allocated. 152 153 Please note when compiling on Win64, you need to explicitly list 154 `-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows 155 subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`. 156 157 If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`; 158 note the "w". 159 160 I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you, 161 but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi). 162 See [EnableWindowsSubsystem] for more information. 163 164 $(PITFALL 165 With the Windows subsystem, there is no console, so standard writeln will throw! 166 You can use [sdpyPrintDebugString] instead of stdio writeln instead which will 167 create a console as needed. 168 ) 169 170 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. 171 172 On Ubuntu, you might need to install X11 development libraries to 173 successfully link. 174 175 $(CONSOLE 176 $ sudo apt-get install libglc-dev 177 $ sudo apt-get install libx11-dev 178 ) 179 180 181 Jump_list: 182 183 Don't worry, you don't have to read this whole documentation file! 184 185 Check out the [#event-example] and [#Pong-example] to get started quickly. 186 187 The main classes you may want to create are [SimpleWindow], [Timer], 188 [Image], and [Sprite]. 189 190 The main functions you'll want are [setClipboardText] and [getClipboardText]. 191 192 There are also platform-specific functions available such as [XDisplayConnection] 193 and [GetAtom] for X11, among others. 194 195 See the examples and topics list below to learn more. 196 197 $(WARNING 198 There should only be one GUI thread per application, 199 and all windows should be created in it and your 200 event loop should run there. 201 202 To do otherwise is undefined behavior and has no 203 cross platform guarantees. 204 ) 205 206 $(H2 About this documentation) 207 208 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. 209 210 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! 211 212 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. 213 214 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. 215 216 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. 217 218 At points, I will talk about implementation details in the documentation. These are sometimes 219 subject to change, but nevertheless useful to understand what is really going on. You can learn 220 more about some of the referenced things by searching the web for info about using them from C. 221 You can always look at the source of simpledisplay.d too for the most authoritative source on 222 its specific implementation. If you disagree with how I did something, please contact me so we 223 can discuss it! 224 225 $(H2 Using with fibers) 226 227 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). 228 229 $(H2 Topics) 230 231 $(H3 $(ID topic-windows) Windows) 232 The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single 233 window on the user's screen. 234 235 You may create multiple windows, if the underlying platform supports it. You may check 236 `static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by 237 SimpleWindow's constructor at runtime to handle those cases. 238 239 A single running event loop will handle as many windows as needed. 240 241 $(H3 $(ID topic-event-loops) Event loops) 242 The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries. 243 244 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: 245 246 --- 247 // dmd example.d simpledisplay.d color.d 248 import arsd.simpledisplay; 249 void main() { 250 auto window = new SimpleWindow(200, 200); 251 window.eventLoop(0, 252 delegate (dchar) { /* got a character key press */ } 253 ); 254 } 255 --- 256 257 $(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.) 258 259 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. 260 261 On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch. 262 263 It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried. 264 265 You can also run the event loop independently of a window, with [EventLoop.run|EventLoop.get.run], though since it will automatically terminate when there are no open windows, you will want to have one anyway. 266 267 $(H3 $(ID topic-notification-areas) Notification area (aka systray) icons) 268 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. 269 270 See the [NotificationAreaIcon] class. 271 272 $(H3 $(ID topic-input-handling) Input handling) 273 There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events. 274 275 See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent]. 276 277 $(H3 $(ID topic-2d-drawing) 2d Drawing) 278 To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods. 279 280 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: 281 282 --- 283 // dmd example.d simpledisplay.d color.d 284 import arsd.simpledisplay; 285 void main() { 286 auto window = new SimpleWindow(200, 200); 287 { // introduce sub-scope 288 auto painter = window.draw(); // begin drawing 289 /* draw here */ 290 painter.outlineColor = Color.red; 291 painter.fillColor = Color.black; 292 painter.drawRectangle(Point(0, 0), 200, 200); 293 } // end scope, calling `painter`'s destructor, drawing to the screen. 294 window.eventLoop(0); // handle events 295 } 296 --- 297 298 Painting is done based on two color properties, a pen and a brush. 299 300 At this time, the 2d drawing does not support alpha blending, except for the [Sprite] class. If you need that, use a 2d OpenGL context instead. 301 302 FIXME Add example of 2d opengl drawing here. 303 $(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL)) 304 simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing. 305 306 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. 307 308 To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor. 309 310 Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame. 311 312 To force a redraw of the scene, call [SimpleWindow.redrawOpenGlSceneNow|window.redrawOpenGlSceneNow()] or to queue a redraw after processing the next batch of pending events, use [SimpleWindow.redrawOpenGlSceneSoon|window.redrawOpenGlSceneSoon]. 313 314 simpledisplay supports both old-style `glBegin` and newer-style shader-based code all through its built-in bindings. See the next section of the docs to see a shader-based program. 315 316 This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color: 317 318 --- 319 import arsd.simpledisplay; 320 321 void main() { 322 auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing); 323 324 float otherColor = 0.0; 325 float colorDelta = 0.05; 326 327 window.redrawOpenGlScene = delegate() { 328 glLoadIdentity(); 329 glBegin(GL_QUADS); 330 331 glColor3f(1.0, otherColor, 0); 332 glVertex3f(-0.8, -0.8, 0); 333 334 glColor3f(1.0, otherColor, 1.0); 335 glVertex3f(0.8, -0.8, 0); 336 337 glColor3f(0, 1.0, otherColor); 338 glVertex3f(0.8, 0.8, 0); 339 340 glColor3f(otherColor, 0, 1.0); 341 glVertex3f(-0.8, 0.8, 0); 342 343 glEnd(); 344 }; 345 346 window.eventLoop(50, () { 347 otherColor += colorDelta; 348 if(otherColor > 1.0) { 349 otherColor = 1.0; 350 colorDelta = -0.05; 351 } 352 if(otherColor < 0) { 353 otherColor = 0; 354 colorDelta = 0.05; 355 } 356 // at the end of the timer, we have to request a redraw 357 // or we won't see the changes. 358 window.redrawOpenGlSceneSoon(); 359 }); 360 } 361 --- 362 363 My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow]. 364 $(H3 $(ID topic-modern-opengl) Modern OpenGL) 365 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 provides helpers for this too. 366 367 This example program shows how you can set up a shader to draw a rectangle: 368 369 --- 370 import arsd.simpledisplay; 371 372 // based on https://learnopengl.com/Getting-started/Hello-Triangle 373 374 void main() { 375 // First thing we do, before creating the window, is declare what version we want. 376 setOpenGLContextVersion(3, 3); 377 // turning off legacy compat is required to use version 3.3 and newer 378 openGLContextCompatible = false; 379 380 uint VAO; 381 OpenGlShader shader; 382 383 // then we can create the window. 384 auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing); 385 386 // additional setup needs to be done when it is visible, simpledisplay offers a property 387 // for exactly that: 388 window.visibleForTheFirstTime = delegate() { 389 // now with the window loaded, we can start loading the modern opengl functions. 390 391 // you MUST set the context first. 392 window.setAsCurrentOpenGlContext; 393 // then load the remainder of the library 394 gl3.loadDynamicLibrary(); 395 396 // now you can create the shaders, etc. 397 shader = new OpenGlShader( 398 OpenGlShader.Source(GL_VERTEX_SHADER, ` 399 #version 330 core 400 layout (location = 0) in vec3 aPos; 401 void main() { 402 gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); 403 } 404 `), 405 OpenGlShader.Source(GL_FRAGMENT_SHADER, ` 406 #version 330 core 407 out vec4 FragColor; 408 uniform vec4 mycolor; 409 void main() { 410 FragColor = mycolor; 411 } 412 `), 413 ); 414 415 // and do whatever other setup you want. 416 417 float[] vertices = [ 418 0.5f, 0.5f, 0.0f, // top right 419 0.5f, -0.5f, 0.0f, // bottom right 420 -0.5f, -0.5f, 0.0f, // bottom left 421 -0.5f, 0.5f, 0.0f // top left 422 ]; 423 uint[] indices = [ // note that we start from 0! 424 0, 1, 3, // first Triangle 425 1, 2, 3 // second Triangle 426 ]; 427 uint VBO, EBO; 428 glGenVertexArrays(1, &VAO); 429 // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 430 glBindVertexArray(VAO); 431 432 glGenBuffers(1, &VBO); 433 glGenBuffers(1, &EBO); 434 435 glBindBuffer(GL_ARRAY_BUFFER, VBO); 436 glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW); 437 438 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 439 glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); 440 441 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null); 442 glEnableVertexAttribArray(0); 443 444 // the library will set the initial viewport and trigger our first draw, 445 // so these next two lines are NOT needed. they are just here as comments 446 // to show what would happen next. 447 448 // glViewport(0, 0, window.width, window.height); 449 // window.redrawOpenGlSceneNow(); 450 }; 451 452 // this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;` 453 // it is our render method. 454 window.redrawOpenGlScene = delegate() { 455 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); 456 glClear(GL_COLOR_BUFFER_BIT); 457 458 glUseProgram(shader.shaderProgram); 459 460 // the shader helper class has methods to set uniforms too 461 shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0); 462 463 glBindVertexArray(VAO); 464 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null); 465 }; 466 467 window.eventLoop(0); 468 } 469 --- 470 471 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. 472 473 $(H3 $(ID vulkan) Vulkan) 474 475 See a couple examples ported from GLFW to simpledisplay using the erupted vulkan bindings: 476 477 https://github.com/adamdruppe/VulkanizeDSdpy 478 479 https://github.com/adamdruppe/VulkanSdpyDemo/tree/demo 480 481 $(H3 $(ID topic-images) Displaying images) 482 You can also load PNG images using [arsd.png]. 483 484 --- 485 // dmd example.d simpledisplay.d color.d png.d 486 import arsd.simpledisplay; 487 import arsd.png; 488 489 void main() { 490 auto image = Image.fromMemoryImage(readPng("image.png")); 491 displayImage(image); 492 } 493 --- 494 495 Compile with `dmd example.d simpledisplay.d png.d`. 496 497 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. 498 499 $(H3 $(ID topic-sprites) Sprites) 500 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. 501 502 [Sprite] is also the only facility that currently supports alpha blending without using OpenGL . 503 504 $(H3 $(ID topic-clipboard) Clipboard) 505 The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time. 506 507 It also has helpers for handling X-specific events. 508 509 $(H3 $(ID topic-dnd) Drag and Drop) 510 See [enableDragAndDrop] and [draggable]. 511 512 $(H3 $(ID topic-timers) Timers) 513 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]. 514 515 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. 516 517 --- 518 import arsd.simpledisplay; 519 520 void main() { 521 auto window = new SimpleWindow(400, 400); 522 // every 100 ms, it will draw a random line 523 // on the window. 524 window.eventLoop(100, { 525 auto painter = window.draw(); 526 527 import std.random; 528 // random color 529 painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256)); 530 // random line 531 painter.drawLine( 532 Point(uniform(0, window.width), uniform(0, window.height)), 533 Point(uniform(0, window.width), uniform(0, window.height))); 534 535 }); 536 } 537 --- 538 539 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. 540 541 The pulse timer and instances of the [Timer] class may be combined at will. 542 543 --- 544 import arsd.simpledisplay; 545 546 void main() { 547 auto window = new SimpleWindow(400, 400); 548 auto timer = new Timer(1000, delegate { 549 auto painter = window.draw(); 550 painter.clear(); 551 }); 552 553 window.eventLoop(0); 554 } 555 --- 556 557 Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop. 558 559 $(H3 $(ID topic-os-helpers) OS-specific helpers) 560 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. 561 562 See also: `xwindows.d` from my github. 563 564 $(H3 $(ID topic-os-extension) Extending with OS-specific functionality) 565 `handleNativeEvent` and `handleNativeGlobalEvent`. 566 567 $(H3 $(ID topic-integration) Integration with other libraries) 568 Integration with a third-party event loop is possible. 569 570 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. 571 572 $(H3 $(ID topic-guis) GUI widgets) 573 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! 574 575 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. 576 577 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.) 578 579 minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes. 580 581 $(H2 Platform-specific tips and tricks) 582 583 X_tips: 584 585 On X11, if you set an environment variable, `ARSD_SCALING_FACTOR`, you can control the per-monitor DPI scaling returned to the application. The format is `ARSD_SCALING_FACTOR=2;1`, for example, to set 2x scaling on your first monitor and 1x scaling on your second monitor. Support for this was added on March 22, 2022, the dub 10.7 release. 586 587 $(H4 apitrace) 588 589 Out of the box, simpledisplay might not work as expected in combination with 590 [apitrace](https://apitrace.github.io). 591 However it can be instructed to specifically load the GL/GLX wrapper libraries provided by apitrace instead of 592 the system libraries. This should restore the lost functionality. 593 594 $(NUMBERED_LIST 595 * Compile with `-version=apitrace`. 596 * When launching such a simpledisplay app, it must be able to locate the apitrace wrapper libraries. 597 * Running the app will generate an apitrace trace file. 598 It should print a log message similar to "apitrace: loaded into /directory" during startup. 599 ) 600 601 There are multiple ways to enable a simpledisplay app to locate the wrapper libraries. 602 603 One way to achieved this is by pointing the `LD_LIBRARY_PATH` environment variable to the directory containing 604 those wrappers. 605 606 ```sh 607 LD_LIBRARY_PATH=/path/to/apitrace/wrappers:$LD_LIBRARY_PATH ./myapp 608 609 # e.g. 610 LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/apitrace/wrappers:$LD_LIBRARY_PATH ./myapp 611 ``` 612 613 Alternatively, the simpledisplay app can also be launched via $(I apitrace). 614 615 ```sh 616 apitrace trace -a gl ./myapp 617 ``` 618 619 Another way that seems to work is to preload `glxtrace.so` through `LD_PRELOAD`. 620 621 ```sh 622 LD_PRELOAD=/path/to/apitrace/wrappers/glxtrace.so ./myapp 623 ``` 624 625 Windows_tips: 626 627 You can add icons or manifest files to your exe using a resource file. 628 629 To create a Windows .ico file, use the gimp or something. I'll write a helper 630 program later. 631 632 Create `yourapp.rc`: 633 634 ```rc 635 1 ICON filename.ico 636 CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest" 637 ``` 638 639 And `yourapp.exe.manifest`: 640 641 ```xml 642 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 643 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 644 <assemblyIdentity 645 version="1.0.0.0" 646 processorArchitecture="*" 647 name="CompanyName.ProductName.YourApplication" 648 type="win32" 649 /> 650 <description>Your application description here.</description> 651 <dependency> 652 <dependentAssembly> 653 <assemblyIdentity 654 type="win32" 655 name="Microsoft.Windows.Common-Controls" 656 version="6.0.0.0" 657 processorArchitecture="*" 658 publicKeyToken="6595b64144ccf1df" 659 language="*" 660 /> 661 </dependentAssembly> 662 </dependency> 663 <application xmlns="urn:schemas-microsoft-com:asm.v3"> 664 <windowsSettings> 665 <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style --> 666 <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style --> 667 <!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text --> 668 <!-- to render crisply in DPI-unaware contexts --> 669 <!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>--> 670 </windowsSettings> 671 </application> 672 </assembly> 673 ``` 674 675 You can also just distribute yourapp.exe.manifest as a separate file alongside yourapp.exe, or link it in to the exe with linker command lines `/manifest:embed` and `/manifestinput:yourfile.exe.manifest`. 676 677 Doing this lets you opt into various new things since Windows XP. 678 679 See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests 680 681 $(H2 Tips) 682 683 $(H3 Name conflicts) 684 685 simpledisplay has a lot of symbols and more are liable to be added without notice, since it contains its own bindings as needed to accomplish its goals. Some of these may conflict with other bindings you use. If so, you can use a static import in D, possibly combined with a selective import: 686 687 --- 688 static import sdpy = arsd.simpledisplay; 689 import arsd.simpledisplay : SimpleWindow; 690 691 void main() { 692 auto window = new SimpleWindow(); 693 sdpy.EventLoop.get.run(); 694 } 695 --- 696 697 $(H2 $(ID developer-notes) Developer notes) 698 699 I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa 700 implementation though. 701 702 The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both 703 suck. If I was rewriting it, I wouldn't do it that way again. 704 705 This file must not have any more required dependencies. If you need bindings, add 706 them right to this file. Once it gets into druntime and is there for a while, remove 707 bindings from here to avoid conflicts (or put them in an appropriate version block 708 so it continues to just work on old dmd), but wait a couple releases before making the 709 transition so this module remains usable with older versions of dmd. 710 711 You may have optional dependencies if needed by putting them in version blocks or 712 template functions. You may also extend the module with other modules with UFCS without 713 actually editing this - that is nice to do if you can. 714 715 Try to make functions work the same way across operating systems. I typically make 716 it thinly wrap Windows, then emulate that on Linux. 717 718 A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding 719 Phobos! So try to avoid it. 720 721 See more comments throughout the source. 722 723 I realize this file is fairly large, but over half that is just bindings at the bottom 724 or documentation at the top. Some of the classes are a bit big too, but hopefully easy 725 to understand. I suggest you jump around the source by looking for a particular 726 declaration you're interested in, like `class SimpleWindow` using your editor's search 727 function, then look at one piece at a time. 728 729 Authors: Adam D. Ruppe with the help of others. If you need help, please email me with 730 destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can 731 ping me, adam_d_ruppe, and I'll usually see it if I'm around. 732 733 I live in the eastern United States, so I will most likely not be around at night in 734 that US east timezone. 735 736 License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License. 737 738 Building documentation: use my adrdox generator, `dub run adrdox`. 739 740 Examples: 741 742 $(DIV $(ID Event-example)) 743 $(H3 $(ID event-example) Event example) 744 This program creates a window and draws events inside them as they 745 happen, scrolling the text in the window as needed. Run this program 746 and experiment to get a feel for where basic input events take place 747 in the library. 748 749 --- 750 // dmd example.d simpledisplay.d color.d 751 import arsd.simpledisplay; 752 import std.conv; 753 754 void main() { 755 auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d"); 756 757 int y = 0; 758 759 void addLine(string text) { 760 auto painter = window.draw(); 761 762 if(y + painter.fontHeight >= window.height) { 763 painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight); 764 y -= painter.fontHeight; 765 } 766 767 painter.outlineColor = Color.red; 768 painter.fillColor = Color.black; 769 painter.drawRectangle(Point(0, y), window.width, painter.fontHeight); 770 771 painter.outlineColor = Color.white; 772 773 painter.drawText(Point(10, y), text); 774 775 y += painter.fontHeight; 776 } 777 778 window.eventLoop(1000, 779 () { 780 addLine("Timer went off!"); 781 }, 782 (KeyEvent event) { 783 addLine(to!string(event)); 784 }, 785 (MouseEvent event) { 786 addLine(to!string(event)); 787 }, 788 (dchar ch) { 789 addLine(to!string(ch)); 790 } 791 ); 792 } 793 --- 794 795 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. 796 797 $(COMMENT 798 This program displays a pie chart. Clicking on a color will increase its share of the pie. 799 800 --- 801 802 --- 803 ) 804 805 History: 806 Initial release in April 2011. 807 808 simpledisplay was stand alone until about 2015. It then added a dependency on [arsd.color] and changed its name to `arsd.simpledisplay`. 809 810 On March 4, 2023 (dub v11.0), it started importing [arsd.core] as well, making that a build-time requirement. 811 812 On October 5, 2024, apitrace support was added for Linux targets. 813 814 The ExperimentalTextComponent and ExperimentalTextComponent2 were both removed on April 12, 2025. Use [arsd.textlayouter] or the [arsd.minigui] widgets instead. 815 +/ 816 module arsd.simpledisplay; 817 818 // FIXME: tetris demo 819 // FIXME: space invaders demo 820 // FIXME: asteroids demo 821 822 /++ $(ID Pong-example) 823 $(H3 Pong) 824 825 This program creates a little Pong-like game. Player one is controlled 826 with the keyboard. Player two is controlled with the mouse. It demos 827 the pulse timer, event handling, and some basic drawing. 828 +/ 829 version(demos) 830 unittest { 831 // dmd example.d simpledisplay.d color.d 832 import arsd.simpledisplay; 833 834 enum paddleMovementSpeed = 8; 835 enum paddleHeight = 48; 836 837 void main() { 838 auto window = new SimpleWindow(600, 400, "Pong game!"); 839 840 int playerOnePosition, playerTwoPosition; 841 int playerOneMovement, playerTwoMovement; 842 int playerOneScore, playerTwoScore; 843 844 int ballX, ballY; 845 int ballDx, ballDy; 846 847 void serve() { 848 import std.random; 849 850 ballX = window.width / 2; 851 ballY = window.height / 2; 852 ballDx = uniform(-4, 4) * 3; 853 ballDy = uniform(-4, 4) * 3; 854 if(ballDx == 0) 855 ballDx = uniform(0, 2) == 0 ? 3 : -3; 856 } 857 858 serve(); 859 860 window.eventLoop(50, // set a 50 ms timer pulls 861 // This runs once per timer pulse 862 delegate () { 863 auto painter = window.draw(); 864 865 painter.clear(); 866 867 // Update everyone's motion 868 playerOnePosition += playerOneMovement; 869 playerTwoPosition += playerTwoMovement; 870 871 ballX += ballDx; 872 ballY += ballDy; 873 874 // Bounce off the top and bottom edges of the window 875 if(ballY + 7 >= window.height) 876 ballDy = -ballDy; 877 if(ballY - 8 <= 0) 878 ballDy = -ballDy; 879 880 // Bounce off the paddle, if it is in position 881 if(ballX - 8 <= 16) { 882 if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) { 883 ballDx = -ballDx + 1; // add some speed to keep it interesting 884 ballDy += playerOneMovement; // and y movement based on your controls too 885 ballX = 24; // move it past the paddle so it doesn't wiggle inside 886 } else { 887 // Missed it 888 playerTwoScore ++; 889 serve(); 890 } 891 } 892 893 if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1 894 if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) { 895 ballDx = -ballDx - 1; 896 ballDy += playerTwoMovement; 897 ballX = window.width - 24; 898 } else { 899 // Missed it 900 playerOneScore ++; 901 serve(); 902 } 903 } 904 905 // Draw the paddles 906 painter.outlineColor = Color.black; 907 painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight)); 908 painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight)); 909 910 // Draw the ball 911 painter.fillColor = Color.red; 912 painter.outlineColor = Color.yellow; 913 painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7)); 914 915 // Draw the score 916 painter.outlineColor = Color.blue; 917 import std.conv; 918 painter.drawText(Point(64, 4), to!string(playerOneScore)); 919 painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore)); 920 921 }, 922 delegate (KeyEvent event) { 923 // Player 1's controls are the arrow keys on the keyboard 924 if(event.key == Key.Down) 925 playerOneMovement = event.pressed ? paddleMovementSpeed : 0; 926 if(event.key == Key.Up) 927 playerOneMovement = event.pressed ? -paddleMovementSpeed : 0; 928 929 }, 930 delegate (MouseEvent event) { 931 // Player 2's controls are mouse movement while the left button is held down 932 if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) { 933 if(event.dy > 0) 934 playerTwoMovement = paddleMovementSpeed; 935 else if(event.dy < 0) 936 playerTwoMovement = -paddleMovementSpeed; 937 } else { 938 playerTwoMovement = 0; 939 } 940 } 941 ); 942 } 943 } 944 945 /++ $(H3 $(ID example-minesweeper) Minesweeper) 946 947 This minesweeper demo shows how we can implement another classic 948 game with simpledisplay and shows some mouse input and basic output 949 code. 950 +/ 951 version(demos) 952 unittest { 953 import arsd.simpledisplay; 954 955 enum GameSquare { 956 mine = 0, 957 clear, 958 m1, m2, m3, m4, m5, m6, m7, m8 959 } 960 961 enum UserSquare { 962 unknown, 963 revealed, 964 flagged, 965 questioned 966 } 967 968 enum GameState { 969 inProgress, 970 lose, 971 win 972 } 973 974 GameSquare[] board; 975 UserSquare[] userState; 976 GameState gameState; 977 int boardWidth; 978 int boardHeight; 979 980 bool isMine(int x, int y) { 981 if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight) 982 return false; 983 return board[y * boardWidth + x] == GameSquare.mine; 984 } 985 986 GameState reveal(int x, int y) { 987 if(board[y * boardWidth + x] == GameSquare.clear) { 988 floodFill(userState, boardWidth, boardHeight, 989 UserSquare.unknown, UserSquare.revealed, 990 x, y, 991 (x, y) { 992 if(board[y * boardWidth + x] == GameSquare.clear) 993 return true; 994 else { 995 userState[y * boardWidth + x] = UserSquare.revealed; 996 return false; 997 } 998 }); 999 } else { 1000 userState[y * boardWidth + x] = UserSquare.revealed; 1001 if(isMine(x, y)) 1002 return GameState.lose; 1003 } 1004 1005 foreach(state; userState) { 1006 if(state == UserSquare.unknown || state == UserSquare.questioned) 1007 return GameState.inProgress; 1008 } 1009 1010 return GameState.win; 1011 } 1012 1013 void initializeBoard(int width, int height, int numberOfMines) { 1014 boardWidth = width; 1015 boardHeight = height; 1016 board.length = width * height; 1017 1018 userState.length = width * height; 1019 userState[] = UserSquare.unknown; 1020 1021 import std.algorithm, std.random, std.range; 1022 1023 board[] = GameSquare.clear; 1024 1025 foreach(minePosition; randomSample(iota(0, board.length), numberOfMines)) 1026 board[minePosition] = GameSquare.mine; 1027 1028 int x; 1029 int y; 1030 foreach(idx, ref square; board) { 1031 if(square == GameSquare.clear) { 1032 int danger = 0; 1033 danger += isMine(x-1, y-1)?1:0; 1034 danger += isMine(x-1, y)?1:0; 1035 danger += isMine(x-1, y+1)?1:0; 1036 danger += isMine(x, y-1)?1:0; 1037 danger += isMine(x, y+1)?1:0; 1038 danger += isMine(x+1, y-1)?1:0; 1039 danger += isMine(x+1, y)?1:0; 1040 danger += isMine(x+1, y+1)?1:0; 1041 1042 square = cast(GameSquare) (danger + 1); 1043 } 1044 1045 x++; 1046 if(x == width) { 1047 x = 0; 1048 y++; 1049 } 1050 } 1051 } 1052 1053 void redraw(SimpleWindow window) { 1054 import std.conv; 1055 1056 auto painter = window.draw(); 1057 1058 painter.clear(); 1059 1060 final switch(gameState) with(GameState) { 1061 case inProgress: 1062 break; 1063 case win: 1064 painter.fillColor = Color.green; 1065 painter.drawRectangle(Point(0, 0), window.width, window.height); 1066 return; 1067 case lose: 1068 painter.fillColor = Color.red; 1069 painter.drawRectangle(Point(0, 0), window.width, window.height); 1070 return; 1071 } 1072 1073 int x = 0; 1074 int y = 0; 1075 1076 foreach(idx, square; board) { 1077 auto state = userState[idx]; 1078 1079 final switch(state) with(UserSquare) { 1080 case unknown: 1081 painter.outlineColor = Color.black; 1082 painter.fillColor = Color(128,128,128); 1083 1084 painter.drawRectangle( 1085 Point(x * 20, y * 20), 1086 20, 20 1087 ); 1088 break; 1089 case revealed: 1090 if(square == GameSquare.clear) { 1091 painter.outlineColor = Color.white; 1092 painter.fillColor = Color.white; 1093 1094 painter.drawRectangle( 1095 Point(x * 20, y * 20), 1096 20, 20 1097 ); 1098 } else { 1099 painter.outlineColor = Color.black; 1100 painter.fillColor = Color.white; 1101 1102 painter.drawText( 1103 Point(x * 20, y * 20), 1104 to!string(square)[1..2], 1105 Point(x * 20 + 20, y * 20 + 20), 1106 TextAlignment.Center | TextAlignment.VerticalCenter); 1107 } 1108 break; 1109 case flagged: 1110 painter.outlineColor = Color.black; 1111 painter.fillColor = Color.red; 1112 painter.drawRectangle( 1113 Point(x * 20, y * 20), 1114 20, 20 1115 ); 1116 break; 1117 case questioned: 1118 painter.outlineColor = Color.black; 1119 painter.fillColor = Color.yellow; 1120 painter.drawRectangle( 1121 Point(x * 20, y * 20), 1122 20, 20 1123 ); 1124 break; 1125 } 1126 1127 x++; 1128 if(x == boardWidth) { 1129 x = 0; 1130 y++; 1131 } 1132 } 1133 1134 } 1135 1136 void main() { 1137 auto window = new SimpleWindow(200, 200); 1138 1139 initializeBoard(10, 10, 10); 1140 1141 redraw(window); 1142 window.eventLoop(0, 1143 delegate (MouseEvent me) { 1144 if(me.type != MouseEventType.buttonPressed) 1145 return; 1146 auto x = me.x / 20; 1147 auto y = me.y / 20; 1148 if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) { 1149 if(me.button == MouseButton.left) { 1150 gameState = reveal(x, y); 1151 } else { 1152 userState[y*boardWidth+x] = UserSquare.flagged; 1153 } 1154 redraw(window); 1155 } 1156 } 1157 ); 1158 } 1159 } 1160 1161 // FIXME: tetris demo 1162 // FIXME: space invaders demo 1163 // FIXME: asteroids demo 1164 1165 import arsd.core; 1166 1167 version(ArsdNoCocoa) { 1168 } else { 1169 version(D_OpenD) { 1170 version(OSX) 1171 version=OSXCocoa; 1172 version(iOS) 1173 version=OSXCocoa; 1174 } else { 1175 version(OSX) version(DigitalMars) version=OSXCocoa; 1176 } 1177 } 1178 1179 1180 version(Emscripten) { 1181 version=allow_unimplemented_features; 1182 version=without_opengl; 1183 } 1184 1185 1186 version(OSXCocoa) { 1187 version=without_opengl; 1188 version=allow_unimplemented_features; 1189 // version=OSXCocoa; 1190 // pragma(linkerDirective, "-framework Cocoa"); 1191 } 1192 1193 version(without_opengl) { 1194 enum SdpyIsUsingIVGLBinds = false; 1195 } else /*version(Posix)*/ { 1196 static if (__traits(compiles, (){import iv.glbinds;})) { 1197 enum SdpyIsUsingIVGLBinds = true; 1198 public import iv.glbinds; 1199 //pragma(msg, "SDPY: using iv.glbinds"); 1200 } else { 1201 enum SdpyIsUsingIVGLBinds = false; 1202 } 1203 //} else { 1204 // enum SdpyIsUsingIVGLBinds = false; 1205 } 1206 1207 version(Windows) { 1208 //import core.sys.windows.windows; 1209 import core.sys.windows.winnls; 1210 import core.sys.windows.windef; 1211 import core.sys.windows.basetyps; 1212 import core.sys.windows.winbase; 1213 import core.sys.windows.winuser; 1214 import core.sys.windows.shellapi; 1215 import core.sys.windows.wingdi; 1216 static import gdi = core.sys.windows.wingdi; // so i 1217 1218 pragma(lib, "gdi32"); 1219 pragma(lib, "user32"); 1220 1221 // for AlphaBlend... a breaking change.... 1222 version(CRuntime_DigitalMars) { } else 1223 pragma(lib, "msimg32"); 1224 1225 // core.sys.windows.dwmapi 1226 private { 1227 /++ 1228 See_also: 1229 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmgetwindowattribute 1230 +/ 1231 extern extern(Windows) HRESULT DwmGetWindowAttribute( 1232 HWND hwnd, 1233 DWORD dwAttribute, 1234 PVOID pvAttribute, 1235 DWORD cbAttribute 1236 ) nothrow @nogc; 1237 1238 /++ 1239 See_also: 1240 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute 1241 +/ 1242 extern extern(Windows) HRESULT DwmSetWindowAttribute( 1243 HWND hwnd, 1244 DWORD dwAttribute, 1245 LPCVOID pvAttribute, 1246 DWORD cbAttribute 1247 ) nothrow @nogc; 1248 1249 /++ 1250 See_also: 1251 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute 1252 +/ 1253 enum DWMWINDOWATTRIBUTE { 1254 // incomplete, only declare what we need 1255 1256 /++ 1257 Usage: 1258 pvAttribute → `DWM_WINDOW_CORNER_PREFERENCE*` 1259 1260 $(NOTE 1261 Requires Windows 11 or later. 1262 ) 1263 +/ 1264 DWMWA_WINDOW_CORNER_PREFERENCE = 33, 1265 } 1266 1267 /++ 1268 See_also: 1269 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference 1270 +/ 1271 enum DWM_WINDOW_CORNER_PREFERENCE { 1272 /// System decision 1273 DWMWCP_DEFAULT = 0, 1274 1275 /// Never 1276 DWMWCP_DONOTROUND = 1, 1277 1278 // If "appropriate" 1279 DWMWCP_ROUND = 2, 1280 1281 // If "appropriate", but smaller radius 1282 DWMWCP_ROUNDSMALL = 3 1283 } 1284 1285 bool fromDWM( 1286 DWM_WINDOW_CORNER_PREFERENCE value, 1287 out CornerStyle result, 1288 ) @safe pure nothrow @nogc { 1289 switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) { 1290 case DWMWCP_DEFAULT: 1291 result = CornerStyle.automatic; 1292 return true; 1293 case DWMWCP_DONOTROUND: 1294 result = CornerStyle.rectangular; 1295 return true; 1296 case DWMWCP_ROUND: 1297 result = CornerStyle.rounded; 1298 return true; 1299 case DWMWCP_ROUNDSMALL: 1300 result = CornerStyle.roundedSlightly; 1301 return true; 1302 default: 1303 return false; 1304 } 1305 } 1306 1307 bool toDWM( 1308 CornerStyle value, 1309 out DWM_WINDOW_CORNER_PREFERENCE result, 1310 ) @safe pure nothrow @nogc { 1311 final switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) { 1312 case CornerStyle.automatic: 1313 result = DWMWCP_DEFAULT; 1314 return true; 1315 case CornerStyle.rectangular: 1316 result = DWMWCP_DONOTROUND; 1317 return true; 1318 case CornerStyle.rounded: 1319 result = DWMWCP_ROUND; 1320 return true; 1321 case CornerStyle.roundedSlightly: 1322 result = DWMWCP_ROUNDSMALL; 1323 return true; 1324 } 1325 } 1326 1327 pragma(lib, "dwmapi"); 1328 } 1329 } else version(Emscripten) { 1330 } else version (linux) { 1331 //k8: this is hack for rdmd. sorry. 1332 static import core.sys.linux.epoll; 1333 static import core.sys.linux.timerfd; 1334 } 1335 1336 1337 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off. 1338 1339 // http://wiki.dlang.org/Simpledisplay.d 1340 1341 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led 1342 1343 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl 1344 // but can i control the scroll lock led 1345 1346 1347 // Note: if you are using Image on X, you might want to do: 1348 /* 1349 static if(UsingSimpledisplayX11) { 1350 if(!Image.impl.xshmAvailable) { 1351 // the images will use the slower XPutImage, you might 1352 // want to consider an alternative method to get better speed 1353 } 1354 } 1355 1356 If the shared memory extension is available though, simpledisplay uses it 1357 for a significant speed boost whenever you draw large Images. 1358 */ 1359 1360 // 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. 1361 1362 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()! 1363 1364 /* 1365 Biggest FIXME: 1366 make sure the key event numbers match between X and Windows OR provide symbolic constants on each system 1367 1368 clean up opengl contexts when their windows close 1369 1370 fix resizing the bitmaps/pixmaps 1371 */ 1372 1373 // BTW on Windows: 1374 // -L/SUBSYSTEM:WINDOWS:5.0 1375 // to dmd will make a nice windows binary w/o a console if you want that. 1376 1377 /* 1378 Stuff to add: 1379 1380 use multibyte functions everywhere we can 1381 1382 OpenGL windows 1383 more event stuff 1384 extremely basic windows w/ no decoration for tooltips, splash screens, etc. 1385 1386 1387 resizeEvent 1388 and make the windows non-resizable by default, 1389 or perhaps stretched (if I can find something in X like StretchBlt) 1390 1391 take a screenshot function! 1392 1393 Pens and brushes? 1394 Maybe a global event loop? 1395 1396 Mouse deltas 1397 Key items 1398 */ 1399 1400 /* 1401 From MSDN: 1402 1403 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate. 1404 1405 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. 1406 1407 */ 1408 1409 version(Emscripten) { 1410 1411 } else version(linux) { 1412 version = X11; 1413 version(without_libnotify) { 1414 // we cool 1415 } 1416 else 1417 version = libnotify; 1418 } 1419 1420 version(libnotify) { 1421 //pragma(lib, "dl"); 1422 import core.sys.posix.dlfcn; 1423 1424 void delegate()[int] libnotify_action_delegates; 1425 int libnotify_action_delegates_count; 1426 extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) { 1427 auto idx = cast(int) user_data; 1428 if(auto dgptr = idx in libnotify_action_delegates) { 1429 (*dgptr)(); 1430 libnotify_action_delegates.remove(idx); 1431 } 1432 } 1433 1434 struct C_DynamicLibrary { 1435 void* handle; 1436 this(string name) { 1437 handle = dlopen((name ~ "\0").ptr, RTLD_NOW); 1438 if(handle is null) 1439 throw new Exception("dlopen"); 1440 } 1441 1442 void close() { 1443 dlclose(handle); 1444 } 1445 1446 ~this() { 1447 // close 1448 } 1449 1450 // FIXME: this looks up by name every time.... 1451 template call(string func, Ret, Args...) { 1452 extern(C) Ret function(Args) fptr; 1453 typeof(fptr) call() { 1454 fptr = cast(typeof(fptr)) dlsym(handle, func); 1455 return fptr; 1456 } 1457 } 1458 } 1459 1460 C_DynamicLibrary* libnotify; 1461 } 1462 1463 version(OSX) { 1464 version(OSXCocoa) {} 1465 else { version = X11; } 1466 } 1467 //version = OSXCocoa; // this was written by KennyTM 1468 version(FreeBSD) 1469 version = X11; 1470 version(Solaris) 1471 version = X11; 1472 1473 version(X11) { 1474 version(without_xft) {} 1475 else version=with_xft; 1476 } 1477 1478 void featureNotImplemented()() { 1479 version(allow_unimplemented_features) 1480 throw new NotYetImplementedException(); 1481 else 1482 static assert(0); 1483 } 1484 1485 // these are so the static asserts don't trigger unless you want to 1486 // add support to it for an OS 1487 version(Windows) 1488 version = with_timer; 1489 version(linux) 1490 version = with_timer; 1491 version(OSXCocoa) 1492 version = with_timer; 1493 1494 version(with_timer) 1495 enum bool SimpledisplayTimerAvailable = true; 1496 else 1497 enum bool SimpledisplayTimerAvailable = false; 1498 1499 /// 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. 1500 version(Windows) 1501 enum bool UsingSimpledisplayWindows = true; 1502 else 1503 enum bool UsingSimpledisplayWindows = false; 1504 1505 /// 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. 1506 version(X11) 1507 enum bool UsingSimpledisplayX11 = true; 1508 else 1509 enum bool UsingSimpledisplayX11 = false; 1510 1511 /// 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. 1512 version(OSXCocoa) 1513 enum bool UsingSimpledisplayCocoa = true; 1514 else 1515 enum bool UsingSimpledisplayCocoa = false; 1516 1517 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception. 1518 version(Windows) 1519 enum multipleWindowsSupported = true; 1520 else version(Emscripten) 1521 enum multipleWindowsSupported = false; 1522 else version(X11) 1523 enum multipleWindowsSupported = true; 1524 else version(OSXCocoa) 1525 enum multipleWindowsSupported = true; 1526 else 1527 static assert(0); 1528 1529 version(without_opengl) 1530 enum bool OpenGlEnabled = false; 1531 else 1532 enum bool OpenGlEnabled = true; 1533 1534 /++ 1535 Adds the necessary pragmas to your application to use the Windows gui subsystem. 1536 If you mix this in above your `main` function, you no longer need to use the linker 1537 flags explicitly. It does the necessary version blocks for various compilers and runtimes. 1538 1539 It does nothing if not compiling for Windows, so you need not version it out yourself. 1540 1541 Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and 1542 stderr writeln. It will fail and throw an exception. 1543 1544 This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`. 1545 1546 History: 1547 Added November 24, 2021 (dub v10.4) 1548 +/ 1549 mixin template EnableWindowsSubsystem() { 1550 version(Windows) 1551 version(CRuntime_Microsoft) { 1552 pragma(linkerDirective, "/subsystem:windows"); 1553 version(D_OpenD) { 1554 pragma(linkerDirective, "/entry:mainCRTStartup"); 1555 } else { 1556 version(LDC) 1557 pragma(linkerDirective, "/entry:wmainCRTStartup"); 1558 else 1559 pragma(linkerDirective, "/entry:mainCRTStartup"); 1560 } 1561 } 1562 } 1563 1564 1565 /++ 1566 After selecting a type from [WindowTypes], you may further customize 1567 its behavior by setting one or more of these flags. 1568 1569 1570 The different window types have different meanings of `normal`. If the 1571 window type already is a good match for what you want to do, you should 1572 just use [WindowFlags.normal], the default, which will do the right thing 1573 for your users. 1574 1575 The window flags will not always be honored by the operating system 1576 and window managers; they are hints, not commands. 1577 +/ 1578 enum WindowFlags : int { 1579 normal = 0, /// 1580 skipTaskbar = 1, /// 1581 alwaysOnTop = 2, /// 1582 alwaysOnBottom = 4, /// 1583 cannotBeActivated = 8, /// 1584 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. 1585 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. 1586 /++ 1587 Sets the window as a short-lived child of its parent, but unlike an ordinary child, 1588 it is still a top-level window. This should NOT be set separately for most window types. 1589 1590 A transient window will not keep the application open if its main window closes. 1591 1592 $(PITFALL This may not be correctly implemented and its behavior is subject to change.) 1593 1594 1595 From the ICCM: 1596 1597 $(BLOCKQUOTE 1598 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. 1599 1600 $(CITE https://tronche.com/gui/x/icccm/sec-4.html) 1601 ) 1602 1603 So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag. 1604 1605 History: 1606 Added February 23, 2021 but not yet stabilized. 1607 +/ 1608 transient = 64, 1609 /++ 1610 This indicates that the window manages its own platform-specific child window input focus. You must use a delegate, [SimpleWindow.setRequestedInputFocus], to set the input when requested. This delegate returns the handle to the window that should receive the focus. 1611 1612 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 1613 1614 History: 1615 Added April 1, 2022 1616 +/ 1617 managesChildWindowFocus = 128, 1618 1619 /++ 1620 +/ 1621 overrideRedirect = 256, 1622 1623 dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually. 1624 } 1625 1626 /++ 1627 When creating a window, you can pass a type to SimpleWindow's constructor, 1628 then further customize the window by changing `WindowFlags`. 1629 1630 1631 You should mostly only need [normal], [undecorated], and [eventOnly] for normal 1632 use. The others are there to build a foundation for a higher level GUI toolkit, 1633 but are themselves not as high level as you might think from their names. 1634 1635 This list is based on the EMWH spec for X11. 1636 http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896 1637 +/ 1638 enum WindowTypes : int { 1639 /// An ordinary application window. 1640 normal, 1641 /// 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. 1642 undecorated, 1643 /// 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. 1644 eventOnly, 1645 /// A drop down menu, such as from a menu bar 1646 dropdownMenu, 1647 /// A popup menu, such as from a right click 1648 popupMenu, 1649 /// A popup bubble notification 1650 notification, 1651 /* 1652 menu, /// a tearable menu bar (not override-redirect - contrast to popups) 1653 toolbar, /// a tearable menu bar (not override-redirect) 1654 splashScreen, /// a loading splash screen for your application 1655 desktop, /// 1656 dockOrPanel, /// think taskbar 1657 utility, /// a palette or something 1658 */ 1659 /// A tiny window showing temporary help text or something. 1660 tooltip, 1661 /// only supported on X; will assert fail elsewhere 1662 dnd, 1663 /// can also be used for other auto-complete presentations 1664 comboBoxDropdown, 1665 /// a dialog box of some sort 1666 dialog, 1667 /// a child nested inside the parent. You must pass a parent window to the ctor 1668 nestedChild, 1669 1670 /++ 1671 The type you get when you pass in an existing browser handle, which means most 1672 of simpledisplay's fancy things will not be done since they were never set up. 1673 1674 Using this to the main SimpleWindow constructor explicitly will trigger an assertion 1675 failure; you should use the existing handle constructor. 1676 1677 History: 1678 Added November 17, 2022 (previously it would have type `normal`) 1679 +/ 1680 minimallyWrapped 1681 } 1682 1683 1684 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call 1685 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features 1686 private __gshared char* sdpyWindowClassStr = null; 1687 private __gshared bool sdpyOpenGLContextAllowFallback = false; 1688 1689 /** 1690 Set OpenGL context version to use. This has no effect on non-OpenGL windows. 1691 You may want to change context version if you want to use advanced shaders or 1692 other modern OpenGL techinques. This setting doesn't affect already created 1693 windows. You may use version 2.1 as your default, which should be supported 1694 by any box since 2006, so seems to be a reasonable choice. 1695 1696 Note that by default version is set to `0`, which forces SimpleDisplay to use 1697 old context creation code without any version specified. This is the safest 1698 way to init OpenGL, but it may not give you access to advanced features. 1699 1700 See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL 1701 */ 1702 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); } 1703 1704 /** 1705 Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed 1706 pipeline functions, and without "compatible" mode you won't be able to use 1707 your old non-shader-based code with such contexts. By default SimpleDisplay 1708 creates compatible context, so you can gradually upgrade your OpenGL code if 1709 you want to (or leave it as is, as it should "just work"). 1710 */ 1711 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; } 1712 1713 /** 1714 Set to `true` to allow creating OpenGL context with lower version than requested 1715 instead of throwing. If fallback was activated (or legacy OpenGL was requested), 1716 `openGLContextFallbackActivated()` will return `true`. 1717 */ 1718 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; } 1719 1720 /** 1721 After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context. 1722 */ 1723 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); } 1724 1725 /++ 1726 History: 1727 Added April 24, 2023 (dub v11.0) 1728 +/ 1729 version(without_opengl) {} else 1730 auto openGLCurrentContext() { 1731 version(Windows) 1732 return wglGetCurrentContext(); 1733 else 1734 return glXGetCurrentContext(); 1735 } 1736 1737 1738 /** 1739 Set window class name for all following `new SimpleWindow()` calls. 1740 1741 WARNING! For Windows, you should set your class name before creating any 1742 window, and NEVER change it after that! 1743 */ 1744 void sdpyWindowClass (const(char)[] v) { 1745 import core.stdc.stdlib : realloc; 1746 if (v.length == 0) v = "SimpleDisplayWindow"; 1747 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1); 1748 if (sdpyWindowClassStr is null) return; // oops 1749 sdpyWindowClassStr[0..v.length+1] = 0; 1750 sdpyWindowClassStr[0..v.length] = v[]; 1751 } 1752 1753 /** 1754 Get current window class name. 1755 */ 1756 string sdpyWindowClass () @trusted { 1757 if (sdpyWindowClassStr is null) return null; 1758 foreach (immutable idx; 0..size_t.max-1) { 1759 if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup; 1760 } 1761 return null; 1762 } 1763 1764 /++ 1765 Returns the logical 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. This isn't necessarily related to the physical side of the screen; it is associated with a user-defined scaling factor. 1766 1767 If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0. 1768 +/ 1769 float[2] getDpi() { 1770 float[2] dpi; 1771 version(Windows) { 1772 HDC screen = GetDC(null); 1773 dpi[0] = GetDeviceCaps(screen, LOGPIXELSX); 1774 dpi[1] = GetDeviceCaps(screen, LOGPIXELSY); 1775 } else version(X11) { 1776 auto display = XDisplayConnection.get; 1777 auto screen = DefaultScreen(display); 1778 1779 void fallback() { 1780 /+ 1781 // 25.4 millimeters in an inch... 1782 dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4; 1783 dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4; 1784 +/ 1785 1786 // the physical size isn't actually as important as the logical size since this is 1787 // all about scaling really 1788 dpi[0] = 96; 1789 dpi[1] = 96; 1790 } 1791 1792 auto xft = getXftDpi(); 1793 if(xft is float.nan) 1794 fallback(); 1795 else { 1796 dpi[0] = xft; 1797 dpi[1] = xft; 1798 } 1799 } 1800 1801 return dpi; 1802 } 1803 1804 version(X11) 1805 float getXftDpi() { 1806 auto display = XDisplayConnection.get; 1807 1808 char* resourceString = XResourceManagerString(display); 1809 XrmInitialize(); 1810 1811 if (resourceString) { 1812 auto db = XrmGetStringDatabase(resourceString); 1813 XrmValue value; 1814 char* type; 1815 if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) { 1816 if (value.addr) { 1817 import core.stdc.stdlib; 1818 return atof(cast(char*) value.addr); 1819 } 1820 } 1821 } 1822 1823 return float.nan; 1824 } 1825 1826 /++ 1827 Implementation used by [SimpleWindow.takeScreenshot]. 1828 1829 Params: 1830 handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen. 1831 width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target. 1832 height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target. 1833 x = the x-offset of the image to capture, from the left. 1834 y = the y-offset of the image to capture, from the top. 1835 1836 History: 1837 Added on March 14, 2021 1838 1839 Documented public on September 23, 2021 with full support for null params (dub 10.3) 1840 1841 +/ 1842 TrueColorImage trueColorImageFromNativeHandle(PaintingHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) { 1843 TrueColorImage got; 1844 version(X11) { 1845 auto display = XDisplayConnection.get; 1846 if(handle == 0) 1847 handle = RootWindow(display, DefaultScreen(display)); 1848 1849 if(width == 0 || height == 0) { 1850 Window root; 1851 int xpos, ypos; 1852 uint widthret, heightret, borderret, depthret; 1853 XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret); 1854 1855 if(width == 0) 1856 width = widthret; 1857 if(height == 0) 1858 height = heightret; 1859 } 1860 1861 auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap); 1862 1863 // https://github.com/adamdruppe/arsd/issues/98 1864 1865 auto i = new Image(image); 1866 got = i.toTrueColorImage(); 1867 1868 XDestroyImage(image); 1869 } else version(Windows) { 1870 auto hdc = GetDC(handle); 1871 scope(exit) ReleaseDC(handle, hdc); 1872 1873 if(width == 0 || height == 0) { 1874 BITMAP bmHeader; 1875 auto bm = GetCurrentObject(hdc, OBJ_BITMAP); 1876 GetObject(bm, BITMAP.sizeof, &bmHeader); 1877 if(width == 0) 1878 width = bmHeader.bmWidth; 1879 if(height == 0) 1880 height = bmHeader.bmHeight; 1881 } 1882 1883 auto i = new Image(width, height); 1884 HDC hdcMem = CreateCompatibleDC(hdc); 1885 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 1886 BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY); 1887 SelectObject(hdcMem, hbmOld); 1888 DeleteDC(hdcMem); 1889 1890 got = i.toTrueColorImage(); 1891 } else featureNotImplemented(); 1892 1893 return got; 1894 } 1895 1896 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE); 1897 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow; 1898 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi; 1899 1900 version(Windows) 1901 shared static this() { 1902 auto lib = LoadLibrary("User32.dll"); 1903 if(lib is null) 1904 return; 1905 //scope(exit) 1906 //FreeLibrary(lib); 1907 1908 SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext"); 1909 1910 if(SetProcessDpiAwarenessContext is null) 1911 return; 1912 1913 enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4; 1914 if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { 1915 //writeln(GetLastError()); 1916 } 1917 1918 GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow"); 1919 SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi"); 1920 } 1921 1922 /++ 1923 Blocking mode for event loop calls associated with a window instance. 1924 1925 History: 1926 Added December 8, 2021 (dub v10.5). Prior to that, all calls to 1927 `window.eventLoop` were the same as calls to `EventLoop.get.run`; that 1928 is, all would block until the application quit. 1929 1930 That behavior can still be achieved here with `untilApplicationQuits`, 1931 or explicitly calling the top-level `EventLoop.get.run` function. 1932 +/ 1933 enum BlockingMode { 1934 /++ 1935 The event loop call will block until the whole application is ready 1936 to quit if it is the only one running, but if it is nested inside 1937 another one, it will only block until the window you're calling it on 1938 closes. 1939 +/ 1940 automatic = 0x00, 1941 /++ 1942 The event loop call will only return when the whole application 1943 is ready to quit. This usually means all windows have been closed. 1944 1945 This is appropriate for your main application event loop. 1946 +/ 1947 untilApplicationQuits = 0x01, 1948 /++ 1949 The event loop will return when the window you're calling it on 1950 closes. If there are other windows still open, they may be destroyed 1951 unless you have another event loop running later. 1952 1953 This might be appropriate for a modal dialog box loop. Remember that 1954 other windows are still processing input though, so you can end up 1955 with a lengthy call stack if this happens in a loop, similar to a 1956 recursive function (well, it literally is a recursive function, just 1957 not an obvious looking one). 1958 +/ 1959 untilWindowCloses = 0x02, 1960 /++ 1961 If an event loop is already running, this call will immediately 1962 return, allowing the existing loop to handle it. If not, this call 1963 will block until the condition you bitwise-or into the flag. 1964 1965 The default is to block until the application quits, same as with 1966 the `automatic` setting (since if it were nested, which triggers until 1967 window closes in automatic, this flag would instead not block at all), 1968 but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`, 1969 it will only nest until the window closes. You might want that if you are 1970 going to open two windows simultaneously and want closing just one of them 1971 to trigger the event loop return. 1972 +/ 1973 onlyIfNotNested = 0x10, 1974 } 1975 1976 /++ 1977 Window corner visuals preference 1978 +/ 1979 enum CornerStyle { 1980 /++ 1981 Use the default style automatically applied by the system or its window manager/compositor. 1982 +/ 1983 automatic, 1984 1985 /++ 1986 Prefer rectangular window corners 1987 +/ 1988 rectangular, 1989 1990 /++ 1991 Prefer rounded window corners 1992 +/ 1993 rounded, 1994 1995 /++ 1996 Prefer slightly-rounded window corners 1997 +/ 1998 roundedSlightly, 1999 } 2000 2001 /++ 2002 The flagship window class. 2003 2004 2005 SimpleWindow tries to make ordinary windows very easy to create and use without locking you 2006 out of more advanced or complex features of the underlying windowing system. 2007 2008 For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")` 2009 and get a suitable window to work with. 2010 2011 From there, you can opt into additional features, like custom resizability and OpenGL support 2012 with the next two constructor arguments. Or, if you need even more, you can set a window type 2013 and customization flags with the final two constructor arguments. 2014 2015 If none of that works for you, you can also create a window using native function calls, then 2016 wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember, 2017 though, if you do this, managing the window is still your own responsibility! Notably, you 2018 will need to destroy it yourself. 2019 +/ 2020 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { 2021 version(D_OpenD) mixin EnableSynchronization; 2022 2023 /++ 2024 Copies the window's current state into a [TrueColorImage]. 2025 2026 Be warned: this can be a very slow operation 2027 2028 History: 2029 Actually implemented on March 14, 2021 2030 +/ 2031 TrueColorImage takeScreenshot() { 2032 version(Windows) 2033 return trueColorImageFromNativeHandle(impl.hwnd, _width, _height); 2034 else version(OSXCocoa) 2035 throw new NotYetImplementedException(); 2036 else 2037 return trueColorImageFromNativeHandle(impl.window, _width, _height); 2038 } 2039 2040 /++ 2041 Returns the actual logical DPI for the window on its current display monitor. If the window 2042 straddles monitors, it will return the value of one or the other in a platform-defined manner. 2043 2044 Please note this function may return zero if it doesn't know the answer! 2045 2046 2047 On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10), 2048 or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up). 2049 2050 On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems, 2051 this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the 2052 window primarily resides on by checking the center point of the window against the monitor map. 2053 2054 Returns: 2055 0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It 2056 assumes the X and Y dpi are the same. 2057 2058 History: 2059 Added November 26, 2021 (dub v10.4) 2060 2061 It said "physical dpi" in the description prior to July 29, 2022, but the behavior was 2062 always a logical value on Windows and usually one on Linux too, so now the docs reflect 2063 that. 2064 2065 Bugs: 2066 Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically 2067 just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to 2068 set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor 2069 and 1.5 on the secondary monitor. 2070 2071 The local dpi is not necessarily related to the physical dpi of the monitor. The name 2072 is a historical misnomer - the real thing of interest is the scale factor and due to 2073 compatibility concerns the scale would modify dpi values to trick applications. But since 2074 that's the terminology common out there, I used it too. 2075 2076 See_Also: 2077 [getDpi] gives the value provided for the default monitor. Not necessarily the same 2078 as this since the window many be on a different monitor, but it is a reasonable fallback 2079 to use if `actualDpi` returns 0. 2080 2081 [onDpiChanged] is changed when `actualDpi` has changed. 2082 +/ 2083 int actualDpi() { 2084 version(X11) bool useFallbackDpi = false; 2085 if(!actualDpiLoadAttempted) { 2086 // FIXME: do the actual monitor we are on 2087 // and on X this is a good chance to load the monitor map. 2088 version(Windows) { 2089 if(GetDpiForWindow) 2090 actualDpi_ = GetDpiForWindow(impl.hwnd); 2091 } else version(X11) { 2092 if(!xRandrInfoLoadAttemped) { 2093 xRandrInfoLoadAttemped = true; 2094 if(!XRandrLibrary.attempted) { 2095 XRandrLibrary.loadDynamicLibrary(); 2096 } 2097 2098 if(XRandrLibrary.loadSuccessful) { 2099 auto display = XDisplayConnection.get; 2100 int scratch; 2101 int major, minor; 2102 if(!XRRQueryExtension(display, &xrrEventBase, &scratch)) 2103 goto fallback; 2104 2105 XRRQueryVersion(display, &major, &minor); 2106 if(major <= 1 && minor < 5) 2107 goto fallback; 2108 2109 int count; 2110 XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count); 2111 if(monitors is null) 2112 goto fallback; 2113 scope(exit) XRRFreeMonitors(monitors); 2114 2115 MonitorInfo.info = MonitorInfo.info[0 .. 0]; 2116 MonitorInfo.info.assumeSafeAppend(); 2117 foreach(idx, monitor; monitors[0 .. count]) { 2118 MonitorInfo.info ~= MonitorInfo( 2119 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 2120 Size(monitor.mwidth, monitor.mheight), 2121 cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0]) 2122 ); 2123 2124 /+ 2125 if(monitor.mwidth == 0 || monitor.mheight == 0) 2126 // unknown physical size, just guess 96 to avoid divide by zero 2127 MonitorInfo.info ~= MonitorInfo( 2128 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 2129 Size(monitor.mwidth, monitor.mheight), 2130 96 2131 ); 2132 else 2133 // and actual thing 2134 MonitorInfo.info ~= MonitorInfo( 2135 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 2136 Size(monitor.mwidth, monitor.mheight), 2137 minInternal( 2138 // millimeter to int then rounding up. 2139 cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5), 2140 cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5) 2141 ) 2142 ); 2143 +/ 2144 } 2145 // writeln("Here", MonitorInfo.info); 2146 } 2147 } 2148 2149 if(XRandrLibrary.loadSuccessful) { 2150 updateActualDpi(true); 2151 // writeln("updated"); 2152 2153 if(!requestedInput) { 2154 // this is what requests live updates should the configuration change 2155 // each time you select input, it sends an initial event, so very important 2156 // to not get into a loop of selecting input, getting event, updating data, 2157 // and reselecting input... 2158 requestedInput = true; 2159 XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask); 2160 // writeln("requested input"); 2161 } 2162 } else { 2163 fallback: 2164 // make sure we disable events that aren't coming 2165 xrrEventBase = -1; 2166 // best guess... respect the custom scaling user command to some extent at least though 2167 useFallbackDpi = true; 2168 } 2169 } else version(OSXCocoa) { 2170 actualDpi_ = cast(int)(96 * customScalingFactorForMonitor(0)); // FIXME 2171 } 2172 actualDpiLoadAttempted = true; 2173 } else version(X11) if(MonitorInfo.info.length == 0) { 2174 useFallbackDpi = true; 2175 } 2176 2177 version(X11) 2178 if(useFallbackDpi || actualDpi_ == 0) // FIXME: the actualDpi_ will be populated eventually when we get the first synthetic configure event from the window manager, but that might be a little while so actualDpi_ can be 0 until then... 2179 actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0)); 2180 return actualDpi_; 2181 } 2182 2183 private int actualDpi_; 2184 private bool actualDpiLoadAttempted; 2185 2186 version(X11) private { 2187 bool requestedInput; 2188 static bool xRandrInfoLoadAttemped; 2189 struct MonitorInfo { 2190 Rectangle position; 2191 Size size; 2192 int dpi; 2193 2194 static MonitorInfo[] info; 2195 } 2196 bool screenPositionKnown; 2197 int screenPositionX; 2198 int screenPositionY; 2199 void updateActualDpi(bool loadingNow = false) { 2200 if(!loadingNow && !actualDpiLoadAttempted) 2201 actualDpi(); // just to make it do the load 2202 foreach(idx, m; MonitorInfo.info) { 2203 if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) { 2204 bool changed = actualDpi_ && actualDpi_ != m.dpi; 2205 actualDpi_ = m.dpi; 2206 // writeln("monitor ", idx); 2207 if(changed && onDpiChanged) 2208 onDpiChanged(); 2209 break; 2210 } 2211 } 2212 } 2213 } 2214 2215 /++ 2216 Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors 2217 or if the window is moved to a new remote connection or a monitor is hot-swapped. 2218 2219 History: 2220 Added November 26, 2021 (dub v10.4) 2221 2222 See_Also: 2223 [actualDpi] 2224 +/ 2225 void delegate() onDpiChanged; 2226 2227 version(X11) { 2228 void recreateAfterDisconnect() { 2229 if(!stateDiscarded) return; 2230 2231 if(_parent !is null && _parent.stateDiscarded) 2232 _parent.recreateAfterDisconnect(); 2233 2234 bool wasHidden = hidden; 2235 2236 activeScreenPainter = null; // should already be done but just to confirm 2237 2238 actualDpi_ = 0; 2239 actualDpiLoadAttempted = false; 2240 xRandrInfoLoadAttemped = false; 2241 2242 impl.createWindow(_width, _height, _title, openglMode, _parent); 2243 2244 if(auto dh = dropHandler) { 2245 dropHandler = null; 2246 enableDragAndDrop(this, dh); 2247 } 2248 2249 if(recreateAdditionalConnectionState) 2250 recreateAdditionalConnectionState(); 2251 2252 hidden = wasHidden; 2253 stateDiscarded = false; 2254 } 2255 2256 bool stateDiscarded; 2257 void discardConnectionState() { 2258 if(XDisplayConnection.display) 2259 impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway 2260 if(discardAdditionalConnectionState) 2261 discardAdditionalConnectionState(); 2262 stateDiscarded = true; 2263 } 2264 2265 void delegate() discardAdditionalConnectionState; 2266 void delegate() recreateAdditionalConnectionState; 2267 2268 } 2269 2270 private DropHandler dropHandler; 2271 2272 SimpleWindow _parent; 2273 bool beingOpenKeepsAppOpen = true; 2274 /++ 2275 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. 2276 2277 The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them. 2278 2279 Params: 2280 2281 width = the width of the window's client area, in pixels 2282 height = the height of the window's client area, in pixels 2283 title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property. 2284 opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window. 2285 resizable = [Resizability] has three options: 2286 $(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.) 2287 $(P `fixedSize` will not allow the user to resize the window.) 2288 $(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.) 2289 windowType = The type of window you want to make. 2290 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. 2291 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". 2292 +/ 2293 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) { 2294 claimGuiThread(); 2295 version(sdpy_thread_checks) assert(thisIsGuiThread); 2296 this._width = this._virtualWidth = width; 2297 this._height = this._virtualHeight = height; 2298 this.openglMode = opengl; 2299 version(X11) { 2300 // auto scale not implemented except with opengl and even there it is kinda weird 2301 if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no) 2302 resizable = Resizability.fixedSize; 2303 } 2304 this.resizability = resizable; 2305 this.windowType = windowType; 2306 this.customizationFlags = customizationFlags; 2307 this._title = (title is null ? "D Application" : title); 2308 this._parent = parent; 2309 impl.createWindow(width, height, this._title, opengl, parent); 2310 2311 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient)) 2312 beingOpenKeepsAppOpen = false; 2313 } 2314 2315 /// ditto 2316 this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 2317 this(width, height, title, opengl, resizable, windowType, customizationFlags, parent); 2318 } 2319 2320 /// Same as above, except using the `Size` struct instead of separate width and height. 2321 this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) { 2322 this(size.width, size.height, title, opengl, resizable); 2323 } 2324 2325 /// ditto 2326 this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) { 2327 this(size, title, opengl, resizable); 2328 } 2329 2330 2331 /++ 2332 Creates a window based on the given [Image]. It's client area 2333 width and height is equal to the image. (A window's client area 2334 is the drawable space inside; it excludes the title bar, etc.) 2335 2336 Windows based on images will not be resizable and do not use OpenGL. 2337 2338 It will draw the image in upon creation, but this will be overwritten 2339 upon any draws, including the initial window visible event. 2340 2341 You probably do not want to use this and it may be removed from 2342 the library eventually, or I might change it to be a "permanent" 2343 background image; one that is automatically drawn on it before any 2344 other drawing event. idk. 2345 +/ 2346 this(Image image, string title = null) { 2347 this(image.width, image.height, title); 2348 this.image = image; 2349 } 2350 2351 /++ 2352 Wraps a native window handle with very little additional processing - notably no destruction 2353 this is incomplete so don't use it for much right now. The purpose of this is to make native 2354 windows created through the low level API (so you can use platform-specific options and 2355 other details SimpleWindow does not expose) available to the event loop wrappers. 2356 +/ 2357 this(NativeWindowHandle nativeWindow) { 2358 windowType = WindowTypes.minimallyWrapped; 2359 version(Windows) 2360 impl.hwnd = nativeWindow; 2361 else version(X11) { 2362 impl.window = nativeWindow; 2363 if(nativeWindow) 2364 display = XDisplayConnection.get(); // get initial display to not segfault 2365 } else version(Emscripten) { 2366 // FIXME 2367 } else version(OSXCocoa) { 2368 if(nativeWindow !is NullWindow) throw new NotYetImplementedException(); 2369 } else featureNotImplemented(); 2370 // FIXME: set the size correctly 2371 _width = 1; 2372 _height = 1; 2373 if(nativeWindow) 2374 nativeMapping[cast(void*) nativeWindow] = this; 2375 2376 beingOpenKeepsAppOpen = false; 2377 useDirectDraw = true; 2378 2379 if(nativeWindow) { 2380 version(OSXCocoa) 2381 CapableOfHandlingNativeEvent.nativeHandleMapping[cast(void*) nativeWindow] = this; 2382 else 2383 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 2384 } 2385 _suppressDestruction = true; // so it doesn't try to close 2386 } 2387 2388 private bool useDirectDraw; 2389 2390 /++ 2391 Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created. 2392 The delegate will be called when the window manager asks you to take focus. 2393 2394 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 2395 2396 History: 2397 Added April 1, 2022 (dub v10.8) 2398 +/ 2399 SimpleWindow delegate() setRequestedInputFocus; 2400 2401 /// Experimental, do not use yet 2402 /++ 2403 Grabs exclusive input from the user until you release it with 2404 [releaseInputGrab]. 2405 2406 2407 Note: it is extremely rude to do this without good reason. 2408 Reasons may include doing some kind of mouse drag operation 2409 or popping up a temporary menu that should get events and will 2410 be dismissed at ease by the user clicking away. 2411 2412 Params: 2413 keyboard = do you want to grab keyboard input? 2414 mouse = grab mouse input? 2415 confine = confine the mouse cursor to inside this window? 2416 2417 History: 2418 Prior to March 11, 2021, grabbing the keyboard would always also 2419 set the X input focus. Now, it only focuses if it is a non-transient 2420 window and otherwise manages the input direction internally. 2421 2422 This means spurious focus/blur events will no longer be sent and the 2423 application will not steal focus from other applications (which the 2424 window manager may have rejected anyway). 2425 +/ 2426 void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) { 2427 static if(UsingSimpledisplayX11) { 2428 XSync(XDisplayConnection.get, 0); 2429 if(keyboard) { 2430 if(isTransient && _parent) { 2431 /* 2432 FIXME: 2433 setting the keyboard focus is not actually that helpful, what I more likely want 2434 is the events from the parent window to be sent over here if we're transient. 2435 */ 2436 2437 _parent.inputProxy = this; 2438 } else { 2439 2440 SimpleWindow setTo; 2441 if(setRequestedInputFocus !is null) 2442 setTo = setRequestedInputFocus(); 2443 if(setTo is null) 2444 setTo = this; 2445 2446 // sdpyPrintDebugString("grabInput() ", setTo.impl.window; 2447 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2448 } 2449 } 2450 if(mouse) { 2451 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 2452 EventMask.PointerMotionMask // FIXME: not efficient 2453 | EventMask.ButtonPressMask 2454 | EventMask.ButtonReleaseMask 2455 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 2456 ) 2457 { 2458 XSync(XDisplayConnection.get, 0); 2459 import core.stdc.stdio; 2460 printf("Grab input failed %d\n", res); 2461 //throw new Exception("Grab input failed"); 2462 } else { 2463 // cool 2464 } 2465 } 2466 2467 } else version(Windows) { 2468 // FIXME: keyboard? 2469 SetCapture(impl.hwnd); 2470 if(confine) { 2471 RECT rcClip; 2472 //RECT rcOldClip; 2473 //GetClipCursor(&rcOldClip); 2474 GetWindowRect(hwnd, &rcClip); 2475 ClipCursor(&rcClip); 2476 } 2477 } else version(Emscripten) { 2478 // nothing necessary 2479 } else version(OSXCocoa) { 2480 // throw new NotYetImplementedException(); 2481 } else static assert(0); 2482 } 2483 2484 private Point imePopupLocation = Point(0, 0); 2485 2486 /++ 2487 Sets the location for the IME (input method editor) to pop up when the user activates it. 2488 2489 Bugs: 2490 Not implemented outside X11. 2491 +/ 2492 void setIMEPopupLocation(Point location) { 2493 static if(UsingSimpledisplayX11) { 2494 imePopupLocation = location; 2495 updateIMEPopupLocation(); 2496 } else { 2497 // this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least 2498 // throw new NotYetImplementedException(); 2499 } 2500 } 2501 2502 /// ditto 2503 void setIMEPopupLocation(int x, int y) { 2504 return setIMEPopupLocation(Point(x, y)); 2505 } 2506 2507 // we need to remind XIM of where we wanted to place the IME whenever the window moves 2508 // so this function gets called in setIMEPopupLocation as well as whenever the window 2509 // receives a ConfigureNotify event 2510 private void updateIMEPopupLocation() { 2511 static if(UsingSimpledisplayX11) { 2512 if (xic is null) { 2513 return; 2514 } 2515 2516 XPoint nspot; 2517 nspot.x = cast(short) imePopupLocation.x; 2518 nspot.y = cast(short) imePopupLocation.y; 2519 XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null); 2520 XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null); 2521 XFree(preeditAttr); 2522 } 2523 } 2524 2525 private bool imeFocused = true; 2526 2527 /++ 2528 Tells the IME whether or not an input field is currently focused in the window. 2529 2530 Bugs: 2531 Not implemented outside X11. 2532 +/ 2533 void setIMEFocused(bool value) { 2534 imeFocused = value; 2535 updateIMEFocused(); 2536 } 2537 2538 // used to focus/unfocus the IC if necessary when the window gains/loses focus 2539 private void updateIMEFocused() { 2540 static if(UsingSimpledisplayX11) { 2541 if (xic is null) { 2542 return; 2543 } 2544 2545 if (focused && imeFocused) { 2546 XSetICFocus(xic); 2547 } else { 2548 XUnsetICFocus(xic); 2549 } 2550 } 2551 } 2552 2553 /++ 2554 Returns the native window. 2555 2556 History: 2557 Added November 5, 2021 (dub v10.4). Prior to that, you'd have 2558 to access it through the `impl` member (which is semi-supported 2559 but platform specific and here it is simple enough to offer an accessor). 2560 2561 Bugs: 2562 Not implemented outside Windows or X11. 2563 +/ 2564 NativeWindowHandle nativeWindowHandle() { 2565 version(X11) 2566 return impl.window; 2567 else version(Windows) 2568 return impl.hwnd; 2569 else 2570 throw new NotYetImplementedException(); 2571 } 2572 2573 private bool isTransient() { 2574 with(WindowTypes) 2575 final switch(windowType) { 2576 case normal, undecorated, eventOnly: 2577 case nestedChild, minimallyWrapped: 2578 return (customizationFlags & WindowFlags.transient) ? true : false; 2579 case dropdownMenu, popupMenu, notification, dialog, tooltip, dnd, comboBoxDropdown: 2580 return true; 2581 } 2582 } 2583 2584 private SimpleWindow inputProxy; 2585 2586 /++ 2587 Releases the grab acquired by [grabInput]. 2588 +/ 2589 void releaseInputGrab() { 2590 static if(UsingSimpledisplayX11) { 2591 XUngrabPointer(XDisplayConnection.get, CurrentTime); 2592 if(_parent) 2593 _parent.inputProxy = null; 2594 } else version(Windows) { 2595 ReleaseCapture(); 2596 ClipCursor(null); 2597 } else version(OSXCocoa) { 2598 // throw new NotYetImplementedException(); 2599 } else version(Emscripten) { 2600 // nothing needed 2601 } else static assert(0); 2602 } 2603 2604 /++ 2605 Sets the input focus to this window. 2606 2607 You shouldn't call this very often - please let the user control the input focus. 2608 +/ 2609 void focus() { 2610 static if(UsingSimpledisplayX11) { 2611 SimpleWindow setTo; 2612 if(setRequestedInputFocus !is null) 2613 setTo = setRequestedInputFocus(); 2614 if(setTo is null) 2615 setTo = this; 2616 // sdpyPrintDebugString("sdpy.focus() ", setTo.impl.window); 2617 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2618 } else version(Windows) { 2619 SetFocus(this.impl.hwnd); 2620 } else version(Emscripten) { 2621 throw new NotYetImplementedException(); 2622 } else version(OSXCocoa) { 2623 throw new NotYetImplementedException(); 2624 } else static assert(0); 2625 } 2626 2627 /++ 2628 Requests attention from the user for this window. 2629 2630 2631 The typical result of this function is to change the color 2632 of the taskbar icon, though it may be tweaked on specific 2633 platforms. 2634 2635 It is meant to unobtrusively tell the user that something 2636 relevant to them happened in the background and they should 2637 check the window when they get a chance. Upon receiving the 2638 keyboard focus, the window will automatically return to its 2639 natural state. 2640 2641 If the window already has the keyboard focus, this function 2642 may do nothing, because the user is presumed to already be 2643 giving the window attention. 2644 2645 Implementation_note: 2646 2647 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 2648 atom on X11 and the FlashWindow function on Windows. 2649 +/ 2650 void requestAttention() { 2651 if(_focused) 2652 return; 2653 2654 version(Windows) { 2655 FLASHWINFO info; 2656 info.cbSize = info.sizeof; 2657 info.hwnd = impl.hwnd; 2658 info.dwFlags = FLASHW_TRAY; 2659 info.uCount = 1; 2660 2661 FlashWindowEx(&info); 2662 2663 } else version(X11) { 2664 demandingAttention = true; 2665 demandAttention(this, true); 2666 } else version(Emscripten) { 2667 throw new NotYetImplementedException(); 2668 } else version(OSXCocoa) { 2669 throw new NotYetImplementedException(); 2670 } else static assert(0); 2671 } 2672 2673 private bool _focused; 2674 2675 version(X11) private bool demandingAttention; 2676 2677 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 2678 /// You'll have to call `close()` manually if you set this delegate. 2679 void delegate () closeQuery; 2680 2681 /// This will be called when window visibility was changed. 2682 void delegate (bool becomesVisible) visibilityChanged; 2683 2684 /// This will be called when window becomes visible for the first time. 2685 /// You can do OpenGL initialization here. Note that in X11 you can't call 2686 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 2687 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 2688 /// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization. 2689 private bool _visibleForTheFirstTimeCalled; 2690 void delegate () visibleForTheFirstTime; 2691 2692 /// Returns true if the window has been closed. 2693 final @property bool closed() { return _closed; } 2694 2695 private final @property bool notClosed() { return !_closed; } 2696 2697 /// Returns true if the window is focused. 2698 final @property bool focused() { return _focused; } 2699 2700 private bool _visible; 2701 /// Returns true if the window is visible (mapped). 2702 final @property bool visible() { return _visible; } 2703 2704 /// Closes the window. If there are no more open windows, the event loop will terminate. 2705 void close() { 2706 if (!_closed) { 2707 runInGuiThread( { 2708 if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued 2709 if (onClosing !is null) onClosing(); 2710 impl.closeWindow(); 2711 _closed = true; 2712 } ); 2713 } 2714 } 2715 2716 /++ 2717 `close` is one of the few methods that can be called from other threads. This `shared` overload reflects that. 2718 2719 History: 2720 Overload added on March 7, 2021. 2721 +/ 2722 void close() shared { 2723 (cast() this).close(); 2724 } 2725 2726 /++ 2727 2728 +/ 2729 void maximize() { 2730 version(Windows) 2731 ShowWindow(impl.hwnd, SW_MAXIMIZE); 2732 else version(X11) { 2733 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get)); 2734 2735 // also note _NET_WM_STATE_FULLSCREEN 2736 } 2737 2738 } 2739 2740 private bool _fullscreen; 2741 version(Windows) 2742 private WINDOWPLACEMENT g_wpPrev; 2743 2744 /// not fully implemented but planned for a future release 2745 void fullscreen(bool yes) { 2746 version(Windows) { 2747 g_wpPrev.length = WINDOWPLACEMENT.sizeof; 2748 DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE); 2749 if (dwStyle & WS_OVERLAPPEDWINDOW) { 2750 MONITORINFO mi; 2751 mi.cbSize = MONITORINFO.sizeof; 2752 if (GetWindowPlacement(hwnd, &g_wpPrev) && 2753 GetMonitorInfo(MonitorFromWindow(hwnd, 2754 MONITOR_DEFAULTTOPRIMARY), &mi)) { 2755 SetWindowLong(hwnd, GWL_STYLE, 2756 dwStyle & ~WS_OVERLAPPEDWINDOW); 2757 SetWindowPos(hwnd, HWND_TOP, 2758 mi.rcMonitor.left, mi.rcMonitor.top, 2759 mi.rcMonitor.right - mi.rcMonitor.left, 2760 mi.rcMonitor.bottom - mi.rcMonitor.top, 2761 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2762 } 2763 } else { 2764 SetWindowLong(hwnd, GWL_STYLE, 2765 dwStyle | WS_OVERLAPPEDWINDOW); 2766 SetWindowPlacement(hwnd, &g_wpPrev); 2767 SetWindowPos(hwnd, null, 0, 0, 0, 0, 2768 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | 2769 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2770 } 2771 2772 } else version(X11) { 2773 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes); 2774 } 2775 2776 _fullscreen = yes; 2777 2778 } 2779 2780 bool fullscreen() { 2781 return _fullscreen; 2782 } 2783 2784 /++ 2785 Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead. 2786 2787 +/ 2788 void minimize() { 2789 version(Windows) 2790 ShowWindow(impl.hwnd, SW_MINIMIZE); 2791 //else version(X11) 2792 //setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true); 2793 } 2794 2795 /// Alias for `hidden = false` 2796 void show() { 2797 hidden = false; 2798 } 2799 2800 /// Alias for `hidden = true` 2801 void hide() { 2802 hidden = true; 2803 } 2804 2805 /// Hide cursor when it enters the window. 2806 void hideCursor() { 2807 version(OSXCocoa) throw new NotYetImplementedException(); else 2808 if (!_closed) impl.hideCursor(); 2809 } 2810 2811 /// Don't hide cursor when it enters the window. 2812 void showCursor() { 2813 version(OSXCocoa) throw new NotYetImplementedException(); else 2814 if (!_closed) impl.showCursor(); 2815 } 2816 2817 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 2818 * 2819 * Please remember that the cursor is a shared resource that should usually be left to the user's 2820 * control. Try to think for other approaches before using this function. 2821 * 2822 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 2823 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 2824 * receive "mouse moved here" event. 2825 */ 2826 bool warpMouse (int x, int y) { 2827 version(X11) { 2828 if (!_closed) { impl.warpMouse(x, y); return true; } 2829 } else version(Windows) { 2830 if (!_closed) { 2831 POINT point; 2832 point.x = x; 2833 point.y = y; 2834 if(ClientToScreen(impl.hwnd, &point)) { 2835 SetCursorPos(point.x, point.y); 2836 return true; 2837 } 2838 } 2839 } 2840 return false; 2841 } 2842 2843 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 2844 void sendDummyEvent () { 2845 version(X11) { 2846 if (!_closed) { impl.sendDummyEvent(); } 2847 } 2848 } 2849 2850 /// Set window minimal size. 2851 void setMinSize (int minwidth, int minheight) { 2852 version(OSXCocoa) throw new NotYetImplementedException(); else 2853 if (!_closed) impl.setMinSize(minwidth, minheight); 2854 } 2855 2856 /// Set window maximal size. 2857 void setMaxSize (int maxwidth, int maxheight) { 2858 version(OSXCocoa) throw new NotYetImplementedException(); else 2859 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 2860 } 2861 2862 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 2863 /// Currently only supported on X11. 2864 void setResizeGranularity (int granx, int grany) { 2865 version(OSXCocoa) throw new NotYetImplementedException(); else 2866 if (!_closed) impl.setResizeGranularity(granx, grany); 2867 } 2868 2869 /// Move window. 2870 void move(int x, int y) { 2871 version(OSXCocoa) throw new NotYetImplementedException(); else 2872 if (!_closed) impl.move(x, y); 2873 } 2874 2875 /// ditto 2876 void move(Point p) { 2877 version(OSXCocoa) throw new NotYetImplementedException(); else 2878 if (!_closed) impl.move(p.x, p.y); 2879 } 2880 2881 /++ 2882 Resize window. 2883 2884 Note that the width and height of the window are NOT instantly 2885 updated - it waits for the window manager to approve the resize 2886 request, which means you must return to the event loop before the 2887 width and height are actually changed. 2888 +/ 2889 void resize(int w, int h) { 2890 if(!_closed && _fullscreen) fullscreen = false; 2891 if (!_closed) impl.resize(w, h); 2892 } 2893 2894 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 2895 void moveResize (int x, int y, int w, int h) { 2896 if(!_closed && _fullscreen) fullscreen = false; 2897 if (!_closed) impl.moveResize(x, y, w, h); 2898 } 2899 2900 private bool _hidden; 2901 2902 /// Returns true if the window is hidden. 2903 final @property bool hidden() { 2904 return _hidden; 2905 } 2906 2907 /// Shows or hides the window based on the bool argument. 2908 final @property void hidden(bool b) { 2909 _hidden = b; 2910 version(Windows) { 2911 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 2912 } else version(X11) { 2913 if(b) 2914 //XUnmapWindow(impl.display, impl.window); 2915 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 2916 else 2917 XMapWindow(impl.display, impl.window); 2918 } else version(OSXCocoa) { 2919 if(impl.window) 2920 impl.window.setIsVisible = !b; 2921 if(!hidden) 2922 impl.view.setNeedsDisplay(true); 2923 } else version(Emscripten) { 2924 } else static assert(0); 2925 } 2926 2927 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 2928 void opacity(double opacity) @property 2929 in { 2930 assert(opacity >= 0 && opacity <= 1); 2931 } do { 2932 version (Windows) { 2933 impl.setOpacity(cast(ubyte)(255 * opacity)); 2934 } else version (X11) { 2935 impl.setOpacity(cast(uint)(uint.max * opacity)); 2936 } else throw new NotYetImplementedException(); 2937 } 2938 2939 /++ 2940 Sets your event handlers, without entering the event loop. Useful if you 2941 have multiple windows - set the handlers on each window, then only do 2942 [eventLoop] on your main window or call `EventLoop.get.run();`. 2943 2944 This assigns the given handlers to [handleKeyEvent], [handleCharEvent], 2945 [handlePulse], and [handleMouseEvent] automatically based on the provide 2946 delegate signatures. 2947 +/ 2948 void setEventHandlers(T...)(T eventHandlers) { 2949 // FIXME: add more events 2950 foreach(handler; eventHandlers) { 2951 static if(__traits(compiles, handleKeyEvent = handler)) { 2952 handleKeyEvent = handler; 2953 } else static if(__traits(compiles, handleCharEvent = handler)) { 2954 handleCharEvent = handler; 2955 } else static if(__traits(compiles, handlePulse = handler)) { 2956 handlePulse = handler; 2957 } else static if(__traits(compiles, handleMouseEvent = handler)) { 2958 handleMouseEvent = handler; 2959 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 2960 } 2961 } 2962 2963 /++ 2964 The event loop automatically returns when the window is closed 2965 pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 2966 pulse timer is created. The event loop will block until an event 2967 arrives or the pulse timer goes off. 2968 2969 The given `eventHandlers` are passed to [setEventHandlers], which in turn 2970 assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and 2971 [handleMouseEvent], based on the signature of delegates you provide. 2972 2973 Give one with no parameters to set a timer pulse handler. Give one that 2974 takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler, 2975 and one that takes `dchar` for a char event handler. You can use as many 2976 or as few handlers as you need for your application. 2977 2978 Bugs: 2979 2980 $(PITFALL 2981 You should always have one event loop live for your application. 2982 If you make two windows in sequence, the second call to eventLoop 2983 might fail: 2984 2985 --- 2986 // don't do this! 2987 auto window = new SimpleWindow(); 2988 window.eventLoop(0); 2989 2990 auto window2 = new SimpleWindow(); 2991 window2.eventLoop(0); // problematic! might crash 2992 --- 2993 2994 simpledisplay's current implementation assumes that final cleanup is 2995 done when the event loop refcount reaches zero. So after the first 2996 eventLoop returns, when there isn't already another one active, it assumes 2997 the program will exit soon and cleans up. 2998 2999 This is arguably a bug that it doesn't reinitialize, and I'll probably change 3000 it eventually, but in the mean time, there's an easy solution: 3001 3002 --- 3003 // do this 3004 EventLoop mainEventLoop = EventLoop.get; // just add this line 3005 3006 auto window = new SimpleWindow(); 3007 window.eventLoop(0); 3008 3009 auto window2 = new SimpleWindow(); 3010 window2.eventLoop(0); // perfectly fine since mainEventLoop still alive 3011 --- 3012 3013 By adding a top-level reference to the event loop, it ensures the final cleanup 3014 is not performed until it goes out of scope too, letting the individual window loops 3015 work without trouble despite the bug. 3016 ) 3017 3018 History: 3019 The overload without `pulseTimeout` was added on December 8, 2021. 3020 3021 On December 9, 2021, the default blocking mode (which is now configurable 3022 because [eventLoopWithBlockingMode] was added) switched from 3023 [BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This 3024 should almost never be noticeable to you since the typical simpledisplay 3025 paradigm has been (and I still recommend) to have one `eventLoop` call. 3026 3027 See_Also: 3028 [eventLoopWithBlockingMode] 3029 +/ 3030 final int eventLoop(T...)( 3031 long pulseTimeout, /// set to zero if you don't want a pulse. 3032 T eventHandlers) /// delegate list like std.concurrency.receive 3033 { 3034 return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers); 3035 } 3036 3037 /// ditto 3038 final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate)) 3039 { 3040 return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers); 3041 } 3042 3043 /++ 3044 This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`. 3045 3046 History: 3047 Added December 8, 2021 (dub v10.5) 3048 3049 Previously, this implementation was right inside [eventLoop], but when I wanted 3050 to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I 3051 just renamed it instead of adding as an overload. Besides, the new name makes it 3052 easier to remember the order and avoids ambiguity between two int-like params anyway. 3053 3054 See_Also: 3055 [SimpleWindow.eventLoop], [EventLoop] 3056 3057 Bugs: 3058 The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop. 3059 +/ 3060 final int eventLoopWithBlockingMode(T...)( 3061 BlockingMode blockingMode, /// when you want this function to block until 3062 long pulseTimeout, /// set to zero if you don't want a pulse. 3063 T eventHandlers) /// delegate list like std.concurrency.receive 3064 { 3065 setEventHandlers(eventHandlers); 3066 3067 version(with_eventloop) { 3068 // delegates event loop to my other module 3069 version(X11) 3070 XFlush(display); 3071 3072 import arsd.eventloop; 3073 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 3074 scope(exit) clearInterval(handle); 3075 3076 loop(); 3077 return 0; 3078 } else { 3079 EventLoop el = EventLoop(pulseTimeout, handlePulse); 3080 3081 if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1) 3082 return 0; 3083 3084 return el.run( 3085 ((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ? 3086 null : 3087 &this.notClosed 3088 ); 3089 } 3090 } 3091 3092 /++ 3093 This lets you draw on the window (or its backing buffer) using basic 3094 2D primitives. 3095 3096 Be sure to call this in a limited scope because your changes will not 3097 actually appear on the window until ScreenPainter's destructor runs. 3098 3099 Returns: an instance of [ScreenPainter], which has the drawing methods 3100 on it to draw on this window. 3101 3102 Params: 3103 manualInvalidations = if you set this to true, you will need to 3104 set the invalid rectangle on the painter yourself. If false, it 3105 assumes the whole window has been redrawn each time you draw. 3106 3107 Only invalidated rectangles are blitted back to the window when 3108 the destructor runs. Doing this yourself can reduce flickering 3109 of child windows. 3110 3111 History: 3112 The `manualInvalidations` parameter overload was added on 3113 December 30, 2021 (dub v10.5) 3114 +/ 3115 ScreenPainter draw() { 3116 return draw(false); 3117 } 3118 /// ditto 3119 ScreenPainter draw(bool manualInvalidations) { 3120 return impl.getPainter(manualInvalidations); 3121 } 3122 3123 // This is here to implement the interface we use for various native handlers. 3124 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 3125 3126 // maps native window handles to SimpleWindow instances, if there are any 3127 // you shouldn't need this, but it is public in case you do in a native event handler or something 3128 // mac uses void* cuz NSObject opHash won't pick up in typeinfo 3129 version(OSXCocoa) 3130 public __gshared SimpleWindow[void*] nativeMapping; 3131 else 3132 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 3133 3134 // the size the user requested in the constructor, in automatic scale modes it always pretends to be this size 3135 private int _virtualWidth; 3136 private int _virtualHeight; 3137 3138 /// Width of the window's drawable client area, in pixels. 3139 @scriptable 3140 final @property int width() const pure nothrow @safe @nogc { 3141 if(resizability == Resizability.automaticallyScaleIfPossible) 3142 return _virtualWidth; 3143 else 3144 return _width; 3145 } 3146 3147 /// Height of the window's drawable client area, in pixels. 3148 @scriptable 3149 final @property int height() const pure nothrow @safe @nogc { 3150 if(resizability == Resizability.automaticallyScaleIfPossible) 3151 return _virtualHeight; 3152 else 3153 return _height; 3154 } 3155 3156 /++ 3157 Returns the actual size of the window, bypassing the logical 3158 illusions of [Resizability.automaticallyScaleIfPossible]. 3159 3160 History: 3161 Added November 11, 2022 (dub v10.10) 3162 +/ 3163 final @property Size actualWindowSize() const pure nothrow @safe @nogc { 3164 return Size(_width, _height); 3165 } 3166 3167 3168 private int _width; 3169 private int _height; 3170 3171 // HACK: making the best of some copy constructor woes with refcounting 3172 private ScreenPainterImplementation* activeScreenPainter_; 3173 3174 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 3175 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 3176 3177 private OpenGlOptions openglMode; 3178 private Resizability resizability; 3179 private WindowTypes windowType; 3180 private int customizationFlags; 3181 3182 /// `true` if OpenGL was initialized for this window. 3183 @property bool isOpenGL () const pure nothrow @safe @nogc { 3184 version(without_opengl) 3185 return false; 3186 else 3187 return (openglMode == OpenGlOptions.yes); 3188 } 3189 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 3190 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 3191 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 3192 3193 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 3194 /// to call this, as it's not recommended to share window between threads. 3195 void mtLock () { 3196 version(X11) { 3197 XLockDisplay(this.display); 3198 } 3199 } 3200 3201 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 3202 /// to call this, as it's not recommended to share window between threads. 3203 void mtUnlock () { 3204 version(X11) { 3205 XUnlockDisplay(this.display); 3206 } 3207 } 3208 3209 /// Emit a beep to get user's attention. 3210 void beep () { 3211 version(X11) { 3212 XBell(this.display, 100); 3213 } else version(Windows) { 3214 MessageBeep(0xFFFFFFFF); 3215 } 3216 } 3217 3218 3219 3220 version(without_opengl) {} else { 3221 3222 /// 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`. 3223 void delegate() redrawOpenGlScene; 3224 3225 /// This will allow you to change OpenGL vsync state. 3226 final @property void vsync (bool wait) { 3227 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3228 version(X11) { 3229 setAsCurrentOpenGlContext(); 3230 glxSetVSync(display, impl.window, wait); 3231 } else version(Windows) { 3232 setAsCurrentOpenGlContext(); 3233 wglSetVSync(wait); 3234 } 3235 } 3236 3237 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 3238 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 3239 /// enough without waiting 'em to finish their frame business. 3240 bool useGLFinish = true; 3241 3242 // FIXME: it should schedule it for the end of the current iteration of the event loop... 3243 /// 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. 3244 void redrawOpenGlSceneNow() { 3245 version(X11) if (!this._visible) return; // no need to do this if window is invisible 3246 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3247 if(redrawOpenGlScene is null) 3248 return; 3249 3250 this.mtLock(); 3251 scope(exit) this.mtUnlock(); 3252 3253 this.setAsCurrentOpenGlContext(); 3254 3255 redrawOpenGlScene(); 3256 3257 this.swapOpenGlBuffers(); 3258 // 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. 3259 if (useGLFinish) glFinish(); 3260 } 3261 3262 private bool redrawOpenGlSceneSoonSet = false; 3263 private static class RedrawOpenGlSceneEvent { 3264 SimpleWindow w; 3265 this(SimpleWindow w) { this.w = w; } 3266 } 3267 private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent; 3268 /++ 3269 Queues an opengl redraw as soon as the other pending events are cleared. 3270 +/ 3271 void redrawOpenGlSceneSoon() { 3272 if(redrawOpenGlScene is null) 3273 return; 3274 3275 if(!redrawOpenGlSceneSoonSet) { 3276 redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this); 3277 this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); }); 3278 redrawOpenGlSceneSoonSet = true; 3279 } 3280 this.postEvent(redrawOpenGlSceneEvent, true); 3281 } 3282 3283 3284 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3285 void setAsCurrentOpenGlContext() { 3286 assert(openglMode == OpenGlOptions.yes); 3287 version(X11) { 3288 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 3289 throw new Exception("glXMakeCurrent"); 3290 } else version(Windows) { 3291 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3292 if (!wglMakeCurrent(ghDC, ghRC)) 3293 throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too 3294 } 3295 } 3296 3297 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3298 /// This doesn't throw, returning success flag instead. 3299 bool setAsCurrentOpenGlContextNT() nothrow { 3300 assert(openglMode == OpenGlOptions.yes); 3301 version(X11) { 3302 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 3303 } else version(Windows) { 3304 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3305 return wglMakeCurrent(ghDC, ghRC) ? true : false; 3306 } 3307 } 3308 3309 /// 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. 3310 /// This doesn't throw, returning success flag instead. 3311 bool releaseCurrentOpenGlContext() nothrow { 3312 assert(openglMode == OpenGlOptions.yes); 3313 version(X11) { 3314 return (glXMakeCurrent(display, 0, null) != 0); 3315 } else version(Windows) { 3316 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3317 return wglMakeCurrent(ghDC, null) ? true : false; 3318 } 3319 } 3320 3321 /++ 3322 simpledisplay always uses double buffering, usually automatically. This 3323 manually swaps the OpenGL buffers. You should only use this if you are NOT 3324 using the [redrawOpenGlScene] delegate. 3325 3326 3327 You must not this yourself if you use [redrawOpenGlScene] because simpledisplay will do it 3328 for you after calling your `redrawOpenGlScene`. Please note that once you swap 3329 buffers, the contents become undefined - the implementation, in the OpenGL driver 3330 or the desktop compositor, may not actually just swap two buffers. The back buffer's 3331 contents are $(B undefined) after calling this function. 3332 3333 See: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-swapbuffers 3334 and https://linux.die.net/man/3/glxswapbuffers 3335 3336 Remember that this may throw an exception, which you can catch in a multithreaded 3337 application to keep your thread from dying from an unhandled exception. 3338 +/ 3339 void swapOpenGlBuffers() { 3340 assert(openglMode == OpenGlOptions.yes); 3341 version(X11) { 3342 if (!this._visible) return; // no need to do this if window is invisible 3343 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3344 glXSwapBuffers(display, impl.window); 3345 } else version(Windows) { 3346 SwapBuffers(ghDC); 3347 } 3348 } 3349 } 3350 3351 /++ 3352 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 3353 3354 3355 --- 3356 auto window = new SimpleWindow(100, 100, "First title"); 3357 window.title = "A new title"; 3358 --- 3359 3360 You may call this function at any time. 3361 +/ 3362 @property void title(string title) { 3363 _title = title; 3364 impl.setTitle(title); 3365 } 3366 3367 private string _title; 3368 3369 /// Gets the title 3370 @property string title() { 3371 if(_title is null) 3372 _title = getRealTitle(); 3373 return _title; 3374 } 3375 3376 /++ 3377 Get the title as set by the window manager. 3378 May not match what you attempted to set. 3379 +/ 3380 string getRealTitle() { 3381 static if(is(typeof(impl.getTitle()))) 3382 return impl.getTitle(); 3383 else 3384 return null; 3385 } 3386 3387 // don't use this generally it is not yet really released 3388 version(X11) 3389 @property Image secret_icon() { 3390 return secret_icon_inner; 3391 } 3392 private Image secret_icon_inner; 3393 3394 3395 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing. 3396 @property void icon(MemoryImage icon) { 3397 if(icon is null) 3398 return; 3399 auto tci = icon.getAsTrueColorImage(); 3400 version(Windows) { 3401 winIcon = new WindowsIcon(icon); 3402 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 3403 } else version(X11) { 3404 secret_icon_inner = Image.fromMemoryImage(icon); 3405 // FIXME: ensure this is correct 3406 auto display = XDisplayConnection.get; 3407 arch_ulong[] buffer; 3408 buffer ~= icon.width; 3409 buffer ~= icon.height; 3410 foreach(c; tci.imageData.colors) { 3411 arch_ulong b; 3412 b |= c.a << 24; 3413 b |= c.r << 16; 3414 b |= c.g << 8; 3415 b |= c.b; 3416 buffer ~= b; 3417 } 3418 3419 XChangeProperty( 3420 display, 3421 impl.window, 3422 GetAtom!("_NET_WM_ICON", true)(display), 3423 GetAtom!"CARDINAL"(display), 3424 32 /* bits */, 3425 0 /*PropModeReplace*/, 3426 buffer.ptr, 3427 cast(int) buffer.length); 3428 } else version(OSXCocoa) { 3429 throw new NotYetImplementedException(); 3430 } else version(Emscripten) { 3431 throw new NotYetImplementedException(); 3432 } else static assert(0); 3433 } 3434 3435 version(Windows) 3436 private WindowsIcon winIcon; 3437 3438 bool _suppressDestruction; 3439 3440 ~this() { 3441 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 3442 if(_suppressDestruction) 3443 return; 3444 impl.dispose(); 3445 } 3446 3447 private bool _closed; 3448 3449 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 3450 /* 3451 ScreenPainter drawTransiently() { 3452 return impl.getPainter(); 3453 } 3454 */ 3455 3456 /// Draws an image on the window. This is meant to provide quick look 3457 /// of a static image generated elsewhere. 3458 @property void image(Image i) { 3459 /+ 3460 version(Windows) { 3461 BITMAP bm; 3462 HDC hdc = GetDC(hwnd); 3463 HDC hdcMem = CreateCompatibleDC(hdc); 3464 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 3465 3466 GetObject(i.handle, bm.sizeof, &bm); 3467 3468 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 3469 3470 SelectObject(hdcMem, hbmOld); 3471 DeleteDC(hdcMem); 3472 ReleaseDC(hwnd, hdc); 3473 3474 /* 3475 RECT r; 3476 r.right = i.width; 3477 r.bottom = i.height; 3478 InvalidateRect(hwnd, &r, false); 3479 */ 3480 } else 3481 version(X11) { 3482 if(!destroyed) { 3483 if(i.usingXshm) 3484 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 3485 else 3486 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 3487 } 3488 } else 3489 version(OSXCocoa) { 3490 draw().drawImage(Point(0, 0), i); 3491 setNeedsDisplay(view, true); 3492 } else static assert(0); 3493 +/ 3494 auto painter = this.draw; 3495 painter.drawImage(Point(0, 0), i); 3496 } 3497 3498 /++ 3499 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 3500 3501 --- 3502 window.cursor = GenericCursor.Help; 3503 // now the window mouse cursor is set to a generic help 3504 --- 3505 3506 +/ 3507 @property void cursor(MouseCursor cursor) { 3508 version(OSXCocoa) 3509 {} // featureNotImplemented(); 3510 else 3511 if(this.impl.curHidden <= 0) { 3512 static if(UsingSimpledisplayX11) { 3513 auto ch = cursor.cursorHandle; 3514 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 3515 } else version(Windows) { 3516 auto ch = cursor.cursorHandle; 3517 impl.currentCursor = ch; 3518 SetCursor(ch); // redraw without waiting for mouse movement to update 3519 } else featureNotImplemented(); 3520 } 3521 3522 } 3523 3524 /// What follows are the event handlers. These are set automatically 3525 /// by the eventLoop function, but are still public so you can change 3526 /// them later. wasPressed == true means key down. false == key up. 3527 3528 /// Handles a low-level keyboard event. Settable through setEventHandlers. 3529 void delegate(KeyEvent ke) handleKeyEvent; 3530 3531 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 3532 void delegate(dchar c) handleCharEvent; 3533 3534 /// Handles a timer pulse. Settable through setEventHandlers. 3535 void delegate() handlePulse; 3536 3537 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 3538 void delegate(bool) onFocusChange; 3539 3540 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 3541 * Sometimes it is easier to setup the delegate instead of subclassing. */ 3542 void delegate() onClosing; 3543 3544 /** Called when we received destroy notification. At this stage we cannot do much with our window 3545 * (as it is already dead, and it's native handle cannot be used), but we still can do some 3546 * last minute cleanup. */ 3547 void delegate() onDestroyed; 3548 3549 static if (UsingSimpledisplayX11) 3550 /** Called when Expose event comes. See Xlib manual to understand the arguments. 3551 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 3552 * You will probably never need to setup this handler, it is for very low-level stuff. 3553 * 3554 * WARNING! Xlib is multithread-locked when this handles is called! */ 3555 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 3556 3557 //version(Windows) 3558 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 3559 3560 private { 3561 int lastMouseX = int.min; 3562 int lastMouseY = int.min; 3563 void mdx(ref MouseEvent ev) { 3564 if(lastMouseX == int.min || lastMouseY == int.min) { 3565 ev.dx = 0; 3566 ev.dy = 0; 3567 } else { 3568 ev.dx = ev.x - lastMouseX; 3569 ev.dy = ev.y - lastMouseY; 3570 } 3571 3572 lastMouseX = ev.x; 3573 lastMouseY = ev.y; 3574 } 3575 } 3576 3577 /// Mouse event handler. Settable through setEventHandlers. 3578 void delegate(MouseEvent) handleMouseEvent; 3579 3580 /// use to redraw child widgets if you use system apis to add stuff 3581 void delegate() paintingFinished; 3582 3583 void delegate() paintingFinishedDg() { 3584 return paintingFinished; 3585 } 3586 3587 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 3588 /// for this to ever happen. 3589 void delegate(int width, int height) windowResized; 3590 3591 /++ 3592 Platform specific - handle any native message this window gets. 3593 3594 Note: this is called *in addition to* other event handlers, unless you either: 3595 3596 1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded. 3597 3598 2) On Windows, set the `mustReturn` parameter to 1 indicating you've done it and your return value should be forwarded to the operating system. If you do not set `mustReturn`, your return value will be discarded. 3599 3600 On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`. 3601 3602 On X, it takes the form of `int delegate(XEvent)`. 3603 3604 History: 3605 In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead. 3606 3607 Prior to November 27, 2021, the `mustReturn` parameter was not present, and the Windows implementation would discard return values. There's still a deprecated shim with that signature, but since the return value is often important, you shouldn't use it. 3608 +/ 3609 NativeEventHandler handleNativeEvent_; 3610 3611 @property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe { 3612 return handleNativeEvent_; 3613 } 3614 @property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe { 3615 handleNativeEvent_ = neh; 3616 } 3617 3618 private void dispatchXInputEvent(InputDeviceEvent ide) @system { 3619 if(auto aih = ide.deviceId in advancedInputHandlers) { 3620 ide.deviceObject = aih.device; 3621 aih.handler(ide); 3622 } 3623 if(auto aih = (cast(typeof(ide.deviceId)) 0) in advancedInputHandlers) { 3624 ide.deviceObject = null; 3625 aih.handler(ide); 3626 } 3627 } 3628 3629 private struct AdvancedInputHandler { 3630 InputDevice device; 3631 void delegate(InputDeviceEvent ide) handler; 3632 } 3633 3634 private AdvancedInputHandler[typeof(InputDevice.deviceId)] advancedInputHandlers; 3635 private void setAdvancedInputHandler(InputDevice id, void delegate(InputDeviceEvent ide) handler) { 3636 advancedInputHandlers[id ? id.deviceId : (cast(typeof(InputDeviceEvent.deviceId)) 0)] = AdvancedInputHandler(id, handler); 3637 } 3638 3639 version(Windows) 3640 // compatibility shim with the old deprecated way 3641 // in this one, if you return 0, it means you must return. otherwise the ret value is ignored. 3642 deprecated("This old api ignored your non-zero return values and that hurt it a lot. Add an `out int pleaseReturn` param to your delegate and set it to one if you must return the result to Windows. Otherwise, leave it zero and processing will continue through to the default window message processor.") @property void handleNativeEvent(int delegate(HWND, UINT, WPARAM, LPARAM) dg) { 3643 handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) { 3644 auto ret = dg(h, m, w, l); 3645 if(ret == 0) 3646 r = 1; 3647 return ret; 3648 }; 3649 } 3650 3651 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 3652 /// If you used to use handleNativeEvent depending on it being static, just change it to use 3653 /// this instead and it will work the same way. 3654 __gshared NativeEventHandler handleNativeGlobalEvent; 3655 3656 // private: 3657 /// The native implementation is available, but you shouldn't use it unless you are 3658 /// familiar with the underlying operating system, don't mind depending on it, and 3659 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 3660 /// do what you need to do with handleNativeEvent instead. 3661 /// 3662 /// This is likely to eventually change to be just a struct holding platform-specific 3663 /// handles instead of a template mixin at some point because I'm not happy with the 3664 /// code duplication here (ironically). 3665 mixin NativeSimpleWindowImplementation!() impl; 3666 3667 /** 3668 This is in-process one-way (from anything to window) event sending mechanics. 3669 It is thread-safe, so it can be used in multi-threaded applications to send, 3670 for example, "wake up and repaint" events when thread completed some operation. 3671 This will allow to avoid using timer pulse to check events with synchronization, 3672 'cause event handler will be called in UI thread. You can stop guessing which 3673 pulse frequency will be enough for your app. 3674 Note that events handlers may be called in arbitrary order, i.e. last registered 3675 handler can be called first, and vice versa. 3676 */ 3677 public: 3678 /** Is our custom event queue empty? Can be used in simple cases to prevent 3679 * "spamming" window with events it can't cope with. 3680 * It is safe to call this from non-UI threads. 3681 */ 3682 @property bool eventQueueEmpty() () { 3683 synchronized(this) { 3684 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 3685 } 3686 return true; 3687 } 3688 3689 /** Does our custom event queue contains at least one with the given type? 3690 * Can be used in simple cases to prevent "spamming" window with events 3691 * it can't cope with. 3692 * It is safe to call this from non-UI threads. 3693 */ 3694 @property bool eventQueued(ET:Object) () { 3695 synchronized(this) { 3696 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 3697 if (!o.doProcess) { 3698 if (cast(ET)(o.evt)) return true; 3699 } 3700 } 3701 } 3702 return false; 3703 } 3704 3705 /++ 3706 Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds. 3707 3708 History: 3709 Added May 12, 2021 3710 +/ 3711 void delegate(Exception e) nothrow eventUncaughtException; 3712 3713 /** Add listener for custom event. Can be used like this: 3714 * 3715 * --------------------- 3716 * auto eid = win.addEventListener((MyStruct evt) { ... }); 3717 * ... 3718 * win.removeEventListener(eid); 3719 * --------------------- 3720 * 3721 * Returns: 0 on failure (should never happen, so ignore it) 3722 * 3723 * $(WARNING Don't use this method in object destructors!) 3724 * 3725 * $(WARNING It is better to register all event handlers and don't remove 'em, 3726 * 'cause if event handler id counter will overflow, you won't be able 3727 * to register any more events.) 3728 */ 3729 uint addEventListener(ET:Object) (void delegate (ET) dg) { 3730 if (dg is null) return 0; // ignore empty handlers 3731 synchronized(this) { 3732 //FIXME: abort on overflow? 3733 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 3734 EventHandlerEntry e; 3735 e.dg = delegate (Object o) { 3736 if (auto co = cast(ET)o) { 3737 try { 3738 dg(co); 3739 } catch (Exception e) { 3740 // sorry! 3741 if(eventUncaughtException) 3742 eventUncaughtException(e); 3743 } 3744 return true; 3745 } 3746 return false; 3747 }; 3748 e.id = lastUsedHandlerId; 3749 auto optr = eventHandlers.ptr; 3750 eventHandlers ~= e; 3751 if (eventHandlers.ptr !is optr) { 3752 import core.memory : GC; 3753 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 3754 } 3755 return lastUsedHandlerId; 3756 } 3757 } 3758 3759 /// Remove event listener. It is safe to pass invalid event id here. 3760 /// $(WARNING Don't use this method in object destructors!) 3761 void removeEventListener() (uint id) { 3762 if (id == 0 || id > lastUsedHandlerId) return; 3763 synchronized(this) { 3764 foreach (immutable idx; 0..eventHandlers.length) { 3765 if (eventHandlers[idx].id == id) { 3766 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 3767 eventHandlers[$-1].dg = null; 3768 eventHandlers.length -= 1; 3769 eventHandlers.assumeSafeAppend; 3770 return; 3771 } 3772 } 3773 } 3774 } 3775 3776 /// Post event to queue. It is safe to call this from non-UI threads. 3777 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 3778 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3779 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3780 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false, bool replaceTime = true) { 3781 if (this.closed) return false; // closed windows can't handle events 3782 3783 MonoTime storedHittime; 3784 3785 // remove all events of type `ET` 3786 void removeAllET () { 3787 uint eidx = 0, ec = eventQueueUsed; 3788 auto eptr = eventQueue.ptr; 3789 while (eidx < ec) { 3790 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 3791 if (cast(ET)eptr.evt !is null) { 3792 if(!replaceTime) { 3793 storedHittime = eptr.hittime; 3794 } 3795 // i found her! 3796 if (inCustomEventProcessor) { 3797 // if we're in custom event processing loop, processor will clear it for us 3798 eptr.evt = null; 3799 ++eidx; 3800 ++eptr; 3801 } else { 3802 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3803 ec = --eventQueueUsed; 3804 // clear last event (it is already copied) 3805 eventQueue.ptr[ec].evt = null; 3806 } 3807 } else { 3808 ++eidx; 3809 ++eptr; 3810 } 3811 } 3812 } 3813 3814 if (evt is null) { 3815 if (replace) { synchronized(this) removeAllET(); } 3816 // ignore empty events, they can't be handled anyway 3817 return false; 3818 } 3819 3820 // add events even if no event FD/event object created yet 3821 synchronized(this) { 3822 if (replace) removeAllET(); 3823 if (eventQueueUsed == uint.max) return false; // just in case 3824 3825 auto nev = (replaceTime || storedHittime == MonoTime.zero) ? QueuedEvent(evt, timeoutmsecs) : QueuedEvent(evt, storedHittime); 3826 3827 if (eventQueueUsed < eventQueue.length) { 3828 eventQueue[eventQueueUsed++] = nev; 3829 } else { 3830 if (eventQueue.capacity == eventQueue.length) { 3831 // need to reallocate; do a trick to ensure that old array is cleared 3832 auto oarr = eventQueue; 3833 if(replaceTime) 3834 eventQueue ~= nev; 3835 else { 3836 eventQueue ~= nev; 3837 } 3838 // just in case, do yet another check 3839 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 3840 import core.memory : GC; 3841 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 3842 } else { 3843 auto optr = eventQueue.ptr; 3844 eventQueue ~= nev; 3845 assert(eventQueue.ptr is optr); 3846 } 3847 ++eventQueueUsed; 3848 assert(eventQueueUsed == eventQueue.length); 3849 } 3850 if (!eventWakeUp()) { 3851 // can't wake up event processor, so there is no reason to keep the event 3852 assert(eventQueueUsed > 0); 3853 eventQueue[--eventQueueUsed].evt = null; 3854 return false; 3855 } 3856 return true; 3857 } 3858 } 3859 3860 /// Post event to queue. It is safe to call this from non-UI threads. 3861 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3862 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3863 bool postEvent(ET:Object) (ET evt, bool replace=false) { 3864 return postTimeout!ET(evt, 0, replace); 3865 } 3866 3867 private: 3868 private import core.time : MonoTime; 3869 3870 version(Posix) { 3871 __gshared int customEventFDRead = -1; 3872 __gshared int customEventFDWrite = -1; 3873 __gshared int customSignalFD = -1; 3874 } else version(Windows) { 3875 __gshared HANDLE customEventH = null; 3876 } 3877 3878 // wake up event processor 3879 static bool eventWakeUp () { 3880 version(X11) { 3881 import core.sys.posix.unistd : write; 3882 ulong n = 1; 3883 if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof); 3884 return true; 3885 } else version(Windows) { 3886 if (customEventH !is null) SetEvent(customEventH); 3887 return true; 3888 } else version(OSXCocoa) { 3889 if(globalAppDelegate) 3890 globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false); 3891 return true; 3892 } else { 3893 // not implemented for other OSes 3894 return false; 3895 } 3896 } 3897 3898 static struct QueuedEvent { 3899 Object evt; 3900 bool timed = false; 3901 MonoTime hittime = MonoTime.zero; 3902 bool doProcess = false; // process event at the current iteration (internal flag) 3903 3904 this (Object aevt, uint toutmsecs) { 3905 evt = aevt; 3906 if (toutmsecs > 0) { 3907 import core.time : msecs; 3908 timed = true; 3909 hittime = MonoTime.currTime+toutmsecs.msecs; 3910 } 3911 } 3912 3913 this (Object aevt, MonoTime hittime) { 3914 evt = aevt; 3915 timed = true; 3916 this.hittime = hittime; 3917 } 3918 } 3919 3920 alias CustomEventHandler = bool delegate (Object o) nothrow; 3921 static struct EventHandlerEntry { 3922 CustomEventHandler dg; 3923 uint id; 3924 } 3925 3926 uint lastUsedHandlerId; 3927 EventHandlerEntry[] eventHandlers; 3928 QueuedEvent[] eventQueue = null; 3929 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 3930 bool inCustomEventProcessor = false; // required to properly remove events 3931 3932 // process queued events and call custom event handlers 3933 // this will not process events posted from called handlers (such events are postponed for the next iteration) 3934 void processCustomEvents () @system { 3935 bool hasSomethingToDo = false; 3936 uint ecount; 3937 bool ocep; 3938 synchronized(this) { 3939 ocep = inCustomEventProcessor; 3940 inCustomEventProcessor = true; 3941 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 3942 auto ctt = MonoTime.currTime; 3943 bool hasEmpty = false; 3944 // mark events to process (this is required for `eventQueued()`) 3945 foreach (ref qe; eventQueue[0..ecount]) { 3946 if (qe.evt is null) { hasEmpty = true; continue; } 3947 if (qe.timed) { 3948 qe.doProcess = (qe.hittime <= ctt); 3949 } else { 3950 qe.doProcess = true; 3951 } 3952 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 3953 } 3954 if (!hasSomethingToDo) { 3955 // remove empty events 3956 if (hasEmpty) { 3957 uint eidx = 0, ec = eventQueueUsed; 3958 auto eptr = eventQueue.ptr; 3959 while (eidx < ec) { 3960 if (eptr.evt is null) { 3961 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3962 ec = --eventQueueUsed; 3963 eventQueue.ptr[ec].evt = null; // make GC life easier 3964 } else { 3965 ++eidx; 3966 ++eptr; 3967 } 3968 } 3969 } 3970 inCustomEventProcessor = ocep; 3971 return; 3972 } 3973 } 3974 // process marked events 3975 uint efree = 0; // non-processed events will be put at this index 3976 EventHandlerEntry[] eh; 3977 Object evt; 3978 foreach (immutable eidx; 0..ecount) { 3979 synchronized(this) { 3980 if (!eventQueue[eidx].doProcess) { 3981 // skip this event 3982 assert(efree <= eidx); 3983 if (efree != eidx) { 3984 // copy this event to queue start 3985 eventQueue[efree] = eventQueue[eidx]; 3986 eventQueue[eidx].evt = null; // just in case 3987 } 3988 ++efree; 3989 continue; 3990 } 3991 evt = eventQueue[eidx].evt; 3992 eventQueue[eidx].evt = null; // in case event handler will hit GC 3993 if (evt is null) continue; // just in case 3994 // try all handlers; this can be slow, but meh... 3995 eh = eventHandlers; 3996 } 3997 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 3998 evt = null; 3999 eh = null; 4000 } 4001 synchronized(this) { 4002 // move all unprocessed events to queue top; efree holds first "free index" 4003 foreach (immutable eidx; ecount..eventQueueUsed) { 4004 assert(efree <= eidx); 4005 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 4006 ++efree; 4007 } 4008 eventQueueUsed = efree; 4009 // wake up event processor on next event loop iteration if we have more queued events 4010 // also, remove empty events 4011 bool awaken = false; 4012 uint eidx = 0, ec = eventQueueUsed; 4013 auto eptr = eventQueue.ptr; 4014 while (eidx < ec) { 4015 if (eptr.evt is null) { 4016 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 4017 ec = --eventQueueUsed; 4018 eventQueue.ptr[ec].evt = null; // make GC life easier 4019 } else { 4020 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 4021 ++eidx; 4022 ++eptr; 4023 } 4024 } 4025 inCustomEventProcessor = ocep; 4026 } 4027 } 4028 4029 // for all windows in nativeMapping 4030 package static void processAllCustomEvents () @system { 4031 4032 cleanupQueue.process(); 4033 4034 justCommunication.processCustomEvents(); 4035 4036 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 4037 if (sw is null || sw.closed) continue; 4038 sw.processCustomEvents(); 4039 } 4040 4041 runPendingRunInGuiThreadDelegates(); 4042 } 4043 4044 // 0: infinite (i.e. no scheduled events in queue) 4045 uint eventQueueTimeoutMSecs () { 4046 synchronized(this) { 4047 if (eventQueueUsed == 0) return 0; 4048 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 4049 uint res = int.max; 4050 auto ctt = MonoTime.currTime; 4051 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 4052 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 4053 if (qe.doProcess) continue; // just in case 4054 if (!qe.timed) return 1; // minimal 4055 if (qe.hittime <= ctt) return 1; // minimal 4056 auto tms = (qe.hittime-ctt).total!"msecs"; 4057 if (tms < 1) tms = 1; // safety net 4058 if (tms >= int.max) tms = int.max-1; // and another safety net 4059 if (res > tms) res = cast(uint)tms; 4060 } 4061 return (res >= int.max ? 0 : res); 4062 } 4063 } 4064 4065 // for all windows in nativeMapping 4066 static uint eventAllQueueTimeoutMSecs () { 4067 uint res = uint.max; 4068 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 4069 if (sw is null || sw.closed) continue; 4070 uint to = sw.eventQueueTimeoutMSecs(); 4071 if (to && to < res) { 4072 res = to; 4073 if (to == 1) break; // can't have less than this 4074 } 4075 } 4076 return (res >= int.max ? 0 : res); 4077 } 4078 4079 version(X11) { 4080 ResizeEvent pendingResizeEvent; 4081 } 4082 4083 /++ 4084 When in opengl mode and automatically resizing, it will set the opengl viewport to stretch. 4085 4086 If you work with multiple opengl contexts and/or threads, this might be more trouble than it is 4087 worth so you can disable it by setting this to `true`. 4088 4089 History: 4090 Added November 13, 2022. 4091 +/ 4092 public bool suppressAutoOpenglViewport = false; 4093 private void updateOpenglViewportIfNeeded(int width, int height) { 4094 if(suppressAutoOpenglViewport) return; 4095 4096 version(without_opengl) {} else 4097 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 4098 // writeln(width, " ", height); 4099 setAsCurrentOpenGlContextNT(); 4100 glViewport(0, 0, width, height); 4101 } 4102 } 4103 4104 // TODO: Implement on non-Windows platforms (where available). 4105 private CornerStyle _fauxCornerStyle = CornerStyle.automatic; 4106 4107 /++ 4108 Style of the window's corners 4109 4110 $(WARNING 4111 Currently only implemented on Windows targets. 4112 Has no visual effect elsewhere. 4113 4114 Windows: Requires Windows 11 or later. 4115 ) 4116 4117 History: 4118 Added September 09, 2024. 4119 +/ 4120 public CornerStyle cornerStyle() @trusted { 4121 version(Windows) { 4122 DWM_WINDOW_CORNER_PREFERENCE dwmCorner; 4123 const apiResult = DwmGetWindowAttribute( 4124 this.hwnd, 4125 DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, 4126 &dwmCorner, 4127 typeof(dwmCorner).sizeof 4128 ); 4129 4130 if (apiResult != S_OK) { 4131 // Unsupported? 4132 if (apiResult == E_INVALIDARG) { 4133 // Feature unsupported; Windows version probably too old. 4134 // Requires Windows 11 (build 22000) or later. 4135 return _fauxCornerStyle; 4136 } 4137 4138 throw new WindowsApiException("DwmGetWindowAttribute", apiResult); 4139 } 4140 4141 CornerStyle corner; 4142 if (!dwmCorner.fromDWM(corner)) { 4143 throw ArsdException!"DwmGetWindowAttribute unfamiliar corner preference"(dwmCorner); 4144 } 4145 return corner; 4146 } else { 4147 return _fauxCornerStyle; 4148 } 4149 } 4150 4151 /// ditto 4152 public void cornerStyle(const CornerStyle corner) @trusted { 4153 version(Windows) { 4154 DWM_WINDOW_CORNER_PREFERENCE dwmCorner; 4155 if (!corner.toDWM(dwmCorner)) { 4156 assert(false, "This should have been impossible because of a final switch."); 4157 } 4158 4159 const apiResult = DwmSetWindowAttribute( 4160 this.hwnd, 4161 DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, 4162 &dwmCorner, 4163 typeof(dwmCorner).sizeof 4164 ); 4165 4166 if (apiResult != S_OK) { 4167 // Unsupported? 4168 if (apiResult == E_INVALIDARG) { 4169 // Feature unsupported; Windows version probably too old. 4170 // Requires Windows 11 (build 22000) or later. 4171 _fauxCornerStyle = corner; 4172 return; 4173 } 4174 4175 throw new WindowsApiException("DwmSetWindowAttribute", apiResult); 4176 } 4177 } else { 4178 _fauxCornerStyle = corner; 4179 } 4180 } 4181 } 4182 4183 version(OSXCocoa) 4184 enum NSWindow NullWindow = null; 4185 else 4186 enum NullWindow = NativeWindowHandle.init; 4187 4188 /++ 4189 Magic pseudo-window for just posting events to a global queue. 4190 4191 Not entirely supported, I might delete it at any time. 4192 4193 Added Nov 5, 2021. 4194 +/ 4195 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow); 4196 4197 /* Advanced input support { */ 4198 4199 /++ 4200 Returns input devices currently attached to the computer that can be used to advanced input event subscriptions. 4201 4202 On Windows, this generally means touch screens and pen tablets. 4203 4204 On X, this can be just about anything. 4205 4206 This may change in the future. 4207 4208 4209 History: 4210 Added December 1, 2025 4211 +/ 4212 InputDevice[] getInputDevices() { 4213 version(Windows) { 4214 POINTER_DEVICE_INFO[32] buffer; 4215 uint count = cast(uint) buffer.length; 4216 if(GetPointerDevices(&count, buffer.ptr)) { 4217 InputDevice[] ret; 4218 foreach(dev; buffer[0 .. count]) { 4219 auto id = new InputDevice; 4220 id.deviceId = dev.device; 4221 id.name = makeUtf8StringFromWindowsString(dev.productString.ptr); 4222 id.enabled = true; 4223 4224 // pointerDeviceType is useful 4225 4226 // FIXME: use more of it i guess 4227 4228 ret ~= id; 4229 } 4230 return ret; 4231 } else { 4232 throw new WindowsApiException("GetPointerDevices", GetLastError()); 4233 } 4234 } else 4235 static if(UsingSimpledisplayX11) { 4236 xi_opcode(); // load XInput2 4237 4238 int cnt; 4239 auto di = XIQueryDevice(XDisplayConnection.get, XIAllDevices, &cnt); 4240 if(di is null) 4241 return null; 4242 scope(exit) 4243 XIFreeDeviceInfo(di); 4244 4245 InputDevice[] ret; 4246 foreach(dev; di[0 .. cnt]) { 4247 auto id = new InputDevice; 4248 id.deviceId = dev.deviceid; 4249 id.name = stringz(dev.name).borrow.idup; 4250 // use, attachment ? 4251 id.enabled = dev.enabled != 0; 4252 4253 foreach(cls; dev.classes[0 .. dev.num_classes]) { 4254 // FIXME: process these somehow... 4255 switch(cls.type) { 4256 case XIDeviceClass.XIKeyClass: 4257 // has num keycodes but it isn't super accurate... 4258 // writeln("\t", *cast(XIKeyClassInfo*) cls); 4259 id.hasKeyClass = true; 4260 break; 4261 case XIDeviceClass.XIButtonClass: 4262 // has num buttons but it isn't super accurate... 4263 // writeln("\t", *cast(XIButtonClassInfo*) cls); 4264 id.hasButtonClass = true; 4265 break; 4266 case XIDeviceClass.XIValuatorClass: 4267 // writeln("\t", getAtomName((cast(XIValuatorClassInfo*) cls).label, XDisplayConnection.get), " ", *cast(XIValuatorClassInfo*) cls); 4268 auto info = cast(XIValuatorClassInfo*) cls; 4269 id.valuators ~= InputDevice.Valuator(info.label, info.min, info.max); 4270 break; 4271 case XIDeviceClass.XIScrollClass: 4272 // FIXME anything else useful? 4273 writeln(id.deviceId, ": ", id.name); 4274 writeln("\t", *cast(XIScrollClassInfo*) cls); 4275 id.hasScrollClass = true; 4276 break; 4277 case XIDeviceClass.XITouchClass: 4278 // FIXME anything else useful? 4279 writeln(id.deviceId, ": ", id.name); 4280 writeln("\t", *cast(XITouchClassInfo*) cls); 4281 id.hasTouchClass = true; 4282 break; 4283 case XIDeviceClass.XIGestureClass: 4284 // FIXME what to do? 4285 writeln(id.deviceId, ": ", id.name); 4286 writeln("\t", *cast(XIGestureClassInfo*) cls); 4287 break; 4288 default: 4289 // writeln("\t", cls.type); 4290 } 4291 } 4292 4293 ret ~= id; 4294 } 4295 return ret; 4296 } else return null; 4297 } 4298 4299 /// ditto 4300 class InputDevice { 4301 private this() {} 4302 4303 static if(UsingSimpledisplayX11) { 4304 int deviceId; 4305 } else version(Windows) { 4306 HANDLE deviceId; 4307 } 4308 string name; 4309 bool enabled; 4310 4311 bool hasKeyClass; 4312 bool hasButtonClass; 4313 bool hasScrollClass; 4314 bool hasTouchClass; 4315 4316 static if(UsingSimpledisplayX11) { 4317 struct Valuator { 4318 Atom label; 4319 double min; 4320 double max; 4321 } 4322 4323 Valuator[] valuators; 4324 } 4325 } 4326 4327 /// ditto 4328 struct InputDeviceEvent { 4329 SimpleWindow window; 4330 InputDevice deviceObject; 4331 4332 int event; 4333 4334 static if(UsingSimpledisplayX11) { 4335 int deviceId; 4336 } else version(Windows) { 4337 HANDLE deviceId; 4338 } 4339 4340 int detail; 4341 int flags; 4342 4343 double rootX; 4344 double rootY; 4345 double windowX; 4346 double windowY; 4347 4348 ulong buttons; 4349 4350 double[16] valuators; 4351 4352 // parsed valuators 4353 static if(UsingSimpledisplayX11) { 4354 double relX() { return findValuator(GetAtom!"Rel X"(XDisplayConnection.get)); } 4355 double relY() { return findValuator(GetAtom!"Rel Y"(XDisplayConnection.get)); } 4356 double absX() { return findValuator(GetAtom!"Abs X"(XDisplayConnection.get)); } 4357 double absY() { return findValuator(GetAtom!"Abs Y"(XDisplayConnection.get)); } 4358 double pressure() { return findValuator(GetAtom!"Abs Pressure"(XDisplayConnection.get)); } 4359 double tiltX() { return findValuator(GetAtom!"Abs Tilt X"(XDisplayConnection.get)); } 4360 double tiltY() { return findValuator(GetAtom!"Abs Tilt Y"(XDisplayConnection.get)); } 4361 4362 /+ 4363 4364 "Rel Horiz Scroll", 4365 "Rel Vert Scroll", 4366 "Abs Wheel" 4367 4368 "Abs MT Position X", 4369 "Abs MT Position Y", 4370 +/ 4371 4372 private double findValuator(Atom what) { 4373 assert(deviceObject !is null); 4374 4375 foreach(idx, v; deviceObject.valuators) { 4376 if(idx >= valuators.length) 4377 break; 4378 if(v.label == what) 4379 return valuators[idx]; 4380 } 4381 return double.nan; 4382 } 4383 } else { 4384 double pressure = double.nan; 4385 double tiltX = double.nan; 4386 double tiltY = double.nan; 4387 } 4388 4389 // parsed flags 4390 // all 1 << 16 4391 bool wasKeyRepeat; // only on key event 4392 bool wasEmulatedPointer; // only on pointer event 4393 bool wasTouchPendingEnd; // only on touch event 4394 4395 bool wasTouchEmulatingPointer; // 1 << 16 4396 } 4397 4398 /// ditto 4399 void subscribeToInputEvents(SimpleWindow window, InputDevice device, void delegate(InputDeviceEvent ide) handler) { 4400 static if(UsingSimpledisplayX11) { 4401 XIEventMask mask = XIEventMask(device.deviceId); 4402 with(XIEventType) 4403 mask.set( 4404 XI_ButtonPress, XI_ButtonRelease, XI_Motion, 4405 XI_FocusIn, XI_FocusOut, 4406 XI_Enter, XI_Leave, 4407 XI_KeyPress, XI_KeyRelease, 4408 // tbh the raw events are useful af too 4409 XI_TouchBegin, XI_TouchUpdate, XI_TouchEnd 4410 ); 4411 4412 XISelectEvents(XDisplayConnection.get, window.window, &mask, 1); 4413 4414 window.setAdvancedInputHandler(device, handler); 4415 } else { 4416 window.setAdvancedInputHandler(null, handler); 4417 } 4418 } 4419 4420 /* } Advanced input support */ 4421 4422 /* Drag and drop support { */ 4423 version(X11) { 4424 4425 } else version(Windows) { 4426 import core.sys.windows.uuid; 4427 import core.sys.windows.ole2; 4428 import core.sys.windows.oleidl; 4429 import core.sys.windows.objidl; 4430 import core.sys.windows.wtypes; 4431 4432 pragma(lib, "ole32"); 4433 void initDnd() { 4434 auto err = OleInitialize(null); 4435 if(err != S_OK && err != S_FALSE) 4436 throw new Exception("init");//err); 4437 } 4438 } 4439 /* } End drag and drop support */ 4440 4441 4442 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 4443 /// See [GenericCursor]. 4444 class MouseCursor { 4445 int osId; 4446 bool isStockCursor; 4447 private this(int osId) { 4448 this.osId = osId; 4449 this.isStockCursor = true; 4450 } 4451 4452 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 4453 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 4454 4455 version(Windows) { 4456 HCURSOR cursor_; 4457 HCURSOR cursorHandle() { 4458 if(cursor_ is null) 4459 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 4460 return cursor_; 4461 } 4462 4463 } else static if(UsingSimpledisplayX11) { 4464 Cursor cursor_ = None; 4465 int xDisplaySequence; 4466 4467 Cursor cursorHandle() { 4468 if(this.osId == None) 4469 return None; 4470 4471 // we need to reload if we on a new X connection 4472 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 4473 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 4474 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 4475 } 4476 return cursor_; 4477 } 4478 } 4479 } 4480 4481 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 4482 // https://tronche.com/gui/x/xlib/appendix/b/ 4483 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 4484 /// 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. 4485 enum GenericCursorType { 4486 Default, /// The default arrow pointer. 4487 Wait, /// A cursor indicating something is loading and the user must wait. 4488 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 4489 Help, /// A cursor indicating the user can get help about the pointer location. 4490 Cross, /// A crosshair. 4491 Text, /// An i-beam shape, typically used to indicate text selection is possible. 4492 Move, /// Pointer indicating movement is possible. May also be used as SizeAll. 4493 UpArrow, /// An arrow pointing straight up. 4494 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 4495 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 4496 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator). 4497 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator). 4498 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator). 4499 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator). 4500 4501 } 4502 4503 /* 4504 X_plus == css cell == Windows ? 4505 */ 4506 4507 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 4508 static struct GenericCursor { 4509 static: 4510 /// 4511 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 4512 static MouseCursor mc; 4513 4514 auto type = __traits(getMember, GenericCursorType, str); 4515 4516 if(mc is null) { 4517 4518 version(Windows) { 4519 int osId; 4520 final switch(type) { 4521 case GenericCursorType.Default: osId = IDC_ARROW; break; 4522 case GenericCursorType.Wait: osId = IDC_WAIT; break; 4523 case GenericCursorType.Hand: osId = IDC_HAND; break; 4524 case GenericCursorType.Help: osId = IDC_HELP; break; 4525 case GenericCursorType.Cross: osId = IDC_CROSS; break; 4526 case GenericCursorType.Text: osId = IDC_IBEAM; break; 4527 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 4528 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 4529 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 4530 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 4531 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 4532 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 4533 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 4534 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 4535 } 4536 } else static if(UsingSimpledisplayX11) { 4537 int osId; 4538 final switch(type) { 4539 case GenericCursorType.Default: osId = None; break; 4540 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 4541 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 4542 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 4543 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 4544 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 4545 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 4546 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 4547 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 4548 4549 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 4550 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 4551 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 4552 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 4553 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 4554 } 4555 4556 } else { 4557 int osId; 4558 // featureNotImplemented(); 4559 } 4560 4561 mc = new MouseCursor(osId); 4562 } 4563 return mc; 4564 } 4565 } 4566 4567 4568 /++ 4569 If you want to get more control over the event loop, you can use this. 4570 4571 Typically though, you can just call [SimpleWindow.eventLoop] which forwards 4572 to `EventLoop.get.run`. 4573 +/ 4574 struct EventLoop { 4575 @disable this(); 4576 4577 /// Gets a reference to an existing event loop 4578 static EventLoop get() { 4579 return EventLoop(0, null); 4580 } 4581 4582 static void quitApplication() { 4583 static if(use_arsd_core) { 4584 import arsd.core; 4585 ICoreEventLoop.exitApplication(); 4586 } 4587 EventLoop.get().exit(); 4588 } 4589 4590 private __gshared static SynchronizableObject monitor = new SynchronizableObject(); // deliberate CTFE usage here fyi 4591 4592 /// Construct an application-global event loop for yourself 4593 /// See_Also: [SimpleWindow.setEventHandlers] 4594 this(long pulseTimeout, void delegate() handlePulse) { 4595 synchronized(monitor) { 4596 if(impl is null) { 4597 claimGuiThread(); 4598 version(sdpy_thread_checks) assert(thisIsGuiThread); 4599 impl = new EventLoopImpl(pulseTimeout, handlePulse); 4600 } else { 4601 if(pulseTimeout) { 4602 impl.pulseTimeout = pulseTimeout; 4603 impl.handlePulse = handlePulse; 4604 } 4605 } 4606 impl.refcount++; 4607 } 4608 } 4609 4610 ~this() { 4611 if(impl is null) 4612 return; 4613 impl.refcount--; 4614 if(impl.refcount == 0) { 4615 impl.dispose(); 4616 if(thisIsGuiThread) 4617 guiThreadFinalize(); 4618 } 4619 4620 } 4621 4622 this(this) { 4623 if(impl is null) 4624 return; 4625 impl.refcount++; 4626 } 4627 4628 /// Runs the event loop until the whileCondition, if present, returns false 4629 int run(bool delegate() whileCondition = null) { 4630 assert(impl !is null); 4631 impl.notExited = true; 4632 return impl.run(whileCondition); 4633 } 4634 4635 /// Exits the event loop, but allows you to reenter it again later (in contrast with quitApplication, which tries to terminate the program) 4636 void exit() { 4637 assert(impl !is null); 4638 impl.notExited = false; 4639 4640 static if(use_arsd_core) { 4641 import arsd.core; 4642 ICoreEventLoop.exitApplication(); 4643 } 4644 } 4645 4646 version(linux) 4647 ref void delegate(int) signalHandler() { 4648 assert(impl !is null); 4649 return impl.signalHandler; 4650 } 4651 4652 __gshared static EventLoopImpl* impl; 4653 } 4654 4655 version(linux) 4656 void delegate(int, int) globalHupHandler; 4657 4658 version(Posix) 4659 void makeNonBlocking(int fd) { 4660 import fcntl = core.sys.posix.fcntl; 4661 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 4662 if(flags == -1) 4663 throw new Exception("fcntl get"); 4664 flags |= fcntl.O_NONBLOCK; 4665 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 4666 if(s == -1) 4667 throw new Exception("fcntl set"); 4668 } 4669 4670 struct EventLoopImpl { 4671 int refcount; 4672 4673 bool notExited = true; 4674 4675 version(Emscripten) { 4676 void delegate(int) signalHandler; 4677 static import unix = core.sys.posix.unistd; 4678 static import err = core.stdc.errno; 4679 } else 4680 version(linux) { 4681 static import ep = core.sys.linux.epoll; 4682 static import unix = core.sys.posix.unistd; 4683 static import err = core.stdc.errno; 4684 import core.sys.linux.timerfd; 4685 4686 void delegate(int) signalHandler; 4687 } 4688 else 4689 version(Posix) { 4690 static import unix = core.sys.posix.unistd; 4691 } 4692 4693 version(X11) { 4694 int pulseFd = -1; 4695 version(Emscripten) {} else 4696 version(linux) ep.epoll_event[16] events = void; 4697 } else version(Windows) { 4698 Timer pulser; 4699 HANDLE[] handles; 4700 } 4701 4702 4703 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 4704 /// to call this, as it's not recommended to share window between threads. 4705 void mtLock () { 4706 version(X11) { 4707 XLockDisplay(this.display); 4708 } 4709 } 4710 4711 version(X11) 4712 auto display() { return XDisplayConnection.get; } 4713 4714 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 4715 /// to call this, as it's not recommended to share window between threads. 4716 void mtUnlock () { 4717 version(X11) { 4718 XUnlockDisplay(this.display); 4719 } 4720 } 4721 4722 version(with_eventloop) 4723 void initialize(long pulseTimeout) {} 4724 else 4725 void initialize(long pulseTimeout) @system { 4726 version(Windows) { 4727 if(pulseTimeout && handlePulse !is null) 4728 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 4729 4730 if (customEventH is null) { 4731 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 4732 if (customEventH !is null) { 4733 handles ~= customEventH; 4734 } else { 4735 // this is something that should not be; better be safe than sorry 4736 throw new Exception("can't create eventfd for custom event processing"); 4737 } 4738 } 4739 4740 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 4741 } 4742 4743 version(Emscripten) { 4744 4745 } else version(linux) { 4746 prepareEventLoop(); 4747 { 4748 auto display = XDisplayConnection.get; 4749 // adding Xlib file 4750 ep.epoll_event ev = void; 4751 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4752 ev.events = ep.EPOLLIN; 4753 ev.data.fd = display.fd; 4754 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 4755 throw new Exception("add x fd");// ~ to!string(epollFd)); 4756 displayFd = display.fd; 4757 } 4758 4759 if(pulseTimeout && handlePulse !is null) { 4760 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 4761 if(pulseFd == -1) 4762 throw new Exception("pulse timer create failed"); 4763 4764 itimerspec value; 4765 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 4766 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4767 4768 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 4769 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4770 4771 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 4772 throw new Exception("couldn't make pulse timer"); 4773 4774 ep.epoll_event ev = void; 4775 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4776 ev.events = ep.EPOLLIN; 4777 ev.data.fd = pulseFd; 4778 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 4779 } 4780 4781 // eventfd for custom events 4782 if (customEventFDWrite == -1) { 4783 customEventFDWrite = eventfd(0, 0); 4784 customEventFDRead = customEventFDWrite; 4785 if (customEventFDRead >= 0) { 4786 ep.epoll_event ev = void; 4787 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4788 ev.events = ep.EPOLLIN; 4789 ev.data.fd = customEventFDRead; 4790 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev); 4791 } else { 4792 // this is something that should not be; better be safe than sorry 4793 throw new Exception("can't create eventfd for custom event processing"); 4794 } 4795 } 4796 4797 if (customSignalFD == -1) { 4798 import core.sys.linux.sys.signalfd; 4799 4800 sigset_t sigset; 4801 auto err = sigemptyset(&sigset); 4802 assert(!err); 4803 err = sigaddset(&sigset, SIGINT); 4804 assert(!err); 4805 err = sigaddset(&sigset, SIGHUP); 4806 assert(!err); 4807 err = sigprocmask(SIG_BLOCK, &sigset, null); 4808 assert(!err); 4809 4810 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 4811 assert(customSignalFD != -1); 4812 4813 ep.epoll_event ev = void; 4814 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4815 ev.events = ep.EPOLLIN; 4816 ev.data.fd = customSignalFD; 4817 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 4818 } 4819 } else version(Posix) { 4820 prepareEventLoop(); 4821 if (customEventFDRead == -1) { 4822 int[2] bfr; 4823 import core.sys.posix.unistd; 4824 auto ret = pipe(bfr); 4825 if(ret == -1) throw new Exception("pipe"); 4826 customEventFDRead = bfr[0]; 4827 customEventFDWrite = bfr[1]; 4828 } 4829 4830 } 4831 4832 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 4833 4834 version(linux) { 4835 this.mtLock(); 4836 scope(exit) this.mtUnlock(); 4837 version(X11) 4838 XPending(display); // no, really 4839 } 4840 4841 disposed = false; 4842 } 4843 4844 bool disposed = true; 4845 version(X11) 4846 int displayFd = -1; 4847 4848 ICoreEventLoop.UnregisterToken[] unregisters; 4849 4850 version(with_eventloop) 4851 void dispose() {} 4852 else 4853 void dispose() @system { 4854 disposed = true; 4855 loopInitialized = false; 4856 4857 foreach(urt; unregisters) 4858 urt.unregister(); 4859 unregisters = null; 4860 4861 version(X11) { 4862 if(pulseFd != -1) { 4863 import unix = core.sys.posix.unistd; 4864 unix.close(pulseFd); 4865 pulseFd = -1; 4866 } 4867 if(customSignalFD != -1) { 4868 import unix = core.sys.posix.unistd; 4869 unix.close(customSignalFD); 4870 customSignalFD = -1; 4871 } 4872 4873 version(Emscripten) {} else 4874 version(linux) 4875 if(displayFd != -1) { 4876 // 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 4877 ep.epoll_event ev = void; 4878 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4879 ev.events = ep.EPOLLIN; 4880 ev.data.fd = displayFd; 4881 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 4882 displayFd = -1; 4883 } 4884 } else version(Windows) { 4885 if(pulser !is null) { 4886 pulser.destroy(); 4887 pulser = null; 4888 } 4889 if (customEventH !is null) { 4890 CloseHandle(customEventH); 4891 customEventH = null; 4892 } 4893 } 4894 } 4895 4896 this(long pulseTimeout, void delegate() handlePulse) { 4897 this.pulseTimeout = pulseTimeout; 4898 this.handlePulse = handlePulse; 4899 initialize(pulseTimeout); 4900 } 4901 4902 private long pulseTimeout; 4903 void delegate() handlePulse; 4904 4905 ~this() { 4906 dispose(); 4907 } 4908 4909 version(Posix) 4910 ref int customEventFDRead() { return SimpleWindow.customEventFDRead; } 4911 version(Posix) 4912 ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; } 4913 version(Posix) 4914 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 4915 version(Windows) 4916 ref auto customEventH() { return SimpleWindow.customEventH; } 4917 4918 version(X11) { 4919 bool doXPending() { 4920 bool done = false; 4921 4922 this.mtLock(); 4923 scope(exit) this.mtUnlock(); 4924 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 4925 while(!done && XPending(display)) { 4926 done = doXNextEvent(this.display); 4927 } 4928 4929 return done; 4930 } 4931 void doXNextEventVoid() { 4932 doXPending(); 4933 } 4934 } 4935 4936 static bool loopInitialized = false; 4937 4938 version(with_eventloop) { 4939 int loopHelper(bool delegate() whileCondition) { 4940 // FIXME: whileCondition 4941 import arsd.eventloop; 4942 loop(); 4943 return 0; 4944 } 4945 } else 4946 int loopHelper(bool delegate() whileCondition) { 4947 version(X11) { 4948 bool done = false; 4949 4950 XFlush(display); 4951 insideXEventLoop = true; 4952 scope(exit) insideXEventLoop = false; 4953 4954 static if(use_arsd_core) { 4955 import arsd.core; 4956 auto el = getThisThreadEventLoop(EventLoopType.Ui); 4957 4958 if(!loopInitialized) { 4959 el.getTimeout = () { auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); return (wto == 0 || wto >= int.max ? -1 : cast(int)wto); }; 4960 unregisters ~= el.addDelegateOnLoopIteration(&doXNextEventVoid, 3); 4961 unregisters ~= el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 3); 4962 4963 if(customSignalFD != -1) 4964 unregisters ~= el.addCallbackOnFdReadable(customSignalFD, new CallbackHelper(() { 4965 version(linux) { 4966 import core.sys.linux.sys.signalfd; 4967 import core.sys.posix.unistd : read; 4968 signalfd_siginfo info; 4969 read(customSignalFD, &info, info.sizeof); 4970 4971 auto sig = info.ssi_signo; 4972 4973 if(EventLoop.get.signalHandler !is null) { 4974 EventLoop.get.signalHandler()(sig); 4975 } else { 4976 EventLoop.get.exit(); 4977 } 4978 } 4979 })); 4980 4981 if(displayFd != -1) 4982 unregisters ~= el.addCallbackOnFdReadable(displayFd, new CallbackHelper(() { 4983 this.mtLock(); 4984 scope(exit) this.mtUnlock(); 4985 while(!done && XPending(display)) { 4986 done = doXNextEvent(this.display); 4987 } 4988 })); 4989 4990 if(pulseFd != -1) 4991 unregisters ~= el.addCallbackOnFdReadable(pulseFd, new CallbackHelper(() { 4992 long expirationCount; 4993 // 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... 4994 4995 handlePulse(); 4996 4997 // read just to clear the buffer so poll doesn't trigger again 4998 // BTW I read AFTER the pulse because if the pulse handler takes 4999 // a lot of time to execute, we don't want the app to get stuck 5000 // in a loop of timer hits without a chance to do anything else 5001 // 5002 // IOW handlePulse happens at most once per pulse interval. 5003 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 5004 })); 5005 5006 if(customEventFDRead != -1) 5007 unregisters ~= el.addCallbackOnFdReadable(customEventFDRead, new CallbackHelper(() { 5008 // we have some custom events; process 'em 5009 import core.sys.posix.unistd : read; 5010 ulong n; 5011 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 5012 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 5013 //SimpleWindow.processAllCustomEvents(); 5014 })); 5015 5016 // FIXME: posix fds 5017 // FIXME up? 5018 5019 5020 loopInitialized = true; 5021 } 5022 5023 if(whileCondition is null) 5024 whileCondition = () => true; 5025 5026 el.run(() => !whileCondition()); 5027 } else version(linux) { 5028 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 5029 bool forceXPending = false; 5030 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 5031 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 5032 { 5033 this.mtLock(); 5034 scope(exit) this.mtUnlock(); 5035 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 5036 } 5037 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 5038 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 5039 if(nfds == -1) { 5040 if(err.errno == err.EINTR) { 5041 //if(forceXPending) goto xpending; 5042 continue; // interrupted by signal, just try again 5043 } 5044 throw new Exception("epoll wait failure"); 5045 } 5046 // writeln(nfds, " ", events[0].data.fd); 5047 5048 SimpleWindow.processAllCustomEvents(); // anyway 5049 //version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 5050 foreach(idx; 0 .. nfds) { 5051 if(done) break; 5052 auto fd = events[idx].data.fd; 5053 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 5054 auto flags = events[idx].events; 5055 if(flags & ep.EPOLLIN) { 5056 if (fd == customSignalFD) { 5057 version(linux) { 5058 import core.sys.linux.sys.signalfd; 5059 import core.sys.posix.unistd : read; 5060 signalfd_siginfo info; 5061 read(customSignalFD, &info, info.sizeof); 5062 5063 auto sig = info.ssi_signo; 5064 5065 if(EventLoop.get.signalHandler !is null) { 5066 EventLoop.get.signalHandler()(sig); 5067 } else { 5068 EventLoop.get.exit(); 5069 } 5070 } 5071 } else if(fd == display.fd) { 5072 version(sdddd) { writeln("X EVENT PENDING!"); } 5073 this.mtLock(); 5074 scope(exit) this.mtUnlock(); 5075 while(!done && XPending(display)) { 5076 done = doXNextEvent(this.display); 5077 } 5078 forceXPending = false; 5079 } else if(fd == pulseFd) { 5080 long expirationCount; 5081 // 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... 5082 5083 handlePulse(); 5084 5085 // read just to clear the buffer so poll doesn't trigger again 5086 // BTW I read AFTER the pulse because if the pulse handler takes 5087 // a lot of time to execute, we don't want the app to get stuck 5088 // in a loop of timer hits without a chance to do anything else 5089 // 5090 // IOW handlePulse happens at most once per pulse interval. 5091 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 5092 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 5093 } else if (fd == customEventFDRead) { 5094 // we have some custom events; process 'em 5095 import core.sys.posix.unistd : read; 5096 ulong n; 5097 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 5098 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 5099 //SimpleWindow.processAllCustomEvents(); 5100 5101 forceXPending = true; 5102 } else { 5103 // some other timer 5104 version(sdddd) { writeln("unknown fd: ", fd); } 5105 5106 if(Timer* t = fd in Timer.mapping) 5107 (*t).trigger(); 5108 5109 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 5110 (*pfr).ready(flags); 5111 5112 // we don't know what the user did in this timer, so we need to assume that 5113 // there's X data to be flushed and potentially processed 5114 forceXPending = true; 5115 5116 // or i might add support for other FDs too 5117 // but for now it is just timer 5118 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 5119 } 5120 } 5121 if(flags & ep.EPOLLHUP) { 5122 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 5123 (*pfr).hup(flags); 5124 if(globalHupHandler) 5125 globalHupHandler(fd, flags); 5126 } 5127 /+ 5128 } else { 5129 // not interested in OUT, we are just reading here. 5130 // 5131 // error or hup might also be reported 5132 // but it shouldn't here since we are only 5133 // using a few types of FD and Xlib will report 5134 // if it dies. 5135 // so instead of thoughtfully handling it, I'll 5136 // just throw. for now at least 5137 5138 throw new Exception("epoll did something else"); 5139 } 5140 +/ 5141 } 5142 // if we won't call `XPending()` here, libX may delay some internal event delivery. 5143 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 5144 xpending: 5145 if (!done && forceXPending) { 5146 done = doXPending(); 5147 } 5148 } 5149 } else { 5150 // Generic fallback: yes to simple pulse support, 5151 // but NO timer support! 5152 5153 // FIXME: we could probably support the POSIX timer_create 5154 // signal-based option, but I'm in no rush to write it since 5155 // I prefer the fd-based functions. 5156 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 5157 5158 import core.sys.posix.poll; 5159 5160 pollfd[] pfds; 5161 pollfd[32] pfdsBuffer; 5162 auto len = PosixFdReader.mapping.length + 2; 5163 // FIXME: i should just reuse the buffer 5164 if(len < pfdsBuffer.length) 5165 pfds = pfdsBuffer[0 .. len]; 5166 else 5167 pfds = new pollfd[](len); 5168 5169 pfds[0].fd = display.fd; 5170 pfds[0].events = POLLIN; 5171 pfds[0].revents = 0; 5172 5173 int slot = 1; 5174 5175 if(customEventFDRead != -1) { 5176 pfds[slot].fd = customEventFDRead; 5177 pfds[slot].events = POLLIN; 5178 pfds[slot].revents = 0; 5179 5180 slot++; 5181 } 5182 5183 foreach(fd, obj; PosixFdReader.mapping) { 5184 if(!obj.enabled) continue; 5185 pfds[slot].fd = fd; 5186 pfds[slot].events = POLLIN; 5187 pfds[slot].revents = 0; 5188 5189 slot++; 5190 } 5191 5192 auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1); 5193 if(ret == -1) throw new Exception("poll"); 5194 5195 if(ret == 0) { 5196 // FIXME it may not necessarily time out if events keep coming 5197 if(handlePulse !is null) 5198 handlePulse(); 5199 } else { 5200 foreach(s; 0 .. slot) { 5201 if(pfds[s].revents == 0) continue; 5202 5203 if(pfds[s].fd == display.fd) { 5204 while(!done && XPending(display)) { 5205 this.mtLock(); 5206 scope(exit) this.mtUnlock(); 5207 done = doXNextEvent(this.display); 5208 } 5209 } else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) { 5210 5211 import core.sys.posix.unistd : read; 5212 ulong n; 5213 read(customEventFDRead, &n, n.sizeof); 5214 SimpleWindow.processAllCustomEvents(); 5215 } else { 5216 auto obj = PosixFdReader.mapping[pfds[s].fd]; 5217 if(pfds[s].revents & POLLNVAL) { 5218 obj.dispose(); 5219 } else { 5220 obj.ready(pfds[s].revents); 5221 } 5222 } 5223 5224 ret--; 5225 if(ret == 0) break; 5226 } 5227 } 5228 } 5229 } 5230 } else 5231 version(Windows) { 5232 5233 static if(use_arsd_core) { 5234 import arsd.core; 5235 auto el = getThisThreadEventLoop(EventLoopType.Ui); 5236 if(!loopInitialized) { 5237 el.getTimeout = () { auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); return (wto == 0 || wto >= int.max ? INFINITE : cast(int)wto); }; 5238 unregisters ~= el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 3); 5239 unregisters ~= el.addDelegateOnLoopIteration(function() { eventLoopRound++; }, 3); 5240 loopInitialized = true; 5241 } 5242 el.run(() => !whileCondition()); 5243 } else { 5244 int ret = -1; 5245 MSG message; 5246 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 5247 eventLoopRound++; 5248 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 5249 auto waitResult = MsgWaitForMultipleObjectsEx( 5250 cast(int) handles.length, handles.ptr, 5251 (wto == 0 ? INFINITE : wto), /* timeout */ 5252 0x04FF, /* QS_ALLINPUT */ 5253 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 5254 5255 SimpleWindow.processAllCustomEvents(); // anyway 5256 enum WAIT_OBJECT_0 = 0; 5257 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 5258 auto h = handles[waitResult - WAIT_OBJECT_0]; 5259 if(auto e = h in WindowsHandleReader.mapping) { 5260 (*e).ready(); 5261 } 5262 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 5263 // message ready 5264 int count; 5265 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 5266 ret = GetMessage(&message, null, 0, 0); 5267 if(ret == -1) 5268 throw new WindowsApiException("GetMessage", GetLastError()); 5269 TranslateMessage(&message); 5270 DispatchMessage(&message); 5271 5272 count++; 5273 if(count > 10) 5274 break; // take the opportunity to catch up on other events 5275 5276 if(ret == 0) { // WM_QUIT 5277 EventLoop.quitApplication(); 5278 break; 5279 } 5280 } 5281 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 5282 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 5283 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 5284 // timeout, should never happen since we aren't using it 5285 } else if(waitResult == 0xFFFFFFFF) { 5286 // failed 5287 throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError()); 5288 } else { 5289 // idk.... 5290 } 5291 } 5292 } 5293 5294 // return message.wParam; 5295 return 0; 5296 } version (OSXCocoa) { 5297 5298 static assert(use_arsd_core); 5299 5300 /+ 5301 if (handlePulse !is null && pulseTimeout != 0) { 5302 NSTimer timer = NSTimer.schedule(pulseTimeout*1e-3, 5303 cast(NSid) view, sel_registerName("simpledisplay_pulse:"), 5304 null, true); 5305 5306 5307 if(timer) 5308 timer.invalidate(); 5309 } 5310 +/ 5311 5312 import arsd.core; 5313 auto el = getThisThreadEventLoop(EventLoopType.Ui); 5314 if(!loopInitialized) { 5315 el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 3); 5316 loopInitialized = true; 5317 sdpyPrintDebugString("one"); 5318 NSApp.run(); 5319 sdpyPrintDebugString("here"); 5320 } 5321 5322 sdpyPrintDebugString("arsd.core loop starting"); 5323 el.run(() => !whileCondition()); 5324 5325 sdpyPrintDebugString("kiio all done"); 5326 5327 return 0; 5328 } else { 5329 return 0; 5330 } 5331 } 5332 5333 int run(bool delegate() whileCondition = null) { 5334 if(disposed) 5335 initialize(this.pulseTimeout); 5336 5337 version(X11) { 5338 try { 5339 return loopHelper(whileCondition); 5340 } catch(XDisconnectException e) { 5341 if(e.userRequested) { 5342 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 5343 item.discardConnectionState(); 5344 XCloseDisplay(XDisplayConnection.display); 5345 } 5346 5347 XDisplayConnection.display = null; 5348 5349 this.dispose(); 5350 5351 throw e; 5352 } 5353 } else { 5354 return loopHelper(whileCondition); 5355 } 5356 } 5357 } 5358 5359 5360 /++ 5361 Provides an icon on the system notification area (also known as the system tray). 5362 5363 5364 If a notification area is not available with the NotificationIcon object is created, 5365 it will silently succeed and simply attempt to create one when an area becomes available. 5366 5367 5368 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for 5369 Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency 5370 with true color was added at that time. I was just too lazy to write the fallback. 5371 5372 If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest 5373 you use arsd 10.x when targeting Windows XP. 5374 +/ 5375 version(Emscripten) {} else 5376 version(OSXCocoa) {} else // NotYetImplementedException 5377 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 5378 5379 version(X11) { 5380 void recreateAfterDisconnect() { 5381 stateDiscarded = false; 5382 clippixmap = None; 5383 throw new Exception("NOT IMPLEMENTED"); 5384 } 5385 5386 bool stateDiscarded; 5387 void discardConnectionState() { 5388 stateDiscarded = true; 5389 } 5390 } 5391 5392 5393 version(X11) { 5394 Image img; 5395 5396 NativeEventHandler getNativeEventHandler() { 5397 return delegate int(XEvent e) { 5398 switch(e.type) { 5399 case EventType.Expose: 5400 //case EventType.VisibilityNotify: 5401 redraw(); 5402 break; 5403 case EventType.ClientMessage: 5404 version(sddddd) { 5405 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 5406 writeln("\t", e.xclient.format); 5407 writeln("\t", e.xclient.data.l); 5408 } 5409 break; 5410 case EventType.ButtonPress: 5411 auto event = e.xbutton; 5412 if (onClick !is null || onClickEx !is null) { 5413 MouseButton mb = cast(MouseButton)0; 5414 switch (event.button) { 5415 case 1: mb = MouseButton.left; break; // left 5416 case 2: mb = MouseButton.middle; break; // middle 5417 case 3: mb = MouseButton.right; break; // right 5418 case 4: mb = MouseButton.wheelUp; break; // scroll up 5419 case 5: mb = MouseButton.wheelDown; break; // scroll down 5420 case 6: mb = MouseButton.wheelLeft; break; // scroll left... 5421 case 7: mb = MouseButton.wheelRight; break; // scroll right... 5422 case 8: mb = MouseButton.backButton; break; 5423 case 9: mb = MouseButton.forwardButton; break; 5424 default: 5425 } 5426 if (mb) { 5427 try { onClick()(mb); } catch (Exception) {} 5428 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 5429 } 5430 } 5431 break; 5432 case EventType.EnterNotify: 5433 if (onEnter !is null) { 5434 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 5435 } 5436 break; 5437 case EventType.LeaveNotify: 5438 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 5439 break; 5440 case EventType.DestroyNotify: 5441 active = false; 5442 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 5443 break; 5444 case EventType.ConfigureNotify: 5445 auto event = e.xconfigure; 5446 this.width = event.width; 5447 this.height = event.height; 5448 // writeln(width, " x " , height, " @ ", event.x, " ", event.y); 5449 redraw(); 5450 break; 5451 default: return 1; 5452 } 5453 return 1; 5454 }; 5455 } 5456 5457 /* private */ void hideBalloon() { 5458 balloon.close(); 5459 version(with_timer) 5460 timer.destroy(); 5461 balloon = null; 5462 version(with_timer) 5463 timer = null; 5464 } 5465 5466 void redraw() { 5467 if (!active) return; 5468 5469 auto display = XDisplayConnection.get; 5470 GC gc; 5471 5472 // from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap 5473 5474 int gc_depth(int depth, Display *dpy, Window root, GC *gc) { 5475 Visual *visual; 5476 XVisualInfo vis_info; 5477 XSetWindowAttributes win_attr; 5478 c_ulong win_mask; 5479 5480 if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) { 5481 assert(0); 5482 // return 1; 5483 } 5484 5485 visual = vis_info.visual; 5486 5487 win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone); 5488 win_attr.background_pixel = 0; 5489 win_attr.border_pixel = 0; 5490 5491 win_mask = CWBackPixel | CWColormap | CWBorderPixel; 5492 5493 *gc = XCreateGC(dpy, nativeHandle, 0, null); 5494 5495 return 0; 5496 } 5497 5498 if(useAlpha) 5499 gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc); 5500 else 5501 gc = DefaultGC(display, DefaultScreen(display)); 5502 5503 XClearWindow(display, nativeHandle); 5504 5505 if(!useAlpha && img !is null) 5506 XSetClipMask(display, gc, clippixmap); 5507 5508 /+ 5509 XSetForeground(display, gc, 5510 cast(uint) 0 << 16 | 5511 cast(uint) 0 << 8 | 5512 cast(uint) 0); 5513 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 5514 +/ 5515 5516 if (img is null) { 5517 XSetForeground(display, gc, 5518 cast(uint) 0 << 16 | 5519 cast(uint) 127 << 8 | 5520 cast(uint) 0); 5521 XFillArc(display, nativeHandle, 5522 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 5523 } else { 5524 int dx = 0; 5525 int dy = 0; 5526 if(width > img.width) 5527 dx = (width - img.width) / 2; 5528 if(height > img.height) 5529 dy = (height - img.height) / 2; 5530 // writeln(img.width, " ", img.height, " vs ", width, " ", height); 5531 XSetClipOrigin(display, gc, dx, dy); 5532 5533 int max(int a, int b) { 5534 if(a > b) return a; else return b; 5535 } 5536 5537 if (img.usingXshm) 5538 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false); 5539 else 5540 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height)); 5541 } 5542 XSetClipMask(display, gc, None); 5543 flushGui(); 5544 } 5545 5546 static Window getTrayOwner() { 5547 auto display = XDisplayConnection.get; 5548 auto i = cast(int) DefaultScreen(display); 5549 if(i < 10 && i >= 0) { 5550 static Atom atom; 5551 if(atom == None) 5552 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 5553 return XGetSelectionOwner(display, atom); 5554 } 5555 return None; 5556 } 5557 5558 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 5559 auto to = getTrayOwner(); 5560 auto display = XDisplayConnection.get; 5561 XEvent ev; 5562 ev.xclient.type = EventType.ClientMessage; 5563 ev.xclient.window = to; 5564 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 5565 ev.xclient.format = 32; 5566 ev.xclient.data.l[0] = CurrentTime; 5567 ev.xclient.data.l[1] = message; 5568 ev.xclient.data.l[2] = d1; 5569 ev.xclient.data.l[3] = d2; 5570 ev.xclient.data.l[4] = d3; 5571 5572 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 5573 } 5574 5575 private static NotificationAreaIcon[] activeIcons; 5576 5577 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 5578 private void newManager() { 5579 close(); 5580 createXWin(); 5581 5582 if(this.clippixmap) 5583 XFreePixmap(XDisplayConnection.get, clippixmap); 5584 if(this.originalMemoryImage) 5585 this.icon = this.originalMemoryImage; 5586 else if(this.img) 5587 this.icon = this.img; 5588 } 5589 5590 private bool useAlpha = false; 5591 5592 private void createXWin () { 5593 // create window 5594 auto display = XDisplayConnection.get; 5595 5596 // to check for MANAGER on root window to catch new/changed tray owners 5597 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 5598 // so if a thing does appear, we can handle it 5599 foreach(ai; activeIcons) 5600 if(ai is this) 5601 goto alreadythere; 5602 activeIcons ~= this; 5603 alreadythere: 5604 5605 // and check for an existing tray 5606 auto trayOwner = getTrayOwner(); 5607 if(trayOwner == None) 5608 return; 5609 //throw new Exception("No notification area found"); 5610 5611 Visual* v = cast(Visual*) CopyFromParent; 5612 5613 // GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales 5614 // from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send 5615 // a resize event later. 5616 width = 22; 5617 height = 22; 5618 5619 // if they system gave us a 32 bit visual we need to switch to it too 5620 int depth = 24; 5621 5622 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 5623 if(visualProp !is null) { 5624 c_ulong[] info = cast(c_ulong[]) visualProp; 5625 if(info.length == 1) { 5626 auto vid = info[0]; 5627 int returned; 5628 XVisualInfo t; 5629 t.visualid = vid; 5630 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 5631 if(got !is null) { 5632 if(returned == 1) { 5633 v = got.visual; 5634 depth = got.depth; 5635 // writeln("using special visual ", got.depth); 5636 // writeln(depth); 5637 } 5638 XFree(got); 5639 } 5640 } 5641 } 5642 5643 int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect; 5644 XSetWindowAttributes attr; 5645 attr.background_pixel = 0; 5646 attr.border_pixel = 0; 5647 attr.override_redirect = 0; 5648 if(v !is cast(Visual*) CopyFromParent) { 5649 attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone); 5650 CWFlags |= CWColormap; 5651 if(depth == 32) 5652 useAlpha = true; 5653 else 5654 goto plain; 5655 } else { 5656 plain: 5657 attr.background_pixmap = 1 /* ParentRelative */; 5658 CWFlags |= CWBackPixmap; 5659 } 5660 5661 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr); 5662 5663 assert(nativeWindow); 5664 5665 if(!useAlpha) 5666 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 5667 5668 nativeHandle = nativeWindow; 5669 5670 ///+ 5671 arch_ulong[2] info; 5672 info[0] = 0; 5673 info[1] = 1; 5674 5675 string title = this.name is null ? "simpledisplay.d program" : this.name; 5676 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 5677 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 5678 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 5679 5680 XChangeProperty( 5681 display, 5682 nativeWindow, 5683 GetAtom!("_XEMBED_INFO", true)(display), 5684 GetAtom!("_XEMBED_INFO", true)(display), 5685 32 /* bits */, 5686 0 /*PropModeReplace*/, 5687 info.ptr, 5688 2); 5689 5690 import core.sys.posix.unistd; 5691 arch_ulong pid = getpid(); 5692 5693 // XSetCommand(display, nativeWindow, ["sdpy".ptr].ptr, 1); 5694 5695 XChangeProperty( 5696 display, 5697 nativeWindow, 5698 GetAtom!("_NET_WM_PID", true)(display), 5699 XA_CARDINAL, 5700 32 /* bits */, 5701 0 /*PropModeReplace*/, 5702 &pid, 5703 1); 5704 5705 updateNetWmIcon(); 5706 5707 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 5708 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 5709 XClassHint klass; 5710 XWMHints wh; 5711 XSizeHints size; 5712 klass.res_name = sdpyWindowClassStr; 5713 klass.res_class = sdpyWindowClassStr; 5714 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 5715 } 5716 5717 // believe it or not, THIS is what xfce needed for the 9999 issue 5718 XSizeHints sh; 5719 c_long spr; 5720 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 5721 sh.flags |= PMaxSize | PMinSize; 5722 // FIXME maybe nicer resizing 5723 sh.min_width = 16; 5724 sh.min_height = 16; 5725 sh.max_width = 22; 5726 sh.max_height = 22; 5727 XSetWMNormalHints(display, nativeWindow, &sh); 5728 5729 5730 //+/ 5731 5732 5733 XSelectInput(display, nativeWindow, 5734 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 5735 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 5736 5737 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 5738 // XMapWindow(display, nativeWindow); // to demo it w/o a tray 5739 5740 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 5741 active = true; 5742 } 5743 5744 void updateNetWmIcon() { 5745 if(img is null) return; 5746 auto display = XDisplayConnection.get; 5747 // FIXME: ensure this is correct 5748 arch_ulong[] buffer; 5749 auto imgMi = img.toTrueColorImage; 5750 buffer ~= imgMi.width; 5751 buffer ~= imgMi.height; 5752 foreach(c; imgMi.imageData.colors) { 5753 arch_ulong b; 5754 b |= c.a << 24; 5755 b |= c.r << 16; 5756 b |= c.g << 8; 5757 b |= c.b; 5758 buffer ~= b; 5759 } 5760 5761 XChangeProperty( 5762 display, 5763 nativeHandle, 5764 GetAtom!"_NET_WM_ICON"(display), 5765 GetAtom!"CARDINAL"(display), 5766 32 /* bits */, 5767 0 /*PropModeReplace*/, 5768 buffer.ptr, 5769 cast(int) buffer.length); 5770 } 5771 5772 5773 5774 private SimpleWindow balloon; 5775 version(with_timer) 5776 private Timer timer; 5777 5778 private Window nativeHandle; 5779 private Pixmap clippixmap = None; 5780 private int width = 16; 5781 private int height = 16; 5782 private bool active = false; 5783 5784 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 5785 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 5786 void delegate () onLeave; /// X11 only. 5787 5788 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 5789 5790 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 5791 void getWindowRect (out int x, out int y, out int width, out int height) { 5792 if (!active) { width = 1; height = 1; return; } // 1: just in case 5793 Window dummyw; 5794 auto dpy = XDisplayConnection.get; 5795 //XWindowAttributes xwa; 5796 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 5797 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 5798 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 5799 width = this.width; 5800 height = this.height; 5801 } 5802 } 5803 5804 /+ 5805 What I actually want from this: 5806 5807 * set / change: icon, tooltip 5808 * handle: mouse click, right click 5809 * show: notification bubble. 5810 +/ 5811 5812 version(Windows) { 5813 WindowsIcon win32Icon; 5814 HWND hwnd; 5815 5816 NOTIFYICONDATAW data; 5817 5818 NativeEventHandler getNativeEventHandler() { 5819 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 5820 if(msg == WM_USER) { 5821 auto event = LOWORD(lParam); 5822 auto iconId = HIWORD(lParam); 5823 //auto x = GET_X_LPARAM(wParam); 5824 //auto y = GET_Y_LPARAM(wParam); 5825 switch(event) { 5826 case WM_LBUTTONDOWN: 5827 onClick()(MouseButton.left); 5828 break; 5829 case WM_RBUTTONDOWN: 5830 onClick()(MouseButton.right); 5831 break; 5832 case WM_MBUTTONDOWN: 5833 onClick()(MouseButton.middle); 5834 break; 5835 case WM_MOUSEMOVE: 5836 // sent, we could use it. 5837 break; 5838 case WM_MOUSEWHEEL: 5839 // NOT SENT 5840 break; 5841 //case NIN_KEYSELECT: 5842 //case NIN_SELECT: 5843 //break; 5844 default: {} 5845 } 5846 } 5847 return 0; 5848 }; 5849 } 5850 5851 enum NIF_SHOWTIP = 0x00000080; 5852 5853 private static struct NOTIFYICONDATAW { 5854 DWORD cbSize; 5855 HWND hWnd; 5856 UINT uID; 5857 UINT uFlags; 5858 UINT uCallbackMessage; 5859 HICON hIcon; 5860 WCHAR[128] szTip; 5861 DWORD dwState; 5862 DWORD dwStateMask; 5863 WCHAR[256] szInfo; 5864 union { 5865 UINT uTimeout; 5866 UINT uVersion; 5867 } 5868 WCHAR[64] szInfoTitle; 5869 DWORD dwInfoFlags; 5870 GUID guidItem; 5871 HICON hBalloonIcon; 5872 } 5873 5874 } 5875 5876 /++ 5877 Note that on Windows, only left, right, and middle buttons are sent. 5878 Mouse wheel buttons are NOT set, so don't rely on those events if your 5879 program is meant to be used on Windows too. 5880 +/ 5881 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 5882 // The canonical constructor for Windows needs the MemoryImage, so it is here, 5883 // but on X, we need an Image, so its canonical ctor is there. They should 5884 // forward to each other though. 5885 version(X11) { 5886 this.name = name; 5887 this.onClick = onClick; 5888 createXWin(); 5889 this.icon = icon; 5890 } else version(Windows) { 5891 this.onClick = onClick; 5892 this.win32Icon = new WindowsIcon(icon); 5893 5894 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 5895 5896 static bool registered = false; 5897 if(!registered) { 5898 WNDCLASSEX wc; 5899 wc.cbSize = wc.sizeof; 5900 wc.hInstance = hInstance; 5901 wc.lpfnWndProc = &WndProc; 5902 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 5903 if(!RegisterClassExW(&wc)) 5904 throw new WindowsApiException("RegisterClass", GetLastError()); 5905 registered = true; 5906 } 5907 5908 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 5909 if(hwnd is null) 5910 throw new WindowsApiException("CreateWindow", GetLastError()); 5911 5912 data.cbSize = data.sizeof; 5913 data.hWnd = hwnd; 5914 data.uID = cast(uint) cast(void*) this; 5915 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 5916 // NIF_INFO means show balloon 5917 data.uCallbackMessage = WM_USER; 5918 data.hIcon = this.win32Icon.hIcon; 5919 data.szTip = ""; // FIXME 5920 data.dwState = 0; // NIS_HIDDEN; // windows vista 5921 data.dwStateMask = NIS_HIDDEN; // windows vista 5922 5923 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 5924 5925 5926 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 5927 5928 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 5929 } else version(OSXCocoa) { 5930 throw new NotYetImplementedException(); 5931 } else static assert(0); 5932 } 5933 5934 /// ditto 5935 this(string name, Image icon, void delegate(MouseButton button) onClick) { 5936 version(X11) { 5937 this.onClick = onClick; 5938 this.name = name; 5939 createXWin(); 5940 this.icon = icon; 5941 } else version(Windows) { 5942 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 5943 } else version(OSXCocoa) { 5944 throw new NotYetImplementedException(); 5945 } else static assert(0); 5946 } 5947 5948 version(X11) { 5949 /++ 5950 X-specific extension (for now at least) 5951 +/ 5952 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5953 this.onClickEx = onClickEx; 5954 createXWin(); 5955 if (icon !is null) this.icon = icon; 5956 } 5957 5958 /// ditto 5959 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5960 this.onClickEx = onClickEx; 5961 createXWin(); 5962 this.icon = icon; 5963 } 5964 } 5965 5966 private void delegate (MouseButton button) onClick_; 5967 5968 /// 5969 @property final void delegate(MouseButton) onClick() { 5970 if(onClick_ is null) 5971 onClick_ = delegate void(MouseButton) {}; 5972 return onClick_; 5973 } 5974 5975 /// ditto 5976 @property final void onClick(void delegate(MouseButton) handler) { 5977 // I made this a property setter so we can wrap smaller arg 5978 // delegates and just forward all to onClickEx or something. 5979 onClick_ = handler; 5980 } 5981 5982 5983 string name_; 5984 @property void name(string n) { 5985 name_ = n; 5986 } 5987 5988 @property string name() { 5989 return name_; 5990 } 5991 5992 private MemoryImage originalMemoryImage; 5993 5994 /// 5995 @property void icon(MemoryImage i) { 5996 version(X11) { 5997 this.originalMemoryImage = i; 5998 if (!active) return; 5999 if (i !is null) { 6000 this.img = Image.fromMemoryImage(i, useAlpha, false); 6001 if(!useAlpha) 6002 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 6003 // writeln("using pixmap ", clippixmap); 6004 updateNetWmIcon(); 6005 redraw(); 6006 } else { 6007 if (this.img !is null) { 6008 this.img = null; 6009 redraw(); 6010 } 6011 } 6012 } else version(Windows) { 6013 this.win32Icon = new WindowsIcon(i); 6014 6015 data.uFlags = NIF_ICON; 6016 data.hIcon = this.win32Icon.hIcon; 6017 6018 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 6019 } else version(OSXCocoa) { 6020 throw new NotYetImplementedException(); 6021 } else static assert(0); 6022 } 6023 6024 /// ditto 6025 @property void icon (Image i) { 6026 version(X11) { 6027 if (!active) return; 6028 if (i !is img) { 6029 originalMemoryImage = null; 6030 img = i; 6031 redraw(); 6032 } 6033 } else version(Windows) { 6034 this.icon(i is null ? null : i.toTrueColorImage()); 6035 } else version(OSXCocoa) { 6036 throw new NotYetImplementedException(); 6037 } else static assert(0); 6038 } 6039 6040 /++ 6041 Shows a balloon notification. You can only show one balloon at a time, if you call 6042 it twice while one is already up, the first balloon will be replaced. 6043 6044 6045 The user is free to block notifications and they will automatically disappear after 6046 a timeout period. 6047 6048 Params: 6049 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 6050 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 6051 icon = the icon to display with the notification. If null, it uses your existing icon. 6052 onclick = delegate called if the user clicks the balloon. (not yet implemented) 6053 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 6054 +/ 6055 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 6056 bool useCustom = true; 6057 version(libnotify) { 6058 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 6059 try { 6060 if(!active) return; 6061 6062 if(libnotify is null) { 6063 libnotify = new C_DynamicLibrary("libnotify.so"); 6064 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 6065 } 6066 6067 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 6068 6069 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 6070 6071 if(onclick) { 6072 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 6073 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); 6074 libnotify_action_delegates_count++; 6075 } 6076 6077 // FIXME icon 6078 6079 // set hint image-data 6080 // set default action for onclick 6081 6082 void* error; 6083 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 6084 6085 useCustom = false; 6086 } catch(Exception e) { 6087 6088 } 6089 } 6090 6091 version(X11) { 6092 if(useCustom) { 6093 if(!active) return; 6094 if(balloon) { 6095 hideBalloon(); 6096 } 6097 // I know there are two specs for this, but one is never 6098 // implemented by any window manager I have ever seen, and 6099 // the other is a bloated mess and too complicated for simpledisplay... 6100 // so doing my own little window instead. 6101 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 6102 6103 int x, y, width, height; 6104 getWindowRect(x, y, width, height); 6105 6106 int bx = x - balloon.width; 6107 int by = y - balloon.height; 6108 if(bx < 0) 6109 bx = x + width + balloon.width; 6110 if(by < 0) 6111 by = y + height; 6112 6113 // just in case, make sure it is actually on scren 6114 if(bx < 0) 6115 bx = 0; 6116 if(by < 0) 6117 by = 0; 6118 6119 balloon.move(bx, by); 6120 auto painter = balloon.draw(); 6121 painter.fillColor = Color(220, 220, 220); 6122 painter.outlineColor = Color.black; 6123 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 6124 auto iconWidth = icon is null ? 0 : icon.width; 6125 if(icon) 6126 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 6127 iconWidth += 6; // margin around the icon 6128 6129 // draw a close button 6130 painter.outlineColor = Color(44, 44, 44); 6131 painter.fillColor = Color(255, 255, 255); 6132 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 6133 painter.pen = Pen(Color.black, 3); 6134 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 6135 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 6136 painter.pen = Pen(Color.black, 1); 6137 painter.fillColor = Color(220, 220, 220); 6138 6139 // Draw the title and message 6140 painter.drawText(Point(4 + iconWidth, 4), title); 6141 painter.drawLine( 6142 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 6143 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 6144 ); 6145 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 6146 6147 balloon.setEventHandlers( 6148 (MouseEvent ev) { 6149 if(ev.type == MouseEventType.buttonPressed) { 6150 if(ev.x > balloon.width - 16 && ev.y < 16) 6151 hideBalloon(); 6152 else if(onclick) 6153 onclick(); 6154 } 6155 } 6156 ); 6157 balloon.show(); 6158 6159 version(with_timer) 6160 timer = new Timer(timeout, &hideBalloon); 6161 else {} // FIXME 6162 } 6163 } else version(Windows) { 6164 enum NIF_INFO = 0x00000010; 6165 6166 data.uFlags = NIF_INFO; 6167 6168 // FIXME: go back to the last valid unicode code point 6169 if(title.length > 40) 6170 title = title[0 .. 40]; 6171 if(message.length > 220) 6172 message = message[0 .. 220]; 6173 6174 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 6175 enum NIIF_LARGE_ICON = 0x00000020; 6176 enum NIIF_NOSOUND = 0x00000010; 6177 enum NIIF_USER = 0x00000004; 6178 enum NIIF_ERROR = 0x00000003; 6179 enum NIIF_WARNING = 0x00000002; 6180 enum NIIF_INFO = 0x00000001; 6181 enum NIIF_NONE = 0; 6182 6183 WCharzBuffer t = WCharzBuffer(title); 6184 WCharzBuffer m = WCharzBuffer(message); 6185 6186 t.copyInto(data.szInfoTitle); 6187 m.copyInto(data.szInfo); 6188 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 6189 6190 if(icon !is null) { 6191 auto i = new WindowsIcon(icon); 6192 data.hBalloonIcon = i.hIcon; 6193 data.dwInfoFlags |= NIIF_USER; 6194 } 6195 6196 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 6197 } else version(OSXCocoa) { 6198 throw new NotYetImplementedException(); 6199 } else static assert(0); 6200 } 6201 6202 /// 6203 //version(Windows) 6204 void show() { 6205 version(X11) { 6206 if(!hidden) 6207 return; 6208 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 6209 hidden = false; 6210 } else version(Windows) { 6211 data.uFlags = NIF_STATE; 6212 data.dwState = 0; // NIS_HIDDEN; // windows vista 6213 data.dwStateMask = NIS_HIDDEN; // windows vista 6214 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 6215 } else version(OSXCocoa) { 6216 throw new NotYetImplementedException(); 6217 } else static assert(0); 6218 } 6219 6220 version(X11) 6221 bool hidden = false; 6222 6223 /// 6224 //version(Windows) 6225 void hide() { 6226 version(X11) { 6227 if(hidden) 6228 return; 6229 hidden = true; 6230 XUnmapWindow(XDisplayConnection.get, nativeHandle); 6231 } else version(Windows) { 6232 data.uFlags = NIF_STATE; 6233 data.dwState = NIS_HIDDEN; // windows vista 6234 data.dwStateMask = NIS_HIDDEN; // windows vista 6235 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 6236 } else version(OSXCocoa) { 6237 throw new NotYetImplementedException(); 6238 } else static assert(0); 6239 } 6240 6241 /// 6242 void close () { 6243 version(X11) { 6244 if (active) { 6245 active = false; // event handler will set this too, but meh 6246 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 6247 XDestroyWindow(XDisplayConnection.get, nativeHandle); 6248 flushGui(); 6249 } 6250 } else version(Windows) { 6251 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 6252 } else version(OSXCocoa) { 6253 throw new NotYetImplementedException(); 6254 } else static assert(0); 6255 } 6256 6257 ~this() { 6258 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 6259 version(X11) 6260 if(clippixmap != None) 6261 XFreePixmap(XDisplayConnection.get, clippixmap); 6262 close(); 6263 } 6264 } 6265 6266 version(X11) 6267 /// Call `XFreePixmap` on the return value. 6268 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 6269 char[] data = new char[](i.width * i.height / 8 + 2); 6270 data[] = 0; 6271 6272 int bitOffset = 0; 6273 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 6274 ubyte v = c.a > 128 ? 1 : 0; 6275 data[bitOffset / 8] |= v << (bitOffset%8); 6276 bitOffset++; 6277 } 6278 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 6279 return handle; 6280 } 6281 6282 6283 // basic functions to make timers 6284 /** 6285 A timer that will trigger your function on a given interval. 6286 6287 6288 You create a timer with an interval and a callback. It will continue 6289 to fire on the interval until it is destroyed. 6290 6291 There are currently no one-off timers (instead, just create one and 6292 destroy it when it is triggered) nor are there pause/resume functions - 6293 the timer must again be destroyed and recreated if you want to pause it. 6294 6295 --- 6296 auto timer = new Timer(50, { it happened!; }); 6297 timer.destroy(); 6298 --- 6299 6300 Timers can only be expected to fire when the event loop is running and only 6301 once per iteration through the event loop. 6302 6303 History: 6304 Prior to December 9, 2020, a timer pulse set too high with a handler too 6305 slow could lock up the event loop. It now guarantees other things will 6306 get a chance to run between timer calls, even if that means not keeping up 6307 with the requested interval. 6308 */ 6309 version(with_timer) { 6310 static if(use_arsd_core) { 6311 alias Timer = arsd.core.Timer; // FIXME should probably wrap it for a stable api 6312 } else 6313 class Timer { 6314 // FIXME: needs pause and unpause 6315 // FIXME: I might add overloads for ones that take a count of 6316 // how many elapsed since last time (on Windows, it will divide 6317 // the ticks thing given, on Linux it is just available) and 6318 // maybe one that takes an instance of the Timer itself too 6319 /// Create a timer with a callback when it triggers. 6320 this(int intervalInMilliseconds, void delegate() onPulse) @trusted { 6321 assert(onPulse !is null); 6322 6323 this.intervalInMilliseconds = intervalInMilliseconds; 6324 this.onPulse = onPulse; 6325 6326 version(Windows) { 6327 /* 6328 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 6329 if(handle == 0) 6330 throw new WindowsApiException("SetTimer", GetLastError()); 6331 */ 6332 6333 // thanks to Archival 998 for the WaitableTimer blocks 6334 handle = CreateWaitableTimer(null, false, null); 6335 long initialTime = -intervalInMilliseconds; 6336 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 6337 throw new WindowsApiException("SetWaitableTimer", GetLastError()); 6338 6339 mapping[handle] = this; 6340 6341 } else version(Emscripten) { 6342 } else version(linux) { 6343 static import ep = core.sys.linux.epoll; 6344 6345 import core.sys.linux.timerfd; 6346 6347 fd = timerfd_create(CLOCK_MONOTONIC, 0); 6348 if(fd == -1) 6349 throw new Exception("timer create failed"); 6350 6351 mapping[fd] = this; 6352 6353 itimerspec value = makeItimerspec(intervalInMilliseconds); 6354 6355 if(timerfd_settime(fd, 0, &value, null) == -1) 6356 throw new Exception("couldn't make pulse timer"); 6357 6358 version(with_eventloop) { 6359 import arsd.eventloop; 6360 addFileEventListeners(fd, &trigger, null, null); 6361 } else { 6362 prepareEventLoop(); 6363 6364 ep.epoll_event ev = void; 6365 ev.events = ep.EPOLLIN; 6366 ev.data.fd = fd; 6367 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 6368 } 6369 } else featureNotImplemented(); 6370 } 6371 6372 private int intervalInMilliseconds; 6373 6374 // just cuz I sometimes call it this. 6375 alias dispose = destroy; 6376 6377 /// Stop and destroy the timer object. 6378 void destroy() { 6379 version(Windows) { 6380 staticDestroy(handle); 6381 handle = null; 6382 } else version(linux) { 6383 staticDestroy(fd); 6384 fd = -1; 6385 } else featureNotImplemented(); 6386 } 6387 6388 version(Windows) 6389 static void staticDestroy(HANDLE handle) { 6390 if(handle) { 6391 // KillTimer(null, handle); 6392 CancelWaitableTimer(cast(void*)handle); 6393 mapping.remove(handle); 6394 CloseHandle(handle); 6395 } 6396 } 6397 else version(Emscripten) 6398 static void staticDestroy(int fd) @system { 6399 assert(0); 6400 } 6401 else version(linux) 6402 static void staticDestroy(int fd) @system { 6403 if(fd != -1) { 6404 import unix = core.sys.posix.unistd; 6405 static import ep = core.sys.linux.epoll; 6406 6407 version(with_eventloop) { 6408 import arsd.eventloop; 6409 removeFileEventListeners(fd); 6410 } else { 6411 ep.epoll_event ev = void; 6412 ev.events = ep.EPOLLIN; 6413 ev.data.fd = fd; 6414 6415 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 6416 } 6417 unix.close(fd); 6418 mapping.remove(fd); 6419 } 6420 } 6421 6422 ~this() { 6423 version(Windows) { if(handle) 6424 cleanupQueue.queue!staticDestroy(handle); 6425 } else version(linux) { if(fd != -1) 6426 cleanupQueue.queue!staticDestroy(fd); 6427 } 6428 } 6429 6430 void changeTime(int intervalInMilliseconds) 6431 { 6432 this.intervalInMilliseconds = intervalInMilliseconds; 6433 version(Windows) 6434 { 6435 if(handle) 6436 { 6437 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 6438 long initialTime = -intervalInMilliseconds; 6439 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 6440 throw new WindowsApiException("couldn't change pulse timer", GetLastError()); 6441 } 6442 } else version(linux) { 6443 import core.sys.linux.timerfd; 6444 6445 itimerspec value = makeItimerspec(intervalInMilliseconds); 6446 if(timerfd_settime(fd, 0, &value, null) == -1) { 6447 throw new Exception("couldn't change pulse timer"); 6448 } 6449 } else { 6450 assert(false, "Timer.changeTime(int) is not implemented for this platform"); 6451 } 6452 } 6453 6454 6455 private: 6456 6457 void delegate() onPulse; 6458 6459 int lastEventLoopRoundTriggered; 6460 6461 version(linux) { 6462 static auto makeItimerspec(int intervalInMilliseconds) { 6463 import core.sys.linux.timerfd; 6464 6465 itimerspec value; 6466 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 6467 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 6468 6469 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 6470 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 6471 6472 return value; 6473 } 6474 } 6475 6476 void trigger() { 6477 version(linux) { 6478 import unix = core.sys.posix.unistd; 6479 long val; 6480 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 6481 } else version(Windows) { 6482 if(this.lastEventLoopRoundTriggered == eventLoopRound) 6483 return; // never try to actually run faster than the event loop 6484 lastEventLoopRoundTriggered = eventLoopRound; 6485 } else featureNotImplemented(); 6486 6487 onPulse(); 6488 } 6489 6490 version(Windows) 6491 void rearm() { 6492 6493 } 6494 6495 version(Windows) 6496 extern(Windows) 6497 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 6498 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 6499 if(Timer* t = timer in mapping) { 6500 try 6501 (*t).trigger(); 6502 catch(Exception e) { sdpy_abort(e); assert(0); } 6503 } 6504 } 6505 6506 version(Windows) { 6507 //UINT_PTR handle; 6508 //static Timer[UINT_PTR] mapping; 6509 HANDLE handle; 6510 __gshared Timer[HANDLE] mapping; 6511 } else version(linux) { 6512 int fd = -1; 6513 __gshared Timer[int] mapping; 6514 } else version(OSXCocoa) { 6515 } else static assert(0, "timer not supported"); 6516 } 6517 } 6518 6519 version(Windows) 6520 private int eventLoopRound; 6521 6522 version(Windows) 6523 /// 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 6524 class WindowsHandleReader { 6525 /// 6526 this(void delegate() onReady, HANDLE handle) { 6527 this.onReady = onReady; 6528 this.handle = handle; 6529 6530 mapping[handle] = this; 6531 6532 enable(); 6533 } 6534 6535 static if(use_arsd_core) 6536 ICoreEventLoop.UnregisterToken unregisterToken; 6537 6538 /// 6539 void enable() { 6540 static if(use_arsd_core) { 6541 unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnHandleReady(handle, new CallbackHelper(&ready)); 6542 } else { 6543 auto el = EventLoop.get().impl; 6544 el.handles ~= handle; 6545 } 6546 } 6547 6548 /// 6549 void disable() { 6550 static if(use_arsd_core) { 6551 unregisterToken.unregister(); 6552 } else { 6553 auto el = EventLoop.get().impl; 6554 for(int i = 0; i < el.handles.length; i++) { 6555 if(el.handles[i] is handle) { 6556 el.handles[i] = el.handles[$-1]; 6557 el.handles = el.handles[0 .. $-1]; 6558 return; 6559 } 6560 } 6561 } 6562 } 6563 6564 void dispose() { 6565 disable(); 6566 if(handle) 6567 mapping.remove(handle); 6568 handle = null; 6569 } 6570 6571 void ready() { 6572 if(onReady) 6573 onReady(); 6574 } 6575 6576 HANDLE handle; 6577 void delegate() onReady; 6578 6579 __gshared WindowsHandleReader[HANDLE] mapping; 6580 } 6581 6582 version(Posix) 6583 /// Lets you add files to the event loop for reading. Use at your own risk. 6584 class PosixFdReader { 6585 /// 6586 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 6587 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 6588 } 6589 6590 /// 6591 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 6592 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 6593 } 6594 6595 /// 6596 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 6597 this.onReady = onReady; 6598 this.fd = fd; 6599 this.captureWrites = captureWrites; 6600 this.captureReads = captureReads; 6601 6602 mapping[fd] = this; 6603 6604 version(with_eventloop) { 6605 import arsd.eventloop; 6606 addFileEventListeners(fd, &readyel); 6607 } else { 6608 enable(); 6609 } 6610 } 6611 6612 bool captureReads; 6613 bool captureWrites; 6614 6615 static if(use_arsd_core) { 6616 import arsd.core; 6617 ICoreEventLoop.UnregisterToken unregisterToken; 6618 } 6619 6620 version(with_eventloop) {} else 6621 /// 6622 void enable() @system { 6623 enabled = true; 6624 6625 static if(use_arsd_core) { 6626 unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnFdReadable(fd, new CallbackHelper( 6627 () { onReady(fd, true, false); } 6628 )); 6629 // FIXME: what if it is writeable? 6630 6631 } else version(linux) { 6632 prepareEventLoop(); 6633 static import ep = core.sys.linux.epoll; 6634 ep.epoll_event ev = void; 6635 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 6636 // writeln("enable ", fd, " ", captureReads, " ", captureWrites); 6637 ev.data.fd = fd; 6638 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 6639 } else { 6640 6641 } 6642 } 6643 6644 version(with_eventloop) {} else 6645 /// 6646 void disable() @system { 6647 enabled = false; 6648 6649 static if(use_arsd_core) { 6650 unregisterToken.unregister(); 6651 } else 6652 version(linux) { 6653 prepareEventLoop(); 6654 static import ep = core.sys.linux.epoll; 6655 ep.epoll_event ev = void; 6656 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 6657 // writeln("disable ", fd, " ", captureReads, " ", captureWrites); 6658 ev.data.fd = fd; 6659 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 6660 } 6661 } 6662 6663 version(with_eventloop) {} else 6664 /// 6665 void dispose() { 6666 if(enabled) 6667 disable(); 6668 if(fd != -1) 6669 mapping.remove(fd); 6670 fd = -1; 6671 } 6672 6673 void delegate(int, bool, bool) onReady; 6674 6675 version(with_eventloop) 6676 void readyel() { 6677 onReady(fd, true, true); 6678 } 6679 6680 void ready(uint flags) { 6681 version(Emscripten) { 6682 assert(0); 6683 } else version(linux) { 6684 static import ep = core.sys.linux.epoll; 6685 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 6686 } else { 6687 import core.sys.posix.poll; 6688 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 6689 } 6690 } 6691 6692 void hup(uint flags) { 6693 if(onHup) 6694 onHup(); 6695 } 6696 6697 void delegate() onHup; 6698 6699 int fd = -1; 6700 private bool enabled; 6701 __gshared PosixFdReader[int] mapping; 6702 } 6703 6704 // basic functions to access the clipboard 6705 /+ 6706 6707 6708 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 6709 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 6710 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 6711 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 6712 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx 6713 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 6714 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx 6715 6716 +/ 6717 6718 /++ 6719 this does a delegate because it is actually an async call on X... 6720 the receiver may never be called if the clipboard is empty or unavailable 6721 gets plain text from the clipboard. 6722 +/ 6723 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) @system { 6724 version(Windows) { 6725 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 6726 if(OpenClipboard(hwndOwner) == 0) 6727 throw new WindowsApiException("OpenClipboard", GetLastError()); 6728 scope(exit) 6729 CloseClipboard(); 6730 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat 6731 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 6732 6733 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 6734 scope(exit) 6735 GlobalUnlock(dataHandle); 6736 6737 // FIXME: CR/LF conversions 6738 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 6739 int len = 0; 6740 auto d = data; 6741 while(*d) { 6742 d++; 6743 len++; 6744 } 6745 string s; 6746 s.reserve(len); 6747 foreach(dchar ch; data[0 .. len]) { 6748 s ~= ch; 6749 } 6750 receiver(s); 6751 } 6752 } 6753 } else version(X11) { 6754 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 6755 } else version(OSXCocoa) { 6756 throw new NotYetImplementedException(); 6757 } else version(Emscripten) { 6758 throw new NotYetImplementedException(); 6759 } else static assert(0); 6760 } 6761 6762 // FIXME: a clipboard listener might be cool btw 6763 6764 /++ 6765 this does a delegate because it is actually an async call on X... 6766 the receiver may never be called if the clipboard is empty or unavailable 6767 gets image from the clipboard. 6768 6769 templated because it introduces an optional dependency on arsd.bmp 6770 +/ 6771 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) { 6772 version(Windows) { 6773 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 6774 if(OpenClipboard(hwndOwner) == 0) 6775 throw new WindowsApiException("OpenClipboard", GetLastError()); 6776 scope(exit) 6777 CloseClipboard(); 6778 if(auto dataHandle = GetClipboardData(CF_DIBV5)) { 6779 if(auto data = cast(ubyte*) GlobalLock(dataHandle)) { 6780 scope(exit) 6781 GlobalUnlock(dataHandle); 6782 6783 auto len = GlobalSize(dataHandle); 6784 6785 import arsd.bmp; 6786 auto img = readBmp(data[0 .. len], false); 6787 receiver(img); 6788 } 6789 } 6790 } else version(X11) { 6791 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 6792 } else version(OSXCocoa) { 6793 throw new NotYetImplementedException(); 6794 } else version(Emscripten) { 6795 throw new NotYetImplementedException(); 6796 } else static assert(0); 6797 } 6798 6799 /// Copies some text to the clipboard. 6800 void setClipboardText(SimpleWindow clipboardOwner, string text) { 6801 assert(clipboardOwner !is null); 6802 version(Windows) { 6803 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6804 throw new WindowsApiException("OpenClipboard", GetLastError()); 6805 scope(exit) 6806 CloseClipboard(); 6807 EmptyClipboard(); 6808 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6809 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 6810 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6811 if(auto data = cast(wchar*) GlobalLock(handle)) { 6812 auto slice = data[0 .. sz]; 6813 scope(failure) 6814 GlobalUnlock(handle); 6815 6816 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6817 6818 GlobalUnlock(handle); 6819 SetClipboardData(CF_UNICODETEXT, handle); 6820 } 6821 } else version(X11) { 6822 // we set BOTH clipboard and primary on an explicit action 6823 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 6824 setX11Selection!"PRIMARY"(clipboardOwner, text); 6825 } else version(OSXCocoa) { 6826 throw new NotYetImplementedException(); 6827 } else version(Emscripten) { 6828 throw new NotYetImplementedException(); 6829 } else static assert(0); 6830 } 6831 6832 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) { 6833 assert(clipboardOwner !is null); 6834 version(Windows) { 6835 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6836 throw new WindowsApiException("OpenClipboard", GetLastError()); 6837 scope(exit) 6838 CloseClipboard(); 6839 EmptyClipboard(); 6840 6841 6842 import arsd.bmp; 6843 ubyte[] mdata; 6844 mdata.reserve(img.width * img.height); 6845 void sink(ubyte b) { 6846 mdata ~= b; 6847 } 6848 writeBmpIndirect(img, &sink, false); 6849 6850 auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length); 6851 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6852 if(auto data = cast(ubyte*) GlobalLock(handle)) { 6853 auto slice = data[0 .. mdata.length]; 6854 scope(failure) 6855 GlobalUnlock(handle); 6856 6857 slice[] = mdata[]; 6858 6859 GlobalUnlock(handle); 6860 SetClipboardData(CF_DIB, handle); 6861 } 6862 } else version(X11) { 6863 static class X11SetSelectionHandler_Image : X11SetSelectionHandler { 6864 mixin X11SetSelectionHandler_Basics; 6865 private const(ubyte)[] mdata; 6866 private const(ubyte)[] mdata_original; 6867 this(MemoryImage img) { 6868 import arsd.bmp; 6869 6870 mdata.reserve(img.width * img.height); 6871 void sink(ubyte b) { 6872 mdata ~= b; 6873 } 6874 writeBmpIndirect(img, &sink, true); 6875 6876 mdata_original = mdata; 6877 } 6878 6879 Atom[] availableFormats() { 6880 auto display = XDisplayConnection.get; 6881 return [ 6882 GetAtom!"image/bmp"(display), 6883 GetAtom!"TARGETS"(display) 6884 ]; 6885 } 6886 6887 ubyte[] getData(Atom format, return scope ubyte[] data) { 6888 if(mdata.length < data.length) { 6889 data[0 .. mdata.length] = mdata[]; 6890 auto ret = data[0 .. mdata.length]; 6891 mdata = mdata[$..$]; 6892 return ret; 6893 } else { 6894 data[] = mdata[0 .. data.length]; 6895 mdata = mdata[data.length .. $]; 6896 return data[]; 6897 } 6898 } 6899 6900 void done() { 6901 mdata = mdata_original; 6902 } 6903 } 6904 6905 auto handler = new X11SetSelectionHandler_Image(img); 6906 setX11Selection!"CLIPBOARD"(clipboardOwner, handler); 6907 setX11Selection!"PRIMARY"(clipboardOwner, handler); 6908 } else version(OSXCocoa) { 6909 throw new NotYetImplementedException(); 6910 } else version(Emscripten) { 6911 throw new NotYetImplementedException(); 6912 } else static assert(0); 6913 } 6914 6915 6916 version(X11) { 6917 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 6918 6919 private __gshared Atom*[] interredAtoms; // for discardAndRecreate 6920 6921 // FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all. 6922 /// Platform-specific for X11. 6923 /// History: On February 21, 2021, I changed the default value of `create` to be true. 6924 @property Atom GetAtom(string name, bool create = true)(Display* display) { 6925 __gshared static Atom a; 6926 if(!a) { 6927 a = XInternAtom(display, name, !create); 6928 // FIXME: might need to synchronize this and attach it to the actual object 6929 interredAtoms ~= &a; 6930 } 6931 if(a == None) 6932 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 6933 return a; 6934 } 6935 6936 /// Platform-specific for X11 - gets atom names as a string. 6937 string getAtomName(Atom atom, Display* display) { 6938 if(atom == None) 6939 return "None"; 6940 auto got = XGetAtomName(display, atom); 6941 if(got is null) 6942 return null; 6943 scope(exit) XFree(got); 6944 import core.stdc.string; 6945 string s = got[0 .. strlen(got)].idup; 6946 return s; 6947 } 6948 6949 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later. 6950 void setPrimarySelection(SimpleWindow window, string text) { 6951 setX11Selection!"PRIMARY"(window, text); 6952 } 6953 6954 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later. 6955 void setSecondarySelection(SimpleWindow window, string text) { 6956 setX11Selection!"SECONDARY"(window, text); 6957 } 6958 6959 interface X11SetSelectionHandler { 6960 // should include TARGETS right now 6961 Atom[] availableFormats(); 6962 // Return the slice of data you filled, empty slice if done. 6963 // this is to support the incremental thing 6964 ubyte[] getData(Atom format, return scope ubyte[] data); 6965 6966 void done(); 6967 6968 void handleRequest(XEvent); 6969 6970 bool matchesIncr(Window, Atom); 6971 void sendMoreIncr(XPropertyEvent*); 6972 } 6973 6974 mixin template X11SetSelectionHandler_Basics() { 6975 Window incrWindow; 6976 Atom incrAtom; 6977 Atom selectionAtom; 6978 Atom formatAtom; 6979 ubyte[] toSend; 6980 bool matchesIncr(Window w, Atom a) { 6981 return incrAtom && incrAtom == a && w == incrWindow; 6982 } 6983 void sendMoreIncr(XPropertyEvent* event) { 6984 auto display = XDisplayConnection.get; 6985 6986 XChangeProperty (display, 6987 incrWindow, 6988 incrAtom, 6989 formatAtom, 6990 8 /* bits */, PropModeReplace, 6991 toSend.ptr, cast(int) toSend.length); 6992 6993 if(toSend.length != 0) { 6994 toSend = this.getData(formatAtom, toSend[]); 6995 } else { 6996 this.done(); 6997 incrWindow = None; 6998 incrAtom = None; 6999 selectionAtom = None; 7000 formatAtom = None; 7001 toSend = null; 7002 } 7003 } 7004 void handleRequest(XEvent ev) { 7005 7006 auto display = XDisplayConnection.get; 7007 7008 XSelectionRequestEvent* event = &ev.xselectionrequest; 7009 XSelectionEvent selectionEvent; 7010 selectionEvent.type = EventType.SelectionNotify; 7011 selectionEvent.display = event.display; 7012 selectionEvent.requestor = event.requestor; 7013 selectionEvent.selection = event.selection; 7014 selectionEvent.time = event.time; 7015 selectionEvent.target = event.target; 7016 7017 bool supportedType() { 7018 foreach(t; this.availableFormats()) 7019 if(t == event.target) 7020 return true; 7021 return false; 7022 } 7023 7024 if(event.property == None) { 7025 selectionEvent.property = event.target; 7026 7027 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 7028 XFlush(display); 7029 } if(event.target == GetAtom!"TARGETS"(display)) { 7030 /* respond with the supported types */ 7031 auto tlist = this.availableFormats(); 7032 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length); 7033 selectionEvent.property = event.property; 7034 7035 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 7036 XFlush(display); 7037 } else if(supportedType()) { 7038 auto buffer = new ubyte[](1024 * 64); 7039 auto toSend = this.getData(event.target, buffer[]); 7040 7041 if(toSend.length < 32 * 1024) { 7042 // small enough to send directly... 7043 selectionEvent.property = event.property; 7044 XChangeProperty (display, 7045 selectionEvent.requestor, 7046 selectionEvent.property, 7047 event.target, 7048 8 /* bits */, 0 /* PropModeReplace */, 7049 toSend.ptr, cast(int) toSend.length); 7050 7051 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 7052 XFlush(display); 7053 } else { 7054 // large, let's send incrementally 7055 arch_ulong l = toSend.length; 7056 7057 // if I wanted other events from this window don't want to clear that out.... 7058 XWindowAttributes xwa; 7059 XGetWindowAttributes(display, selectionEvent.requestor, &xwa); 7060 7061 XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask)); 7062 7063 incrWindow = event.requestor; 7064 incrAtom = event.property; 7065 formatAtom = event.target; 7066 selectionAtom = event.selection; 7067 this.toSend = toSend; 7068 7069 selectionEvent.property = event.property; 7070 XChangeProperty (display, 7071 selectionEvent.requestor, 7072 selectionEvent.property, 7073 GetAtom!"INCR"(display), 7074 32 /* bits */, PropModeReplace, 7075 &l, 1); 7076 7077 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 7078 XFlush(display); 7079 } 7080 //if(after) 7081 //after(); 7082 } else { 7083 debug(sdpy_clip) { 7084 writeln("Unsupported data ", getAtomName(event.target, display)); 7085 } 7086 selectionEvent.property = None; // I don't know how to handle this type... 7087 XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent); 7088 XFlush(display); 7089 } 7090 } 7091 } 7092 7093 class X11SetSelectionHandler_Text : X11SetSelectionHandler { 7094 mixin X11SetSelectionHandler_Basics; 7095 private const(ubyte)[] text; 7096 private const(ubyte)[] text_original; 7097 this(string text) { 7098 this.text = cast(const ubyte[]) text; 7099 this.text_original = this.text; 7100 } 7101 Atom[] availableFormats() { 7102 auto display = XDisplayConnection.get; 7103 return [ 7104 GetAtom!"UTF8_STRING"(display), 7105 GetAtom!"text/plain"(display), 7106 XA_STRING, 7107 GetAtom!"TARGETS"(display) 7108 ]; 7109 } 7110 7111 ubyte[] getData(Atom format, return scope ubyte[] data) { 7112 if(text.length < data.length) { 7113 data[0 .. text.length] = text[]; 7114 return data[0 .. text.length]; 7115 } else { 7116 data[] = text[0 .. data.length]; 7117 text = text[data.length .. $]; 7118 return data[]; 7119 } 7120 } 7121 7122 void done() { 7123 text = text_original; 7124 } 7125 } 7126 7127 /// 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?!) 7128 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 7129 setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after); 7130 } 7131 7132 private __gshared bool mightShortCircuitClipboard; 7133 7134 void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) { 7135 assert(window !is null); 7136 7137 auto display = XDisplayConnection.get(); 7138 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 7139 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 7140 else Atom a = GetAtom!atomName(display); 7141 7142 if(a == XA_PRIMARY && mightShortCircuitClipboard) 7143 if(auto ptr = a in window.impl.setSelectionHandlers) { 7144 // we already have it, don't even need to inform the X server 7145 // sdpyPrintDebugString("short circuit in set"); 7146 *ptr = data; 7147 return; 7148 } 7149 7150 // we don't have it, tell X we want it 7151 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 7152 window.impl.setSelectionHandlers[a] = data; 7153 if(a == XA_PRIMARY) 7154 mightShortCircuitClipboard = true; 7155 } 7156 7157 /+ 7158 /++ 7159 History: 7160 Added September 28, 2024 7161 +/ 7162 bool hasX11Selection(string atomName)(SimpleWindow window) { 7163 auto display = XDisplayConnection.get(); 7164 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 7165 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 7166 else Atom a = GetAtom!atomName(display); 7167 7168 if(a in window.impl.setSelectionHandlers) 7169 return true; 7170 else 7171 return false; 7172 } 7173 +/ 7174 7175 /// 7176 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 7177 getX11Selection!"PRIMARY"(window, handler); 7178 } 7179 7180 // added July 28, 2020 7181 // undocumented as experimental tho 7182 interface X11GetSelectionHandler { 7183 void handleData(Atom target, in ubyte[] data); 7184 Atom findBestFormat(Atom[] answer); 7185 7186 void prepareIncremental(Window, Atom); 7187 bool matchesIncr(Window, Atom); 7188 void handleIncrData(Atom, in ubyte[] data); 7189 } 7190 7191 mixin template X11GetSelectionHandler_Basics() { 7192 Window incrWindow; 7193 Atom incrAtom; 7194 7195 void prepareIncremental(Window w, Atom a) { 7196 incrWindow = w; 7197 incrAtom = a; 7198 } 7199 bool matchesIncr(Window w, Atom a) { 7200 return incrWindow == w && incrAtom == a; 7201 } 7202 7203 Atom incrFormatAtom; 7204 ubyte[] incrData; 7205 void handleIncrData(Atom format, in ubyte[] data) { 7206 incrFormatAtom = format; 7207 7208 if(data.length) 7209 incrData ~= data; 7210 else 7211 handleData(incrFormatAtom, incrData); 7212 7213 } 7214 } 7215 7216 static class X11GetSelectionHandler_Text : X11GetSelectionHandler { 7217 this(void delegate(in char[]) handler) { 7218 this.handler = handler; 7219 } 7220 7221 mixin X11GetSelectionHandler_Basics; 7222 7223 void delegate(in char[]) handler; 7224 7225 void handleData(Atom target, in ubyte[] data) { 7226 // import std.stdio; writeln(target, " ", data); 7227 if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 7228 handler(cast(const char[]) data); 7229 else if(target == None && data is null) 7230 handler(null); // no suitable selection exists 7231 } 7232 7233 Atom findBestFormat(Atom[] answer) { 7234 Atom best = None; 7235 foreach(option; answer) { 7236 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 7237 best = option; 7238 break; 7239 } else if(option == XA_STRING) { 7240 best = option; 7241 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 7242 best = option; 7243 } 7244 } 7245 return best; 7246 } 7247 } 7248 7249 /// 7250 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) { 7251 assert(window !is null); 7252 7253 auto display = XDisplayConnection.get(); 7254 7255 static if (atomName == "PRIMARY") Atom atom = XA_PRIMARY; 7256 else static if (atomName == "SECONDARY") Atom atom = XA_SECONDARY; 7257 else Atom atom = GetAtom!atomName(display); 7258 7259 if(atom == XA_PRIMARY && mightShortCircuitClipboard) 7260 if(auto ptr = atom in window.impl.setSelectionHandlers) { 7261 if(auto txt = (cast(X11SetSelectionHandler_Text) *ptr)) { 7262 // we already have it! short circuit everything 7263 7264 // sdpyPrintDebugString("short circuit in get"); 7265 handler(cast(char[]) txt.text_original); 7266 return; 7267 } 7268 } 7269 7270 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler); 7271 7272 auto target = GetAtom!"TARGETS"(display); 7273 7274 // SDD_DATA is "simpledisplay.d data" 7275 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp); 7276 } 7277 7278 /// Gets the image on the clipboard, if there is one. Added July 2020. 7279 /// only supports bmps. using this function will import arsd.bmp. 7280 void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) { 7281 assert(window !is null); 7282 7283 auto display = XDisplayConnection.get(); 7284 auto atom = GetAtom!atomName(display); 7285 7286 static class X11GetSelectionHandler_Image : X11GetSelectionHandler { 7287 this(void delegate(MemoryImage) handler) { 7288 this.handler = handler; 7289 } 7290 7291 mixin X11GetSelectionHandler_Basics; 7292 7293 void delegate(MemoryImage) handler; 7294 7295 void handleData(Atom target, in ubyte[] data) { 7296 if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) { 7297 import arsd.bmp; 7298 handler(readBmp(data)); 7299 } 7300 } 7301 7302 Atom findBestFormat(Atom[] answer) { 7303 Atom best = None; 7304 foreach(option; answer) { 7305 if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) { 7306 best = option; 7307 } 7308 } 7309 return best; 7310 } 7311 7312 } 7313 7314 7315 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler); 7316 7317 auto target = GetAtom!"TARGETS"(display); 7318 7319 // SDD_DATA is "simpledisplay.d data" 7320 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 7321 } 7322 7323 7324 /// 7325 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 7326 Atom actualType; 7327 int actualFormat; 7328 arch_ulong actualItems; 7329 arch_ulong bytesRemaining; 7330 void* data; 7331 7332 auto display = XDisplayConnection.get(); 7333 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 7334 if(actualFormat == 0) 7335 return null; 7336 else { 7337 int byteLength; 7338 if(actualFormat == 32) { 7339 // 32 means it is a C long... which is variable length 7340 actualFormat = cast(int) arch_long.sizeof * 8; 7341 } 7342 7343 // then it is just a bit count 7344 byteLength = cast(int) (actualItems * actualFormat / 8); 7345 7346 auto d = new ubyte[](byteLength); 7347 d[] = cast(ubyte[]) data[0 .. byteLength]; 7348 XFree(data); 7349 return d; 7350 } 7351 } 7352 return null; 7353 } 7354 7355 /* defined in the systray spec */ 7356 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 7357 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 7358 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 7359 7360 7361 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 7362 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 7363 public class GlobalHotkey { 7364 KeyEvent key; 7365 void delegate () handler; 7366 7367 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 7368 7369 /// Create from initialzed KeyEvent object 7370 this (KeyEvent akey, void delegate () ahandler=null) { 7371 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 7372 key = akey; 7373 handler = ahandler; 7374 } 7375 7376 /// Create from emacs-like key name ("C-M-Y", etc.) 7377 this (const(char)[] akey, void delegate () ahandler=null) { 7378 key = KeyEvent.parse(akey); 7379 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 7380 handler = ahandler; 7381 } 7382 7383 } 7384 7385 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 7386 //conwriteln("failed to grab key"); 7387 GlobalHotkeyManager.ghfailed = true; 7388 return 0; 7389 } 7390 7391 private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 7392 Image.impl.xshmfailed = true; 7393 return 0; 7394 } 7395 7396 private __gshared int errorHappened; 7397 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 7398 import core.stdc.stdio; 7399 char[265] buffer; 7400 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 7401 debug printf("X Error %d: %s / Serial: %lld, Opcode: %d.%d, XID: 0x%llx\n", evt.error_code, buffer.ptr, cast(long) evt.serial, evt.request_code, evt.minor_code, cast(long) evt.resourceid); 7402 errorHappened = true; 7403 return 0; 7404 } 7405 7406 /++ 7407 Global hotkey manager. It contains static methods to manage global hotkeys. 7408 7409 --- 7410 try { 7411 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 7412 } catch (Exception e) { 7413 conwriteln("ERROR registering hotkey!"); 7414 } 7415 EventLoop.get.run(); 7416 --- 7417 7418 The key strings are based on Emacs. In practical terms, 7419 `M` means `alt` and `H` means the Windows logo key. `C` 7420 is `ctrl`. 7421 7422 $(WARNING 7423 This is X-specific right now. If you are on 7424 Windows, try [registerHotKey] instead. 7425 7426 We will probably merge these into a single 7427 interface later. 7428 ) 7429 +/ 7430 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 7431 version(X11) { 7432 void recreateAfterDisconnect() { 7433 throw new Exception("NOT IMPLEMENTED"); 7434 } 7435 void discardConnectionState() { 7436 throw new Exception("NOT IMPLEMENTED"); 7437 } 7438 } 7439 7440 private static immutable uint[8] masklist = [ 0, 7441 KeyOrButtonMask.LockMask, 7442 KeyOrButtonMask.Mod2Mask, 7443 KeyOrButtonMask.Mod3Mask, 7444 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 7445 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 7446 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 7447 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 7448 ]; 7449 private __gshared GlobalHotkeyManager ghmanager; 7450 private __gshared bool ghfailed = false; 7451 7452 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 7453 if (modmask == 0) return false; 7454 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 7455 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 7456 return true; 7457 } 7458 7459 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 7460 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 7461 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 7462 return modmask; 7463 } 7464 7465 private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) { 7466 uint keycode = cast(uint)ke.key; 7467 auto dpy = XDisplayConnection.get; 7468 return XKeysymToKeycode(dpy, keycode); 7469 } 7470 7471 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 7472 7473 private __gshared GlobalHotkey[ulong] globalHotkeyList; 7474 7475 NativeEventHandler getNativeEventHandler () { 7476 return delegate int (XEvent e) { 7477 if (e.type != EventType.KeyPress) return 1; 7478 auto kev = cast(const(XKeyEvent)*)&e; 7479 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 7480 if (auto ghkp = hash in globalHotkeyList) { 7481 try { 7482 ghkp.doHandle(); 7483 } catch (Exception e) { 7484 import core.stdc.stdio : stderr, fprintf; 7485 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 7486 } 7487 } 7488 return 1; 7489 }; 7490 } 7491 7492 private this () { 7493 auto dpy = XDisplayConnection.get; 7494 auto root = RootWindow(dpy, DefaultScreen(dpy)); 7495 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 7496 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 7497 } 7498 7499 /// Register new global hotkey with initialized `GlobalHotkey` object. 7500 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 7501 static void register (GlobalHotkey gh) { 7502 if (gh is null) return; 7503 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 7504 7505 auto dpy = XDisplayConnection.get; 7506 immutable keycode = keyEvent2KeyCode(gh.key); 7507 7508 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 7509 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 7510 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 7511 XSync(dpy, 0/*False*/); 7512 7513 Window root = RootWindow(dpy, DefaultScreen(dpy)); 7514 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 7515 ghfailed = false; 7516 foreach (immutable uint ormask; masklist[]) { 7517 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 7518 } 7519 XSync(dpy, 0/*False*/); 7520 XSetErrorHandler(savedErrorHandler); 7521 7522 if (ghfailed) { 7523 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 7524 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 7525 XSync(dpy, 0/*False*/); 7526 XSetErrorHandler(savedErrorHandler); 7527 throw new Exception("cannot register global hotkey"); 7528 } 7529 7530 globalHotkeyList[hash] = gh; 7531 } 7532 7533 /// Ditto 7534 static void register (const(char)[] akey, void delegate () ahandler) { 7535 register(new GlobalHotkey(akey, ahandler)); 7536 } 7537 7538 private static void removeByHash (ulong hash) { 7539 if (auto ghp = hash in globalHotkeyList) { 7540 auto dpy = XDisplayConnection.get; 7541 immutable keycode = keyEvent2KeyCode(ghp.key); 7542 Window root = RootWindow(dpy, DefaultScreen(dpy)); 7543 XSync(dpy, 0/*False*/); 7544 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 7545 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 7546 XSync(dpy, 0/*False*/); 7547 XSetErrorHandler(savedErrorHandler); 7548 globalHotkeyList.remove(hash); 7549 } 7550 } 7551 7552 /// Register new global hotkey with previously used `GlobalHotkey` object. 7553 /// It is safe to unregister unknown or invalid hotkey. 7554 static void unregister (GlobalHotkey gh) { 7555 //TODO: add second AA for faster search? prolly doesn't worth it. 7556 if (gh is null) return; 7557 foreach (const ref kv; globalHotkeyList.byKeyValue) { 7558 if (kv.value is gh) { 7559 removeByHash(kv.key); 7560 return; 7561 } 7562 } 7563 } 7564 7565 /// Ditto. 7566 static void unregister (const(char)[] key) { 7567 auto kev = KeyEvent.parse(key); 7568 immutable keycode = keyEvent2KeyCode(kev); 7569 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 7570 } 7571 } 7572 } 7573 7574 version(Windows) { 7575 /++ 7576 See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications. 7577 7578 This is platform-specific UTF-16 function for Windows. Sends a string as key press and release events to the actively focused window (not necessarily your application). 7579 +/ 7580 void sendSyntheticInput(wstring s) { 7581 INPUT[] inputs; 7582 inputs.reserve(s.length * 2); 7583 7584 foreach(wchar c; s) { 7585 INPUT input; 7586 input.type = INPUT_KEYBOARD; 7587 input.ki.wScan = c; 7588 input.ki.dwFlags = KEYEVENTF_UNICODE; 7589 inputs ~= input; 7590 7591 input.ki.dwFlags |= KEYEVENTF_KEYUP; 7592 inputs ~= input; 7593 } 7594 7595 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 7596 throw new WindowsApiException("SendInput", GetLastError()); 7597 } 7598 7599 } 7600 7601 7602 // global hotkey helper function 7603 7604 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these. 7605 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) @system { 7606 __gshared int hotkeyId = 0; 7607 int id = ++hotkeyId; 7608 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 7609 throw new Exception("RegisterHotKey"); 7610 7611 __gshared void delegate()[WPARAM][HWND] handlers; 7612 7613 handlers[window.impl.hwnd][id] = handler; 7614 7615 int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler; 7616 7617 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 7618 switch(msg) { 7619 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 7620 case WM_HOTKEY: 7621 if(auto list = hwnd in handlers) { 7622 if(auto h = wParam in *list) { 7623 (*h)(); 7624 return 0; 7625 } 7626 } 7627 goto default; 7628 default: 7629 } 7630 if(oldHandler) 7631 return oldHandler(hwnd, msg, wParam, lParam, mustReturn); 7632 return 1; // pass it on 7633 }; 7634 7635 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 7636 oldHandler = window.handleNativeEvent; 7637 window.handleNativeEvent = nativeEventHandler; 7638 } 7639 7640 return id; 7641 } 7642 7643 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey]. 7644 void unregisterHotKey(SimpleWindow window, int id) { 7645 if(!UnregisterHotKey(window.impl.hwnd, id)) 7646 throw new WindowsApiException("UnregisterHotKey", GetLastError()); 7647 } 7648 } 7649 7650 version (X11) { 7651 //pragma(lib, "dl"); // already done by the standard compiler build and specifying it again messes up zig cross compile 7652 import core.sys.posix.dlfcn; 7653 } 7654 7655 /++ 7656 Allows for sending synthetic input to the X server via the Xtst 7657 extension or on Windows using SendInput. 7658 7659 Please remember user input is meant to be user - don't use this 7660 if you have some other alternative! 7661 7662 History: 7663 Added May 17, 2020 with the X implementation. 7664 7665 Added unified implementation for Windows on April 3, 2022. (Prior to that, you had to use the top-level [sendSyntheticInput] or the Windows SendInput call directly.) 7666 Bugs: 7667 All methods on OSX Cocoa will throw not yet implemented exceptions. 7668 +/ 7669 struct SyntheticInput { 7670 @disable this(); 7671 7672 private int* refcount; 7673 7674 version(X11) { 7675 private void* lib; 7676 7677 private extern(C) { 7678 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 7679 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 7680 } 7681 } 7682 7683 /// The dummy param must be 0. 7684 this(int dummy) { 7685 version(X11) { 7686 lib = dlopen("libXtst.so", RTLD_NOW); 7687 if(lib is null) 7688 throw new Exception("cannot load xtest lib extension"); 7689 scope(failure) 7690 dlclose(lib); 7691 7692 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 7693 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 7694 7695 if(XTestFakeKeyEvent is null) 7696 throw new Exception("No XTestFakeKeyEvent"); 7697 if(XTestFakeButtonEvent is null) 7698 throw new Exception("No XTestFakeButtonEvent"); 7699 } 7700 7701 refcount = new int; 7702 *refcount = 1; 7703 } 7704 7705 this(this) { 7706 if(refcount) 7707 *refcount += 1; 7708 } 7709 7710 ~this() { 7711 if(refcount) { 7712 *refcount -= 1; 7713 if(*refcount == 0) 7714 // I commented this because if I close the lib before 7715 // XCloseDisplay, it is liable to segfault... so just 7716 // gonna keep it loaded if it is loaded, no big deal 7717 // anyway. 7718 {} // dlclose(lib); 7719 } 7720 } 7721 7722 /++ 7723 Simulates typing a string into the keyboard. 7724 7725 Bugs: 7726 On X11, this ONLY works with basic ascii! On Windows, it can handle more. 7727 7728 Not implemented except on Windows and X11. 7729 +/ 7730 void sendSyntheticInput(string s) { 7731 version(Windows) { 7732 INPUT[] inputs; 7733 inputs.reserve(s.length * 2); 7734 7735 auto ei = GetMessageExtraInfo(); 7736 7737 foreach(wchar c; s) { 7738 INPUT input; 7739 input.type = INPUT_KEYBOARD; 7740 input.ki.wScan = c; 7741 input.ki.dwFlags = KEYEVENTF_UNICODE; 7742 input.ki.dwExtraInfo = ei; 7743 inputs ~= input; 7744 7745 input.ki.dwFlags |= KEYEVENTF_KEYUP; 7746 inputs ~= input; 7747 } 7748 7749 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 7750 throw new WindowsApiException("SendInput", GetLastError()); 7751 } 7752 } else version(X11) { 7753 int delay = 0; 7754 foreach(ch; s) { 7755 pressKey(cast(Key) ch, true, delay); 7756 pressKey(cast(Key) ch, false, delay); 7757 delay += 5; 7758 } 7759 } else throw new NotYetImplementedException(); 7760 } 7761 7762 /++ 7763 Sends a fake press or release key event. 7764 7765 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 7766 7767 Bugs: 7768 The `delay` parameter is not implemented yet on Windows. 7769 7770 Not implemented except on Windows and X11. 7771 +/ 7772 void pressKey(Key key, bool pressed, int delay = 0) { 7773 version(Windows) { 7774 INPUT input; 7775 input.type = INPUT_KEYBOARD; 7776 input.ki.wVk = cast(ushort) key; 7777 7778 input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP; 7779 input.ki.dwExtraInfo = GetMessageExtraInfo(); 7780 7781 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7782 throw new WindowsApiException("SendInput", GetLastError()); 7783 } 7784 } else version(X11) { 7785 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 7786 } else throw new NotYetImplementedException(); 7787 } 7788 7789 /++ 7790 Sends a fake mouse button press or release event. 7791 7792 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 7793 7794 `pressed` param must be `true` if button is `wheelUp` or `wheelDown`. 7795 7796 Bugs: 7797 The `delay` parameter is not implemented yet on Windows. 7798 7799 The backButton and forwardButton will throw NotYetImplementedException on Windows. 7800 7801 All arguments will throw NotYetImplementedException on OSX Cocoa. 7802 +/ 7803 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 7804 version(Windows) { 7805 INPUT input; 7806 input.type = INPUT_MOUSE; 7807 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7808 7809 // input.mi.mouseData for a wheel event 7810 7811 switch(button) { 7812 case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break; 7813 case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break; 7814 case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break; 7815 case MouseButton.wheelUp: 7816 case MouseButton.wheelDown: 7817 input.mi.dwFlags = MOUSEEVENTF_WHEEL; 7818 input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120; 7819 break; 7820 case MouseButton.wheelLeft: throw new NotYetImplementedException(); 7821 case MouseButton.wheelRight: throw new NotYetImplementedException(); 7822 case MouseButton.backButton: throw new NotYetImplementedException(); 7823 case MouseButton.forwardButton: throw new NotYetImplementedException(); 7824 default: 7825 } 7826 7827 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7828 throw new WindowsApiException("SendInput", GetLastError()); 7829 } 7830 } else version(X11) { 7831 int btn; 7832 7833 switch(button) { 7834 case MouseButton.left: btn = 1; break; 7835 case MouseButton.middle: btn = 2; break; 7836 case MouseButton.right: btn = 3; break; 7837 case MouseButton.wheelUp: btn = 4; break; 7838 case MouseButton.wheelDown: btn = 5; break; 7839 case MouseButton.wheelLeft: btn = 6; break; 7840 case MouseButton.wheelRight: btn = 7; break; 7841 case MouseButton.backButton: btn = 8; break; 7842 case MouseButton.forwardButton: btn = 9; break; 7843 default: 7844 } 7845 7846 assert(btn); 7847 7848 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 7849 } else throw new NotYetImplementedException(); 7850 } 7851 7852 /// 7853 static void moveMouseArrowBy(int dx, int dy) { 7854 version(Windows) { 7855 INPUT input; 7856 input.type = INPUT_MOUSE; 7857 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7858 input.mi.dx = dx; 7859 input.mi.dy = dy; 7860 input.mi.dwFlags = MOUSEEVENTF_MOVE; 7861 7862 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7863 throw new WindowsApiException("SendInput", GetLastError()); 7864 } 7865 } else version(X11) { 7866 auto disp = XDisplayConnection.get(); 7867 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 7868 XFlush(disp); 7869 } else throw new NotYetImplementedException(); 7870 } 7871 7872 /// 7873 static void moveMouseArrowTo(int x, int y) { 7874 version(Windows) { 7875 INPUT input; 7876 input.type = INPUT_MOUSE; 7877 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7878 input.mi.dx = x; 7879 input.mi.dy = y; 7880 input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; 7881 7882 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7883 throw new WindowsApiException("SendInput", GetLastError()); 7884 } 7885 } else version(X11) { 7886 auto disp = XDisplayConnection.get(); 7887 auto root = RootWindow(disp, DefaultScreen(disp)); 7888 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 7889 XFlush(disp); 7890 } else throw new NotYetImplementedException(); 7891 } 7892 } 7893 7894 7895 7896 /++ 7897 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 7898 7899 See_Also: 7900 $(LIST 7901 *[ScreenPainter] 7902 *[ScreenPainter.rasterOp] 7903 ) 7904 +/ 7905 enum RasterOp { 7906 normal, /// Replaces the pixel. 7907 xor, /// Uses bitwise xor to draw. 7908 } 7909 7910 // being phobos-free keeps the size WAY down 7911 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 7912 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 7913 package(arsd) const(wchar)* toWStringz(string s) { 7914 wstring r; 7915 foreach(dchar c; s) 7916 r ~= c; 7917 r ~= '\0'; 7918 return r.ptr; 7919 } 7920 private string[] split(in void[] a, char c) { 7921 string[] ret; 7922 size_t previous = 0; 7923 foreach(i, char ch; cast(ubyte[]) a) { 7924 if(ch == c) { 7925 ret ~= cast(string) a[previous .. i]; 7926 previous = i + 1; 7927 } 7928 } 7929 if(previous != a.length) 7930 ret ~= cast(string) a[previous .. $]; 7931 return ret; 7932 } 7933 7934 version(without_opengl) { 7935 enum OpenGlOptions { 7936 no, 7937 } 7938 } else { 7939 /++ 7940 Determines if you want an OpenGL context created on the new window. 7941 7942 7943 See more: [#topics-3d|in the 3d topic]. 7944 7945 --- 7946 import arsd.simpledisplay; 7947 void main() { 7948 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 7949 7950 // Set up the matrix 7951 window.setAsCurrentOpenGlContext(); // make this window active 7952 7953 // This is called on each frame, we will draw our scene 7954 window.redrawOpenGlScene = delegate() { 7955 7956 }; 7957 7958 window.eventLoop(0); 7959 } 7960 --- 7961 +/ 7962 enum OpenGlOptions { 7963 no, /// No OpenGL context is created 7964 yes, /// Yes, create an OpenGL context 7965 } 7966 7967 version(X11) { 7968 static if (!SdpyIsUsingIVGLBinds) { 7969 7970 7971 struct __GLXFBConfigRec {} 7972 alias GLXFBConfig = __GLXFBConfigRec*; 7973 7974 //pragma(lib, "GL"); 7975 //pragma(lib, "GLU"); 7976 interface GLX { 7977 extern(C) nothrow @nogc { 7978 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 7979 const int *attrib_list); 7980 7981 void glXCopyContext(Display *dpy, GLXContext src, 7982 GLXContext dst, arch_ulong mask); 7983 7984 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 7985 GLXContext share_list, Bool direct); 7986 7987 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 7988 Pixmap pixmap); 7989 7990 void glXDestroyContext(Display *dpy, GLXContext ctx); 7991 7992 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 7993 7994 int glXGetConfig(Display *dpy, XVisualInfo *vis, 7995 int attrib, int *value); 7996 7997 GLXContext glXGetCurrentContext(); 7998 7999 GLXDrawable glXGetCurrentDrawable(); 8000 8001 Bool glXIsDirect(Display *dpy, GLXContext ctx); 8002 8003 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 8004 GLXContext ctx); 8005 8006 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 8007 8008 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 8009 8010 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 8011 8012 void glXUseXFont(Font font, int first, int count, int list_base); 8013 8014 void glXWaitGL(); 8015 8016 void glXWaitX(); 8017 8018 8019 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 8020 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 8021 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 8022 8023 char* glXQueryExtensionsString (Display*, int); 8024 void* glXGetProcAddress (const(char)*); 8025 8026 } 8027 } 8028 8029 version(OSX) 8030 mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx; 8031 else 8032 mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx; 8033 shared static this() { 8034 glx.loadDynamicLibrary(); 8035 } 8036 8037 alias glbindGetProcAddress = glXGetProcAddress; 8038 } 8039 } else version(Windows) { 8040 /* it is done below by interface GL */ 8041 } else 8042 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."); 8043 } 8044 8045 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 8046 alias Resizablity = Resizability; 8047 8048 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 8049 enum Resizability { 8050 fixedSize, /// the window cannot be resized. If it is resized anyway, simpledisplay will position and truncate your drawn content without necessarily informing your program, maintaining the API illusion of a non-resizable window. 8051 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. This allows most control for both user and you as the library consumer, but you also have to do the most work to handle it well. 8052 /++ 8053 $(PITFALL 8054 Planned for the future but not implemented. 8055 ) 8056 8057 Allow the user to resize the window, but try to maintain the original aspect ratio of the client area. The simpledisplay library may letterbox your content if necessary but will not stretch it. The windowResized delegate and width and height members will be updated with the size. 8058 8059 History: 8060 Added November 11, 2022, but not yet implemented and may not be for some time. 8061 +/ 8062 /*@__future*/ allowResizingMaintainingAspectRatio, 8063 /++ 8064 If possible, your drawing buffer will remain the same size and simply be automatically scaled to the new window size, letterboxing if needed to keep the aspect ratio. If this is impossible, it will fallback to [fixedSize]. The simpledisplay library will always provide the illusion that your window is the same size you requested, even if it scales things for you, meaning [width] and [height] will never change. 8065 8066 History: 8067 Prior to November 11, 2022, width and height would change, which made this mode harder to use than intended. While I had documented this as a possiblity, I still considered it a bug, a leaky abstraction, and changed the code to tighten it up. After that date, the width and height members, as well as mouse coordinates, are always scaled to maintain the illusion of a fixed canvas size. 8068 8069 Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all. 8070 +/ 8071 automaticallyScaleIfPossible, 8072 } 8073 /// ditto 8074 alias Resizeability = Resizability; 8075 8076 8077 /++ 8078 Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 8079 +/ 8080 enum TextAlignment : uint { 8081 Left = 0, /// 8082 Center = 1, /// 8083 Right = 2, /// 8084 8085 VerticalTop = 0, /// 8086 VerticalCenter = 4, /// 8087 VerticalBottom = 8, /// 8088 } 8089 8090 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 8091 alias Rectangle = arsd.color.Rectangle; 8092 8093 8094 /++ 8095 Keyboard press and release events. 8096 +/ 8097 struct KeyEvent { 8098 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 8099 Key key; 8100 ubyte hardwareCode; /// A platform and hardware specific code for the key 8101 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 8102 8103 deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character; 8104 8105 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 8106 8107 SimpleWindow window; /// associated Window 8108 8109 /++ 8110 A view into the upcoming buffer holding coming character events that are sent if and only if neither 8111 the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))` 8112 to predict if char events are actually coming.. 8113 8114 Only available on X systems since this information is not given ahead of time elsewhere. 8115 (Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.) 8116 8117 I'm adding this because it is useful to the terminal emulator, but given its platform specificness 8118 and potential quirks I'd recommend avoiding it. 8119 8120 History: 8121 Added April 26, 2021 (dub v9.5) 8122 +/ 8123 version(X11) 8124 dchar[] charsPossible; 8125 8126 // convert key event to simplified string representation a-la emacs 8127 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 8128 uint dpos = 0; 8129 void put (const(char)[] s...) nothrow @trusted { 8130 static if (growdest) { 8131 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 8132 } else { 8133 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 8134 } 8135 } 8136 8137 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 8138 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 8139 } 8140 8141 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 8142 8143 // put modifiers 8144 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 8145 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 8146 putMod(ModifierState.alt, Key.Alt, "Alt+"); 8147 putMod(ModifierState.windows, Key.Shift, "Windows+"); 8148 putMod(ModifierState.shift, Key.Shift, "Shift+"); 8149 8150 if (this.key) { 8151 foreach (string kn; __traits(allMembers, Key)) { 8152 if (this.key == __traits(getMember, Key, kn)) { 8153 // HACK! 8154 static if (kn == "N0") put("0"); 8155 else static if (kn == "N1") put("1"); 8156 else static if (kn == "N2") put("2"); 8157 else static if (kn == "N3") put("3"); 8158 else static if (kn == "N4") put("4"); 8159 else static if (kn == "N5") put("5"); 8160 else static if (kn == "N6") put("6"); 8161 else static if (kn == "N7") put("7"); 8162 else static if (kn == "N8") put("8"); 8163 else static if (kn == "N9") put("9"); 8164 else put(kn); 8165 return dest[0..dpos]; 8166 } 8167 } 8168 put("Unknown"); 8169 } else { 8170 if (dpos && dest[dpos-1] == '+') --dpos; 8171 } 8172 return dest[0..dpos]; 8173 } 8174 8175 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 8176 8177 /** Parse string into key name with modifiers. It accepts things like: 8178 * 8179 * C-H-1 -- emacs style (ctrl, and windows, and 1) 8180 * 8181 * Ctrl+Win+1 -- windows style 8182 * 8183 * Ctrl-Win-1 -- '-' is a valid delimiter too 8184 * 8185 * Ctrl Win 1 -- and space 8186 * 8187 * and even "Win + 1 + Ctrl". 8188 */ 8189 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 8190 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 8191 8192 // remove trailing spaces 8193 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 8194 8195 // tokens delimited by blank, '+', or '-' 8196 // null on eol 8197 const(char)[] getToken () nothrow @trusted @nogc { 8198 // remove leading spaces and delimiters 8199 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 8200 if (name.length == 0) return null; // oops, no more tokens 8201 // get token 8202 size_t epos = 0; 8203 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 8204 assert(epos > 0 && epos <= name.length); 8205 auto res = name[0..epos]; 8206 name = name[epos..$]; 8207 return res; 8208 } 8209 8210 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 8211 if (s0.length != s1.length) return false; 8212 foreach (immutable ci, char c0; s0) { 8213 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 8214 char c1 = s1[ci]; 8215 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 8216 if (c0 != c1) return false; 8217 } 8218 return true; 8219 } 8220 8221 if (ignoreModsOut !is null) *ignoreModsOut = false; 8222 if (updown !is null) *updown = -1; 8223 KeyEvent res; 8224 res.key = cast(Key)0; // just in case 8225 const(char)[] tk, tkn; // last token 8226 bool allowEmascStyle = true; 8227 bool ignoreModifiers = false; 8228 tokenloop: for (;;) { 8229 tk = tkn; 8230 tkn = getToken(); 8231 //k8: yay, i took "Bloody Mess" trait from Fallout! 8232 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 8233 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 8234 if (allowEmascStyle && tkn.length != 0) { 8235 if (tk.length == 1) { 8236 char mdc = tk[0]; 8237 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 8238 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 8239 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 8240 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 8241 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 8242 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 8243 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 8244 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 8245 } 8246 } 8247 allowEmascStyle = false; 8248 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 8249 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 8250 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 8251 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 8252 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 8253 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 8254 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 8255 if (tk.length == 0) continue; 8256 // try key name 8257 if (res.key == 0) { 8258 // little hack 8259 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 8260 final switch (tk[0]) { 8261 case '0': tk = "N0"; break; 8262 case '1': tk = "N1"; break; 8263 case '2': tk = "N2"; break; 8264 case '3': tk = "N3"; break; 8265 case '4': tk = "N4"; break; 8266 case '5': tk = "N5"; break; 8267 case '6': tk = "N6"; break; 8268 case '7': tk = "N7"; break; 8269 case '8': tk = "N8"; break; 8270 case '9': tk = "N9"; break; 8271 } 8272 } 8273 foreach (string kn; __traits(allMembers, Key)) { 8274 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 8275 } 8276 } 8277 // unknown or duplicate key name, get out of here 8278 break; 8279 } 8280 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 8281 return res; // something 8282 } 8283 8284 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 8285 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 8286 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 8287 if (kk == k) { mask |= mst; kk = cast(Key)0; } 8288 } 8289 bool ignoreMods; 8290 int updown; 8291 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 8292 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 8293 if (this.key != ke.key) { 8294 // things like "ctrl+alt" are complicated 8295 uint tkm = this.modifierState&modmask; 8296 uint kkm = ke.modifierState&modmask; 8297 Key tk = this.key; 8298 // ke 8299 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 8300 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 8301 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 8302 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 8303 // this 8304 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 8305 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 8306 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 8307 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 8308 return (tk == ke.key && tkm == kkm); 8309 } 8310 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 8311 } 8312 } 8313 8314 /// Sets the application name. 8315 @property string ApplicationName(string name) { 8316 return _applicationName = name; 8317 } 8318 8319 string _applicationName; 8320 8321 /// ditto 8322 @property string ApplicationName() { 8323 if(_applicationName is null) { 8324 import core.runtime; 8325 return Runtime.args[0]; 8326 } 8327 return _applicationName; 8328 } 8329 8330 8331 /// Type of a [MouseEvent]. 8332 enum MouseEventType : int { 8333 motion = 0, /// The mouse moved inside the window 8334 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 8335 buttonReleased = 2, /// A mouse button was released 8336 } 8337 8338 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 8339 /++ 8340 Listen for this on your event listeners if you are interested in mouse action. 8341 8342 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. 8343 8344 Examples: 8345 8346 This will draw boxes on the window with the mouse as you hold the left button. 8347 --- 8348 import arsd.simpledisplay; 8349 8350 void main() { 8351 auto window = new SimpleWindow(); 8352 8353 window.eventLoop(0, 8354 (MouseEvent ev) { 8355 if(ev.modifierState & ModifierState.leftButtonDown) { 8356 auto painter = window.draw(); 8357 painter.fillColor = Color.red; 8358 painter.outlineColor = Color.black; 8359 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 8360 } 8361 } 8362 ); 8363 } 8364 --- 8365 +/ 8366 struct MouseEvent { 8367 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 8368 8369 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. 8370 int y; /// Current Y position of the cursor when the event fired. 8371 8372 int dx; /// Change in X position since last report 8373 int dy; /// Change in Y position since last report 8374 8375 MouseButton button; /// See [MouseButton] 8376 int modifierState; /// See [ModifierState] 8377 8378 version(X11) 8379 private Time timestamp; 8380 8381 /++ 8382 Returns true if this is a mouse wheel/touchpad scroll event. 8383 8384 History: 8385 Added December 21, 2025 8386 +/ 8387 bool isMouseWheel() const { 8388 return button == MouseButton.wheelLeft || button == MouseButton.wheelRight || button == MouseButton.wheelUp || button == MouseButton.wheelDown; 8389 } 8390 8391 /// Returns a linear representation of mouse button, 8392 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 8393 /// 8394 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 8395 @property ubyte buttonLinear() const { 8396 import core.bitop; 8397 if(button == 0) 8398 return 0; 8399 return (bsf(button) + 1) & 0b1111; 8400 } 8401 8402 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 8403 8404 SimpleWindow window; /// The window in which the event happened. 8405 8406 Point globalCoordinates() { 8407 Point p; 8408 if(window is null) 8409 throw new Exception("wtf"); 8410 static if(UsingSimpledisplayX11) { 8411 Window child; 8412 XTranslateCoordinates( 8413 XDisplayConnection.get, 8414 window.impl.window, 8415 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 8416 x, y, &p.x, &p.y, &child); 8417 return p; 8418 } else version(Windows) { 8419 POINT[1] points; 8420 points[0].x = x; 8421 points[0].y = y; 8422 MapWindowPoints( 8423 window.impl.hwnd, 8424 null, 8425 points.ptr, 8426 points.length 8427 ); 8428 p.x = points[0].x; 8429 p.y = points[0].y; 8430 8431 return p; 8432 } else version(OSXCocoa) { 8433 auto rect = window.window.frame; 8434 // FIXME: mapped right? 8435 return Point(cast(int) rect.origin.x + x, cast(int) rect.origin.y + y); 8436 } else version(Emscripten) { 8437 throw new NotYetImplementedException(); 8438 } else static assert(0); 8439 } 8440 8441 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 8442 8443 /** 8444 can contain emacs-like modifier prefix 8445 case-insensitive names: 8446 lmbX/leftX 8447 rmbX/rightX 8448 mmbX/middleX 8449 wheelX 8450 motion (no prefix allowed) 8451 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 8452 */ 8453 static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 8454 if (str.length == 0) return false; // just in case 8455 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 8456 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 8457 auto anchor = str; 8458 uint mods = 0; // uint.max == any 8459 // interesting bits in kmod 8460 uint kmodmask = 8461 ModifierState.shift| 8462 ModifierState.ctrl| 8463 ModifierState.alt| 8464 ModifierState.windows| 8465 ModifierState.leftButtonDown| 8466 ModifierState.middleButtonDown| 8467 ModifierState.rightButtonDown| 8468 0; 8469 uint lastButt = uint.max; // otherwise, bit 31 means "down" 8470 bool wasButtons = false; 8471 while (str.length) { 8472 if (str.ptr[0] <= ' ') { 8473 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 8474 continue; 8475 } 8476 // one-letter modifier? 8477 if (str.length >= 2 && str.ptr[1] == '-') { 8478 switch (str.ptr[0]) { 8479 case '*': // "any" modifier (cannot be undone) 8480 mods = mods.max; 8481 break; 8482 case 'C': case 'c': // emacs "ctrl" 8483 if (mods != mods.max) mods |= ModifierState.ctrl; 8484 break; 8485 case 'M': case 'm': // emacs "meta" 8486 if (mods != mods.max) mods |= ModifierState.alt; 8487 break; 8488 case 'S': case 's': // emacs "shift" 8489 if (mods != mods.max) mods |= ModifierState.shift; 8490 break; 8491 case 'H': case 'h': // emacs "hyper" (aka winkey) 8492 if (mods != mods.max) mods |= ModifierState.windows; 8493 break; 8494 default: 8495 return false; // unknown modifier 8496 } 8497 str = str[2..$]; 8498 continue; 8499 } 8500 // word 8501 char[16] buf = void; // locased 8502 auto wep = 0; 8503 while (str.length) { 8504 immutable char ch = str.ptr[0]; 8505 if (ch <= ' ' || ch == '-') break; 8506 str = str[1..$]; 8507 if (wep > buf.length) return false; // too long 8508 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 8509 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 8510 else return false; // invalid char 8511 } 8512 if (wep == 0) return false; // just in case 8513 uint bnum; 8514 enum UpDown { None = -1, Up, Down, Any } 8515 auto updown = UpDown.None; // 0: up; 1: down 8516 switch (buf[0..wep]) { 8517 // left button 8518 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 8519 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 8520 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 8521 case "lmb": case "left": bnum = 0; break; 8522 // middle button 8523 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 8524 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 8525 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 8526 case "mmb": case "middle": bnum = 1; break; 8527 // right button 8528 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 8529 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 8530 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 8531 case "rmb": case "right": bnum = 2; break; 8532 // wheel 8533 case "wheelup": updown = UpDown.Up; goto case "wheel"; 8534 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 8535 case "wheelany": updown = UpDown.Any; goto case "wheel"; 8536 case "wheel": bnum = 3; break; 8537 // motion 8538 case "motion": bnum = 7; break; 8539 // unknown 8540 default: return false; 8541 } 8542 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 8543 // parse possible "-up" or "-down" 8544 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 8545 wep = 0; 8546 foreach (immutable idx, immutable char ch; str[1..$]) { 8547 if (ch <= ' ' || ch == '-') break; 8548 assert(idx == wep); // for now; trick 8549 if (wep > buf.length) { wep = 0; break; } // too long 8550 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 8551 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 8552 else { wep = 0; break; } // invalid char 8553 } 8554 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 8555 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 8556 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 8557 // remove parsed part 8558 if (updown != UpDown.None) str = str[wep+1..$]; 8559 } 8560 if (updown == UpDown.None) { 8561 updown = UpDown.Down; 8562 } 8563 wasButtons = wasButtons || (bnum <= 2); 8564 //assert(updown != UpDown.None); 8565 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 8566 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 8567 if (lastButt != lastButt.max) { 8568 if ((lastButt&0xff) >= 3) return false; // wheel or motion 8569 if (mods != mods.max) { 8570 uint butbit = 0; 8571 final switch (lastButt&0x03) { 8572 case 0: butbit = ModifierState.leftButtonDown; break; 8573 case 1: butbit = ModifierState.middleButtonDown; break; 8574 case 2: butbit = ModifierState.rightButtonDown; break; 8575 } 8576 if (lastButt&Flag.Down) mods |= butbit; 8577 else if (lastButt&Flag.Up) mods &= ~butbit; 8578 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 8579 } 8580 } 8581 // remember last button 8582 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 8583 } 8584 // no button -- nothing to do 8585 if (lastButt == lastButt.max) return false; 8586 // done parsing, check if something's left 8587 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 8588 // remove action button from mask 8589 if ((lastButt&0xff) < 3) { 8590 final switch (lastButt&0x03) { 8591 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 8592 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 8593 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 8594 } 8595 } 8596 // special case: "Motion" means "ignore buttons" 8597 if ((lastButt&0xff) == 7 && !wasButtons) { 8598 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 8599 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 8600 } 8601 uint kmod = event.modifierState&kmodmask; 8602 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 8603 // check modifier state 8604 if (mods != mods.max) { 8605 if (kmod != mods) return false; 8606 } 8607 // now check type 8608 if ((lastButt&0xff) == 7) { 8609 // motion 8610 if (event.type != MouseEventType.motion) return false; 8611 } else if ((lastButt&0xff) == 3) { 8612 // wheel 8613 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 8614 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 8615 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 8616 return false; 8617 } else { 8618 // buttons 8619 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 8620 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 8621 { 8622 return false; 8623 } 8624 // button number 8625 switch (lastButt&0x03) { 8626 case 0: if (event.button != MouseButton.left) return false; break; 8627 case 1: if (event.button != MouseButton.middle) return false; break; 8628 case 2: if (event.button != MouseButton.right) return false; break; 8629 default: return false; 8630 } 8631 } 8632 return true; 8633 } 8634 } 8635 8636 version(arsd_mevent_strcmp_test) unittest { 8637 MouseEvent event; 8638 event.type = MouseEventType.buttonPressed; 8639 event.button = MouseButton.left; 8640 event.modifierState = ModifierState.ctrl; 8641 assert(event == "C-LMB"); 8642 assert(event != "C-LMBUP"); 8643 assert(event != "C-LMB-UP"); 8644 assert(event != "C-S-LMB"); 8645 assert(event == "*-LMB"); 8646 assert(event != "*-LMB-UP"); 8647 8648 event.type = MouseEventType.buttonReleased; 8649 assert(event != "C-LMB"); 8650 assert(event == "C-LMBUP"); 8651 assert(event == "C-LMB-UP"); 8652 assert(event != "C-S-LMB"); 8653 assert(event != "*-LMB"); 8654 assert(event == "*-LMB-UP"); 8655 8656 event.button = MouseButton.right; 8657 event.modifierState |= ModifierState.shift; 8658 event.type = MouseEventType.buttonPressed; 8659 assert(event != "C-LMB"); 8660 assert(event != "C-LMBUP"); 8661 assert(event != "C-LMB-UP"); 8662 assert(event != "C-S-LMB"); 8663 assert(event != "*-LMB"); 8664 assert(event != "*-LMB-UP"); 8665 8666 assert(event != "C-RMB"); 8667 assert(event != "C-RMBUP"); 8668 assert(event != "C-RMB-UP"); 8669 assert(event == "C-S-RMB"); 8670 assert(event == "*-RMB"); 8671 assert(event != "*-RMB-UP"); 8672 } 8673 8674 /// This gives a few more options to drawing lines and such 8675 struct Pen { 8676 Color color; /// the foreground color 8677 int width = 1; /// width of the line. please note that on X, wide lines are drawn centered on the coordinates, so you may have to offset things. 8678 Style style; /// See [Style] 8679 /+ 8680 // From X.h 8681 8682 #define LineSolid 0 8683 #define LineOnOffDash 1 8684 #define LineDoubleDash 2 8685 LineDou- The full path of the line is drawn, but the 8686 bleDash even dashes are filled differently from the 8687 odd dashes (see fill-style) with CapButt 8688 style used where even and odd dashes meet. 8689 8690 8691 8692 /* capStyle */ 8693 8694 #define CapNotLast 0 8695 #define CapButt 1 8696 #define CapRound 2 8697 #define CapProjecting 3 8698 8699 /* joinStyle */ 8700 8701 #define JoinMiter 0 8702 #define JoinRound 1 8703 #define JoinBevel 2 8704 8705 /* fillStyle */ 8706 8707 #define FillSolid 0 8708 #define FillTiled 1 8709 #define FillStippled 2 8710 #define FillOpaqueStippled 3 8711 8712 8713 +/ 8714 /// Style of lines drawn 8715 enum Style { 8716 Solid, /// a solid line 8717 Dashed, /// a dashed line 8718 Dotted, /// a dotted line 8719 } 8720 } 8721 8722 8723 /++ 8724 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 8725 8726 You can load an image with an alpha channel, but you cannot draw that in the current implementation. If you want alpha blending when drawing, use [Sprite] instead. 8727 8728 8729 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 8730 8731 $(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.) 8732 8733 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. 8734 8735 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 8736 8737 $(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. 8738 8739 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! 8740 8741 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!) 8742 8743 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 8744 8745 --- 8746 auto image = new Image(256, 256); 8747 scope(exit) destroy(image); 8748 --- 8749 8750 As long as you don't hold on to it outside the scope. 8751 8752 I might change it to be an owned pointer at some point in the future. 8753 8754 ) 8755 8756 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 8757 you can also often get a fair amount of speedup by getting the raw data format and 8758 writing some custom code. 8759 8760 FIXME INSERT EXAMPLES HERE 8761 8762 8763 +/ 8764 final class Image { 8765 /// 8766 this(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 8767 this.width = width; 8768 this.height = height; 8769 this.enableAlpha = enableAlpha; 8770 8771 impl.createImage(width, height, forcexshm, enableAlpha); 8772 } 8773 8774 /// 8775 this(Size size, bool forcexshm=false, bool enableAlpha = false) { 8776 this(size.width, size.height, forcexshm, enableAlpha); 8777 } 8778 8779 private bool suppressDestruction; 8780 8781 version(X11) 8782 this(XImage* handle) { 8783 this.handle = handle; 8784 this.rawData = cast(ubyte*) handle.data; 8785 this.width = handle.width; 8786 this.height = handle.height; 8787 this.enableAlpha = handle.depth == 32; 8788 suppressDestruction = true; 8789 } 8790 8791 ~this() { 8792 if(suppressDestruction) return; 8793 impl.dispose(); 8794 } 8795 8796 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 8797 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 8798 pure const @system nothrow { 8799 /* 8800 To use these to draw a blue rectangle with size WxH at position X,Y... 8801 8802 // make certain that it will fit before we proceed 8803 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! 8804 8805 // gather all the values you'll need up front. These can be kept until the image changes size if you want 8806 // (though calculating them isn't really that expensive). 8807 auto nextLineAdjustment = img.adjustmentForNextLine(); 8808 auto offR = img.redByteOffset(); 8809 auto offB = img.blueByteOffset(); 8810 auto offG = img.greenByteOffset(); 8811 auto bpp = img.bytesPerPixel(); 8812 8813 auto data = img.getDataPointer(); 8814 8815 // figure out the starting byte offset 8816 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 8817 8818 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 8819 8820 // and now our drawing loop for the rectangle 8821 foreach(y; 0 .. H) { 8822 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 8823 foreach(x; 0 .. W) { 8824 // write our color 8825 data[offR] = 0; 8826 data[offG] = 0; 8827 data[offB] = 255; 8828 8829 data += bpp; // moving to the next pixel is just an addition... 8830 } 8831 startOfLine += nextLineAdjustment; 8832 } 8833 8834 8835 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 8836 8837 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 8838 can be made into a bitmask or something so we can write them as *uint... 8839 */ 8840 8841 /// 8842 int offsetForTopLeftPixel() { 8843 version(X11) { 8844 return 0; 8845 } else version(Windows) { 8846 if(enableAlpha) { 8847 return (width * 4) * (height - 1); 8848 } else { 8849 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 8850 } 8851 } else version(OSXCocoa) { 8852 return 0 ; //throw new NotYetImplementedException(); 8853 } else version(Emscripten) { 8854 return 0; 8855 } else static assert(0, "fill in this info for other OSes"); 8856 } 8857 8858 /// 8859 int offsetForPixel(int x, int y) { 8860 version(X11) { 8861 auto offset = (y * width + x) * 4; 8862 return offset; 8863 } else version(Windows) { 8864 if(enableAlpha) { 8865 auto itemsPerLine = width * 4; 8866 // remember, bmps are upside down 8867 auto offset = itemsPerLine * (height - y - 1) + x * 4; 8868 return offset; 8869 } else { 8870 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 8871 // remember, bmps are upside down 8872 auto offset = itemsPerLine * (height - y - 1) + x * 3; 8873 return offset; 8874 } 8875 } else version(OSXCocoa) { 8876 return (y * width + x) * 4 ; //throw new NotYetImplementedException(); 8877 } else version(Emscripten) { 8878 return (y * width + x) * 4 ; //throw new NotYetImplementedException(); 8879 } else static assert(0, "fill in this info for other OSes"); 8880 } 8881 8882 /// 8883 int adjustmentForNextLine() { 8884 version(X11) { 8885 return width * 4; 8886 } else version(Windows) { 8887 // windows bmps are upside down, so the adjustment is actually negative 8888 if(enableAlpha) 8889 return - (cast(int) width * 4); 8890 else 8891 return -((cast(int) width * 3 + 3) / 4) * 4; 8892 } else version(OSXCocoa) { 8893 return width * 4 ; //throw new NotYetImplementedException(); 8894 } else version(Emscripten) { 8895 return width * 4 ; //throw new NotYetImplementedException(); 8896 } else static assert(0, "fill in this info for other OSes"); 8897 } 8898 8899 /// once you have the position of a pixel, use these to get to the proper color 8900 int redByteOffset() { 8901 version(X11) { 8902 return 2; 8903 } else version(Windows) { 8904 return 2; 8905 } else version(OSXCocoa) { 8906 return 2 ; //throw new NotYetImplementedException(); 8907 } else version(Emscripten) { 8908 return 2 ; //throw new NotYetImplementedException(); 8909 } else static assert(0, "fill in this info for other OSes"); 8910 } 8911 8912 /// 8913 int greenByteOffset() { 8914 version(X11) { 8915 return 1; 8916 } else version(Windows) { 8917 return 1; 8918 } else version(OSXCocoa) { 8919 return 1 ; //throw new NotYetImplementedException(); 8920 } else version(Emscripten) { 8921 return 1 ; //throw new NotYetImplementedException(); 8922 } else static assert(0, "fill in this info for other OSes"); 8923 } 8924 8925 /// 8926 int blueByteOffset() { 8927 version(X11) { 8928 return 0; 8929 } else version(Windows) { 8930 return 0; 8931 } else version(OSXCocoa) { 8932 return 0 ; //throw new NotYetImplementedException(); 8933 } else version(Emscripten) { 8934 return 0 ; //throw new NotYetImplementedException(); 8935 } else static assert(0, "fill in this info for other OSes"); 8936 } 8937 8938 /// Only valid if [enableAlpha] is true 8939 int alphaByteOffset() { 8940 version(X11) { 8941 return 3; 8942 } else version(Windows) { 8943 return 3; 8944 } else version(OSXCocoa) { 8945 return 3; //throw new NotYetImplementedException(); 8946 } else version(Emscripten) { 8947 return 3 ; //throw new NotYetImplementedException(); 8948 } else static assert(0, "fill in this info for other OSes"); 8949 } 8950 } 8951 8952 /// 8953 final void putPixel(int x, int y, Color c) { 8954 if(x < 0 || x >= width) 8955 return; 8956 if(y < 0 || y >= height) 8957 return; 8958 8959 impl.setPixel(x, y, c); 8960 } 8961 8962 /// 8963 final Color getPixel(int x, int y) { 8964 if(x < 0 || x >= width) 8965 return Color.transparent; 8966 if(y < 0 || y >= height) 8967 return Color.transparent; 8968 8969 version(OSXCocoa) throw new NotYetImplementedException(); else 8970 return impl.getPixel(x, y); 8971 } 8972 8973 /// 8974 final void opIndexAssign(Color c, int x, int y) { 8975 putPixel(x, y, c); 8976 } 8977 8978 /// 8979 TrueColorImage toTrueColorImage() { 8980 auto tci = new TrueColorImage(width, height); 8981 convertToRgbaBytes(tci.imageData.bytes); 8982 return tci; 8983 } 8984 8985 /// 8986 static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) { 8987 auto tci = i.getAsTrueColorImage(); 8988 auto img = new Image(tci.width, tci.height, false, enableAlpha); 8989 static if(UsingSimpledisplayX11) 8990 img.premultiply = premultiply; 8991 img.setRgbaBytes(tci.imageData.bytes); 8992 return img; 8993 } 8994 8995 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 8996 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 8997 /// if you pass null, it will allocate a new one. 8998 ubyte[] getRgbaBytes(ubyte[] where = null) { 8999 if(where is null) 9000 where = new ubyte[this.width*this.height*4]; 9001 convertToRgbaBytes(where); 9002 return where; 9003 } 9004 9005 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 9006 void setRgbaBytes(in ubyte[] from ) { 9007 assert(from.length == this.width * this.height * 4); 9008 setFromRgbaBytes(from); 9009 } 9010 9011 // FIXME: make properly cross platform by getting rgba right 9012 9013 /// warning: this is not portable across platforms because the data format can change 9014 ubyte* getDataPointer() { 9015 return impl.rawData; 9016 } 9017 9018 /// for use with getDataPointer 9019 final int bytesPerLine() const pure @safe nothrow { 9020 version(Windows) 9021 return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 9022 else version(X11) 9023 return 4 * width; 9024 else version(OSXCocoa) 9025 return 4 * width; 9026 else static assert(0); 9027 } 9028 9029 /// for use with getDataPointer 9030 final int bytesPerPixel() const pure @safe nothrow { 9031 version(Windows) 9032 return enableAlpha ? 4 : 3; 9033 else version(X11) 9034 return 4; 9035 else version(OSXCocoa) 9036 return 4; 9037 else static assert(0); 9038 } 9039 9040 /// 9041 immutable int width; 9042 9043 /// 9044 immutable int height; 9045 9046 /// 9047 immutable bool enableAlpha; 9048 //private: 9049 mixin NativeImageImplementation!() impl; 9050 } 9051 9052 /++ 9053 A convenience function to pop up a window displaying the image. 9054 If you pass a win, it will draw the image in it. Otherwise, it will 9055 create a window with the size of the image and run its event loop, closing 9056 when a key is pressed. 9057 9058 History: 9059 `BlockingMode` parameter added on December 8, 2021. Previously, it would 9060 always block until the application quit which could cause bizarre behavior 9061 inside a more complex application. Now, the default is to block until 9062 this window closes if it is the only event loop running, and otherwise, 9063 not to block at all and just pop up the display window asynchronously. 9064 +/ 9065 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) { 9066 if(win is null) { 9067 win = new SimpleWindow(image); 9068 { 9069 auto p = win.draw; 9070 p.drawImage(Point(0, 0), image); 9071 } 9072 win.eventLoopWithBlockingMode( 9073 bm, 0, 9074 (KeyEvent ev) { 9075 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 9076 } ); 9077 } else { 9078 win.image = image; 9079 } 9080 } 9081 9082 enum FontWeight : int { 9083 dontcare = 0, 9084 thin = 100, 9085 extralight = 200, 9086 light = 300, 9087 regular = 400, 9088 medium = 500, 9089 semibold = 600, 9090 bold = 700, 9091 extrabold = 800, 9092 heavy = 900 9093 } 9094 9095 /++ 9096 Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont]. 9097 9098 History: 9099 Added October 24, 2022. The methods were already on [OperatingSystemFont] before that. 9100 +/ 9101 interface MeasurableFont { 9102 /++ 9103 History: 9104 Added April 12, 2025 9105 +/ 9106 //version(OSXCocoa) 9107 alias fnum = float; 9108 //else 9109 //alias fnum = int; 9110 9111 9112 /++ 9113 Returns true if it is a monospace font, meaning each of the 9114 glyphs (at least the ascii characters) have matching width 9115 and no kerning, so you can determine the display width of some 9116 strings by simply multiplying the string width by [averageWidth]. 9117 9118 (Please note that multiply doesn't $(I actually) work in general, 9119 consider characters like tab and newline, but it does sometimes.) 9120 +/ 9121 bool isMonospace(); 9122 9123 /++ 9124 The average width of glyphs in the font, traditionally equal to the 9125 width of the lowercase x. Can be used to estimate bounding boxes, 9126 especially if the font [isMonospace]. 9127 9128 Given in pixels. 9129 +/ 9130 fnum averageWidth(); 9131 /++ 9132 The height of the bounding box of a line. 9133 +/ 9134 fnum height(); 9135 /++ 9136 The maximum ascent of a glyph above the baseline. 9137 9138 Given in pixels. 9139 +/ 9140 fnum ascent(); 9141 /++ 9142 The maximum descent of a glyph below the baseline. For example, how low the g might go. 9143 9144 Given in pixels. 9145 +/ 9146 fnum descent(); 9147 /++ 9148 The display width of the given string, and if you provide a window, it will use it to 9149 make the pixel count on screen more accurate too, but this shouldn't generally be necessary. 9150 9151 Given in pixels. 9152 +/ 9153 fnum stringWidth(scope const(char)[] s, SimpleWindow window = null); 9154 9155 } 9156 9157 int castFnumToCnum(MeasurableFont.fnum i) { 9158 static if(is(MeasurableFont.fnum : long)) 9159 return cast(int) i; 9160 else 9161 return cast(int) (i + 0.9); 9162 } 9163 9164 // FIXME: i need a font cache and it needs to handle disconnects. 9165 9166 /++ 9167 Represents a font loaded off the operating system or the X server. 9168 9169 9170 While the api here is unified cross platform, the fonts are not necessarily 9171 available, even across machines of the same platform, so be sure to always check 9172 for null (using [isNull]) and have a fallback plan. 9173 9174 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 9175 9176 Worst case, a null font will automatically fall back to the default font loaded 9177 for your system. 9178 +/ 9179 class OperatingSystemFont : MeasurableFont { 9180 // FIXME: when the X Connection is lost, these need to be invalidated! 9181 // that means I need to store the original stuff again to reconstruct it too. 9182 9183 version(Emscripten) { 9184 void* font; 9185 } else version(X11) { 9186 XFontStruct* font; 9187 XFontSet fontset; 9188 9189 version(with_xft) { 9190 XftFont* xftFont; 9191 bool isXft; 9192 } 9193 } else version(Windows) { 9194 HFONT font; 9195 int width_; 9196 int height_; 9197 } else version(OSXCocoa) { 9198 NSFont font; 9199 } else static assert(0); 9200 9201 /++ 9202 Constructs the class and immediately calls [load]. 9203 +/ 9204 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 9205 load(name, size, weight, italic); 9206 } 9207 9208 /++ 9209 Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object. 9210 9211 You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you. 9212 9213 History: 9214 Added January 24, 2021. 9215 +/ 9216 this() { 9217 // this space intentionally left blank 9218 } 9219 9220 /++ 9221 Constructs a copy of the given font object. 9222 9223 History: 9224 Added January 7, 2023. 9225 +/ 9226 this(OperatingSystemFont font) { 9227 if(font is null || font.loadedInfo is LoadedInfo.init) 9228 loadDefault(); 9229 else 9230 load(font.loadedInfo.tupleof); 9231 } 9232 9233 /++ 9234 Loads specifically with the Xft library - a freetype font from a fontconfig string. 9235 9236 History: 9237 Added November 13, 2020. 9238 +/ 9239 version(with_xft) 9240 bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 9241 unload(); 9242 9243 if(!XftLibrary.attempted) { 9244 XftLibrary.loadDynamicLibrary(); 9245 } 9246 9247 if(!XftLibrary.loadSuccessful) 9248 return false; 9249 9250 auto display = XDisplayConnection.get; 9251 9252 char[256] nameBuffer = void; 9253 int nbp = 0; 9254 9255 void add(in char[] a) { 9256 nameBuffer[nbp .. nbp + a.length] = a[]; 9257 nbp += a.length; 9258 } 9259 add(name); 9260 9261 if(size) { 9262 add(":size="); 9263 add(toInternal!string(size)); 9264 } 9265 if(weight != FontWeight.dontcare && weight != 400) { 9266 if(weight < 400) 9267 add(":style=Light"); 9268 else 9269 add(":style=Bold"); 9270 add(":weight="); 9271 add(weightToString(weight)); 9272 } 9273 if(italic) { 9274 if(weight == FontWeight.dontcare) 9275 add(":style=Italic"); 9276 add(":slant=100"); 9277 } 9278 9279 nameBuffer[nbp] = 0; 9280 9281 this.xftFont = XftFontOpenName( 9282 display, 9283 DefaultScreen(display), 9284 nameBuffer.ptr 9285 ); 9286 9287 this.isXft = true; 9288 9289 if(xftFont !is null) { 9290 isMonospace_ = stringWidth("x") == stringWidth("M"); 9291 ascent_ = xftFont.ascent; 9292 descent_ = xftFont.descent; 9293 } 9294 9295 return !isNull(); 9296 } 9297 9298 /++ 9299 Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor. 9300 9301 9302 Fonts will be fed to you (possibly! it is platform and implementation dependent on if it is called immediately or later) asynchronously through the given delegate. It should return `true` if you want more, `false` if you are done. The delegate will be called once after finishing with a `init` value to let you know it is done and you can do final processing. 9303 9304 If `pattern` is null, it returns all available font families. 9305 9306 Please note that you may also receive fonts that do not match your given pattern. You should still filter them in the handler; the pattern is really just an optimization hint rather than a formal guarantee. 9307 9308 The format of the pattern is platform-specific. 9309 9310 History: 9311 Added May 1, 2021 (dub v9.5) 9312 +/ 9313 static void listFonts(string pattern, bool delegate(in char[] name) handler) { 9314 version(Windows) { 9315 auto hdc = GetDC(null); 9316 scope(exit) ReleaseDC(null, hdc); 9317 LOGFONT logfont; 9318 static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) { 9319 auto localHandler = *(cast(typeof(handler)*) p); 9320 return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0; 9321 } 9322 EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0); 9323 } else version(X11) { 9324 //import core.stdc.stdio; 9325 bool done = false; 9326 version(with_xft) { 9327 if(!XftLibrary.attempted) { 9328 XftLibrary.loadDynamicLibrary(); 9329 } 9330 9331 if(!XftLibrary.loadSuccessful) 9332 goto skipXft; 9333 9334 if(!FontConfigLibrary.attempted) 9335 FontConfigLibrary.loadDynamicLibrary(); 9336 if(!FontConfigLibrary.loadSuccessful) 9337 goto skipXft; 9338 9339 { 9340 auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null); 9341 if(got is null) 9342 goto skipXft; 9343 scope(exit) FcFontSetDestroy(got); 9344 9345 auto fontPatterns = got.fonts[0 .. got.nfont]; 9346 foreach(candidate; fontPatterns) { 9347 char* where, whereStyle; 9348 9349 char* pmg = FcNameUnparse(candidate); 9350 9351 //FcPatternGetString(candidate, "family", 0, &where); 9352 //FcPatternGetString(candidate, "style", 0, &whereStyle); 9353 //if(where && whereStyle) { 9354 if(pmg) { 9355 if(!handler(pmg.sliceCString)) 9356 return; 9357 //printf("%s || %s %s\n", pmg, where, whereStyle); 9358 } 9359 } 9360 } 9361 } 9362 9363 skipXft: 9364 9365 if(pattern is null) 9366 pattern = "*"; 9367 9368 int count; 9369 auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count); 9370 scope(exit) XFreeFontNames(coreFontsRaw); 9371 9372 auto coreFonts = coreFontsRaw[0 .. count]; 9373 9374 foreach(font; coreFonts) { 9375 char[128] tmp; 9376 tmp[0 ..5] = "core:"; 9377 auto cf = font.sliceCString; 9378 if(5 + cf.length > tmp.length) 9379 assert(0, "a font name was too long, sorry i didn't bother implementing a fallback"); 9380 tmp[5 .. 5 + cf.length] = cf; 9381 if(!handler(tmp[0 .. 5 + cf.length])) 9382 return; 9383 } 9384 } 9385 } 9386 9387 /++ 9388 Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont 9389 to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega]. 9390 9391 Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the 9392 underlying system doesn't support returning the raw bytes. 9393 9394 History: 9395 Added September 10, 2021 (dub v10.3) 9396 +/ 9397 ubyte[] getTtfBytes() { 9398 if(isNull) 9399 return null; 9400 9401 version(Windows) { 9402 auto dc = GetDC(null); 9403 auto orig = SelectObject(dc, font); 9404 9405 scope(exit) { 9406 SelectObject(dc, orig); 9407 ReleaseDC(null, dc); 9408 } 9409 9410 auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0); 9411 if(res == GDI_ERROR) 9412 return null; 9413 9414 ubyte[] buffer = new ubyte[](res); 9415 res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length); 9416 if(res == GDI_ERROR) 9417 return null; // wtf really tbh 9418 9419 return buffer; 9420 } else version(with_xft) { 9421 if(isXft && xftFont) { 9422 if(!FontConfigLibrary.attempted) 9423 FontConfigLibrary.loadDynamicLibrary(); 9424 if(!FontConfigLibrary.loadSuccessful) 9425 return null; 9426 9427 char* file; 9428 if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) { 9429 if (file !is null && file[0]) { 9430 import core.stdc.stdio; 9431 auto fp = fopen(file, "rb"); 9432 if(fp is null) 9433 return null; 9434 scope(exit) 9435 fclose(fp); 9436 fseek(fp, 0, SEEK_END); 9437 ubyte[] buffer = new ubyte[](ftell(fp)); 9438 fseek(fp, 0, SEEK_SET); 9439 9440 auto got = fread(buffer.ptr, 1, buffer.length, fp); 9441 if(got != buffer.length) 9442 return null; 9443 9444 return buffer; 9445 } 9446 } 9447 } 9448 return null; 9449 } else throw new NotYetImplementedException(); 9450 } 9451 9452 // see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352 9453 9454 private string weightToString(FontWeight weight) { 9455 with(FontWeight) 9456 final switch(weight) { 9457 case dontcare: return "*"; 9458 case thin: return "extralight"; 9459 case extralight: return "extralight"; 9460 case light: return "light"; 9461 case regular: return "regular"; 9462 case medium: return "medium"; 9463 case semibold: return "demibold"; 9464 case bold: return "bold"; 9465 case extrabold: return "demibold"; 9466 case heavy: return "black"; 9467 } 9468 } 9469 9470 /++ 9471 Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance. 9472 9473 History: 9474 Added November 13, 2020. Before then, this code was integrated in the [load] function. 9475 +/ 9476 version(X11) 9477 bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 9478 unload(); 9479 9480 string xfontstr; 9481 9482 if(name.length > 3 && name[0 .. 3] == "-*-") { 9483 // this is kinda a disgusting hack but if the user sends an exact 9484 // string I'd like to honor it... 9485 xfontstr = name; 9486 } else { 9487 string weightstr = weightToString(weight); 9488 string sizestr; 9489 if(size == 0) 9490 sizestr = "*"; 9491 else 9492 sizestr = toInternal!string(size); 9493 xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 9494 } 9495 9496 // writeln(xfontstr); 9497 9498 auto display = XDisplayConnection.get; 9499 9500 font = XLoadQueryFont(display, xfontstr.ptr); 9501 if(font is null) 9502 return false; 9503 9504 char** lol; 9505 int lol2; 9506 char* lol3; 9507 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 9508 9509 prepareFontInfo(); 9510 9511 return !isNull(); 9512 } 9513 9514 version(X11) 9515 private void prepareFontInfo() { 9516 if(font !is null) { 9517 isMonospace_ = stringWidth("l") == stringWidth("M"); 9518 ascent_ = font.max_bounds.ascent; 9519 descent_ = font.max_bounds.descent; 9520 } 9521 } 9522 9523 version(OSXCocoa) 9524 private void prepareFontInfo() { 9525 if(font !is null) { 9526 isMonospace_ = font.isFixedPitch; 9527 ascent_ = cast(int) font.ascender; 9528 descent_ = cast(int) - font.descender; 9529 } 9530 } 9531 9532 9533 /++ 9534 Loads a Windows font. You probably want to use [load] instead to be more generic. 9535 9536 History: 9537 Added November 13, 2020. Before then, this code was integrated in the [load] function. 9538 +/ 9539 version(Windows) 9540 bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) { 9541 unload(); 9542 9543 WCharzBuffer buffer = WCharzBuffer(name); 9544 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 9545 9546 prepareFontInfo(hdc); 9547 9548 return !isNull(); 9549 } 9550 9551 version(Windows) 9552 void prepareFontInfo(HDC hdc = null) { 9553 if(font is null) 9554 return; 9555 9556 TEXTMETRIC tm; 9557 auto dc = hdc ? hdc : GetDC(null); 9558 auto orig = SelectObject(dc, font); 9559 GetTextMetrics(dc, &tm); 9560 SelectObject(dc, orig); 9561 if(hdc is null) 9562 ReleaseDC(null, dc); 9563 9564 width_ = tm.tmAveCharWidth; 9565 height_ = tm.tmHeight; 9566 ascent_ = tm.tmAscent; 9567 descent_ = tm.tmDescent; 9568 // 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. 9569 isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0; 9570 } 9571 9572 9573 /++ 9574 `name` is a font name, but it can also be a more complicated string parsed in an OS-specific way. 9575 9576 On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise, 9577 it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX]. 9578 9579 On Windows, it forwards directly to [loadWin32]. 9580 9581 Params: 9582 name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences. 9583 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. 9584 weight = approximate boldness, results may vary. 9585 italic = try to get a slanted version of the given font. 9586 9587 History: 9588 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. 9589 +/ 9590 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 9591 this.loadedInfo = LoadedInfo(name, size, weight, italic); 9592 version(X11) { 9593 version(with_xft) { 9594 if(name.length > 5 && name[0 .. 5] == "core:") { 9595 goto core; 9596 } 9597 9598 if(loadXft(name, size, weight, italic)) 9599 return true; 9600 // if xft fails, fallback to core to avoid breaking 9601 // code that already depended on this. 9602 } 9603 9604 core: 9605 9606 if(name.length > 5 && name[0 .. 5] == "core:") { 9607 name = name[5 .. $]; 9608 } 9609 9610 return loadCoreX(name, size, weight, italic); 9611 } else version(Windows) { 9612 return loadWin32(name, size, weight, italic); 9613 } else version(OSXCocoa) { 9614 return loadCocoa(name, size, weight, italic); 9615 } else static assert(0); 9616 } 9617 9618 version(OSXCocoa) 9619 bool loadCocoa(string name, int size, FontWeight weight, bool italic) { 9620 unload(); 9621 9622 font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic? 9623 font.retain(); 9624 prepareFontInfo(); 9625 9626 return !isNull(); 9627 } 9628 9629 private struct LoadedInfo { 9630 string name; 9631 int size; 9632 FontWeight weight; 9633 bool italic; 9634 } 9635 private LoadedInfo loadedInfo; 9636 9637 // int size() { return loadedInfo.size; } 9638 9639 /// 9640 void unload() { 9641 if(isNull()) 9642 return; 9643 9644 version(X11) { 9645 auto display = XDisplayConnection.display; 9646 9647 if(display is null) 9648 return; 9649 9650 version(with_xft) { 9651 if(isXft) { 9652 if(xftFont) 9653 XftFontClose(display, xftFont); 9654 isXft = false; 9655 xftFont = null; 9656 return; 9657 } 9658 } 9659 9660 if(font && font !is ScreenPainterImplementation.defaultfont) 9661 XFreeFont(display, font); 9662 if(fontset && fontset !is ScreenPainterImplementation.defaultfontset) 9663 XFreeFontSet(display, fontset); 9664 9665 font = null; 9666 fontset = null; 9667 } else version(Windows) { 9668 DeleteObject(font); 9669 font = null; 9670 } else version(OSXCocoa) { 9671 font.release(); 9672 font = null; 9673 } else static assert(0); 9674 } 9675 9676 private bool isMonospace_; 9677 9678 /++ 9679 History: 9680 Added January 16, 2021 9681 +/ 9682 bool isMonospace() { 9683 return isMonospace_; 9684 } 9685 9686 /++ 9687 Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character. 9688 9689 History: 9690 Added March 26, 2020 9691 Documented January 16, 2021 9692 +/ 9693 fnum averageWidth() { 9694 version(X11) { 9695 return stringWidth("x"); 9696 } version(OSXCocoa) { 9697 return stringWidth("x"); 9698 } else version(Windows) 9699 return width_; 9700 else assert(0); 9701 } 9702 9703 /++ 9704 Returns the width of the string as drawn on the specified window, or the default screen if the window is null. 9705 9706 History: 9707 Added January 16, 2021 9708 +/ 9709 fnum stringWidth(scope const(char)[] s, SimpleWindow window = null) { 9710 // FIXME: what about tab? 9711 if(isNull) 9712 return 0; 9713 9714 version(X11) { 9715 version(with_xft) 9716 if(isXft && xftFont !is null) { 9717 //return xftFont.max_advance_width; 9718 XGlyphInfo extents; 9719 XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents); 9720 // writeln(extents); 9721 return extents.xOff; 9722 } 9723 if(font is null) 9724 return 0; 9725 else if(fontset) { 9726 XRectangle rect; 9727 Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect); 9728 9729 return rect.width; 9730 } else { 9731 return XTextWidth(font, s.ptr, cast(int) s.length); 9732 } 9733 } else version(Windows) { 9734 WCharzBuffer buffer = WCharzBuffer(s); 9735 9736 return stringWidth(buffer.slice, window); 9737 } else version(OSXCocoa) { 9738 /+ 9739 int charCount = [string length]; 9740 CGGlyph glyphs[charCount]; 9741 CGRect rects[charCount]; 9742 9743 CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount); 9744 CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount); 9745 9746 int totalwidth = 0, maxheight = 0; 9747 for (int i=0; i < charCount; i++) 9748 { 9749 totalwidth += rects[i].size.width; 9750 maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight; 9751 } 9752 9753 dim = CGSizeMake(totalwidth, maxheight); 9754 +/ 9755 MacString str = MacString(s); 9756 NSDictionary dict = NSDictionary.dictionaryWithObject( 9757 font, 9758 /*forKey:*/cast(void*) NSFontAttributeName 9759 ); 9760 // scope(exit) dict.release(); 9761 NSSize size = str.borrow.sizeWithAttributes(dict); 9762 9763 // import std.stdio; writeln(s, " ", size); 9764 9765 return size.width; // cast(int) (size.width + 0.9 /* to round up */); // FIXME 9766 } 9767 else assert(0); 9768 } 9769 9770 version(Windows) 9771 /// ditto 9772 int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) { 9773 if(isNull) 9774 return 0; 9775 version(Windows) { 9776 SIZE size; 9777 9778 prepareContext(window); 9779 scope(exit) releaseContext(); 9780 9781 GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size); 9782 9783 return size.cx; 9784 } else { 9785 // std.conv can do this easily but it is slow to import and i don't think it is worth it 9786 static assert(0, "not implemented yet"); 9787 //return stringWidth(s, window); 9788 } 9789 } 9790 9791 private { 9792 int prepRefcount; 9793 9794 version(Windows) { 9795 HDC dc; 9796 HANDLE orig; 9797 HWND hwnd; 9798 } 9799 } 9800 /++ 9801 [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. 9802 9803 History: 9804 Added January 23, 2021 9805 +/ 9806 void prepareContext(SimpleWindow window = null) { 9807 prepRefcount++; 9808 if(prepRefcount == 1) { 9809 version(Windows) { 9810 hwnd = window is null ? null : window.impl.hwnd; 9811 dc = GetDC(hwnd); 9812 orig = SelectObject(dc, font); 9813 } 9814 } 9815 } 9816 /// ditto 9817 void releaseContext() { 9818 prepRefcount--; 9819 if(prepRefcount == 0) { 9820 version(Windows) { 9821 SelectObject(dc, orig); 9822 ReleaseDC(hwnd, dc); 9823 hwnd = null; 9824 dc = null; 9825 orig = null; 9826 } 9827 } 9828 } 9829 9830 /+ 9831 FIXME: I think I need advance and kerning pair 9832 9833 int advance(dchar from, dchar to) { } // use dchar.init for first item in string 9834 +/ 9835 9836 /++ 9837 Returns the height of the font. 9838 9839 History: 9840 Added March 26, 2020 9841 Documented January 16, 2021 9842 +/ 9843 fnum height() { 9844 version(X11) { 9845 version(with_xft) 9846 if(isXft && xftFont !is null) { 9847 return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel 9848 } 9849 if(font is null) 9850 return 0; 9851 return font.max_bounds.ascent + font.max_bounds.descent; 9852 } else version(Windows) { 9853 return height_; 9854 } else version(OSXCocoa) { 9855 if(font is null) 9856 return 0; 9857 // the descender likely negative so minus means we actually add 9858 return cast(int) (font.ascender - font.descender + 0.9 /* to round up */); 9859 // return cast(int) font.capHeight; 9860 } 9861 else assert(0); 9862 } 9863 9864 private fnum ascent_; 9865 private fnum descent_; 9866 9867 /++ 9868 Max ascent above the baseline. 9869 9870 History: 9871 Added January 22, 2021 9872 +/ 9873 fnum ascent() { 9874 return ascent_; 9875 } 9876 9877 /++ 9878 Max descent below the baseline. 9879 9880 History: 9881 Added January 22, 2021 9882 +/ 9883 fnum descent() { 9884 return descent_; 9885 } 9886 9887 /++ 9888 Loads the default font used by [ScreenPainter] if none others are loaded. 9889 9890 Returns: 9891 This method mutates the `this` object, but then returns `this` for 9892 easy chaining like: 9893 9894 --- 9895 auto font = foo.isNull ? foo : foo.loadDefault 9896 --- 9897 9898 History: 9899 Added previously, but left unimplemented until January 24, 2021. 9900 +/ 9901 OperatingSystemFont loadDefault() { 9902 unload(); 9903 9904 loadedInfo = LoadedInfo.init; 9905 9906 version(X11) { 9907 // another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html 9908 // but meh since sdpy does its own thing, this should be ok too 9909 9910 ScreenPainterImplementation.ensureDefaultFontLoaded(); 9911 this.font = ScreenPainterImplementation.defaultfont; 9912 this.fontset = ScreenPainterImplementation.defaultfontset; 9913 9914 prepareFontInfo(); 9915 return this; 9916 } else version(Windows) { 9917 ScreenPainterImplementation.ensureDefaultFontLoaded(); 9918 this.font = ScreenPainterImplementation.defaultGuiFont; 9919 9920 prepareFontInfo(); 9921 return this; 9922 } else version(OSXCocoa) { 9923 this.font = NSFont.systemFontOfSize(15); 9924 font.retain(); 9925 9926 prepareFontInfo(); 9927 9928 // import std.stdio; writeln("Load default: ", this.height()); 9929 return this; 9930 } else throw new NotYetImplementedException(); 9931 } 9932 9933 /// 9934 bool isNull() { 9935 version(with_xft) 9936 if(isXft) 9937 return xftFont is null; 9938 return font is null; 9939 } 9940 9941 /* Metrics */ 9942 /+ 9943 GetABCWidth 9944 GetKerningPairs 9945 9946 if I do it right, I can size it all here, and match 9947 what happens when I draw the full string with the OS functions. 9948 9949 subclasses might do the same thing while getting the glyphs on images 9950 struct GlyphInfo { 9951 int glyph; 9952 9953 size_t stringIdxStart; 9954 size_t stringIdxEnd; 9955 9956 Rectangle boundingBox; 9957 } 9958 GlyphInfo[] getCharBoxes() { 9959 // XftTextExtentsUtf8 9960 return null; 9961 9962 } 9963 +/ 9964 9965 ~this() { 9966 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 9967 unload(); 9968 } 9969 } 9970 9971 version(Windows) 9972 private string sliceCString(const(wchar)[] w) { 9973 return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr); 9974 } 9975 9976 private inout(char)[] sliceCString(inout(char)* s) { 9977 import core.stdc.string; 9978 auto len = strlen(s); 9979 return s[0 .. len]; 9980 } 9981 9982 version(OSXCocoa) 9983 alias PaintingHandle = NSObject; 9984 else 9985 alias PaintingHandle = NativeWindowHandle; 9986 9987 /** 9988 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 9989 than constructing it directly. Then, it is reference counted so you can pass it 9990 at around and when the last ref goes out of scope, the buffered drawing activities 9991 are all carried out. 9992 9993 9994 Most functions use the outlineColor instead of taking a color themselves. 9995 ScreenPainter is reference counted and draws its buffer to the screen when its 9996 final reference goes out of scope. 9997 */ 9998 struct ScreenPainter { 9999 CapableOfBeingDrawnUpon window; 10000 this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) { 10001 this.window = window; 10002 if(window.closed) 10003 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 10004 //currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 10005 currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max); 10006 if(window.activeScreenPainter !is null) { 10007 impl = window.activeScreenPainter; 10008 if(impl.referenceCount == 0) { 10009 impl.window = window; 10010 impl.create(handle); 10011 } 10012 impl.manualInvalidations = manualInvalidations; 10013 impl.referenceCount++; 10014 // writeln("refcount ++ ", impl.referenceCount); 10015 } else { 10016 impl = new ScreenPainterImplementation; 10017 impl.window = window; 10018 impl.create(handle); 10019 impl.referenceCount = 1; 10020 impl.manualInvalidations = manualInvalidations; 10021 window.activeScreenPainter = impl; 10022 // writeln("constructed"); 10023 } 10024 10025 copyActiveOriginals(); 10026 } 10027 10028 /++ 10029 EXPERIMENTAL. subject to change. 10030 10031 When you draw a cursor, you can draw this to notify your window of where it is, 10032 for IME systems to use. 10033 +/ 10034 void notifyCursorPosition(int x, int y, int width, int height) { 10035 if(auto w = cast(SimpleWindow) window) { 10036 w.setIMEPopupLocation(x + _originX + width, y + _originY + height); 10037 } 10038 } 10039 10040 /++ 10041 If you are using manual invalidations, this informs the 10042 window system that a section needs to be redrawn. 10043 10044 If you didn't opt into manual invalidation, you don't 10045 have to call this. 10046 10047 History: 10048 Added December 30, 2021 (dub v10.5) 10049 +/ 10050 void invalidateRect(Rectangle rect) { 10051 if(impl is null) return; 10052 10053 // transform(rect) 10054 rect.left += _originX; 10055 rect.right += _originX; 10056 rect.top += _originY; 10057 rect.bottom += _originY; 10058 10059 impl.invalidateRect(rect); 10060 } 10061 10062 private Pen originalPen; 10063 private Color originalFillColor; 10064 private arsd.color.Rectangle originalClipRectangle; 10065 private OperatingSystemFont originalFont; 10066 void copyActiveOriginals() { 10067 if(impl is null) return; 10068 originalPen = impl._activePen; 10069 originalFillColor = impl._fillColor; 10070 originalClipRectangle = impl._clipRectangle; 10071 version(OSXCocoa) {} else 10072 originalFont = impl._activeFont; 10073 } 10074 10075 ~this() { 10076 if(impl is null) return; 10077 impl.referenceCount--; 10078 //writeln("refcount -- ", impl.referenceCount); 10079 if(impl.referenceCount == 0) { 10080 // writeln("destructed"); 10081 impl.dispose(); 10082 *window.activeScreenPainter = ScreenPainterImplementation.init; 10083 // writeln("paint finished"); 10084 } else { 10085 // there is still an active reference, reset stuff so the 10086 // next user doesn't get weirdness via the reference 10087 this.rasterOp = RasterOp.normal; 10088 pen = originalPen; 10089 fillColor = originalFillColor; 10090 if(originalFont) 10091 setFont(originalFont); 10092 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 10093 } 10094 } 10095 10096 this(this) { 10097 if(impl is null) return; 10098 impl.referenceCount++; 10099 //writeln("refcount ++ ", impl.referenceCount); 10100 10101 copyActiveOriginals(); 10102 } 10103 10104 private int _originX; 10105 private int _originY; 10106 @property int originX() { return _originX; } 10107 @property int originY() { return _originY; } 10108 @property int originX(int a) { 10109 _originX = a; 10110 return _originX; 10111 } 10112 @property int originY(int a) { 10113 _originY = a; 10114 return _originY; 10115 } 10116 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 10117 private void transform(ref Point p) { 10118 if(impl is null) return; 10119 p.x += _originX; 10120 p.y += _originY; 10121 } 10122 10123 // this needs to be checked BEFORE the originX/Y transformation 10124 private bool isClipped(Point p) { 10125 return !currentClipRectangle.contains(p); 10126 } 10127 private bool isClipped(Point p, int width, int height) { 10128 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 10129 } 10130 private bool isClipped(Point p, Size s) { 10131 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 10132 } 10133 private bool isClipped(Point p, Point p2) { 10134 // need to ensure the end points are actually included inside, so the +1 does that 10135 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 10136 } 10137 10138 10139 /++ 10140 Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 10141 10142 Returns: 10143 The old clip rectangle. 10144 10145 History: 10146 Return value was `void` prior to May 10, 2021. 10147 10148 +/ 10149 arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) { 10150 if(impl is null) return currentClipRectangle; 10151 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 10152 return currentClipRectangle; // no need to do anything 10153 auto old = currentClipRectangle; 10154 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 10155 transform(pt); 10156 10157 impl.setClipRectangle(pt.x, pt.y, width, height); 10158 10159 return old; 10160 } 10161 10162 /// ditto 10163 arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) { 10164 if(impl is null) return currentClipRectangle; 10165 return setClipRectangle(rect.upperLeft, rect.width, rect.height); 10166 } 10167 10168 /// 10169 void setFont(OperatingSystemFont font) { 10170 if(impl is null) return; 10171 impl.setFont(font); 10172 } 10173 10174 /// 10175 int fontHeight() { 10176 if(impl is null) return 0; 10177 return impl.fontHeight(); 10178 } 10179 10180 private Pen activePen; 10181 10182 /// 10183 @property void pen(Pen p) { 10184 if(impl is null) return; 10185 activePen = p; 10186 impl.pen(p); 10187 } 10188 10189 /// 10190 @scriptable 10191 @property void outlineColor(Color c) { 10192 if(impl is null) return; 10193 if(activePen.color == c) 10194 return; 10195 activePen.color = c; 10196 impl.pen(activePen); 10197 } 10198 10199 /// 10200 @scriptable 10201 @property void fillColor(Color c) { 10202 if(impl is null) return; 10203 impl.fillColor(c); 10204 } 10205 10206 /// 10207 @property void rasterOp(RasterOp op) { 10208 if(impl is null) return; 10209 impl.rasterOp(op); 10210 } 10211 10212 10213 void updateDisplay() { 10214 // FIXME this should do what the dtor does 10215 } 10216 10217 /// 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) 10218 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 10219 if(impl is null) return; 10220 if(isClipped(upperLeft, width, height)) return; 10221 transform(upperLeft); 10222 version(Windows) { 10223 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 10224 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 10225 RECT clip = scroll; 10226 RECT uncovered; 10227 HRGN hrgn; 10228 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 10229 throw new WindowsApiException("ScrollDC", GetLastError()); 10230 10231 } else version(X11) { 10232 // FIXME: clip stuff outside this rectangle 10233 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 10234 } else version(OSXCocoa) { 10235 throw new NotYetImplementedException(); 10236 } else static assert(0); 10237 } 10238 10239 /// 10240 void clear(Color color = Color.white()) { 10241 if(impl is null) return; 10242 fillColor = color; 10243 outlineColor = color; 10244 drawRectangle(Point(0, 0), window.width, window.height); 10245 } 10246 10247 /++ 10248 Draws a pixmap (represented by the [Sprite] class) on the drawable. 10249 10250 Params: 10251 upperLeft = point on the window where the upper left corner of the image will be drawn 10252 imageUpperLeft = point on the image to start the slice to draw 10253 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. 10254 History: 10255 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 10256 +/ 10257 void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 10258 if(impl is null) return; 10259 if(isClipped(upperLeft, s.width, s.height)) return; 10260 transform(upperLeft); 10261 impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height); 10262 } 10263 10264 /++ 10265 Draws an [Image] to the window. 10266 10267 $(WARNING 10268 Even if the Image was loaded with `enableAlpha`, drawing may not work! 10269 10270 Use [Sprite.fromMemoryImage] and [drawPixmap] instead if you want alpha blending to work better. 10271 +/ 10272 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 10273 if(impl is null) return; 10274 //if(isClipped(upperLeft, w, h)) return; // FIXME 10275 transform(upperLeft); 10276 if(w == 0 || w > i.width) 10277 w = i.width; 10278 if(h == 0 || h > i.height) 10279 h = i.height; 10280 if(upperLeftOfImage.x < 0) 10281 upperLeftOfImage.x = 0; 10282 if(upperLeftOfImage.y < 0) 10283 upperLeftOfImage.y = 0; 10284 10285 assert(i.enableAlpha == false, "Alpha blending is not implemented for Image drawing - use Sprite and drawPixmap instead"); 10286 10287 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 10288 } 10289 10290 /// 10291 Size textSize(in char[] text) { 10292 if(impl is null) return Size(0, 0); 10293 return impl.textSize(text); 10294 } 10295 10296 /++ 10297 Draws a string in the window with the set font (see [setFont] to change it). 10298 10299 Params: 10300 upperLeft = the upper left point of the bounding box of the text 10301 text = the string to draw 10302 lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound. 10303 alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags 10304 +/ 10305 @scriptable 10306 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 10307 if(impl is null) return; 10308 if(lowerRight.x != 0 || lowerRight.y != 0) { 10309 if(isClipped(upperLeft, lowerRight)) return; 10310 transform(lowerRight); 10311 } else { 10312 if(isClipped(upperLeft, textSize(text))) return; 10313 } 10314 transform(upperLeft); 10315 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 10316 } 10317 10318 /++ 10319 Draws text using a custom font. 10320 10321 This is still MAJOR work in progress. 10322 10323 Creating a [DrawableFont] can be tricky and require additional dependencies. 10324 +/ 10325 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 10326 if(impl is null) return; 10327 if(isClipped(upperLeft, Point(int.max, int.max))) return; 10328 transform(upperLeft); 10329 font.drawString(this, upperLeft, text); 10330 } 10331 10332 version(Windows) 10333 void drawText(Point upperLeft, scope const(wchar)[] text) { 10334 if(impl is null) return; 10335 if(isClipped(upperLeft, Point(int.max, int.max))) return; 10336 transform(upperLeft); 10337 10338 if(text.length && text[$-1] == '\n') 10339 text = text[0 .. $-1]; // tailing newlines are weird on windows... 10340 10341 TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length); 10342 } 10343 10344 static struct TextDrawingContext { 10345 Point boundingBoxUpperLeft; 10346 Point boundingBoxLowerRight; 10347 10348 Point currentLocation; 10349 10350 Point lastDrewUpperLeft; 10351 Point lastDrewLowerRight; 10352 10353 // how do i do right aligned rich text? 10354 // i kinda want to do a pre-made drawing then right align 10355 // draw the whole block. 10356 // 10357 // That's exactly the diff: inline vs block stuff. 10358 10359 // I need to get coordinates of an inline section out too, 10360 // not just a bounding box, but a series of bounding boxes 10361 // should be ok. Consider what's needed to detect a click 10362 // on a link in the middle of a paragraph breaking a line. 10363 // 10364 // Generally, we should be able to get the rectangles of 10365 // any portion we draw. 10366 // 10367 // It also needs to tell what text is left if it overflows 10368 // out of the box, so we can do stuff like float images around 10369 // it. It should not attempt to draw a letter that would be 10370 // clipped. 10371 // 10372 // I might also turn off word wrap stuff. 10373 } 10374 10375 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 10376 if(impl is null) return; 10377 // FIXME 10378 } 10379 10380 /// Drawing an individual pixel is slow. Avoid it if possible. 10381 void drawPixel(Point where) { 10382 if(impl is null) return; 10383 if(isClipped(where)) return; 10384 transform(where); 10385 impl.drawPixel(where.x, where.y); 10386 } 10387 10388 10389 /// Draws a pen using the current pen / outlineColor 10390 @scriptable 10391 void drawLine(Point starting, Point ending) { 10392 if(impl is null) return; 10393 if(isClipped(starting, ending)) return; 10394 transform(starting); 10395 transform(ending); 10396 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 10397 } 10398 10399 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 10400 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 10401 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 10402 @scriptable 10403 void drawRectangle(Point upperLeft, int width, int height) { 10404 if(impl is null) return; 10405 if(isClipped(upperLeft, width, height)) return; 10406 transform(upperLeft); 10407 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 10408 } 10409 10410 /// ditto 10411 void drawRectangle(Point upperLeft, Size size) { 10412 if(impl is null) return; 10413 if(isClipped(upperLeft, size.width, size.height)) return; 10414 transform(upperLeft); 10415 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 10416 } 10417 10418 /// ditto 10419 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 10420 if(impl is null) return; 10421 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 10422 transform(upperLeft); 10423 transform(lowerRightInclusive); 10424 impl.drawRectangle(upperLeft.x, upperLeft.y, 10425 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 10426 } 10427 10428 // overload added on May 12, 2021 10429 /// ditto 10430 void drawRectangle(Rectangle rect) { 10431 drawRectangle(rect.upperLeft, rect.size); 10432 } 10433 10434 /// Arguments are the points of the bounding rectangle 10435 void drawEllipse(Point upperLeft, Point lowerRight) { 10436 if(impl is null) return; 10437 if(isClipped(upperLeft, lowerRight)) return; 10438 transform(upperLeft); 10439 transform(lowerRight); 10440 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 10441 } 10442 10443 /++ 10444 Draws an arc inside the bounding box given by `upperLeft`, `width`, and `height`, from the angle (`start` / 64) degrees for (`length` / 64) degrees of rotation. 10445 10446 10447 If `length` is positive, it travels counter-clockwise from `start`. If negative, it goes clockwise. `start` == 0 at the three o'clock position of the bounding box - the center of the line at the right-hand side of the screen. 10448 10449 The arc is outlined with the current pen and filled with the current fill. On Windows, the line segments back to the middle are also drawn unless you have a full length ellipse. 10450 10451 Bugs: 10452 They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now). 10453 10454 The arc outline on Linux sometimes goes over the target. 10455 10456 The fill on Windows sometimes stops short. 10457 10458 History: 10459 This function was broken af, totally inconsistent on platforms until September 24, 2021. 10460 10461 The interpretation of the final argument was incorrectly documented and implemented until August 2, 2024. 10462 +/ 10463 void drawArc(Point upperLeft, int width, int height, int start, int length) { 10464 if(impl is null) return; 10465 // FIXME: not actually implemented 10466 if(isClipped(upperLeft, width, height)) return; 10467 transform(upperLeft); 10468 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, length); 10469 } 10470 10471 /// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 10472 void drawCircle(Point upperLeft, int diameter) { 10473 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 10474 } 10475 10476 /++ 10477 Draws a rectangle with rounded corners. It is outlined with the current foreground pen and filled with the current background brush. 10478 10479 10480 Bugs: 10481 Not implemented on Mac; it will instead draw a non-rounded rectangle for now. 10482 10483 History: 10484 Added August 3, 2024 10485 +/ 10486 void drawRectangleRounded(Rectangle rect, int borderRadius) { 10487 drawRectangleRounded(rect.upperLeft, rect.lowerRight, borderRadius); 10488 } 10489 10490 /// ditto 10491 void drawRectangleRounded(Point upperLeft, Size size, int borderRadius) { 10492 drawRectangleRounded(upperLeft, upperLeft + Point(size.width, size.height), borderRadius); 10493 } 10494 10495 /// ditto 10496 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 10497 if(borderRadius <= 0) { 10498 drawRectangle(upperLeft, lowerRight); 10499 return; 10500 } 10501 10502 transform(upperLeft); 10503 transform(lowerRight); 10504 10505 impl.drawRectangleRounded(upperLeft, lowerRight, borderRadius); 10506 } 10507 10508 /// . 10509 void drawPolygon(Point[] vertexes) { 10510 if(impl is null) return; 10511 assert(vertexes.length); 10512 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 10513 foreach(ref vertex; vertexes) { 10514 if(vertex.x < minX) 10515 minX = vertex.x; 10516 if(vertex.y < minY) 10517 minY = vertex.y; 10518 if(vertex.x > maxX) 10519 maxX = vertex.x; 10520 if(vertex.y > maxY) 10521 maxY = vertex.y; 10522 transform(vertex); 10523 } 10524 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 10525 impl.drawPolygon(vertexes); 10526 } 10527 10528 /// ditto 10529 void drawPolygon(Point[] vertexes...) { 10530 if(impl is null) return; 10531 drawPolygon(vertexes); 10532 } 10533 10534 10535 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 10536 10537 //mixin NativeScreenPainterImplementation!() impl; 10538 10539 10540 // HACK: if I mixin the impl directly, it won't let me override the copy 10541 // constructor! The linker complains about there being multiple definitions. 10542 // I'll make the best of it and reference count it though. 10543 ScreenPainterImplementation* impl; 10544 } 10545 10546 // HACK: I need a pointer to the implementation so it's separate 10547 struct ScreenPainterImplementation { 10548 CapableOfBeingDrawnUpon window; 10549 int referenceCount; 10550 mixin NativeScreenPainterImplementation!(); 10551 } 10552 10553 // FIXME: i haven't actually tested the sprite class on MS Windows 10554 10555 /** 10556 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 10557 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 10558 10559 10560 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 10561 though I'm not sure that's ideal and the implementation might change. 10562 10563 You create one by giving a window and an image. It optimizes for that window, 10564 and copies the image into it to use as the initial picture. Creating a sprite 10565 can be quite slow (especially over a network connection) so you should do it 10566 as little as possible and just hold on to your sprite handles after making them. 10567 simpledisplay does try to do its best though, using the XSHM extension if available, 10568 but you should still write your code as if it will always be slow. 10569 10570 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 10571 a fast operation - much faster than drawing the Image itself every time. 10572 10573 `Sprite` represents a scarce resource which should be freed when you 10574 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 10575 after it has been disposed. If you are unsure about this, don't take chances, 10576 just let the garbage collector do it for you. But ideally, you can manage its 10577 lifetime more efficiently. 10578 10579 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 10580 support alpha blending in its drawing at this time. That might change in the 10581 future, but if you need alpha blending right now, use OpenGL instead. See 10582 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 10583 10584 Update: on April 23, 2021, I finally added alpha blending support. You must opt 10585 in by setting the enableAlpha = true in the constructor. 10586 */ 10587 class Sprite : CapableOfBeingDrawnUpon { 10588 10589 /// 10590 ScreenPainter draw() { 10591 return ScreenPainter(this, handle, false); 10592 } 10593 10594 /++ 10595 Copies the sprite's current state into a [TrueColorImage]. 10596 10597 Be warned: this can be a very slow operation 10598 10599 History: 10600 Actually implemented on March 14, 2021 10601 +/ 10602 TrueColorImage takeScreenshot() { 10603 return trueColorImageFromNativeHandle(handle, width, height); 10604 } 10605 10606 void delegate() paintingFinishedDg() { return null; } 10607 bool closed() { return false; } 10608 ScreenPainterImplementation* activeScreenPainter_; 10609 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 10610 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 10611 10612 version(Windows) 10613 private ubyte* rawData; 10614 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 10615 // ditto on the XPicture stuff 10616 10617 version(X11) { 10618 private static XRenderPictFormat* RGB24; 10619 private static XRenderPictFormat* ARGB32; 10620 10621 private Picture xrenderPicture; 10622 } 10623 10624 version(X11) 10625 private static void requireXRender() { 10626 if(!XRenderLibrary.loadAttempted) { 10627 XRenderLibrary.loadDynamicLibrary(); 10628 } 10629 10630 if(!XRenderLibrary.loadSuccessful) 10631 throw new Exception("XRender library load failure"); 10632 10633 auto display = XDisplayConnection.get; 10634 10635 // FIXME: if we migrate X displays, these need to be changed 10636 if(RGB24 is null) 10637 RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); 10638 if(ARGB32 is null) 10639 ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); 10640 } 10641 10642 protected this() {} 10643 10644 this(SimpleWindow win, int width, int height, bool enableAlpha = false) { 10645 this._width = width; 10646 this._height = height; 10647 this.enableAlpha = enableAlpha; 10648 10649 version(X11) { 10650 auto display = XDisplayConnection.get(); 10651 10652 if(enableAlpha) { 10653 requireXRender(); 10654 } 10655 10656 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display)); 10657 10658 if(enableAlpha) { 10659 XRenderPictureAttributes attrs; 10660 xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs); 10661 } 10662 } else version(Windows) { 10663 version(CRuntime_DigitalMars) { 10664 //if(enableAlpha) 10665 //throw new Exception("Alpha support not available, try recompiling with -m32mscoff"); 10666 } 10667 10668 BITMAPINFO infoheader; 10669 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 10670 infoheader.bmiHeader.biWidth = width; 10671 infoheader.bmiHeader.biHeight = height; 10672 infoheader.bmiHeader.biPlanes = 1; 10673 infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24; 10674 infoheader.bmiHeader.biCompression = BI_RGB; 10675 10676 // FIXME: this should prolly be a device dependent bitmap... 10677 handle = CreateDIBSection( 10678 null, 10679 &infoheader, 10680 DIB_RGB_COLORS, 10681 cast(void**) &rawData, 10682 null, 10683 0); 10684 10685 if(handle is null) 10686 throw new WindowsApiException("couldn't create pixmap", GetLastError()); 10687 } 10688 } 10689 10690 /// Makes a sprite based on the image with the initial contents from the Image 10691 this(SimpleWindow win, Image i) { 10692 this(win, i.width, i.height, i.enableAlpha); 10693 10694 version(X11) { 10695 auto display = XDisplayConnection.get(); 10696 auto gc = XCreateGC(display, this.handle, 0, null); 10697 scope(exit) XFreeGC(display, gc); 10698 if(i.usingXshm) 10699 XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 10700 else 10701 XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 10702 } else version(Windows) { 10703 auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4); 10704 auto arrLength = itemsPerLine * height; 10705 rawData[0..arrLength] = i.rawData[0..arrLength]; 10706 } else version(OSXCocoa) { 10707 // FIXME: I have no idea if this is even any good 10708 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 10709 handle = CGBitmapContextCreate(null, width, height, 8, 4*width, 10710 colorSpace, 10711 kCGImageAlphaPremultipliedLast 10712 |kCGBitmapByteOrder32Big); 10713 CGColorSpaceRelease(colorSpace); 10714 auto rawData = CGBitmapContextGetData(handle); 10715 10716 auto rdl = (width * height * 4); 10717 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 10718 } else static assert(0); 10719 } 10720 10721 /++ 10722 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 10723 10724 Params: 10725 where = point on the window where the upper left corner of the image will be drawn 10726 imageUpperLeft = point on the image to start the slice to draw 10727 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. 10728 History: 10729 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 10730 +/ 10731 void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 10732 painter.drawPixmap(this, where, imageUpperLeft, sliceSize); 10733 } 10734 10735 /// Call this when you're ready to get rid of it 10736 void dispose() { 10737 version(X11) { 10738 staticDispose(xrenderPicture, handle); 10739 xrenderPicture = None; 10740 handle = None; 10741 } else version(Windows) { 10742 staticDispose(handle); 10743 handle = null; 10744 } else version(OSXCocoa) { 10745 staticDispose(handle); 10746 handle = null; 10747 } else static assert(0); 10748 10749 } 10750 10751 version(X11) 10752 static void staticDispose(Picture xrenderPicture, Pixmap handle) { 10753 if(xrenderPicture) 10754 XRenderFreePicture(XDisplayConnection.get, xrenderPicture); 10755 if(handle) 10756 XFreePixmap(XDisplayConnection.get(), handle); 10757 } 10758 else version(Windows) 10759 static void staticDispose(HBITMAP handle) { 10760 if(handle) 10761 DeleteObject(handle); 10762 } 10763 else version(OSXCocoa) 10764 static void staticDispose(CGContextRef context) { 10765 if(context) 10766 CGContextRelease(context); 10767 } 10768 10769 ~this() { 10770 version(X11) { if(xrenderPicture || handle) 10771 cleanupQueue.queue!staticDispose(xrenderPicture, handle); 10772 } else version(Windows) { if(handle) 10773 cleanupQueue.queue!staticDispose(handle); 10774 } else version(OSXCocoa) { if(handle) 10775 cleanupQueue.queue!staticDispose(handle); 10776 } else static assert(0); 10777 } 10778 10779 /// 10780 final @property int width() { return _width; } 10781 10782 /// 10783 final @property int height() { return _height; } 10784 10785 /// 10786 static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) { 10787 return new Sprite(win, Image.fromMemoryImage(img, enableAlpha)); 10788 } 10789 10790 auto nativeHandle() { 10791 return handle; 10792 } 10793 10794 private: 10795 10796 int _width; 10797 int _height; 10798 bool enableAlpha; 10799 version(X11) 10800 Pixmap handle; 10801 else version(Windows) 10802 HBITMAP handle; 10803 else version(OSXCocoa) 10804 CGContextRef handle; 10805 else version(Emscripten) 10806 void* handle; 10807 else static assert(0); 10808 } 10809 10810 /++ 10811 Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient]. 10812 10813 History: 10814 Added November 20, 2021 (dub v10.4) 10815 +/ 10816 version(OSXCocoa) {} else // NotYetImplementedException 10817 abstract class Gradient : Sprite { 10818 protected this(int w, int h) { 10819 version(X11) { 10820 Sprite.requireXRender(); 10821 10822 super(); 10823 enableAlpha = true; 10824 _width = w; 10825 _height = h; 10826 } else version(Windows) { 10827 super(null, w, h, true); // on Windows i'm just making a bitmap myself 10828 } 10829 } 10830 10831 version(Windows) 10832 final void forEachPixel(scope Color delegate(int x, int y) dg) @system { 10833 auto ptr = rawData; 10834 foreach(j; 0 .. _height) 10835 foreach(i; 0 .. _width) { 10836 auto color = dg(i, _height - j - 1); // cuz of upside down bitmap 10837 *rawData = (color.a * color.b) / 255; rawData++; 10838 *rawData = (color.a * color.g) / 255; rawData++; 10839 *rawData = (color.a * color.r) / 255; rawData++; 10840 *rawData = color.a; rawData++; 10841 } 10842 } 10843 10844 version(X11) 10845 protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) { 10846 assert(stops.length > 0); 10847 assert(stops.length <= 16, "I got lazy with buffers"); 10848 10849 XFixed[16] stopsPositions = void; 10850 XRenderColor[16] colors = void; 10851 10852 foreach(idx, stop; stops) { 10853 stopsPositions[idx] = cast(int)(stop.percentage * ushort.max); 10854 auto c = stop.c; 10855 colors[idx] = XRenderColor( 10856 cast(ushort)(c.r * ushort.max / 255), 10857 cast(ushort)(c.g * ushort.max / 255), 10858 cast(ushort)(c.b * ushort.max / 255), 10859 cast(ushort)(c.a * ubyte.max) // max value here is fractional 10860 ); 10861 } 10862 10863 xrenderPicture = dg(stopsPositions, colors); 10864 } 10865 10866 /// 10867 static struct Stop { 10868 float percentage; /// between 0 and 1.0 10869 Color c; 10870 } 10871 } 10872 10873 /++ 10874 Creates a linear gradient between p1 and p2. 10875 10876 X ONLY RIGHT NOW 10877 10878 History: 10879 Added November 20, 2021 (dub v10.4) 10880 10881 Bugs: 10882 Not yet implemented on Windows. 10883 +/ 10884 version(OSXCocoa) {} else // NotYetImplementedException 10885 class LinearGradient : Gradient { 10886 /++ 10887 10888 +/ 10889 this(Point p1, Point p2, Stop[] stops...) { 10890 super(p2.x, p2.y); 10891 10892 version(X11) { 10893 XLinearGradient gradient; 10894 gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max); 10895 gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max); 10896 10897 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10898 return XRenderCreateLinearGradient( 10899 XDisplayConnection.get, 10900 &gradient, 10901 stopsPositions.ptr, 10902 colors.ptr, 10903 cast(int) stops.length); 10904 }); 10905 } else version(Windows) { 10906 // FIXME 10907 forEachPixel((int x, int y) { 10908 import core.stdc.math; 10909 10910 //sqrtf( 10911 10912 return Color.transparent; 10913 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10914 }); 10915 } 10916 } 10917 } 10918 10919 /++ 10920 A conical gradient goes from color to color around a circumference from a center point. 10921 10922 X ONLY RIGHT NOW 10923 10924 History: 10925 Added November 20, 2021 (dub v10.4) 10926 10927 Bugs: 10928 Not yet implemented on Windows. 10929 +/ 10930 version(OSXCocoa) {} else // NotYetImplementedException 10931 class ConicalGradient : Gradient { 10932 /++ 10933 10934 +/ 10935 this(Point center, float angleInDegrees, Stop[] stops...) { 10936 super(center.x * 2, center.y * 2); 10937 10938 version(X11) { 10939 XConicalGradient gradient; 10940 gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max); 10941 gradient.angle = cast(int)(angleInDegrees * ushort.max); 10942 10943 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10944 return XRenderCreateConicalGradient( 10945 XDisplayConnection.get, 10946 &gradient, 10947 stopsPositions.ptr, 10948 colors.ptr, 10949 cast(int) stops.length); 10950 }); 10951 } else version(Windows) { 10952 // FIXME 10953 forEachPixel((int x, int y) { 10954 import core.stdc.math; 10955 10956 //sqrtf( 10957 10958 return Color.transparent; 10959 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10960 }); 10961 10962 } 10963 } 10964 } 10965 10966 /++ 10967 A radial gradient goes from color to color based on distance from the center. 10968 It is like rings of color. 10969 10970 X ONLY RIGHT NOW 10971 10972 10973 More specifically, you create two circles: an inner circle and an outer circle. 10974 The gradient is only drawn in the area outside the inner circle but inside the outer 10975 circle. The closest line between those two circles forms the line for the gradient 10976 and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around. 10977 10978 History: 10979 Added November 20, 2021 (dub v10.4) 10980 10981 Bugs: 10982 Not yet implemented on Windows. 10983 +/ 10984 version(OSXCocoa) {} else // NotYetImplementedException 10985 class RadialGradient : Gradient { 10986 /++ 10987 10988 +/ 10989 this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) { 10990 super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5)); 10991 10992 version(X11) { 10993 XRadialGradient gradient; 10994 gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max)); 10995 gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max)); 10996 10997 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10998 return XRenderCreateRadialGradient( 10999 XDisplayConnection.get, 11000 &gradient, 11001 stopsPositions.ptr, 11002 colors.ptr, 11003 cast(int) stops.length); 11004 }); 11005 } else version(Windows) { 11006 // FIXME 11007 forEachPixel((int x, int y) { 11008 import core.stdc.math; 11009 11010 //sqrtf( 11011 11012 return Color.transparent; 11013 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 11014 }); 11015 } 11016 } 11017 } 11018 11019 11020 11021 /+ 11022 NOT IMPLEMENTED 11023 11024 A display-stored image optimized for relatively quick drawing, like 11025 [Sprite], but this one supports alpha channel blending and does NOT 11026 support direct drawing upon it with a [ScreenPainter]. 11027 11028 You can think of it as an [arsd.game.OpenGlTexture] for usage with a 11029 plain [ScreenPainter]... sort of. 11030 11031 On X11, it requires the Xrender extension and library. This is available 11032 almost everywhere though. 11033 11034 History: 11035 Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED 11036 +/ 11037 version(none) 11038 class AlphaSprite { 11039 /++ 11040 Copies the given image into it. 11041 +/ 11042 this(MemoryImage img) { 11043 11044 if(!XRenderLibrary.loadAttempted) { 11045 XRenderLibrary.loadDynamicLibrary(); 11046 11047 // FIXME: this needs to be reconstructed when the X server changes 11048 repopulateX(); 11049 } 11050 if(!XRenderLibrary.loadSuccessful) 11051 throw new Exception("XRender library load failure"); 11052 11053 // I probably need to put the alpha mask in a separate Picture 11054 // ugh 11055 // maybe the Sprite itself can have an alpha bitmask anyway 11056 11057 11058 auto display = XDisplayConnection.get(); 11059 pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 11060 11061 11062 XRenderPictureAttributes attrs; 11063 11064 handle = XRenderCreatePicture( 11065 XDisplayConnection.get, 11066 pixmap, 11067 RGBA, 11068 0, 11069 &attrs 11070 ); 11071 11072 } 11073 11074 // maybe i'll use the create gradient functions too with static factories.. 11075 11076 void drawAt(ScreenPainter painter, Point where) { 11077 //painter.drawPixmap(this, where); 11078 11079 XRenderPictureAttributes attrs; 11080 11081 auto pic = XRenderCreatePicture( 11082 XDisplayConnection.get, 11083 painter.impl.d, 11084 RGB, 11085 0, 11086 &attrs 11087 ); 11088 11089 XRenderComposite( 11090 XDisplayConnection.get, 11091 3, // PictOpOver 11092 handle, 11093 None, 11094 pic, 11095 0, // src 11096 0, 11097 0, // mask 11098 0, 11099 10, // dest 11100 10, 11101 100, // width 11102 100 11103 ); 11104 11105 /+ 11106 XRenderFreePicture( 11107 XDisplayConnection.get, 11108 pic 11109 ); 11110 11111 XRenderFreePicture( 11112 XDisplayConnection.get, 11113 fill 11114 ); 11115 +/ 11116 // on Windows you can stretch but Xrender still can't :( 11117 } 11118 11119 static XRenderPictFormat* RGB; 11120 static XRenderPictFormat* RGBA; 11121 static void repopulateX() { 11122 auto display = XDisplayConnection.get; 11123 RGB = XRenderFindStandardFormat(display, PictStandardRGB24); 11124 RGBA = XRenderFindStandardFormat(display, PictStandardARGB32); 11125 } 11126 11127 XPixmap pixmap; 11128 Picture handle; 11129 } 11130 11131 /// 11132 interface CapableOfBeingDrawnUpon { 11133 /// 11134 ScreenPainter draw(); 11135 /// 11136 int width(); 11137 /// 11138 int height(); 11139 protected ScreenPainterImplementation* activeScreenPainter(); 11140 protected void activeScreenPainter(ScreenPainterImplementation*); 11141 bool closed(); 11142 11143 void delegate() paintingFinishedDg(); 11144 11145 /// Be warned: this can be a very slow operation 11146 TrueColorImage takeScreenshot(); 11147 } 11148 11149 /// Flushes any pending gui buffers. Necessary if you are using with_eventloop with X - flush after you create your windows but before you call [arsd.eventloop.loop]. 11150 void flushGui() { 11151 version(X11) { 11152 auto dpy = XDisplayConnection.get(); 11153 XLockDisplay(dpy); 11154 scope(exit) XUnlockDisplay(dpy); 11155 XFlush(dpy); 11156 } 11157 } 11158 11159 /++ 11160 Runs the given code in the GUI thread when its event loop 11161 is available, blocking until it completes. This allows you 11162 to create and manipulate windows from another thread without 11163 invoking undefined behavior. 11164 11165 If this is the gui thread, it runs the code immediately. 11166 11167 If no gui thread exists yet, the current thread is assumed 11168 to be it. Attempting to create windows or run the event loop 11169 in any other thread will cause an assertion failure. 11170 11171 11172 $(TIP 11173 Did you know you can use UFCS on delegate literals? 11174 11175 () { 11176 // code here 11177 }.runInGuiThread; 11178 ) 11179 11180 Returns: 11181 `true` if the function was called, `false` if it was not. 11182 The function may not be called because the gui thread had 11183 already terminated by the time you called this. 11184 11185 History: 11186 Added April 10, 2020 (v7.2.0) 11187 11188 Return value added and implementation tweaked to avoid locking 11189 at program termination on February 24, 2021 (v9.2.1). 11190 +/ 11191 bool runInGuiThread(scope void delegate() dg) @trusted { 11192 claimGuiThread(); 11193 11194 if(thisIsGuiThread) { 11195 dg(); 11196 return true; 11197 } 11198 11199 if(guiThreadTerminating) 11200 return false; 11201 11202 import core.sync.semaphore; 11203 static Semaphore sc; 11204 if(sc is null) 11205 sc = new Semaphore(); 11206 11207 static RunQueueMember* rqm; 11208 if(rqm is null) 11209 rqm = new RunQueueMember; 11210 rqm.dg = cast(typeof(rqm.dg)) dg; 11211 rqm.signal = sc; 11212 rqm.thrown = null; 11213 11214 synchronized(runInGuiThreadLock) { 11215 runInGuiThreadQueue ~= rqm; 11216 } 11217 11218 if(!SimpleWindow.eventWakeUp()) 11219 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 11220 11221 rqm.signal.wait(); 11222 auto t = rqm.thrown; 11223 11224 if(t) 11225 throw t; 11226 11227 return true; 11228 } 11229 11230 // note it runs sync if this is the gui thread.... 11231 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow { 11232 claimGuiThread(); 11233 11234 try { 11235 11236 if(thisIsGuiThread) { 11237 dg(); 11238 return; 11239 } 11240 11241 if(guiThreadTerminating) 11242 return; 11243 11244 RunQueueMember* rqm = new RunQueueMember; 11245 rqm.dg = cast(typeof(rqm.dg)) dg; 11246 rqm.signal = null; 11247 rqm.thrown = null; 11248 11249 synchronized(runInGuiThreadLock) { 11250 runInGuiThreadQueue ~= rqm; 11251 } 11252 11253 if(!SimpleWindow.eventWakeUp()) 11254 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 11255 } catch(Exception e) { 11256 // try sdpyPrintDebugString(e.toString); catch(Exception wtf) {} 11257 if(handleError) 11258 handleError(e); 11259 } 11260 } 11261 11262 private void runPendingRunInGuiThreadDelegates() { 11263 more: 11264 RunQueueMember* next; 11265 synchronized(runInGuiThreadLock) { 11266 if(runInGuiThreadQueue.length) { 11267 next = runInGuiThreadQueue[0]; 11268 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 11269 } else { 11270 next = null; 11271 } 11272 } 11273 11274 if(next) { 11275 try { 11276 next.dg(); 11277 next.thrown = null; 11278 } catch(Throwable t) { 11279 next.thrown = t; 11280 } 11281 11282 if(next.signal) 11283 next.signal.notify(); 11284 11285 goto more; 11286 } 11287 } 11288 11289 private void claimGuiThread() nothrow { 11290 import core.atomic; 11291 if(cas(&guiThreadExists_, false, true)) 11292 thisIsGuiThread = true; 11293 } 11294 11295 private struct RunQueueMember { 11296 void delegate() dg; 11297 import core.sync.semaphore; 11298 Semaphore signal; 11299 Throwable thrown; 11300 } 11301 11302 private __gshared RunQueueMember*[] runInGuiThreadQueue; 11303 private __gshared SynchronizableObject runInGuiThreadLock = new SynchronizableObject; // intentional CTFE 11304 private bool thisIsGuiThread = false; 11305 private shared bool guiThreadExists_ = false; 11306 private shared bool guiThreadTerminating = false; 11307 11308 /++ 11309 Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d 11310 event loop. All windows must be exclusively created and managed by a single thread. 11311 11312 If no gui thread exists, simpledisplay.d will automatically adopt the current thread 11313 when you call one of its constructors. 11314 11315 If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this 11316 one. If so, you can run gui functions on it. If not, don't. The helper functions 11317 [runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically. 11318 11319 The reason this function is available is in case you want to message pass between a gui 11320 thread and your current thread. If no gui thread exists or if this is the gui thread, 11321 you're liable to deadlock when trying to communicate since you'd end up talking to yourself. 11322 11323 History: 11324 Added December 3, 2021 (dub v10.5) 11325 +/ 11326 public bool guiThreadExists() { 11327 return guiThreadExists_; 11328 } 11329 11330 /++ 11331 Returns `true` if this thread is either running or set to be running the 11332 simpledisplay.d gui core event loop because it owns windows. 11333 11334 It is important to keep gui-related functionality in the right thread, so you will 11335 want to `runInGuiThread` when you call them (with some specific exceptions called 11336 out in those specific functions' documentation). Notably, all windows must be 11337 created and managed only from the gui thread. 11338 11339 Will return false if simpledisplay's other functions haven't been called 11340 yet; check [guiThreadExists] in addition to this. 11341 11342 History: 11343 Added December 3, 2021 (dub v10.5) 11344 +/ 11345 public bool thisThreadRunningGui() { 11346 return thisIsGuiThread; 11347 } 11348 11349 /++ 11350 Function to help temporarily print debugging info. It will bypass any stdout/err redirection 11351 and go to the controlling tty or console (attaching to the parent and/or allocating one as 11352 needed on Windows. Please note it may overwrite output from other programs in the parent and the 11353 allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log 11354 file instead if you are in one of those situations). 11355 11356 It does not support outputting very many types; just strings and ints are likely to actually work. 11357 11358 It will perform very slowly and swallows any errors that may occur. Moreover, the specific output 11359 is unspecified meaning I can change it at any time. The only point of this function is to help 11360 in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword 11361 and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use 11362 in those contexts. 11363 11364 $(WARNING 11365 I reserve the right to change this function at any time. You can use it if it helps you 11366 but do not rely on it for anything permanent. 11367 ) 11368 11369 History: 11370 Added December 3, 2021. Not formally supported under any stable tag. 11371 +/ 11372 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted { 11373 try { 11374 version(Windows) { 11375 import core.sys.windows.wincon; 11376 if(!AttachConsole(ATTACH_PARENT_PROCESS)) 11377 AllocConsole(); 11378 const(char)* fn = "CONOUT$"; 11379 } else version(Posix) { 11380 const(char)* fn = "/dev/tty"; 11381 } else static assert(0, "Function not implemented for your system"); 11382 11383 if(fileOverride.length) 11384 fn = fileOverride.ptr; 11385 11386 import core.stdc.stdio; 11387 auto fp = fopen(fn, "wt"); 11388 if(fp is null) return; 11389 scope(exit) fclose(fp); 11390 11391 string str; 11392 foreach(item; t) { 11393 static if(is(typeof(item) : const(char)[])) 11394 str ~= item; 11395 else 11396 str ~= toInternal!string(item); 11397 str ~= " "; 11398 } 11399 str ~= "\n"; 11400 11401 fwrite(str.ptr, 1, str.length, fp); 11402 fflush(fp); 11403 } catch(Exception e) { 11404 // sorry no hope 11405 } 11406 } 11407 11408 private void guiThreadFinalize() { 11409 assert(thisIsGuiThread); 11410 11411 guiThreadTerminating = true; // don't add any more from this point on 11412 runPendingRunInGuiThreadDelegates(); 11413 } 11414 11415 /+ 11416 interface IPromise { 11417 void reportProgress(int current, int max, string message); 11418 11419 /+ // not formally in cuz of templates but still 11420 IPromise Then(); 11421 IPromise Catch(); 11422 IPromise Finally(); 11423 +/ 11424 } 11425 11426 /+ 11427 auto promise = async({ ... }); 11428 promise.Then(whatever). 11429 Then(whateverelse). 11430 Catch((exception) { }); 11431 11432 11433 A promise is run inside a fiber and it looks something like: 11434 11435 try { 11436 auto res = whatever(); 11437 auto res2 = whateverelse(res); 11438 } catch(Exception e) { 11439 { }(e); 11440 } 11441 11442 When a thing succeeds, it is passed as an arg to the next 11443 +/ 11444 class Promise(T) : IPromise { 11445 auto Then() { return null; } 11446 auto Catch() { return null; } 11447 auto Finally() { return null; } 11448 11449 // wait for it to resolve and return the value, or rethrow the error if that occurred. 11450 // cannot be called from the gui thread, but this is caught at runtime instead of compile time. 11451 T await(); 11452 } 11453 11454 interface Task { 11455 } 11456 11457 interface Resolvable(T) : Task { 11458 void run(); 11459 11460 void resolve(T); 11461 11462 Resolvable!T then(void delegate(T)); // returns a new promise 11463 Resolvable!T error(Throwable); // js catch 11464 Resolvable!T completed(); // js finally 11465 11466 } 11467 11468 /++ 11469 Runs `work` in a helper thread and sends its return value back to the main gui 11470 thread as the argument to `uponCompletion`. If `work` throws, the exception is 11471 sent to the `uponThrown` if given, or if null, rethrown from the event loop to 11472 kill the program. 11473 11474 You can call reportProgress(position, max, message) to update your parent window 11475 on your progress. 11476 11477 I should also use `shared` methods. FIXME 11478 11479 History: 11480 Added March 6, 2021 (dub version 9.3). 11481 +/ 11482 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) { 11483 uponCompletion(work(null)); 11484 } 11485 11486 +/ 11487 11488 /// Used internal to dispatch events to various classes. 11489 interface CapableOfHandlingNativeEvent { 11490 NativeEventHandler getNativeEventHandler(); 11491 11492 version(OSXCocoa) 11493 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[void*] nativeHandleMapping; // to avoid typeinfo problems 11494 else 11495 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 11496 11497 version(X11) { 11498 // if this is impossible, you are allowed to just throw from it 11499 // Note: if you call it from another object, set a flag cuz the manger will call you again 11500 void recreateAfterDisconnect(); 11501 // discard any *connection specific* state, but keep enough that you 11502 // can be recreated if possible. discardConnectionState() is always called immediately 11503 // before recreateAfterDisconnect(), so you can set a flag there to decide if 11504 // you need initialization order 11505 void discardConnectionState(); 11506 } 11507 } 11508 11509 version(X11) 11510 /++ 11511 State of keys on mouse events, especially motion. 11512 11513 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 11514 +/ 11515 enum ModifierState : uint { 11516 shift = 1, /// 11517 capsLock = 2, /// 11518 ctrl = 4, /// 11519 alt = 8, /// Not always available on Windows 11520 windows = 64, /// ditto 11521 numLock = 16, /// 11522 11523 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 11524 middleButtonDown = 512, /// ditto 11525 rightButtonDown = 1024, /// ditto 11526 } 11527 else version(Emscripten) 11528 enum ModifierState : uint { 11529 shift = 1, /// 11530 capsLock = 2, /// 11531 ctrl = 4, /// 11532 alt = 8, /// Not always available on Windows 11533 windows = 64, /// ditto 11534 numLock = 16, /// 11535 11536 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 11537 middleButtonDown = 512, /// ditto 11538 rightButtonDown = 1024, /// ditto 11539 } 11540 else version(Windows) 11541 /// ditto 11542 enum ModifierState : uint { 11543 shift = 4, /// 11544 ctrl = 8, /// 11545 11546 // i'm not sure if the next two are available 11547 alt = 256, /// not always available on Windows 11548 windows = 512, /// ditto 11549 11550 capsLock = 1024, /// 11551 numLock = 2048, /// 11552 11553 leftButtonDown = 1, /// not available on key events 11554 middleButtonDown = 16, /// ditto 11555 rightButtonDown = 2, /// ditto 11556 11557 backButtonDown = 0x20, /// not available on X 11558 forwardButtonDown = 0x40, /// ditto 11559 } 11560 else version(OSXCocoa) 11561 // FIXME FIXME NotYetImplementedException 11562 enum ModifierState : uint { 11563 shift = 1, /// 11564 capsLock = 2, /// 11565 ctrl = 4, /// 11566 alt = 8, /// Not always available on Windows 11567 windows = 64, /// ditto 11568 numLock = 16, /// 11569 11570 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 11571 middleButtonDown = 512, /// ditto 11572 rightButtonDown = 1024, /// ditto 11573 } 11574 11575 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them. 11576 enum MouseButton : int { 11577 none = 0, 11578 left = 1, /// 11579 right = 2, /// 11580 middle = 4, /// 11581 wheelUp = 8, /// 11582 wheelDown = 16, /// 11583 wheelLeft = 32, /// 11584 wheelRight = 64, /// 11585 backButton = 128, /// often found on the thumb and used for back in browsers 11586 forwardButton = 256, /// often found on the thumb and used for forward in browsers 11587 } 11588 11589 /// Corresponds to the values found in MouseEvent.buttonLinear, being equal to `core.bitop.bsf(button) + 1` 11590 enum MouseButtonLinear : ubyte { 11591 left = 1, /// 11592 right, /// 11593 middle, /// 11594 wheelUp, /// 11595 wheelDown, /// 11596 wheelLeft, /// Added Dec 21, 2025 11597 wheelRight, /// ditto 11598 backButton, /// often found on the thumb and used for back in browsers 11599 forwardButton, /// often found on the thumb and used for forward in browsers 11600 } 11601 11602 version(WebAssembly) { 11603 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 11604 enum Key { 11605 Escape = 0xff1b, /// 11606 F1 = 0xffbe, /// 11607 F2 = 0xffbf, /// 11608 F3 = 0xffc0, /// 11609 F4 = 0xffc1, /// 11610 F5 = 0xffc2, /// 11611 F6 = 0xffc3, /// 11612 F7 = 0xffc4, /// 11613 F8 = 0xffc5, /// 11614 F9 = 0xffc6, /// 11615 F10 = 0xffc7, /// 11616 F11 = 0xffc8, /// 11617 F12 = 0xffc9, /// 11618 PrintScreen = 0xff61, /// 11619 ScrollLock = 0xff14, /// 11620 Pause = 0xff13, /// 11621 Grave = 0x60, /// The $(BACKTICK) ~ key 11622 // number keys across the top of the keyboard 11623 N1 = 0x31, /// Number key atop the keyboard 11624 N2 = 0x32, /// 11625 N3 = 0x33, /// 11626 N4 = 0x34, /// 11627 N5 = 0x35, /// 11628 N6 = 0x36, /// 11629 N7 = 0x37, /// 11630 N8 = 0x38, /// 11631 N9 = 0x39, /// 11632 N0 = 0x30, /// 11633 Dash = 0x2d, /// 11634 Equals = 0x3d, /// 11635 Backslash = 0x5c, /// The \ | key 11636 Backspace = 0xff08, /// 11637 Insert = 0xff63, /// 11638 Home = 0xff50, /// 11639 PageUp = 0xff55, /// 11640 Delete = 0xffff, /// 11641 End = 0xff57, /// 11642 PageDown = 0xff56, /// 11643 Up = 0xff52, /// 11644 Down = 0xff54, /// 11645 Left = 0xff51, /// 11646 Right = 0xff53, /// 11647 11648 Tab = 0xff09, /// 11649 Q = 0x71, /// 11650 W = 0x77, /// 11651 E = 0x65, /// 11652 R = 0x72, /// 11653 T = 0x74, /// 11654 Y = 0x79, /// 11655 U = 0x75, /// 11656 I = 0x69, /// 11657 O = 0x6f, /// 11658 P = 0x70, /// 11659 LeftBracket = 0x5b, /// the [ { key 11660 RightBracket = 0x5d, /// the ] } key 11661 CapsLock = 0xffe5, /// 11662 A = 0x61, /// 11663 S = 0x73, /// 11664 D = 0x64, /// 11665 F = 0x66, /// 11666 G = 0x67, /// 11667 H = 0x68, /// 11668 J = 0x6a, /// 11669 K = 0x6b, /// 11670 L = 0x6c, /// 11671 Semicolon = 0x3b, /// 11672 Apostrophe = 0x27, /// 11673 Enter = 0xff0d, /// 11674 Shift = 0xffe1, /// 11675 Z = 0x7a, /// 11676 X = 0x78, /// 11677 C = 0x63, /// 11678 V = 0x76, /// 11679 B = 0x62, /// 11680 N = 0x6e, /// 11681 M = 0x6d, /// 11682 Comma = 0x2c, /// 11683 Period = 0x2e, /// 11684 Slash = 0x2f, /// the / ? key 11685 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 11686 Ctrl = 0xffe3, /// 11687 Windows = 0xffeb, /// 11688 Alt = 0xffe9, /// 11689 Space = 0x20, /// 11690 Alt_r = 0xffea, /// ditto of shift_r 11691 Windows_r = 0xffec, /// 11692 Menu = 0xff67, /// 11693 Ctrl_r = 0xffe4, /// 11694 11695 NumLock = 0xff7f, /// 11696 Divide = 0xffaf, /// The / key on the number pad 11697 Multiply = 0xffaa, /// The * key on the number pad 11698 Minus = 0xffad, /// The - key on the number pad 11699 Plus = 0xffab, /// The + key on the number pad 11700 PadEnter = 0xff8d, /// Numberpad enter key 11701 Pad1 = 0xff9c, /// Numberpad keys 11702 Pad2 = 0xff99, /// 11703 Pad3 = 0xff9b, /// 11704 Pad4 = 0xff96, /// 11705 Pad5 = 0xff9d, /// 11706 Pad6 = 0xff98, /// 11707 Pad7 = 0xff95, /// 11708 Pad8 = 0xff97, /// 11709 Pad9 = 0xff9a, /// 11710 Pad0 = 0xff9e, /// 11711 PadDot = 0xff9f, /// 11712 } 11713 } version(X11) { 11714 // FIXME: match ASCII whenever we can. Most of it is already there, 11715 // but there's a few exceptions and mismatches with Windows 11716 11717 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 11718 enum Key { 11719 Escape = 0xff1b, /// 11720 F1 = 0xffbe, /// 11721 F2 = 0xffbf, /// 11722 F3 = 0xffc0, /// 11723 F4 = 0xffc1, /// 11724 F5 = 0xffc2, /// 11725 F6 = 0xffc3, /// 11726 F7 = 0xffc4, /// 11727 F8 = 0xffc5, /// 11728 F9 = 0xffc6, /// 11729 F10 = 0xffc7, /// 11730 F11 = 0xffc8, /// 11731 F12 = 0xffc9, /// 11732 PrintScreen = 0xff61, /// 11733 ScrollLock = 0xff14, /// 11734 Pause = 0xff13, /// 11735 Grave = 0x60, /// The $(BACKTICK) ~ key 11736 // number keys across the top of the keyboard 11737 N1 = 0x31, /// Number key atop the keyboard 11738 N2 = 0x32, /// 11739 N3 = 0x33, /// 11740 N4 = 0x34, /// 11741 N5 = 0x35, /// 11742 N6 = 0x36, /// 11743 N7 = 0x37, /// 11744 N8 = 0x38, /// 11745 N9 = 0x39, /// 11746 N0 = 0x30, /// 11747 Dash = 0x2d, /// 11748 Equals = 0x3d, /// 11749 Backslash = 0x5c, /// The \ | key 11750 Backspace = 0xff08, /// 11751 Insert = 0xff63, /// 11752 Home = 0xff50, /// 11753 PageUp = 0xff55, /// 11754 Delete = 0xffff, /// 11755 End = 0xff57, /// 11756 PageDown = 0xff56, /// 11757 Up = 0xff52, /// 11758 Down = 0xff54, /// 11759 Left = 0xff51, /// 11760 Right = 0xff53, /// 11761 11762 Tab = 0xff09, /// 11763 Q = 0x71, /// 11764 W = 0x77, /// 11765 E = 0x65, /// 11766 R = 0x72, /// 11767 T = 0x74, /// 11768 Y = 0x79, /// 11769 U = 0x75, /// 11770 I = 0x69, /// 11771 O = 0x6f, /// 11772 P = 0x70, /// 11773 LeftBracket = 0x5b, /// the [ { key 11774 RightBracket = 0x5d, /// the ] } key 11775 CapsLock = 0xffe5, /// 11776 A = 0x61, /// 11777 S = 0x73, /// 11778 D = 0x64, /// 11779 F = 0x66, /// 11780 G = 0x67, /// 11781 H = 0x68, /// 11782 J = 0x6a, /// 11783 K = 0x6b, /// 11784 L = 0x6c, /// 11785 Semicolon = 0x3b, /// 11786 Apostrophe = 0x27, /// 11787 Enter = 0xff0d, /// 11788 Shift = 0xffe1, /// 11789 Z = 0x7a, /// 11790 X = 0x78, /// 11791 C = 0x63, /// 11792 V = 0x76, /// 11793 B = 0x62, /// 11794 N = 0x6e, /// 11795 M = 0x6d, /// 11796 Comma = 0x2c, /// 11797 Period = 0x2e, /// 11798 Slash = 0x2f, /// the / ? key 11799 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 11800 Ctrl = 0xffe3, /// 11801 Windows = 0xffeb, /// 11802 Alt = 0xffe9, /// 11803 Space = 0x20, /// 11804 Alt_r = 0xffea, /// ditto of shift_r 11805 Windows_r = 0xffec, /// 11806 Menu = 0xff67, /// 11807 Ctrl_r = 0xffe4, /// 11808 11809 NumLock = 0xff7f, /// 11810 Divide = 0xffaf, /// The / key on the number pad 11811 Multiply = 0xffaa, /// The * key on the number pad 11812 Minus = 0xffad, /// The - key on the number pad 11813 Plus = 0xffab, /// The + key on the number pad 11814 PadEnter = 0xff8d, /// Numberpad enter key 11815 Pad1 = 0xff9c, /// Numberpad keys 11816 Pad2 = 0xff99, /// 11817 Pad3 = 0xff9b, /// 11818 Pad4 = 0xff96, /// 11819 Pad5 = 0xff9d, /// 11820 Pad6 = 0xff98, /// 11821 Pad7 = 0xff95, /// 11822 Pad8 = 0xff97, /// 11823 Pad9 = 0xff9a, /// 11824 Pad0 = 0xff9e, /// 11825 PadDot = 0xff9f, /// 11826 } 11827 } else version(Windows) { 11828 // the character here is for en-us layouts and for illustration only 11829 // if you actually want to get characters, wait for character events 11830 // (the argument to your event handler is simply a dchar) 11831 // those will be converted by the OS for the right locale. 11832 11833 enum Key { 11834 Escape = 0x1b, 11835 F1 = 0x70, 11836 F2 = 0x71, 11837 F3 = 0x72, 11838 F4 = 0x73, 11839 F5 = 0x74, 11840 F6 = 0x75, 11841 F7 = 0x76, 11842 F8 = 0x77, 11843 F9 = 0x78, 11844 F10 = 0x79, 11845 F11 = 0x7a, 11846 F12 = 0x7b, 11847 PrintScreen = 0x2c, 11848 ScrollLock = 0x91, 11849 Pause = 0x13, 11850 Grave = 0xc0, 11851 // number keys across the top of the keyboard 11852 N1 = 0x31, 11853 N2 = 0x32, 11854 N3 = 0x33, 11855 N4 = 0x34, 11856 N5 = 0x35, 11857 N6 = 0x36, 11858 N7 = 0x37, 11859 N8 = 0x38, 11860 N9 = 0x39, 11861 N0 = 0x30, 11862 Dash = 0xbd, 11863 Equals = 0xbb, 11864 Backslash = 0xdc, 11865 Backspace = 0x08, 11866 Insert = 0x2d, 11867 Home = 0x24, 11868 PageUp = 0x21, 11869 Delete = 0x2e, 11870 End = 0x23, 11871 PageDown = 0x22, 11872 Up = 0x26, 11873 Down = 0x28, 11874 Left = 0x25, 11875 Right = 0x27, 11876 11877 Tab = 0x09, 11878 Q = 0x51, 11879 W = 0x57, 11880 E = 0x45, 11881 R = 0x52, 11882 T = 0x54, 11883 Y = 0x59, 11884 U = 0x55, 11885 I = 0x49, 11886 O = 0x4f, 11887 P = 0x50, 11888 LeftBracket = 0xdb, 11889 RightBracket = 0xdd, 11890 CapsLock = 0x14, 11891 A = 0x41, 11892 S = 0x53, 11893 D = 0x44, 11894 F = 0x46, 11895 G = 0x47, 11896 H = 0x48, 11897 J = 0x4a, 11898 K = 0x4b, 11899 L = 0x4c, 11900 Semicolon = 0xba, 11901 Apostrophe = 0xde, 11902 Enter = 0x0d, 11903 Shift = 0x10, 11904 Z = 0x5a, 11905 X = 0x58, 11906 C = 0x43, 11907 V = 0x56, 11908 B = 0x42, 11909 N = 0x4e, 11910 M = 0x4d, 11911 Comma = 0xbc, 11912 Period = 0xbe, 11913 Slash = 0xbf, 11914 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 11915 Ctrl = 0x11, 11916 Windows = 0x5b, 11917 Alt = -5, // FIXME 11918 Space = 0x20, 11919 Alt_r = 0xffea, // ditto of shift_r 11920 Windows_r = 0x5c, // ditto of shift_r 11921 Menu = 0x5d, 11922 Ctrl_r = 0xa3, // ditto of shift_r 11923 11924 NumLock = 0x90, 11925 Divide = 0x6f, 11926 Multiply = 0x6a, 11927 Minus = 0x6d, 11928 Plus = 0x6b, 11929 PadEnter = -8, // FIXME 11930 Pad1 = 0x61, 11931 Pad2 = 0x62, 11932 Pad3 = 0x63, 11933 Pad4 = 0x64, 11934 Pad5 = 0x65, 11935 Pad6 = 0x66, 11936 Pad7 = 0x67, 11937 Pad8 = 0x68, 11938 Pad9 = 0x69, 11939 Pad0 = 0x60, 11940 PadDot = 0x6e, 11941 } 11942 11943 // I'm keeping this around for reference purposes 11944 // ideally all these buttons will be listed for all platforms, 11945 // but now now I'm just focusing on my US keyboard 11946 version(none) 11947 enum Key { 11948 LBUTTON = 0x01, 11949 RBUTTON = 0x02, 11950 CANCEL = 0x03, 11951 MBUTTON = 0x04, 11952 //static if (_WIN32_WINNT > = 0x500) { 11953 XBUTTON1 = 0x05, 11954 XBUTTON2 = 0x06, 11955 //} 11956 BACK = 0x08, 11957 TAB = 0x09, 11958 CLEAR = 0x0C, 11959 RETURN = 0x0D, 11960 SHIFT = 0x10, 11961 CONTROL = 0x11, 11962 MENU = 0x12, 11963 PAUSE = 0x13, 11964 CAPITAL = 0x14, 11965 KANA = 0x15, 11966 HANGEUL = 0x15, 11967 HANGUL = 0x15, 11968 JUNJA = 0x17, 11969 FINAL = 0x18, 11970 HANJA = 0x19, 11971 KANJI = 0x19, 11972 ESCAPE = 0x1B, 11973 CONVERT = 0x1C, 11974 NONCONVERT = 0x1D, 11975 ACCEPT = 0x1E, 11976 MODECHANGE = 0x1F, 11977 SPACE = 0x20, 11978 PRIOR = 0x21, 11979 NEXT = 0x22, 11980 END = 0x23, 11981 HOME = 0x24, 11982 LEFT = 0x25, 11983 UP = 0x26, 11984 RIGHT = 0x27, 11985 DOWN = 0x28, 11986 SELECT = 0x29, 11987 PRINT = 0x2A, 11988 EXECUTE = 0x2B, 11989 SNAPSHOT = 0x2C, 11990 INSERT = 0x2D, 11991 DELETE = 0x2E, 11992 HELP = 0x2F, 11993 LWIN = 0x5B, 11994 RWIN = 0x5C, 11995 APPS = 0x5D, 11996 SLEEP = 0x5F, 11997 NUMPAD0 = 0x60, 11998 NUMPAD1 = 0x61, 11999 NUMPAD2 = 0x62, 12000 NUMPAD3 = 0x63, 12001 NUMPAD4 = 0x64, 12002 NUMPAD5 = 0x65, 12003 NUMPAD6 = 0x66, 12004 NUMPAD7 = 0x67, 12005 NUMPAD8 = 0x68, 12006 NUMPAD9 = 0x69, 12007 MULTIPLY = 0x6A, 12008 ADD = 0x6B, 12009 SEPARATOR = 0x6C, 12010 SUBTRACT = 0x6D, 12011 DECIMAL = 0x6E, 12012 DIVIDE = 0x6F, 12013 F1 = 0x70, 12014 F2 = 0x71, 12015 F3 = 0x72, 12016 F4 = 0x73, 12017 F5 = 0x74, 12018 F6 = 0x75, 12019 F7 = 0x76, 12020 F8 = 0x77, 12021 F9 = 0x78, 12022 F10 = 0x79, 12023 F11 = 0x7A, 12024 F12 = 0x7B, 12025 F13 = 0x7C, 12026 F14 = 0x7D, 12027 F15 = 0x7E, 12028 F16 = 0x7F, 12029 F17 = 0x80, 12030 F18 = 0x81, 12031 F19 = 0x82, 12032 F20 = 0x83, 12033 F21 = 0x84, 12034 F22 = 0x85, 12035 F23 = 0x86, 12036 F24 = 0x87, 12037 NUMLOCK = 0x90, 12038 SCROLL = 0x91, 12039 LSHIFT = 0xA0, 12040 RSHIFT = 0xA1, 12041 LCONTROL = 0xA2, 12042 RCONTROL = 0xA3, 12043 LMENU = 0xA4, 12044 RMENU = 0xA5, 12045 //static if (_WIN32_WINNT > = 0x500) { 12046 BROWSER_BACK = 0xA6, 12047 BROWSER_FORWARD = 0xA7, 12048 BROWSER_REFRESH = 0xA8, 12049 BROWSER_STOP = 0xA9, 12050 BROWSER_SEARCH = 0xAA, 12051 BROWSER_FAVORITES = 0xAB, 12052 BROWSER_HOME = 0xAC, 12053 VOLUME_MUTE = 0xAD, 12054 VOLUME_DOWN = 0xAE, 12055 VOLUME_UP = 0xAF, 12056 MEDIA_NEXT_TRACK = 0xB0, 12057 MEDIA_PREV_TRACK = 0xB1, 12058 MEDIA_STOP = 0xB2, 12059 MEDIA_PLAY_PAUSE = 0xB3, 12060 LAUNCH_MAIL = 0xB4, 12061 LAUNCH_MEDIA_SELECT = 0xB5, 12062 LAUNCH_APP1 = 0xB6, 12063 LAUNCH_APP2 = 0xB7, 12064 //} 12065 OEM_1 = 0xBA, 12066 //static if (_WIN32_WINNT > = 0x500) { 12067 OEM_PLUS = 0xBB, 12068 OEM_COMMA = 0xBC, 12069 OEM_MINUS = 0xBD, 12070 OEM_PERIOD = 0xBE, 12071 //} 12072 OEM_2 = 0xBF, 12073 OEM_3 = 0xC0, 12074 OEM_4 = 0xDB, 12075 OEM_5 = 0xDC, 12076 OEM_6 = 0xDD, 12077 OEM_7 = 0xDE, 12078 OEM_8 = 0xDF, 12079 //static if (_WIN32_WINNT > = 0x500) { 12080 OEM_102 = 0xE2, 12081 //} 12082 PROCESSKEY = 0xE5, 12083 //static if (_WIN32_WINNT > = 0x500) { 12084 PACKET = 0xE7, 12085 //} 12086 ATTN = 0xF6, 12087 CRSEL = 0xF7, 12088 EXSEL = 0xF8, 12089 EREOF = 0xF9, 12090 PLAY = 0xFA, 12091 ZOOM = 0xFB, 12092 NONAME = 0xFC, 12093 PA1 = 0xFD, 12094 OEM_CLEAR = 0xFE, 12095 } 12096 12097 } else version(OSXCocoa) { 12098 enum Key { 12099 Escape = 53, 12100 F1 = 122, 12101 F2 = 120, 12102 F3 = 99, 12103 F4 = 118, 12104 F5 = 96, 12105 F6 = 97, 12106 F7 = 98, 12107 F8 = 100, 12108 F9 = 101, 12109 F10 = 109, 12110 F11 = 103, 12111 F12 = 111, 12112 PrintScreen = 105, 12113 ScrollLock = 107, 12114 Pause = 113, 12115 Grave = 50, 12116 // number keys across the top of the keyboard 12117 N1 = 18, 12118 N2 = 19, 12119 N3 = 20, 12120 N4 = 21, 12121 N5 = 23, 12122 N6 = 22, 12123 N7 = 26, 12124 N8 = 28, 12125 N9 = 25, 12126 N0 = 29, 12127 Dash = 27, 12128 Equals = 24, 12129 Backslash = 42, 12130 Backspace = 51, 12131 Insert = 114, 12132 Home = 115, 12133 PageUp = 116, 12134 Delete = 117, 12135 End = 119, 12136 PageDown = 121, 12137 Up = 126, 12138 Down = 125, 12139 Left = 123, 12140 Right = 124, 12141 12142 Tab = 48, 12143 Q = 12, 12144 W = 13, 12145 E = 14, 12146 R = 15, 12147 T = 17, 12148 Y = 16, 12149 U = 32, 12150 I = 34, 12151 O = 31, 12152 P = 35, 12153 LeftBracket = 33, 12154 RightBracket = 30, 12155 CapsLock = 57, 12156 A = 0, 12157 S = 1, 12158 D = 2, 12159 F = 3, 12160 G = 5, 12161 H = 4, 12162 J = 38, 12163 K = 40, 12164 L = 37, 12165 Semicolon = 41, 12166 Apostrophe = 39, 12167 Enter = 36, 12168 Shift = 56, 12169 Z = 6, 12170 X = 7, 12171 C = 8, 12172 V = 9, 12173 B = 11, 12174 N = 45, 12175 M = 46, 12176 Comma = 43, 12177 Period = 47, 12178 Slash = 44, 12179 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 12180 Ctrl = 59, 12181 Windows = 55, 12182 Alt = 58, 12183 Space = 49, 12184 Alt_r = -3, // ditto of shift_r 12185 Windows_r = -2, 12186 Menu = 110, 12187 Ctrl_r = -1, 12188 12189 NumLock = 1, 12190 Divide = 75, 12191 Multiply = 67, 12192 Minus = 78, 12193 Plus = 69, 12194 PadEnter = 76, 12195 Pad1 = 83, 12196 Pad2 = 84, 12197 Pad3 = 85, 12198 Pad4 = 86, 12199 Pad5 = 87, 12200 Pad6 = 88, 12201 Pad7 = 89, 12202 Pad8 = 91, 12203 Pad9 = 92, 12204 Pad0 = 82, 12205 PadDot = 65, 12206 } 12207 12208 } 12209 12210 char keyToLetterCharAssumingLotsOfThingsThatYouMightBetterNotAssume(Key key) { 12211 version(OSXCocoa) { 12212 return char.init; // FIXME 12213 } else { 12214 return cast(char)(key - Key.A + 'a'); 12215 } 12216 } 12217 12218 /* Additional utilities */ 12219 12220 12221 Color fromHsl(real h, real s, real l) { 12222 return arsd.color.fromHsl([h,s,l]); 12223 } 12224 12225 12226 12227 /* ********** What follows is the system-specific implementations *********/ 12228 version(Windows) { 12229 12230 12231 // helpers for making HICONs from MemoryImages 12232 class WindowsIcon { 12233 struct Win32Icon { 12234 align(1): 12235 uint biSize; 12236 int biWidth; 12237 int biHeight; 12238 ushort biPlanes; 12239 ushort biBitCount; 12240 uint biCompression; 12241 uint biSizeImage; 12242 int biXPelsPerMeter; 12243 int biYPelsPerMeter; 12244 uint biClrUsed; 12245 uint biClrImportant; 12246 // RGBQUAD[colorCount] biColors; 12247 /* Pixels: 12248 Uint8 pixels[] 12249 */ 12250 /* Mask: 12251 Uint8 mask[] 12252 */ 12253 } 12254 12255 ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 12256 12257 assert(mi.width <= 256, "image too wide"); 12258 assert(mi.height <= 256, "image too tall"); 12259 assert(mi.width % 8 == 0, "image not multiple of 8 width"); // i don't want padding nor do i want the and mask to get fancy 12260 assert(mi.height % 4 == 0, "image not multiple of 4 height"); 12261 12262 int icon_plen = mi.width * mi.height * 4; 12263 int icon_mlen = mi.width * mi.height / 8; 12264 12265 int colorCount = 0; 12266 icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 12267 12268 ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen); 12269 Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr; 12270 12271 auto data = memory[Win32Icon.sizeof .. $]; 12272 12273 width = mi.width; 12274 height = mi.height; 12275 12276 auto trueColorImage = mi.getAsTrueColorImage(); 12277 12278 icon_win32.biSize = 40; 12279 icon_win32.biWidth = mi.width; 12280 icon_win32.biHeight = mi.height*2; 12281 icon_win32.biPlanes = 1; 12282 icon_win32.biBitCount = 32; 12283 icon_win32.biSizeImage = icon_plen + icon_mlen; 12284 12285 int offset = 0; 12286 int andOff = icon_plen * 8; // the and offset is in bits 12287 12288 // leaving the and mask as the default 0 so the rgba alpha blend 12289 // does its thing instead 12290 for(int y = height - 1; y >= 0; y--) { 12291 int off2 = y * width * 4; 12292 foreach(x; 0 .. width) { 12293 data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0]; 12294 data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1]; 12295 data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2]; 12296 data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3]; 12297 12298 offset += 4; 12299 off2 += 4; 12300 } 12301 } 12302 12303 return memory; 12304 } 12305 12306 this(MemoryImage mi) { 12307 int icon_len, width, height; 12308 12309 auto icon_win32 = fromMemoryImage(mi, icon_len, width, height); 12310 12311 /* 12312 PNG* png = readPnpngData); 12313 PNGHeader pngh = getHeader(png); 12314 void* icon_win32; 12315 if(pngh.depth == 4) { 12316 auto i = new Win32Icon!(16); 12317 i.fromPNG(png, pngh, icon_len, width, height); 12318 icon_win32 = i; 12319 } 12320 else if(pngh.depth == 8) { 12321 auto i = new Win32Icon!(256); 12322 i.fromPNG(png, pngh, icon_len, width, height); 12323 icon_win32 = i; 12324 } else assert(0); 12325 */ 12326 12327 hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0); 12328 12329 if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError()); 12330 } 12331 12332 ~this() { 12333 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 12334 DestroyIcon(hIcon); 12335 } 12336 12337 HICON hIcon; 12338 } 12339 12340 12341 12342 12343 12344 12345 alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler; 12346 alias HWND NativeWindowHandle; 12347 12348 extern(Windows) 12349 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 12350 try { 12351 if(SimpleWindow.handleNativeGlobalEvent !is null) { 12352 // it returns zero if the message is handled, so we won't do anything more there 12353 // do I like that though? 12354 int mustReturn; 12355 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn); 12356 if(mustReturn) 12357 return ret; 12358 } 12359 12360 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 12361 if(window.getNativeEventHandler !is null) { 12362 int mustReturn; 12363 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn); 12364 if(mustReturn) 12365 return ret; 12366 } 12367 if(auto w = cast(SimpleWindow) (*window)) 12368 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 12369 else 12370 return DefWindowProc(hWnd, iMessage, wParam, lParam); 12371 } else { 12372 return DefWindowProc(hWnd, iMessage, wParam, lParam); 12373 } 12374 } catch (Exception e) { 12375 try { 12376 sdpy_abort(e); 12377 return 0; 12378 } catch(Exception e) { assert(0); } 12379 } 12380 } 12381 12382 void sdpy_abort(Throwable e) nothrow { 12383 try 12384 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 12385 catch(Exception e) 12386 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 12387 ExitProcess(1); 12388 } 12389 12390 mixin template NativeScreenPainterImplementation() { 12391 HDC hdc; 12392 HWND hwnd; 12393 //HDC windowHdc; 12394 HBITMAP oldBmp; 12395 12396 void create(PaintingHandle window) { 12397 hwnd = window; 12398 12399 if(auto sw = cast(SimpleWindow) this.window) { 12400 // drawing on a window, double buffer 12401 auto windowHdc = GetDC(hwnd); 12402 12403 auto buffer = sw.impl.buffer; 12404 if(buffer is null) { 12405 hdc = windowHdc; 12406 windowDc = true; 12407 } else { 12408 hdc = CreateCompatibleDC(windowHdc); 12409 12410 ReleaseDC(hwnd, windowHdc); 12411 12412 oldBmp = SelectObject(hdc, buffer); 12413 } 12414 } else { 12415 // drawing on something else, draw directly 12416 hdc = CreateCompatibleDC(null); 12417 SelectObject(hdc, window); 12418 } 12419 12420 // X doesn't draw a text background, so neither should we 12421 SetBkMode(hdc, TRANSPARENT); 12422 12423 ensureDefaultFontLoaded(); 12424 12425 if(defaultGuiFont) { 12426 SelectObject(hdc, defaultGuiFont); 12427 // DeleteObject(defaultGuiFont); 12428 } 12429 } 12430 12431 static HFONT defaultGuiFont; 12432 static void ensureDefaultFontLoaded() { 12433 static bool triedDefaultGuiFont = false; 12434 if(!triedDefaultGuiFont) { 12435 NONCLIENTMETRICS params; 12436 params.cbSize = params.sizeof; 12437 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 12438 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 12439 } 12440 triedDefaultGuiFont = true; 12441 } 12442 } 12443 12444 private OperatingSystemFont _activeFont; 12445 12446 void setFont(OperatingSystemFont font) { 12447 _activeFont = font; 12448 if(font && font.font) { 12449 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 12450 // error... how to handle tho? 12451 } else { 12452 12453 } 12454 } 12455 else if(defaultGuiFont) 12456 SelectObject(hdc, defaultGuiFont); 12457 } 12458 12459 arsd.color.Rectangle _clipRectangle; 12460 12461 void setClipRectangle(int x, int y, int width, int height) { 12462 auto old = _clipRectangle; 12463 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 12464 if(old == _clipRectangle) 12465 return; 12466 12467 if(width == 0 || height == 0) { 12468 SelectClipRgn(hdc, null); 12469 } else { 12470 auto region = CreateRectRgn(x, y, x + width, y + height); 12471 SelectClipRgn(hdc, region); 12472 DeleteObject(region); 12473 } 12474 } 12475 12476 12477 // just because we can on Windows... 12478 //void create(Image image); 12479 12480 void invalidateRect(Rectangle invalidRect) { 12481 RECT rect; 12482 rect.left = invalidRect.left; 12483 rect.right = invalidRect.right; 12484 rect.top = invalidRect.top; 12485 rect.bottom = invalidRect.bottom; 12486 InvalidateRect(hwnd, &rect, false); 12487 } 12488 bool manualInvalidations; 12489 12490 void dispose() { 12491 // FIXME: this.window.width/height is probably wrong 12492 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 12493 // ReleaseDC(hwnd, windowHdc); 12494 12495 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 12496 if(cast(SimpleWindow) this.window) { 12497 if(!manualInvalidations) 12498 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 12499 } 12500 12501 if(originalPen !is null) 12502 SelectObject(hdc, originalPen); 12503 if(currentPen !is null) 12504 DeleteObject(currentPen); 12505 if(originalBrush !is null) 12506 SelectObject(hdc, originalBrush); 12507 if(currentBrush !is null) 12508 DeleteObject(currentBrush); 12509 12510 SelectObject(hdc, oldBmp); 12511 12512 if(windowDc) 12513 ReleaseDC(hwnd, hdc); 12514 else 12515 DeleteDC(hdc); 12516 12517 if(window.paintingFinishedDg !is null) 12518 window.paintingFinishedDg()(); 12519 } 12520 12521 bool windowDc; 12522 HPEN originalPen; 12523 HPEN currentPen; 12524 12525 Pen _activePen; 12526 12527 Color _outlineColor; 12528 12529 @property void pen(Pen p) { 12530 _activePen = p; 12531 _outlineColor = p.color; 12532 12533 HPEN pen; 12534 if(p.color.a == 0) { 12535 pen = GetStockObject(NULL_PEN); 12536 } else { 12537 int style = PS_SOLID; 12538 final switch(p.style) { 12539 case Pen.Style.Solid: 12540 style = PS_SOLID; 12541 break; 12542 case Pen.Style.Dashed: 12543 style = PS_DASH; 12544 break; 12545 case Pen.Style.Dotted: 12546 style = PS_DOT; 12547 break; 12548 } 12549 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 12550 } 12551 auto orig = SelectObject(hdc, pen); 12552 if(originalPen is null) 12553 originalPen = orig; 12554 12555 if(currentPen !is null) 12556 DeleteObject(currentPen); 12557 12558 currentPen = pen; 12559 12560 // the outline is like a foreground since it's done that way on X 12561 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 12562 12563 } 12564 12565 @property void rasterOp(RasterOp op) { 12566 int mode; 12567 final switch(op) { 12568 case RasterOp.normal: 12569 mode = R2_COPYPEN; 12570 break; 12571 case RasterOp.xor: 12572 mode = R2_XORPEN; 12573 break; 12574 } 12575 SetROP2(hdc, mode); 12576 } 12577 12578 HBRUSH originalBrush; 12579 HBRUSH currentBrush; 12580 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 12581 @property void fillColor(Color c) { 12582 if(c == _fillColor) 12583 return; 12584 _fillColor = c; 12585 HBRUSH brush; 12586 if(c.a == 0) { 12587 brush = GetStockObject(HOLLOW_BRUSH); 12588 } else { 12589 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 12590 } 12591 auto orig = SelectObject(hdc, brush); 12592 if(originalBrush is null) 12593 originalBrush = orig; 12594 12595 if(currentBrush !is null) 12596 DeleteObject(currentBrush); 12597 12598 currentBrush = brush; 12599 12600 // background color is NOT set because X doesn't draw text backgrounds 12601 // SetBkColor(hdc, RGB(255, 255, 255)); 12602 } 12603 12604 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 12605 BITMAP bm; 12606 12607 HDC hdcMem = CreateCompatibleDC(hdc); 12608 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 12609 12610 GetObject(i.handle, bm.sizeof, &bm); 12611 12612 // or should I AlphaBlend!??!?! 12613 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 12614 12615 SelectObject(hdcMem, hbmOld); 12616 DeleteDC(hdcMem); 12617 } 12618 12619 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 12620 BITMAP bm; 12621 12622 HDC hdcMem = CreateCompatibleDC(hdc); 12623 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 12624 12625 GetObject(s.handle, bm.sizeof, &bm); 12626 12627 version(CRuntime_DigitalMars) goto noalpha; 12628 12629 // or should I AlphaBlend!??!?! note it is supposed to be premultiplied http://www.fengyuan.com/article/alphablend.html 12630 if(s.enableAlpha) { 12631 auto dw = w ? w : bm.bmWidth; 12632 auto dh = h ? h : bm.bmHeight; 12633 BLENDFUNCTION bf; 12634 bf.BlendOp = AC_SRC_OVER; 12635 bf.SourceConstantAlpha = 255; 12636 bf.AlphaFormat = AC_SRC_ALPHA; 12637 AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf); 12638 } else { 12639 noalpha: 12640 BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY); 12641 } 12642 12643 SelectObject(hdcMem, hbmOld); 12644 DeleteDC(hdcMem); 12645 } 12646 12647 Size textSize(scope const(char)[] text) { 12648 bool dummyX; 12649 if(text.length == 0) { 12650 text = " "; 12651 dummyX = true; 12652 } 12653 RECT rect; 12654 WCharzBuffer buffer = WCharzBuffer(text); 12655 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX); 12656 return Size(dummyX ? 0 : rect.right, rect.bottom); 12657 } 12658 12659 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 12660 if(text.length && text[$-1] == '\n') 12661 text = text[0 .. $-1]; // tailing newlines are weird on windows... 12662 if(text.length && text[$-1] == '\r') 12663 text = text[0 .. $-1]; 12664 12665 WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines); 12666 if(x2 == 0 && y2 == 0) { 12667 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 12668 } else { 12669 RECT rect; 12670 rect.left = x; 12671 rect.top = y; 12672 rect.right = x2; 12673 rect.bottom = y2; 12674 12675 uint mode = DT_LEFT; 12676 if(alignment & TextAlignment.Right) 12677 mode = DT_RIGHT; 12678 else if(alignment & TextAlignment.Center) 12679 mode = DT_CENTER; 12680 12681 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 12682 if(alignment & TextAlignment.VerticalCenter) 12683 mode |= DT_VCENTER | DT_SINGLELINE; 12684 12685 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX); 12686 } 12687 12688 /* 12689 uint mode; 12690 12691 if(alignment & TextAlignment.Center) 12692 mode = TA_CENTER; 12693 12694 SetTextAlign(hdc, mode); 12695 */ 12696 } 12697 12698 int fontHeight() { 12699 TEXTMETRIC metric; 12700 if(GetTextMetricsW(hdc, &metric)) { 12701 return metric.tmHeight; 12702 } 12703 12704 return 16; // idk just guessing here, maybe we should throw 12705 } 12706 12707 void drawPixel(int x, int y) { 12708 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 12709 } 12710 12711 // The basic shapes, outlined 12712 12713 void drawLine(int x1, int y1, int x2, int y2) { 12714 MoveToEx(hdc, x1, y1, null); 12715 LineTo(hdc, x2, y2); 12716 } 12717 12718 void drawRectangle(int x, int y, int width, int height) { 12719 // FIXME: with a wider pen this might not draw quite right. im not sure. 12720 gdi.Rectangle(hdc, x, y, x + width, y + height); 12721 } 12722 12723 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 12724 RoundRect( 12725 hdc, 12726 upperLeft.x, upperLeft.y, 12727 lowerRight.x, lowerRight.y, 12728 borderRadius, borderRadius 12729 ); 12730 } 12731 12732 /// Arguments are the points of the bounding rectangle 12733 void drawEllipse(int x1, int y1, int x2, int y2) { 12734 Ellipse(hdc, x1, y1, x2, y2); 12735 } 12736 12737 void drawArc(int x1, int y1, int width, int height, int start, int length) { 12738 //if(length > 360*64) 12739 //length = 360*64; 12740 12741 if((start == 0 && length == 360*64)) { 12742 drawEllipse(x1, y1, x1 + width, y1 + height); 12743 } else { 12744 import core.stdc.math; 12745 12746 bool clockwise = false; 12747 if(length < 0) { 12748 clockwise = true; 12749 length = -length; 12750 } 12751 12752 double startAngle = cast(double) start / 64.0 / 180.0 * 3.14159265358979323; 12753 double endAngle = cast(double) (start + length) / 64.0 / 180.0 * 3.14159265358979323; 12754 12755 auto c1 = cast(int) (cos(startAngle) * width / 2.0 + double(x1) + double(width) / 2.0); 12756 auto c2 = cast(int) (-sin(startAngle) * height / 2.0 + double(y1) + double(height) / 2.0); 12757 auto c3 = cast(int) (cos(endAngle) * width / 2.0 + double(x1) + double(width) / 2.0); 12758 auto c4 = cast(int) (-sin(endAngle) * height / 2.0 + double(y1) + double(height) / 2.0); 12759 12760 if(clockwise) { 12761 auto t1 = c1; 12762 auto t2 = c2; 12763 c1 = c3; 12764 c2 = c4; 12765 c3 = t1; 12766 c4 = t2; 12767 } 12768 12769 //if(_activePen.color.a) 12770 //Arc(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4); 12771 //if(_fillColor.a) 12772 12773 Pie(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4); 12774 } 12775 } 12776 12777 void drawPolygon(Point[] vertexes) { 12778 POINT[] points; 12779 points.length = vertexes.length; 12780 12781 foreach(i, p; vertexes) { 12782 points[i].x = p.x; 12783 points[i].y = p.y; 12784 } 12785 12786 Polygon(hdc, points.ptr, cast(int) points.length); 12787 } 12788 } 12789 12790 12791 // Mix this into the SimpleWindow class 12792 mixin template NativeSimpleWindowImplementation() { 12793 int curHidden = 0; // counter 12794 __gshared static bool[string] knownWinClasses; 12795 static bool altPressed = false; 12796 12797 HANDLE oldCursor; 12798 12799 void hideCursor () { 12800 if(curHidden == 0) 12801 oldCursor = SetCursor(null); 12802 ++curHidden; 12803 } 12804 12805 void showCursor () { 12806 --curHidden; 12807 if(curHidden == 0) { 12808 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 12809 } 12810 } 12811 12812 12813 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 12814 12815 void setMinSize (int minwidth, int minheight) { 12816 minWidth = minwidth; 12817 minHeight = minheight; 12818 } 12819 void setMaxSize (int maxwidth, int maxheight) { 12820 maxWidth = maxwidth; 12821 maxHeight = maxheight; 12822 } 12823 12824 // FIXME i'm not sure that Windows has this functionality 12825 // though it is nonessential anyway. 12826 void setResizeGranularity (int granx, int grany) {} 12827 12828 ScreenPainter getPainter(bool manualInvalidations) { 12829 return ScreenPainter(this, hwnd, manualInvalidations); 12830 } 12831 12832 HBITMAP buffer; 12833 12834 void setTitle(string title) { 12835 WCharzBuffer bfr = WCharzBuffer(title); 12836 SetWindowTextW(hwnd, bfr.ptr); 12837 } 12838 12839 string getTitle() { 12840 auto len = GetWindowTextLengthW(hwnd); 12841 if (!len) 12842 return null; 12843 wchar[256] tmpBuffer; 12844 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 12845 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 12846 auto str = buffer[0 .. len2]; 12847 return makeUtf8StringFromWindowsString(str); 12848 } 12849 12850 void move(int x, int y) { 12851 RECT rect; 12852 GetWindowRect(hwnd, &rect); 12853 // move it while maintaining the same size... 12854 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 12855 } 12856 12857 void resize(int w, int h) { 12858 RECT rect; 12859 GetWindowRect(hwnd, &rect); 12860 12861 RECT client; 12862 GetClientRect(hwnd, &client); 12863 12864 rect.right = rect.right - client.right + w; 12865 rect.bottom = rect.bottom - client.bottom + h; 12866 12867 // same position, new size for the client rectangle 12868 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 12869 12870 updateOpenglViewportIfNeeded(w, h); 12871 } 12872 12873 void moveResize (int x, int y, int w, int h) { 12874 // what's given is the client rectangle, we need to adjust 12875 12876 RECT rect; 12877 rect.left = x; 12878 rect.top = y; 12879 rect.right = w + x; 12880 rect.bottom = h + y; 12881 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 12882 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12883 12884 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 12885 updateOpenglViewportIfNeeded(w, h); 12886 if (windowResized !is null) windowResized(w, h); 12887 } 12888 12889 version(without_opengl) {} else { 12890 HGLRC ghRC; 12891 HDC ghDC; 12892 } 12893 12894 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 12895 string cnamec; 12896 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 12897 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 12898 cnamec = "DSimpleWindow"; 12899 } else { 12900 cnamec = sdpyWindowClass; 12901 } 12902 12903 WCharzBuffer cn = WCharzBuffer(cnamec); 12904 12905 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 12906 12907 if(cnamec !in knownWinClasses) { 12908 WNDCLASSEX wc; 12909 12910 // FIXME: I might be able to use cbWndExtra to hold the pointer back 12911 // to the object. Maybe. 12912 wc.cbSize = wc.sizeof; 12913 wc.cbClsExtra = 0; 12914 wc.cbWndExtra = 0; 12915 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 12916 wc.hCursor = LoadCursorW(null, IDC_ARROW); 12917 wc.hIcon = LoadIcon(hInstance, null); 12918 wc.hInstance = hInstance; 12919 wc.lpfnWndProc = &WndProc; 12920 wc.lpszClassName = cn.ptr; 12921 wc.hIconSm = null; 12922 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 12923 if(!RegisterClassExW(&wc)) 12924 throw new WindowsApiException("RegisterClassExW", GetLastError()); 12925 knownWinClasses[cnamec] = true; 12926 } 12927 12928 int style; 12929 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 12930 12931 // FIXME: windowType and customizationFlags 12932 final switch(windowType) { 12933 case WindowTypes.normal: 12934 if(resizability == Resizability.fixedSize) { 12935 style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION; 12936 } else { 12937 style = WS_OVERLAPPEDWINDOW; 12938 } 12939 break; 12940 case WindowTypes.undecorated: 12941 style = WS_POPUP | WS_SYSMENU; 12942 break; 12943 case WindowTypes.eventOnly: 12944 _hidden = true; 12945 break; 12946 case WindowTypes.tooltip: 12947 case WindowTypes.dnd: 12948 case WindowTypes.comboBoxDropdown: 12949 case WindowTypes.dropdownMenu: 12950 case WindowTypes.popupMenu: 12951 case WindowTypes.notification: 12952 style = WS_POPUP; 12953 flags |= WS_EX_NOACTIVATE; 12954 break; 12955 case WindowTypes.dialog: 12956 style = WS_OVERLAPPEDWINDOW; 12957 break; 12958 case WindowTypes.nestedChild: 12959 style = WS_CHILD; 12960 break; 12961 case WindowTypes.minimallyWrapped: 12962 assert(0, "construct minimally wrapped through the other ctor overlad"); 12963 } 12964 12965 if ((customizationFlags & WindowFlags.extraComposite) != 0) 12966 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 12967 12968 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 12969 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 12970 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 12971 12972 if(!hwnd) 12973 throw new WindowsApiException("CreateWindowEx", GetLastError()); 12974 12975 if ((customizationFlags & WindowFlags.extraComposite) != 0) 12976 setOpacity(255); 12977 12978 SimpleWindow.nativeMapping[hwnd] = this; 12979 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 12980 12981 if(windowType == WindowTypes.eventOnly) 12982 return; 12983 12984 HDC hdc = GetDC(hwnd); 12985 12986 if(!hdc) 12987 throw new WindowsApiException("GetDC", GetLastError()); 12988 12989 version(without_opengl) {} 12990 else { 12991 if(opengl == OpenGlOptions.yes) { 12992 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 12993 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 12994 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 12995 ghDC = hdc; 12996 PIXELFORMATDESCRIPTOR pfd; 12997 12998 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 12999 pfd.nVersion = 1; 13000 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 13001 pfd.dwLayerMask = PFD_MAIN_PLANE; 13002 pfd.iPixelType = PFD_TYPE_RGBA; 13003 pfd.cColorBits = 24; 13004 pfd.cDepthBits = 24; 13005 pfd.cAccumBits = 0; 13006 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 13007 13008 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 13009 13010 if (pixelformat == 0) 13011 throw new WindowsApiException("ChoosePixelFormat", GetLastError()); 13012 13013 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 13014 throw new WindowsApiException("SetPixelFormat", GetLastError()); 13015 13016 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 13017 // windoze is idiotic: we have to have OpenGL context to get function addresses 13018 // so we will create fake context to get that stupid address 13019 auto tmpcc = wglCreateContext(ghDC); 13020 if (tmpcc !is null) { 13021 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 13022 wglMakeCurrent(ghDC, tmpcc); 13023 wglInitOtherFunctions(); 13024 } 13025 } 13026 13027 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 13028 int[9] contextAttribs = [ 13029 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 13030 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 13031 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 13032 // for modern context, set "forward compatibility" flag too 13033 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 13034 0/*None*/, 13035 ]; 13036 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 13037 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 13038 // activate fallback mode 13039 // 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; 13040 ghRC = wglCreateContext(ghDC); 13041 } 13042 if (ghRC is null) 13043 throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError()); 13044 } else { 13045 // try to do at least something 13046 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 13047 sdpyOpenGLContextVersion = 0; 13048 ghRC = wglCreateContext(ghDC); 13049 } 13050 if (ghRC is null) 13051 throw new WindowsApiException("wglCreateContext", GetLastError()); 13052 } 13053 } 13054 } 13055 13056 if(opengl == OpenGlOptions.no) { 13057 buffer = CreateCompatibleBitmap(hdc, width, height); 13058 13059 auto hdcBmp = CreateCompatibleDC(hdc); 13060 // make sure it's filled with a blank slate 13061 auto oldBmp = SelectObject(hdcBmp, buffer); 13062 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 13063 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 13064 gdi.Rectangle(hdcBmp, 0, 0, width, height); 13065 SelectObject(hdcBmp, oldBmp); 13066 SelectObject(hdcBmp, oldBrush); 13067 SelectObject(hdcBmp, oldPen); 13068 DeleteDC(hdcBmp); 13069 13070 bmpWidth = width; 13071 bmpHeight = height; 13072 13073 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 13074 } 13075 13076 // We want the window's client area to match the image size 13077 RECT rcClient, rcWindow; 13078 POINT ptDiff; 13079 GetClientRect(hwnd, &rcClient); 13080 GetWindowRect(hwnd, &rcWindow); 13081 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 13082 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 13083 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 13084 13085 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 13086 ShowWindow(hwnd, SW_SHOWNORMAL); 13087 } else { 13088 _hidden = true; 13089 } 13090 this._visibleForTheFirstTimeCalled = false; // hack! 13091 } 13092 13093 13094 void dispose() { 13095 if(buffer) 13096 DeleteObject(buffer); 13097 } 13098 13099 void closeWindow() { 13100 if(ghRC) { 13101 wglDeleteContext(ghRC); 13102 ghRC = null; 13103 } 13104 DestroyWindow(hwnd); 13105 } 13106 13107 bool setOpacity(ubyte alpha) { 13108 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 13109 } 13110 13111 HANDLE currentCursor; 13112 13113 // returns zero if it recognized the event 13114 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 13115 MouseEvent mouse; 13116 13117 void mouseEvent(bool isScreen, ulong mods) { 13118 auto x = LOWORD(lParam); 13119 auto y = HIWORD(lParam); 13120 if(isScreen) { 13121 POINT p; 13122 p.x = x; 13123 p.y = y; 13124 ScreenToClient(hwnd, &p); 13125 x = cast(ushort) p.x; 13126 y = cast(ushort) p.y; 13127 } 13128 13129 if(wind.resizability == Resizability.automaticallyScaleIfPossible) { 13130 x = cast(ushort)( x * wind._virtualWidth / wind._width ); 13131 y = cast(ushort)( y * wind._virtualHeight / wind._height ); 13132 } 13133 13134 mouse.x = x + offsetX; 13135 mouse.y = y + offsetY; 13136 13137 wind.mdx(mouse); 13138 mouse.modifierState = cast(int) mods; 13139 mouse.window = wind; 13140 13141 if(wind.handleMouseEvent) 13142 wind.handleMouseEvent(mouse); 13143 } 13144 13145 switch(msg) { 13146 case WM_GETMINMAXINFO: 13147 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 13148 13149 if(wind.minWidth > 0) { 13150 RECT rect; 13151 rect.left = 100; 13152 rect.top = 100; 13153 rect.right = wind.minWidth + 100; 13154 rect.bottom = wind.minHeight + 100; 13155 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 13156 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 13157 13158 mmi.ptMinTrackSize.x = rect.right - rect.left; 13159 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 13160 } 13161 13162 if(wind.maxWidth < int.max) { 13163 RECT rect; 13164 rect.left = 100; 13165 rect.top = 100; 13166 rect.right = wind.maxWidth + 100; 13167 rect.bottom = wind.maxHeight + 100; 13168 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 13169 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 13170 13171 mmi.ptMaxTrackSize.x = rect.right - rect.left; 13172 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 13173 } 13174 break; 13175 case WM_CHAR: 13176 wchar c = cast(wchar) wParam; 13177 if(wind.handleCharEvent) 13178 wind.handleCharEvent(cast(dchar) c); 13179 break; 13180 case WM_SETFOCUS: 13181 case WM_KILLFOCUS: 13182 wind._focused = (msg == WM_SETFOCUS); 13183 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 13184 if(wind.onFocusChange) 13185 wind.onFocusChange(msg == WM_SETFOCUS); 13186 break; 13187 13188 case WM_SYSKEYDOWN: 13189 goto case; 13190 case WM_SYSKEYUP: 13191 if(lParam & (1 << 29)) { 13192 goto case; 13193 } else { 13194 // no window has keyboard focus 13195 goto default; 13196 } 13197 case WM_KEYDOWN: 13198 case WM_KEYUP: 13199 KeyEvent ev; 13200 ev.key = cast(Key) wParam; 13201 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 13202 if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way 13203 13204 ev.hardwareCode = (lParam & 0xff0000) >> 16; 13205 13206 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 13207 ev.modifierState |= ModifierState.shift; 13208 //k8: this doesn't work; thanks for nothing, windows 13209 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 13210 ev.modifierState |= ModifierState.alt;*/ 13211 // this never seems to actually be set 13212 // if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 13213 13214 if (wParam == 0x12) { 13215 altPressed = (msg == WM_SYSKEYDOWN); 13216 } 13217 13218 if(msg == WM_KEYDOWN || msg == WM_KEYUP) { 13219 altPressed = false; 13220 } 13221 // sdpyPrintDebugString(altPressed ? "alt down" : " up "); 13222 13223 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 13224 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 13225 ev.modifierState |= ModifierState.ctrl; 13226 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 13227 ev.modifierState |= ModifierState.windows; 13228 if(GetKeyState(Key.NumLock)) 13229 ev.modifierState |= ModifierState.numLock; 13230 if(GetKeyState(Key.CapsLock)) 13231 ev.modifierState |= ModifierState.capsLock; 13232 13233 /+ 13234 // we always want to send the character too, so let's convert it 13235 ubyte[256] state; 13236 wchar[16] buffer; 13237 GetKeyboardState(state.ptr); 13238 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 13239 13240 foreach(dchar d; buffer) { 13241 ev.character = d; 13242 break; 13243 } 13244 +/ 13245 13246 ev.window = wind; 13247 if(wind.handleKeyEvent) 13248 wind.handleKeyEvent(ev); 13249 break; 13250 case 0x0249 /* WM_POINTERENTER */: 13251 case 0x024A /* WM_POINTERLEAVE */: 13252 case 0x0246 /* WM_POINTERDOWN */: 13253 case 0x0247 /* WM_POINTERUP */: 13254 case 0x0245 /* WM_POINTERUPDATE */: 13255 //import std.conv; import arsd.core; writeln("update ", LOWORD(wParam), " ", to!string(HIWORD(wParam), 2), " ", cast(short) LOWORD(lParam), "x", cast(short) HIWORD(lParam)); 13256 13257 auto pointerId = LOWORD(wParam); 13258 auto flags = HIWORD(wParam); 13259 auto x = cast(short) LOWORD(lParam); 13260 auto y = cast(short) HIWORD(lParam); 13261 13262 void dispatchIde(InputDeviceEvent ide) { 13263 ide.event = msg; 13264 ide.rootX = x; 13265 ide.rootY = y; 13266 ide.buttons = 0; 13267 ide.valuators[] = double.nan; 13268 13269 ide.window = wind; 13270 wind.dispatchXInputEvent(ide); 13271 } 13272 13273 POINTER_INPUT_TYPE pit; 13274 if(GetPointerType(pointerId, &pit)) { 13275 switch(pit) { 13276 case POINTER_INPUT_TYPE.PT_TOUCH: 13277 POINTER_TOUCH_INFO[16] buffer; 13278 uint count = cast(uint) buffer.length; 13279 if(GetPointerFrameTouchInfo(pointerId, &count, buffer.ptr)) { 13280 auto frame = buffer[0 .. count]; 13281 13282 foreach(f; frame) { 13283 InputDeviceEvent ide; 13284 13285 // check f.pointerInfo.hwndTarget to translate these correctly... 13286 ide.windowX = f.pointerInfo.ptPixelLocation.x; 13287 ide.windowY = f.pointerInfo.ptPixelLocation.y; 13288 ide.deviceId = f.pointerInfo.sourceDevice; 13289 ide.detail = pointerId; 13290 ide.pressure = f.pressure; 13291 13292 dispatchIde(ide); 13293 } 13294 } else { 13295 throw new WindowsApiException("GetPointerFrameTouchInfo", GetLastError()); 13296 } 13297 break; 13298 case POINTER_INPUT_TYPE.PT_PEN: 13299 POINTER_PEN_INFO[16] buffer; 13300 uint count = cast(uint) buffer.length; 13301 if(GetPointerFramePenInfo(pointerId, &count, buffer.ptr)) { 13302 auto frame = buffer[0 .. count]; 13303 13304 foreach(f; frame) { 13305 InputDeviceEvent ide; 13306 13307 // check f.pointerInfo.hwndTarget to translate these correctly... 13308 ide.windowX = f.pointerInfo.ptPixelLocation.x; 13309 ide.windowY = f.pointerInfo.ptPixelLocation.y; 13310 ide.deviceId = f.pointerInfo.sourceDevice; 13311 ide.detail = pointerId; 13312 ide.pressure = f.pressure; 13313 ide.tiltX = f.tiltX; 13314 ide.tiltY = f.tiltY; 13315 13316 dispatchIde(ide); 13317 } 13318 } else { 13319 throw new WindowsApiException("GetPointerFramePenInfo", GetLastError()); 13320 } 13321 break; 13322 case POINTER_INPUT_TYPE.PT_MOUSE: 13323 case POINTER_INPUT_TYPE.PT_TOUCHPAD: 13324 // generic GetPointerInfo 13325 13326 break; 13327 case POINTER_INPUT_TYPE.PT_POINTER: 13328 // should never happen according to docs 13329 default: 13330 } 13331 } else { 13332 throw new WindowsApiException("GetPointerType", GetLastError()); 13333 } 13334 13335 //wind.dispatchXInputEvent 13336 13337 break; 13338 case 0x024C /* WM_POINTERCAPTURECHANGED */: 13339 break; 13340 case 0x020a /*WM_MOUSEWHEEL*/: 13341 // send click 13342 mouse.type = cast(MouseEventType) 1; 13343 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 13344 mouseEvent(true, LOWORD(wParam)); 13345 13346 // also send release 13347 mouse.type = cast(MouseEventType) 2; 13348 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 13349 mouseEvent(true, LOWORD(wParam)); 13350 break; 13351 case 0x020E /* WM_MOUSEHWHEEL */: 13352 // send click 13353 mouse.type = cast(MouseEventType) 1; 13354 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) < 0) ? MouseButton.wheelLeft : MouseButton.wheelRight); 13355 mouseEvent(true, LOWORD(wParam)); 13356 13357 // also send release 13358 mouse.type = cast(MouseEventType) 2; 13359 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) < 0) ? MouseButton.wheelLeft : MouseButton.wheelRight); 13360 mouseEvent(true, LOWORD(wParam)); 13361 break; 13362 case WM_MOUSEMOVE: 13363 mouse.type = cast(MouseEventType) 0; 13364 mouseEvent(false, wParam); 13365 break; 13366 case WM_LBUTTONDOWN: 13367 case WM_LBUTTONDBLCLK: 13368 mouse.type = cast(MouseEventType) 1; 13369 mouse.button = MouseButton.left; 13370 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 13371 mouseEvent(false, wParam); 13372 break; 13373 case WM_LBUTTONUP: 13374 mouse.type = cast(MouseEventType) 2; 13375 mouse.button = MouseButton.left; 13376 mouseEvent(false, wParam); 13377 break; 13378 case WM_RBUTTONDOWN: 13379 case WM_RBUTTONDBLCLK: 13380 mouse.type = cast(MouseEventType) 1; 13381 mouse.button = MouseButton.right; 13382 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 13383 mouseEvent(false, wParam); 13384 break; 13385 case WM_RBUTTONUP: 13386 mouse.type = cast(MouseEventType) 2; 13387 mouse.button = MouseButton.right; 13388 mouseEvent(false, wParam); 13389 break; 13390 case WM_MBUTTONDOWN: 13391 case WM_MBUTTONDBLCLK: 13392 mouse.type = cast(MouseEventType) 1; 13393 mouse.button = MouseButton.middle; 13394 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 13395 mouseEvent(false, wParam); 13396 break; 13397 case WM_MBUTTONUP: 13398 mouse.type = cast(MouseEventType) 2; 13399 mouse.button = MouseButton.middle; 13400 mouseEvent(false, wParam); 13401 break; 13402 case WM_XBUTTONDOWN: 13403 case WM_XBUTTONDBLCLK: 13404 mouse.type = cast(MouseEventType) 1; 13405 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 13406 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 13407 mouseEvent(false, wParam); 13408 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 13409 case WM_XBUTTONUP: 13410 mouse.type = cast(MouseEventType) 2; 13411 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 13412 mouseEvent(false, wParam); 13413 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 13414 13415 default: return 1; 13416 } 13417 return 0; 13418 } 13419 13420 HWND hwnd; 13421 private int oldWidth; 13422 private int oldHeight; 13423 private bool inSizeMove; 13424 13425 /++ 13426 If this is true, the live resize events will trigger all the size things as they drag. If false, those events only come when the size is complete; when the user lets go of the mouse button. 13427 13428 History: 13429 Added November 23, 2021 13430 13431 Not fully stable, may be moved out of the impl struct. 13432 13433 Default value changed to `true` on February 15, 2021 13434 +/ 13435 bool doLiveResizing = true; 13436 13437 package int bmpWidth; 13438 package int bmpHeight; 13439 13440 // the extern(Windows) wndproc should just forward to this 13441 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 13442 try { 13443 assert(hwnd is this.hwnd); 13444 13445 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 13446 switch(msg) { 13447 case WM_MENUCHAR: // menu active but key not associated with a thing. 13448 // you would ideally use this for like a search function but sdpy not that ideally designed. alas. 13449 // The main things we can do are select, execute, close, or ignore 13450 // the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to 13451 // the user. This can be a bit annoying for sdpy things so instead im overriding and setting it 13452 // to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy 13453 // that's the lesser bad choice rn. Can always override by returning true in triggerEvents.... 13454 13455 // returns the value in the *high order word* of the return value 13456 // hence the << 16 13457 return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user 13458 case WM_SETCURSOR: 13459 if(cast(HWND) wParam !is hwnd) 13460 return 0; // further processing elsewhere 13461 13462 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 13463 SetCursor(this.curHidden > 0 ? null : currentCursor); 13464 return 1; 13465 } else { 13466 return DefWindowProc(hwnd, msg, wParam, lParam); 13467 } 13468 //break; 13469 13470 case WM_CLOSE: 13471 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 13472 break; 13473 case WM_DESTROY: 13474 if (this.visibilityChanged !is null && this._visible) this.visibilityChanged(false); 13475 13476 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 13477 SimpleWindow.nativeMapping.remove(hwnd); 13478 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 13479 13480 bool anyImportant = false; 13481 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 13482 if(w.beingOpenKeepsAppOpen) { 13483 anyImportant = true; 13484 break; 13485 } 13486 if(!anyImportant) { 13487 PostQuitMessage(0); 13488 } 13489 break; 13490 case 0x02E0 /*WM_DPICHANGED*/: 13491 this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs 13492 13493 RECT* prcNewWindow = cast(RECT*)lParam; 13494 // docs say this is the recommended position and we should honor it 13495 SetWindowPos(hwnd, 13496 null, 13497 prcNewWindow.left, 13498 prcNewWindow.top, 13499 prcNewWindow.right - prcNewWindow.left, 13500 prcNewWindow.bottom - prcNewWindow.top, 13501 SWP_NOZORDER | SWP_NOACTIVATE); 13502 13503 // doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp 13504 // im not sure it is completely correct 13505 // but without it the tabs and such do look weird as things change. 13506 if(SystemParametersInfoForDpi) { 13507 LOGFONT lfText; 13508 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_); 13509 HFONT hFontNew = CreateFontIndirect(&lfText); 13510 if (hFontNew) 13511 { 13512 //DeleteObject(hFontOld); 13513 static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) { 13514 SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0)); 13515 return TRUE; 13516 } 13517 EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew); 13518 } 13519 } 13520 13521 if(this.onDpiChanged) 13522 this.onDpiChanged(); 13523 break; 13524 case WM_ENTERIDLE: 13525 // when a menu is up, it stops normal event processing (modal message loop) 13526 // but this at least gives us a chance to SOMETIMES catch up 13527 // FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it. 13528 SimpleWindow.processAllCustomEvents; 13529 SimpleWindow.processAllCustomEvents; 13530 SleepEx(0, true); 13531 break; 13532 case WM_SIZE: 13533 if(wParam == 1 /* SIZE_MINIMIZED */) 13534 break; 13535 _width = LOWORD(lParam); 13536 _height = HIWORD(lParam); 13537 13538 // I want to avoid tearing in the windows (my code is inefficient 13539 // so this is a hack around that) so while sizing, we don't trigger, 13540 // but we do want to trigger on events like mazimize. 13541 if(!inSizeMove || doLiveResizing) 13542 goto size_changed; 13543 break; 13544 /+ 13545 case WM_SIZING: 13546 writeln("size"); 13547 break; 13548 +/ 13549 // I don't like the tearing I get when redrawing on WM_SIZE 13550 // (I know there's other ways to fix that but I don't like that behavior anyway) 13551 // so instead it is going to redraw only at the end of a size. 13552 case 0x0231: /* WM_ENTERSIZEMOVE */ 13553 inSizeMove = true; 13554 break; 13555 case 0x0232: /* WM_EXITSIZEMOVE */ 13556 inSizeMove = false; 13557 13558 size_changed: 13559 13560 // nothing relevant changed, don't bother redrawing 13561 if(oldWidth == _width && oldHeight == _height) { 13562 if(msg == 0x0232) 13563 goto finalize_resize; 13564 break; 13565 } 13566 13567 // note: OpenGL windows don't use a backing bmp, so no need to change them 13568 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 13569 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 13570 // gotta get the double buffer bmp to match the window 13571 // FIXME: could this be more efficient? it never relinquishes a large bitmap 13572 13573 // if it is auto-scaled, we keep the backing bitmap the same size all the time 13574 if(resizability != Resizability.automaticallyScaleIfPossible) 13575 if(_width > bmpWidth || _height > bmpHeight) { 13576 auto hdc = GetDC(hwnd); 13577 auto oldBuffer = buffer; 13578 buffer = CreateCompatibleBitmap(hdc, _width, _height); 13579 13580 auto hdcBmp = CreateCompatibleDC(hdc); 13581 auto oldBmp = SelectObject(hdcBmp, buffer); 13582 13583 auto hdcOldBmp = CreateCompatibleDC(hdc); 13584 auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer); 13585 13586 /+ 13587 RECT r; 13588 r.left = 0; 13589 r.top = 0; 13590 r.right = width; 13591 r.bottom = height; 13592 auto c = Color.green; 13593 auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 13594 FillRect(hdcBmp, &r, brush); 13595 DeleteObject(brush); 13596 +/ 13597 13598 BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY); 13599 13600 bmpWidth = _width; 13601 bmpHeight = _height; 13602 13603 SelectObject(hdcOldBmp, oldOldBmp); 13604 DeleteDC(hdcOldBmp); 13605 13606 SelectObject(hdcBmp, oldBmp); 13607 DeleteDC(hdcBmp); 13608 13609 ReleaseDC(hwnd, hdc); 13610 13611 DeleteObject(oldBuffer); 13612 } 13613 } 13614 13615 updateOpenglViewportIfNeeded(_width, _height); 13616 13617 if(resizability != Resizability.automaticallyScaleIfPossible) 13618 if(windowResized !is null) 13619 windowResized(_width, _height); 13620 13621 /+ 13622 if(inSizeMove) { 13623 // SimpleWindow.processAllCustomEvents(); 13624 // SimpleWindow.processAllCustomEvents(); 13625 13626 //RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 13627 //sdpyPrintDebugString("redraw b"); 13628 } else { 13629 +/ { 13630 finalize_resize: 13631 // when it is all done, make sure everything is freshly drawn or there might be 13632 // weird bugs left. 13633 SimpleWindow.processAllCustomEvents(); 13634 SimpleWindow.processAllCustomEvents(); 13635 13636 RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 13637 // sdpyPrintDebugString("redraw"); 13638 } 13639 13640 oldWidth = this._width; 13641 oldHeight = this._height; 13642 break; 13643 case WM_ERASEBKGND: 13644 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 13645 if (!this._visibleForTheFirstTimeCalled) { 13646 this._visibleForTheFirstTimeCalled = true; 13647 if (this.visibleForTheFirstTime !is null) { 13648 this.visibleForTheFirstTime(); 13649 } 13650 } 13651 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 13652 version(without_opengl) {} else { 13653 if (openglMode == OpenGlOptions.yes) return 1; 13654 } 13655 // call windows default handler, so it can paint standard controls 13656 goto default; 13657 case WM_CTLCOLORBTN: 13658 case WM_CTLCOLORSTATIC: 13659 SetBkMode(cast(HDC) wParam, TRANSPARENT); 13660 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 13661 GetSysColorBrush(COLOR_3DFACE); 13662 //break; 13663 case WM_SHOWWINDOW: 13664 auto before = this._visible; 13665 this._visible = (wParam != 0); 13666 if (!this._visibleForTheFirstTimeCalled && this._visible) { 13667 this._visibleForTheFirstTimeCalled = true; 13668 if (this.visibleForTheFirstTime !is null) { 13669 this.visibleForTheFirstTime(); 13670 } 13671 } 13672 if (this.visibilityChanged !is null && this._visible != before) this.visibilityChanged(this._visible); 13673 break; 13674 case WM_PAINT: { 13675 if (!this._visibleForTheFirstTimeCalled) { 13676 this._visibleForTheFirstTimeCalled = true; 13677 if (this.visibleForTheFirstTime !is null) { 13678 this.visibleForTheFirstTime(); 13679 } 13680 } 13681 13682 BITMAP bm; 13683 PAINTSTRUCT ps; 13684 13685 HDC hdc = BeginPaint(hwnd, &ps); 13686 13687 if(openglMode == OpenGlOptions.no) { 13688 13689 HDC hdcMem = CreateCompatibleDC(hdc); 13690 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 13691 13692 GetObject(buffer, bm.sizeof, &bm); 13693 13694 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 13695 if(resizability == Resizability.automaticallyScaleIfPossible) 13696 StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 13697 else 13698 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 13699 //BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY); 13700 13701 SelectObject(hdcMem, hbmOld); 13702 DeleteDC(hdcMem); 13703 EndPaint(hwnd, &ps); 13704 } else { 13705 EndPaint(hwnd, &ps); 13706 version(without_opengl) {} else 13707 redrawOpenGlSceneSoon(); 13708 } 13709 } break; 13710 default: 13711 return DefWindowProc(hwnd, msg, wParam, lParam); 13712 } 13713 return 0; 13714 13715 } 13716 catch(Throwable t) { 13717 sdpyPrintDebugString(t.toString); 13718 return 0; 13719 } 13720 } 13721 } 13722 13723 mixin template NativeImageImplementation() { 13724 HBITMAP handle; 13725 ubyte* rawData; 13726 13727 final: 13728 13729 Color getPixel(int x, int y) @system { 13730 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 13731 // remember, bmps are upside down 13732 auto offset = itemsPerLine * (height - y - 1) + x * 3; 13733 13734 Color c; 13735 if(enableAlpha) 13736 c.a = rawData[offset + 3]; 13737 else 13738 c.a = 255; 13739 c.b = rawData[offset + 0]; 13740 c.g = rawData[offset + 1]; 13741 c.r = rawData[offset + 2]; 13742 c.unPremultiply(); 13743 return c; 13744 } 13745 13746 void setPixel(int x, int y, Color c) @system { 13747 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 13748 // remember, bmps are upside down 13749 auto offset = itemsPerLine * (height - y - 1) + x * 3; 13750 13751 if(enableAlpha) 13752 c.premultiply(); 13753 13754 rawData[offset + 0] = c.b; 13755 rawData[offset + 1] = c.g; 13756 rawData[offset + 2] = c.r; 13757 if(enableAlpha) 13758 rawData[offset + 3] = c.a; 13759 } 13760 13761 void convertToRgbaBytes(ubyte[] where) @system { 13762 assert(where.length == this.width * this.height * 4); 13763 13764 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 13765 int idx = 0; 13766 int offset = itemsPerLine * (height - 1); 13767 // remember, bmps are upside down 13768 for(int y = height - 1; y >= 0; y--) { 13769 auto offsetStart = offset; 13770 for(int x = 0; x < width; x++) { 13771 where[idx + 0] = rawData[offset + 2]; // r 13772 where[idx + 1] = rawData[offset + 1]; // g 13773 where[idx + 2] = rawData[offset + 0]; // b 13774 if(enableAlpha) { 13775 where[idx + 3] = rawData[offset + 3]; // a 13776 unPremultiplyRgba(where[idx .. idx + 4]); 13777 offset++; 13778 } else 13779 where[idx + 3] = 255; // a 13780 idx += 4; 13781 offset += 3; 13782 } 13783 13784 offset = offsetStart - itemsPerLine; 13785 } 13786 } 13787 13788 void setFromRgbaBytes(in ubyte[] what) @system { 13789 assert(what.length == this.width * this.height * 4); 13790 13791 auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 13792 int idx = 0; 13793 int offset = itemsPerLine * (height - 1); 13794 // remember, bmps are upside down 13795 for(int y = height - 1; y >= 0; y--) { 13796 auto offsetStart = offset; 13797 for(int x = 0; x < width; x++) { 13798 if(enableAlpha) { 13799 auto a = what[idx + 3]; 13800 13801 rawData[offset + 2] = (a * what[idx + 0]) / 255; // r 13802 rawData[offset + 1] = (a * what[idx + 1]) / 255; // g 13803 rawData[offset + 0] = (a * what[idx + 2]) / 255; // b 13804 rawData[offset + 3] = a; // a 13805 //premultiplyBgra(rawData[offset .. offset + 4]); 13806 offset++; 13807 } else { 13808 rawData[offset + 2] = what[idx + 0]; // r 13809 rawData[offset + 1] = what[idx + 1]; // g 13810 rawData[offset + 0] = what[idx + 2]; // b 13811 } 13812 idx += 4; 13813 offset += 3; 13814 } 13815 13816 offset = offsetStart - itemsPerLine; 13817 } 13818 } 13819 13820 13821 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 13822 BITMAPINFO infoheader; 13823 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 13824 infoheader.bmiHeader.biWidth = width; 13825 infoheader.bmiHeader.biHeight = height; 13826 infoheader.bmiHeader.biPlanes = 1; 13827 infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24; 13828 infoheader.bmiHeader.biCompression = BI_RGB; 13829 13830 handle = CreateDIBSection( 13831 null, 13832 &infoheader, 13833 DIB_RGB_COLORS, 13834 cast(void**) &rawData, 13835 null, 13836 0); 13837 if(handle is null) 13838 throw new WindowsApiException("create image failed", GetLastError()); 13839 13840 } 13841 13842 void dispose() { 13843 DeleteObject(handle); 13844 } 13845 } 13846 13847 enum KEY_ESCAPE = 27; 13848 } 13849 13850 version(Emscripten) { 13851 alias int delegate(void*) NativeEventHandler; 13852 alias void* NativeWindowHandle; 13853 13854 mixin template NativeSimpleWindowImplementation() { } 13855 mixin template NativeScreenPainterImplementation() { } 13856 mixin template NativeImageImplementation() { } 13857 } 13858 13859 version(X11) { 13860 /// This is the default font used. You might change this before doing anything else with 13861 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 13862 /// for cross-platform compatibility. 13863 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 13864 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 13865 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 13866 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 13867 13868 alias int delegate(XEvent) NativeEventHandler; 13869 alias Window NativeWindowHandle; 13870 13871 enum KEY_ESCAPE = 9; 13872 13873 mixin template NativeScreenPainterImplementation() { 13874 Display* display; 13875 Drawable d; 13876 Drawable destiny; 13877 13878 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 13879 GC gc; 13880 13881 __gshared bool fontAttempted; 13882 13883 __gshared XFontStruct* defaultfont; 13884 __gshared XFontSet defaultfontset; 13885 13886 XFontStruct* font; 13887 XFontSet fontset; 13888 13889 void create(PaintingHandle window) { 13890 this.display = XDisplayConnection.get(); 13891 13892 Drawable buffer = None; 13893 if(auto sw = cast(SimpleWindow) this.window) { 13894 if(sw.useDirectDraw) 13895 goto direct_draw; 13896 buffer = sw.impl.buffer; 13897 this.destiny = cast(Drawable) window; 13898 } else { 13899 direct_draw: 13900 buffer = cast(Drawable) window; 13901 this.destiny = None; 13902 } 13903 13904 this.d = cast(Drawable) buffer; 13905 13906 auto dgc = DefaultGC(display, DefaultScreen(display)); 13907 13908 this.gc = XCreateGC(display, d, 0, null); 13909 13910 XCopyGC(display, dgc, 0xffffffff, this.gc); 13911 13912 ensureDefaultFontLoaded(); 13913 13914 font = defaultfont; 13915 fontset = defaultfontset; 13916 13917 if(font) { 13918 XSetFont(display, gc, font.fid); 13919 } 13920 } 13921 13922 static void ensureDefaultFontLoaded() { 13923 if(!fontAttempted) { 13924 auto display = XDisplayConnection.get; 13925 auto font = XLoadQueryFont(display, xfontstr.ptr); 13926 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 13927 if(font is null) { 13928 xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"; 13929 font = XLoadQueryFont(display, xfontstr.ptr); 13930 } 13931 13932 char** lol; 13933 int lol2; 13934 char* lol3; 13935 auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 13936 13937 fontAttempted = true; 13938 13939 defaultfont = font; 13940 defaultfontset = fontset; 13941 } 13942 } 13943 13944 arsd.color.Rectangle _clipRectangle; 13945 void setClipRectangle(int x, int y, int width, int height) { 13946 auto old = _clipRectangle; 13947 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 13948 if(old == _clipRectangle) 13949 return; 13950 13951 if(width == 0 || height == 0) { 13952 XSetClipMask(display, gc, None); 13953 13954 if(xrenderPicturePainter) { 13955 13956 XRectangle[1] rects; 13957 rects[0] = XRectangle(short.min, short.min, short.max, short.max); 13958 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 13959 } 13960 13961 version(with_xft) { 13962 if(xftDraw is null) 13963 return; 13964 XftDrawSetClip(xftDraw, null); 13965 } 13966 } else { 13967 XRectangle[1] rects; 13968 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 13969 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 13970 13971 if(xrenderPicturePainter) 13972 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 13973 13974 version(with_xft) { 13975 if(xftDraw is null) 13976 return; 13977 XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1); 13978 } 13979 } 13980 } 13981 13982 version(with_xft) { 13983 XftFont* xftFont; 13984 XftDraw* xftDraw; 13985 13986 XftColor xftColor; 13987 13988 void updateXftColor() { 13989 if(xftFont is null) 13990 return; 13991 13992 // not bothering with XftColorFree since p sure i don't need it on 24 bit displays.... 13993 XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255); 13994 13995 XftColorAllocValue( 13996 display, 13997 DefaultVisual(display, DefaultScreen(display)), 13998 DefaultColormap(display, 0), 13999 &colorIn, 14000 &xftColor 14001 ); 14002 } 14003 } 14004 14005 void enableXftDraw() { 14006 if(xftDraw is null) { 14007 xftDraw = XftDrawCreate( 14008 display, 14009 d, 14010 DefaultVisual(display, DefaultScreen(display)), 14011 DefaultColormap(display, 0) 14012 ); 14013 14014 updateXftColor(); 14015 } 14016 } 14017 14018 private OperatingSystemFont _activeFont; 14019 void setFont(OperatingSystemFont font) { 14020 _activeFont = font; 14021 version(with_xft) { 14022 if(font && font.isXft && font.xftFont) 14023 this.xftFont = font.xftFont; 14024 else 14025 this.xftFont = null; 14026 14027 if(this.xftFont) { 14028 enableXftDraw(); 14029 return; 14030 } 14031 } 14032 14033 if(font && font.font) { 14034 this.font = font.font; 14035 this.fontset = font.fontset; 14036 XSetFont(display, gc, font.font.fid); 14037 } else { 14038 this.font = defaultfont; 14039 this.fontset = defaultfontset; 14040 } 14041 14042 } 14043 14044 private Picture xrenderPicturePainter; 14045 14046 bool manualInvalidations; 14047 void invalidateRect(Rectangle invalidRect) { 14048 // FIXME if manualInvalidations 14049 } 14050 14051 void dispose() { 14052 this.rasterOp = RasterOp.normal; 14053 14054 if(xrenderPicturePainter) { 14055 XRenderFreePicture(display, xrenderPicturePainter); 14056 xrenderPicturePainter = None; 14057 } 14058 14059 // FIXME: this.window.width/height is probably wrong 14060 14061 // src x,y then dest x, y 14062 if(destiny != None) { 14063 // FIXME: if manual invalidations we can actually only copy some of the area. 14064 // if(manualInvalidations) 14065 XSetClipMask(display, gc, None); 14066 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 14067 } 14068 14069 XFreeGC(display, gc); 14070 14071 version(with_xft) 14072 if(xftDraw) { 14073 XftDrawDestroy(xftDraw); 14074 xftDraw = null; 14075 } 14076 14077 /+ 14078 // this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource. 14079 if(font && font !is defaultfont) { 14080 XFreeFont(display, font); 14081 font = null; 14082 } 14083 if(fontset && fontset !is defaultfontset) { 14084 XFreeFontSet(display, fontset); 14085 fontset = null; 14086 } 14087 +/ 14088 XFlush(display); 14089 14090 if(window.paintingFinishedDg !is null) 14091 window.paintingFinishedDg()(); 14092 } 14093 14094 bool backgroundIsNotTransparent = true; 14095 bool foregroundIsNotTransparent = true; 14096 14097 bool _penInitialized = false; 14098 Pen _activePen; 14099 14100 Color _outlineColor; 14101 Color _fillColor; 14102 14103 @property void pen(Pen p) { 14104 if(_penInitialized && p == _activePen) { 14105 return; 14106 } 14107 _penInitialized = true; 14108 _activePen = p; 14109 _outlineColor = p.color; 14110 14111 int style; 14112 14113 byte dashLength; 14114 14115 final switch(p.style) { 14116 case Pen.Style.Solid: 14117 style = 0 /*LineSolid*/; 14118 break; 14119 case Pen.Style.Dashed: 14120 style = 1 /*LineOnOffDash*/; 14121 dashLength = 4; 14122 break; 14123 case Pen.Style.Dotted: 14124 style = 1 /*LineOnOffDash*/; 14125 dashLength = 1; 14126 break; 14127 } 14128 14129 XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0); 14130 if(dashLength) 14131 XSetDashes(display, gc, 0, &dashLength, 1); 14132 14133 if(p.color.a == 0) { 14134 foregroundIsNotTransparent = false; 14135 return; 14136 } 14137 14138 foregroundIsNotTransparent = true; 14139 14140 XSetForeground(display, gc, colorToX(p.color, display)); 14141 14142 version(with_xft) 14143 updateXftColor(); 14144 } 14145 14146 RasterOp _currentRasterOp; 14147 bool _currentRasterOpInitialized = false; 14148 @property void rasterOp(RasterOp op) { 14149 if(_currentRasterOpInitialized && _currentRasterOp == op) 14150 return; 14151 _currentRasterOp = op; 14152 _currentRasterOpInitialized = true; 14153 int mode; 14154 final switch(op) { 14155 case RasterOp.normal: 14156 mode = GXcopy; 14157 break; 14158 case RasterOp.xor: 14159 mode = GXxor; 14160 break; 14161 } 14162 XSetFunction(display, gc, mode); 14163 } 14164 14165 14166 bool _fillColorInitialized = false; 14167 14168 @property void fillColor(Color c) { 14169 if(_fillColorInitialized && _fillColor == c) 14170 return; // already good, no need to waste time calling it 14171 _fillColor = c; 14172 _fillColorInitialized = true; 14173 if(c.a == 0) { 14174 backgroundIsNotTransparent = false; 14175 return; 14176 } 14177 14178 backgroundIsNotTransparent = true; 14179 14180 XSetBackground(display, gc, colorToX(c, display)); 14181 14182 } 14183 14184 void swapColors() { 14185 auto tmp = _fillColor; 14186 fillColor = _outlineColor; 14187 auto newPen = _activePen; 14188 newPen.color = tmp; 14189 pen(newPen); 14190 } 14191 14192 uint colorToX(Color c, Display* display) { 14193 auto visual = DefaultVisual(display, DefaultScreen(display)); 14194 import core.bitop; 14195 uint color = 0; 14196 { 14197 auto startBit = bsf(visual.red_mask); 14198 auto lastBit = bsr(visual.red_mask); 14199 auto r = cast(uint) c.r; 14200 r >>= 7 - (lastBit - startBit); 14201 r <<= startBit; 14202 color |= r; 14203 } 14204 { 14205 auto startBit = bsf(visual.green_mask); 14206 auto lastBit = bsr(visual.green_mask); 14207 auto g = cast(uint) c.g; 14208 g >>= 7 - (lastBit - startBit); 14209 g <<= startBit; 14210 color |= g; 14211 } 14212 { 14213 auto startBit = bsf(visual.blue_mask); 14214 auto lastBit = bsr(visual.blue_mask); 14215 auto b = cast(uint) c.b; 14216 b >>= 7 - (lastBit - startBit); 14217 b <<= startBit; 14218 color |= b; 14219 } 14220 14221 14222 14223 return color; 14224 } 14225 14226 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 14227 // source x, source y 14228 if(ix >= i.width) return; 14229 if(iy >= i.height) return; 14230 if(ix + w > i.width) w = i.width - ix; 14231 if(iy + h > i.height) h = i.height - iy; 14232 if(i.usingXshm) 14233 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 14234 else 14235 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 14236 } 14237 14238 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 14239 if(s.enableAlpha) { 14240 // the Sprite must be created first, meaning if we're here, XRender is already loaded 14241 if(this.xrenderPicturePainter == None) { 14242 XRenderPictureAttributes attrs; 14243 // FIXME: I can prolly reuse this as long as the pixmap itself is valid. 14244 xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs); 14245 14246 // need to initialize the clip 14247 XRectangle[1] rects; 14248 rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height); 14249 14250 if(_clipRectangle != Rectangle.init) 14251 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 14252 } 14253 14254 XRenderComposite( 14255 display, 14256 3, // PicOpOver 14257 s.xrenderPicture, 14258 None, 14259 this.xrenderPicturePainter, 14260 ix, 14261 iy, 14262 0, 14263 0, 14264 x, 14265 y, 14266 w ? w : s.width, 14267 h ? h : s.height 14268 ); 14269 } else { 14270 XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y); 14271 } 14272 } 14273 14274 int fontHeight() { 14275 version(with_xft) 14276 if(xftFont !is null) 14277 return xftFont.height; 14278 if(font) 14279 return font.max_bounds.ascent + font.max_bounds.descent; 14280 return 12; // pretty common default... 14281 } 14282 14283 int textWidth(in char[] line) { 14284 version(with_xft) 14285 if(xftFont) { 14286 if(line.length == 0) 14287 return 0; 14288 XGlyphInfo extents; 14289 XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents); 14290 return extents.width; 14291 } 14292 14293 if(fontset) { 14294 if(line.length == 0) 14295 return 0; 14296 XRectangle rect; 14297 Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect); 14298 14299 return rect.width; 14300 } 14301 14302 if(font) 14303 // FIXME: unicode 14304 return XTextWidth( font, line.ptr, cast(int) line.length); 14305 else 14306 return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 14307 } 14308 14309 Size textSize(in char[] text) { 14310 auto maxWidth = 0; 14311 auto lineHeight = fontHeight; 14312 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 14313 foreach(line; text.split('\n')) { 14314 int textWidth = this.textWidth(line); 14315 if(textWidth > maxWidth) 14316 maxWidth = textWidth; 14317 h += lineHeight + 4; 14318 } 14319 return Size(maxWidth, h); 14320 } 14321 14322 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 14323 const(char)[] text; 14324 version(with_xft) 14325 if(xftFont) { 14326 text = originalText; 14327 goto loaded; 14328 } 14329 14330 if(fontset) 14331 text = originalText; 14332 else { 14333 text.reserve(originalText.length); 14334 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 14335 // then strip the rest so there isn't garbage 14336 foreach(dchar ch; originalText) 14337 if(ch < 256) 14338 text ~= cast(ubyte) ch; 14339 else 14340 text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space 14341 } 14342 loaded: 14343 if(text.length == 0) 14344 return; 14345 14346 // FIXME: should we clip it to the bounding box? 14347 int textHeight = fontHeight; 14348 14349 auto lines = text.split('\n'); 14350 14351 const lineHeight = textHeight; 14352 textHeight *= lines.length; 14353 14354 int cy = y; 14355 14356 if(alignment & TextAlignment.VerticalBottom) { 14357 if(y2 <= 0) 14358 return; 14359 auto h = y2 - y; 14360 if(h > textHeight) { 14361 cy += h - textHeight; 14362 cy -= lineHeight / 2; 14363 } 14364 } else if(alignment & TextAlignment.VerticalCenter) { 14365 if(y2 <= 0) 14366 return; 14367 auto h = y2 - y; 14368 if(textHeight < h) { 14369 cy += (h - textHeight) / 2; 14370 //cy -= lineHeight / 4; 14371 } 14372 } 14373 14374 foreach(line; text.split('\n')) { 14375 int textWidth = this.textWidth(line); 14376 14377 int px = x, py = cy; 14378 14379 if(alignment & TextAlignment.Center) { 14380 if(x2 <= 0) 14381 return; 14382 auto w = x2 - x; 14383 if(w > textWidth) 14384 px += (w - textWidth) / 2; 14385 } else if(alignment & TextAlignment.Right) { 14386 if(x2 <= 0) 14387 return; 14388 auto pos = x2 - textWidth; 14389 if(pos > x) 14390 px = pos; 14391 } 14392 14393 version(with_xft) 14394 if(xftFont) { 14395 XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length); 14396 14397 goto carry_on; 14398 } 14399 14400 if(fontset) 14401 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 14402 else 14403 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 14404 carry_on: 14405 cy += lineHeight + 4; 14406 } 14407 } 14408 14409 void drawPixel(int x, int y) { 14410 XDrawPoint(display, d, gc, x, y); 14411 } 14412 14413 // The basic shapes, outlined 14414 14415 void drawLine(int x1, int y1, int x2, int y2) { 14416 if(foregroundIsNotTransparent) 14417 XDrawLine(display, d, gc, x1, y1, x2, y2); 14418 } 14419 14420 void drawRectangle(int x, int y, int width, int height) { 14421 if(backgroundIsNotTransparent) { 14422 swapColors(); 14423 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 14424 swapColors(); 14425 } 14426 // since X centers the line on the coordinates, we try to undo that with the width/2 thing here so it is aligned in the rectangle's bounds 14427 if(foregroundIsNotTransparent) 14428 XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2); 14429 } 14430 14431 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 14432 int[4] radii = borderRadius; 14433 auto r = Rectangle(upperLeft, lowerRight); 14434 14435 if(backgroundIsNotTransparent) { 14436 swapColors(); 14437 // FIXME these overlap and thus draw the pixels multiple times 14438 XFillRectangle(display, d, gc, r.left, r.top + borderRadius/2, r.width, r.height - borderRadius); 14439 XFillRectangle(display, d, gc, r.left + borderRadius/2, r.top, r.width - borderRadius, r.height); 14440 swapColors(); 14441 } 14442 14443 drawLine(r.left + borderRadius / 2, r.top, r.right - borderRadius / 2, r.top); 14444 drawLine(r.left + borderRadius / 2, r.bottom-1, r.right - borderRadius / 2, r.bottom-1); 14445 drawLine(r.left, r.top + borderRadius / 2, r.left, r.bottom - borderRadius / 2); 14446 drawLine(r.right - 1, r.top + borderRadius / 2, r.right - 1, r.bottom - borderRadius / 2); 14447 14448 //drawRectangle(r.left + borderRadius/2, r.top, r.width - borderRadius, r.height); 14449 14450 drawArc(r.upperLeft.x, r.upperLeft.y, radii[0], radii[0], 90*64, 90*64); 14451 drawArc(r.upperRight.x - radii[1], r.upperRight.y, radii[1] - 1, radii[1], 0*64, 90*64); 14452 drawArc(r.lowerLeft.x, r.lowerLeft.y - radii[2], radii[2], radii[2] - 1, 180*64, 90*64); 14453 drawArc(r.lowerRight.x - radii[3], r.lowerRight.y - radii[3], radii[3] - 1, radii[3] - 1, 270*64, 90*64); 14454 } 14455 14456 14457 /// Arguments are the points of the bounding rectangle 14458 void drawEllipse(int x1, int y1, int x2, int y2) { 14459 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 14460 } 14461 14462 // NOTE: start and finish are in units of degrees * 64 14463 void drawArc(int x1, int y1, int width, int height, int start, int length) { 14464 if(backgroundIsNotTransparent) { 14465 swapColors(); 14466 XFillArc(display, d, gc, x1, y1, width, height, start, length); 14467 swapColors(); 14468 } 14469 if(foregroundIsNotTransparent) { 14470 XDrawArc(display, d, gc, x1, y1, width, height, start, length); 14471 14472 // Windows draws the straight lines on the edges too so FIXME sort of 14473 } 14474 } 14475 14476 void drawPolygon(Point[] vertexes) { 14477 XPoint[16] pointsBuffer; 14478 XPoint[] points; 14479 if(vertexes.length <= pointsBuffer.length) 14480 points = pointsBuffer[0 .. vertexes.length]; 14481 else 14482 points.length = vertexes.length; 14483 14484 foreach(i, p; vertexes) { 14485 points[i].x = cast(short) p.x; 14486 points[i].y = cast(short) p.y; 14487 } 14488 14489 if(backgroundIsNotTransparent) { 14490 swapColors(); 14491 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 14492 swapColors(); 14493 } 14494 if(foregroundIsNotTransparent) { 14495 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 14496 } 14497 } 14498 } 14499 14500 /* XRender { */ 14501 14502 struct XRenderColor { 14503 ushort red; 14504 ushort green; 14505 ushort blue; 14506 ushort alpha; 14507 } 14508 14509 alias Picture = XID; 14510 alias PictFormat = XID; 14511 14512 struct XGlyphInfo { 14513 ushort width; 14514 ushort height; 14515 short x; 14516 short y; 14517 short xOff; 14518 short yOff; 14519 } 14520 14521 struct XRenderDirectFormat { 14522 short red; 14523 short redMask; 14524 short green; 14525 short greenMask; 14526 short blue; 14527 short blueMask; 14528 short alpha; 14529 short alphaMask; 14530 } 14531 14532 struct XRenderPictFormat { 14533 PictFormat id; 14534 int type; 14535 int depth; 14536 XRenderDirectFormat direct; 14537 Colormap colormap; 14538 } 14539 14540 enum PictFormatID = (1 << 0); 14541 enum PictFormatType = (1 << 1); 14542 enum PictFormatDepth = (1 << 2); 14543 enum PictFormatRed = (1 << 3); 14544 enum PictFormatRedMask =(1 << 4); 14545 enum PictFormatGreen = (1 << 5); 14546 enum PictFormatGreenMask=(1 << 6); 14547 enum PictFormatBlue = (1 << 7); 14548 enum PictFormatBlueMask =(1 << 8); 14549 enum PictFormatAlpha = (1 << 9); 14550 enum PictFormatAlphaMask=(1 << 10); 14551 enum PictFormatColormap =(1 << 11); 14552 14553 struct XRenderPictureAttributes { 14554 int repeat; 14555 Picture alpha_map; 14556 int alpha_x_origin; 14557 int alpha_y_origin; 14558 int clip_x_origin; 14559 int clip_y_origin; 14560 Pixmap clip_mask; 14561 Bool graphics_exposures; 14562 int subwindow_mode; 14563 int poly_edge; 14564 int poly_mode; 14565 Atom dither; 14566 Bool component_alpha; 14567 } 14568 14569 alias int XFixed; 14570 14571 struct XPointFixed { 14572 XFixed x, y; 14573 } 14574 14575 struct XCircle { 14576 XFixed x; 14577 XFixed y; 14578 XFixed radius; 14579 } 14580 14581 struct XTransform { 14582 XFixed[3][3] matrix; 14583 } 14584 14585 struct XFilters { 14586 int nfilter; 14587 char **filter; 14588 int nalias; 14589 short *alias_; 14590 } 14591 14592 struct XIndexValue { 14593 c_ulong pixel; 14594 ushort red, green, blue, alpha; 14595 } 14596 14597 struct XAnimCursor { 14598 Cursor cursor; 14599 c_ulong delay; 14600 } 14601 14602 struct XLinearGradient { 14603 XPointFixed p1; 14604 XPointFixed p2; 14605 } 14606 14607 struct XRadialGradient { 14608 XCircle inner; 14609 XCircle outer; 14610 } 14611 14612 struct XConicalGradient { 14613 XPointFixed center; 14614 XFixed angle; /* in degrees */ 14615 } 14616 14617 enum PictStandardARGB32 = 0; 14618 enum PictStandardRGB24 = 1; 14619 enum PictStandardA8 = 2; 14620 enum PictStandardA4 = 3; 14621 enum PictStandardA1 = 4; 14622 enum PictStandardNUM = 5; 14623 14624 interface XRender { 14625 extern(C) @nogc: 14626 14627 Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep); 14628 14629 Status XRenderQueryVersion (Display *dpy, 14630 int *major_versionp, 14631 int *minor_versionp); 14632 14633 Status XRenderQueryFormats (Display *dpy); 14634 14635 int XRenderQuerySubpixelOrder (Display *dpy, int screen); 14636 14637 Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel); 14638 14639 XRenderPictFormat * 14640 XRenderFindVisualFormat (Display *dpy, const Visual *visual); 14641 14642 XRenderPictFormat * 14643 XRenderFindFormat (Display *dpy, 14644 c_ulong mask, 14645 const XRenderPictFormat *templ, 14646 int count); 14647 XRenderPictFormat * 14648 XRenderFindStandardFormat (Display *dpy, 14649 int format); 14650 14651 XIndexValue * 14652 XRenderQueryPictIndexValues(Display *dpy, 14653 const XRenderPictFormat *format, 14654 int *num); 14655 14656 Picture XRenderCreatePicture( 14657 Display *dpy, 14658 Drawable drawable, 14659 const XRenderPictFormat *format, 14660 c_ulong valuemask, 14661 const XRenderPictureAttributes *attributes); 14662 14663 void XRenderChangePicture (Display *dpy, 14664 Picture picture, 14665 c_ulong valuemask, 14666 const XRenderPictureAttributes *attributes); 14667 14668 void 14669 XRenderSetPictureClipRectangles (Display *dpy, 14670 Picture picture, 14671 int xOrigin, 14672 int yOrigin, 14673 const XRectangle *rects, 14674 int n); 14675 14676 void 14677 XRenderSetPictureClipRegion (Display *dpy, 14678 Picture picture, 14679 Region r); 14680 14681 void 14682 XRenderSetPictureTransform (Display *dpy, 14683 Picture picture, 14684 XTransform *transform); 14685 14686 void 14687 XRenderFreePicture (Display *dpy, 14688 Picture picture); 14689 14690 void 14691 XRenderComposite (Display *dpy, 14692 int op, 14693 Picture src, 14694 Picture mask, 14695 Picture dst, 14696 int src_x, 14697 int src_y, 14698 int mask_x, 14699 int mask_y, 14700 int dst_x, 14701 int dst_y, 14702 uint width, 14703 uint height); 14704 14705 14706 Picture XRenderCreateSolidFill (Display *dpy, 14707 const XRenderColor *color); 14708 14709 Picture XRenderCreateLinearGradient (Display *dpy, 14710 const XLinearGradient *gradient, 14711 const XFixed *stops, 14712 const XRenderColor *colors, 14713 int nstops); 14714 14715 Picture XRenderCreateRadialGradient (Display *dpy, 14716 const XRadialGradient *gradient, 14717 const XFixed *stops, 14718 const XRenderColor *colors, 14719 int nstops); 14720 14721 Picture XRenderCreateConicalGradient (Display *dpy, 14722 const XConicalGradient *gradient, 14723 const XFixed *stops, 14724 const XRenderColor *colors, 14725 int nstops); 14726 14727 14728 14729 Cursor 14730 XRenderCreateCursor (Display *dpy, 14731 Picture source, 14732 uint x, 14733 uint y); 14734 14735 XFilters * 14736 XRenderQueryFilters (Display *dpy, Drawable drawable); 14737 14738 void 14739 XRenderSetPictureFilter (Display *dpy, 14740 Picture picture, 14741 const char *filter, 14742 XFixed *params, 14743 int nparams); 14744 14745 Cursor 14746 XRenderCreateAnimCursor (Display *dpy, 14747 int ncursor, 14748 XAnimCursor *cursors); 14749 } 14750 14751 __gshared bool XRenderLibrarySuccessfullyLoaded = true; 14752 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary; 14753 14754 /* XRender } */ 14755 14756 /* Xrandr { */ 14757 14758 struct XRRMonitorInfo { 14759 Atom name; 14760 Bool primary; 14761 Bool automatic; 14762 int noutput; 14763 int x; 14764 int y; 14765 int width; 14766 int height; 14767 int mwidth; 14768 int mheight; 14769 /*RROutput*/ void *outputs; 14770 } 14771 14772 struct XRRScreenChangeNotifyEvent { 14773 int type; /* event base */ 14774 c_ulong serial; /* # of last request processed by server */ 14775 Bool send_event; /* true if this came from a SendEvent request */ 14776 Display *display; /* Display the event was read from */ 14777 Window window; /* window which selected for this event */ 14778 Window root; /* Root window for changed screen */ 14779 Time timestamp; /* when the screen change occurred */ 14780 Time config_timestamp; /* when the last configuration change */ 14781 ushort/*SizeID*/ size_index; 14782 ushort/*SubpixelOrder*/ subpixel_order; 14783 ushort/*Rotation*/ rotation; 14784 int width; 14785 int height; 14786 int mwidth; 14787 int mheight; 14788 } 14789 14790 enum RRScreenChangeNotify = 0; 14791 14792 enum RRScreenChangeNotifyMask = 1; 14793 14794 __gshared int xrrEventBase = -1; 14795 14796 14797 interface XRandr { 14798 extern(C) @nogc: 14799 Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return); 14800 Status XRRQueryVersion (Display *dpy, int *major_version_return, int *minor_version_return); 14801 14802 XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors); 14803 void XRRFreeMonitors(XRRMonitorInfo *monitors); 14804 14805 void XRRSelectInput(Display *dpy, Window window, int mask); 14806 } 14807 14808 __gshared bool XRandrLibrarySuccessfullyLoaded = true; 14809 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary; 14810 /* Xrandr } */ 14811 14812 /* Xft { */ 14813 14814 // actually freetype 14815 alias void FT_Face; 14816 14817 // actually fontconfig 14818 private alias FcBool = int; 14819 alias void FcCharSet; 14820 alias void FcPattern; 14821 alias void FcResult; 14822 enum FcEndian { FcEndianBig, FcEndianLittle } 14823 struct FcFontSet { 14824 int nfont; 14825 int sfont; 14826 FcPattern** fonts; 14827 } 14828 14829 // actually XRegion 14830 struct BOX { 14831 short x1, x2, y1, y2; 14832 } 14833 struct _XRegion { 14834 c_long size; 14835 c_long numRects; 14836 BOX* rects; 14837 BOX extents; 14838 } 14839 14840 alias Region = _XRegion*; 14841 14842 // ok actually Xft 14843 14844 struct XftFontInfo; 14845 14846 struct XftFont { 14847 int ascent; 14848 int descent; 14849 int height; 14850 int max_advance_width; 14851 FcCharSet* charset; 14852 FcPattern* pattern; 14853 } 14854 14855 struct XftDraw; 14856 14857 struct XftColor { 14858 c_ulong pixel; 14859 XRenderColor color; 14860 } 14861 14862 struct XftCharSpec { 14863 dchar ucs4; 14864 short x; 14865 short y; 14866 } 14867 14868 struct XftCharFontSpec { 14869 XftFont *font; 14870 dchar ucs4; 14871 short x; 14872 short y; 14873 } 14874 14875 struct XftGlyphSpec { 14876 uint glyph; 14877 short x; 14878 short y; 14879 } 14880 14881 struct XftGlyphFontSpec { 14882 XftFont *font; 14883 uint glyph; 14884 short x; 14885 short y; 14886 } 14887 14888 interface Xft { 14889 extern(C) @nogc pure: 14890 14891 Bool XftColorAllocName (Display *dpy, 14892 const Visual *visual, 14893 Colormap cmap, 14894 const char *name, 14895 XftColor *result); 14896 14897 Bool XftColorAllocValue (Display *dpy, 14898 Visual *visual, 14899 Colormap cmap, 14900 const XRenderColor *color, 14901 XftColor *result); 14902 14903 void XftColorFree (Display *dpy, 14904 Visual *visual, 14905 Colormap cmap, 14906 XftColor *color); 14907 14908 Bool XftDefaultHasRender (Display *dpy); 14909 14910 Bool XftDefaultSet (Display *dpy, FcPattern *defaults); 14911 14912 void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern); 14913 14914 XftDraw * XftDrawCreate (Display *dpy, 14915 Drawable drawable, 14916 Visual *visual, 14917 Colormap colormap); 14918 14919 XftDraw * XftDrawCreateBitmap (Display *dpy, 14920 Pixmap bitmap); 14921 14922 XftDraw * XftDrawCreateAlpha (Display *dpy, 14923 Pixmap pixmap, 14924 int depth); 14925 14926 void XftDrawChange (XftDraw *draw, 14927 Drawable drawable); 14928 14929 Display * XftDrawDisplay (XftDraw *draw); 14930 14931 Drawable XftDrawDrawable (XftDraw *draw); 14932 14933 Colormap XftDrawColormap (XftDraw *draw); 14934 14935 Visual * XftDrawVisual (XftDraw *draw); 14936 14937 void XftDrawDestroy (XftDraw *draw); 14938 14939 Picture XftDrawPicture (XftDraw *draw); 14940 14941 Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color); 14942 14943 void XftDrawGlyphs (XftDraw *draw, 14944 const XftColor *color, 14945 XftFont *pub, 14946 int x, 14947 int y, 14948 const uint *glyphs, 14949 int nglyphs); 14950 14951 void XftDrawString8 (XftDraw *draw, 14952 const XftColor *color, 14953 XftFont *pub, 14954 int x, 14955 int y, 14956 const char *string, 14957 int len); 14958 14959 void XftDrawString16 (XftDraw *draw, 14960 const XftColor *color, 14961 XftFont *pub, 14962 int x, 14963 int y, 14964 const wchar *string, 14965 int len); 14966 14967 void XftDrawString32 (XftDraw *draw, 14968 const XftColor *color, 14969 XftFont *pub, 14970 int x, 14971 int y, 14972 const dchar *string, 14973 int len); 14974 14975 void XftDrawStringUtf8 (XftDraw *draw, 14976 const XftColor *color, 14977 XftFont *pub, 14978 int x, 14979 int y, 14980 const char *string, 14981 int len); 14982 void XftDrawStringUtf16 (XftDraw *draw, 14983 const XftColor *color, 14984 XftFont *pub, 14985 int x, 14986 int y, 14987 const char *string, 14988 FcEndian endian, 14989 int len); 14990 14991 void XftDrawCharSpec (XftDraw *draw, 14992 const XftColor *color, 14993 XftFont *pub, 14994 const XftCharSpec *chars, 14995 int len); 14996 14997 void XftDrawCharFontSpec (XftDraw *draw, 14998 const XftColor *color, 14999 const XftCharFontSpec *chars, 15000 int len); 15001 15002 void XftDrawGlyphSpec (XftDraw *draw, 15003 const XftColor *color, 15004 XftFont *pub, 15005 const XftGlyphSpec *glyphs, 15006 int len); 15007 15008 void XftDrawGlyphFontSpec (XftDraw *draw, 15009 const XftColor *color, 15010 const XftGlyphFontSpec *glyphs, 15011 int len); 15012 15013 void XftDrawRect (XftDraw *draw, 15014 const XftColor *color, 15015 int x, 15016 int y, 15017 uint width, 15018 uint height); 15019 15020 Bool XftDrawSetClip (XftDraw *draw, 15021 Region r); 15022 15023 15024 Bool XftDrawSetClipRectangles (XftDraw *draw, 15025 int xOrigin, 15026 int yOrigin, 15027 const XRectangle *rects, 15028 int n); 15029 15030 void XftDrawSetSubwindowMode (XftDraw *draw, 15031 int mode); 15032 15033 void XftGlyphExtents (Display *dpy, 15034 XftFont *pub, 15035 const uint *glyphs, 15036 int nglyphs, 15037 XGlyphInfo *extents); 15038 15039 void XftTextExtents8 (Display *dpy, 15040 XftFont *pub, 15041 const char *string, 15042 int len, 15043 XGlyphInfo *extents); 15044 15045 void XftTextExtents16 (Display *dpy, 15046 XftFont *pub, 15047 const wchar *string, 15048 int len, 15049 XGlyphInfo *extents); 15050 15051 void XftTextExtents32 (Display *dpy, 15052 XftFont *pub, 15053 const dchar *string, 15054 int len, 15055 XGlyphInfo *extents); 15056 15057 void XftTextExtentsUtf8 (Display *dpy, 15058 XftFont *pub, 15059 const char *string, 15060 int len, 15061 XGlyphInfo *extents); 15062 15063 void XftTextExtentsUtf16 (Display *dpy, 15064 XftFont *pub, 15065 const char *string, 15066 FcEndian endian, 15067 int len, 15068 XGlyphInfo *extents); 15069 15070 FcPattern * XftFontMatch (Display *dpy, 15071 int screen, 15072 const FcPattern *pattern, 15073 FcResult *result); 15074 15075 XftFont * XftFontOpen (Display *dpy, int screen, ...); 15076 15077 XftFont * XftFontOpenName (Display *dpy, int screen, const char *name); 15078 15079 XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd); 15080 15081 FT_Face XftLockFace (XftFont *pub); 15082 15083 void XftUnlockFace (XftFont *pub); 15084 15085 XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern); 15086 15087 void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi); 15088 15089 dchar XftFontInfoHash (const XftFontInfo *fi); 15090 15091 FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b); 15092 15093 XftFont * XftFontOpenInfo (Display *dpy, 15094 FcPattern *pattern, 15095 XftFontInfo *fi); 15096 15097 XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern); 15098 15099 XftFont * XftFontCopy (Display *dpy, XftFont *pub); 15100 15101 void XftFontClose (Display *dpy, XftFont *pub); 15102 15103 FcBool XftInitFtLibrary(); 15104 void XftFontLoadGlyphs (Display *dpy, 15105 XftFont *pub, 15106 FcBool need_bitmaps, 15107 const uint *glyphs, 15108 int nglyph); 15109 15110 void XftFontUnloadGlyphs (Display *dpy, 15111 XftFont *pub, 15112 const uint *glyphs, 15113 int nglyph); 15114 15115 FcBool XftFontCheckGlyph (Display *dpy, 15116 XftFont *pub, 15117 FcBool need_bitmaps, 15118 uint glyph, 15119 uint *missing, 15120 int *nmissing); 15121 15122 FcBool XftCharExists (Display *dpy, 15123 XftFont *pub, 15124 dchar ucs4); 15125 15126 uint XftCharIndex (Display *dpy, 15127 XftFont *pub, 15128 dchar ucs4); 15129 FcBool XftInit (const char *config); 15130 15131 int XftGetVersion (); 15132 15133 FcFontSet * XftListFonts (Display *dpy, 15134 int screen, 15135 ...); 15136 15137 FcPattern *XftNameParse (const char *name); 15138 15139 void XftGlyphRender (Display *dpy, 15140 int op, 15141 Picture src, 15142 XftFont *pub, 15143 Picture dst, 15144 int srcx, 15145 int srcy, 15146 int x, 15147 int y, 15148 const uint *glyphs, 15149 int nglyphs); 15150 15151 void XftGlyphSpecRender (Display *dpy, 15152 int op, 15153 Picture src, 15154 XftFont *pub, 15155 Picture dst, 15156 int srcx, 15157 int srcy, 15158 const XftGlyphSpec *glyphs, 15159 int nglyphs); 15160 15161 void XftCharSpecRender (Display *dpy, 15162 int op, 15163 Picture src, 15164 XftFont *pub, 15165 Picture dst, 15166 int srcx, 15167 int srcy, 15168 const XftCharSpec *chars, 15169 int len); 15170 void XftGlyphFontSpecRender (Display *dpy, 15171 int op, 15172 Picture src, 15173 Picture dst, 15174 int srcx, 15175 int srcy, 15176 const XftGlyphFontSpec *glyphs, 15177 int nglyphs); 15178 15179 void XftCharFontSpecRender (Display *dpy, 15180 int op, 15181 Picture src, 15182 Picture dst, 15183 int srcx, 15184 int srcy, 15185 const XftCharFontSpec *chars, 15186 int len); 15187 15188 void XftTextRender8 (Display *dpy, 15189 int op, 15190 Picture src, 15191 XftFont *pub, 15192 Picture dst, 15193 int srcx, 15194 int srcy, 15195 int x, 15196 int y, 15197 const char *string, 15198 int len); 15199 void XftTextRender16 (Display *dpy, 15200 int op, 15201 Picture src, 15202 XftFont *pub, 15203 Picture dst, 15204 int srcx, 15205 int srcy, 15206 int x, 15207 int y, 15208 const wchar *string, 15209 int len); 15210 15211 void XftTextRender16BE (Display *dpy, 15212 int op, 15213 Picture src, 15214 XftFont *pub, 15215 Picture dst, 15216 int srcx, 15217 int srcy, 15218 int x, 15219 int y, 15220 const char *string, 15221 int len); 15222 15223 void XftTextRender16LE (Display *dpy, 15224 int op, 15225 Picture src, 15226 XftFont *pub, 15227 Picture dst, 15228 int srcx, 15229 int srcy, 15230 int x, 15231 int y, 15232 const char *string, 15233 int len); 15234 15235 void XftTextRender32 (Display *dpy, 15236 int op, 15237 Picture src, 15238 XftFont *pub, 15239 Picture dst, 15240 int srcx, 15241 int srcy, 15242 int x, 15243 int y, 15244 const dchar *string, 15245 int len); 15246 15247 void XftTextRender32BE (Display *dpy, 15248 int op, 15249 Picture src, 15250 XftFont *pub, 15251 Picture dst, 15252 int srcx, 15253 int srcy, 15254 int x, 15255 int y, 15256 const char *string, 15257 int len); 15258 15259 void XftTextRender32LE (Display *dpy, 15260 int op, 15261 Picture src, 15262 XftFont *pub, 15263 Picture dst, 15264 int srcx, 15265 int srcy, 15266 int x, 15267 int y, 15268 const char *string, 15269 int len); 15270 15271 void XftTextRenderUtf8 (Display *dpy, 15272 int op, 15273 Picture src, 15274 XftFont *pub, 15275 Picture dst, 15276 int srcx, 15277 int srcy, 15278 int x, 15279 int y, 15280 const char *string, 15281 int len); 15282 15283 void XftTextRenderUtf16 (Display *dpy, 15284 int op, 15285 Picture src, 15286 XftFont *pub, 15287 Picture dst, 15288 int srcx, 15289 int srcy, 15290 int x, 15291 int y, 15292 const char *string, 15293 FcEndian endian, 15294 int len); 15295 FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete); 15296 15297 } 15298 15299 interface FontConfig { 15300 extern(C) @nogc pure: 15301 int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s); 15302 void FcFontSetDestroy(FcFontSet*); 15303 char* FcNameUnparse(const FcPattern *); 15304 } 15305 15306 mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary; 15307 mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary; 15308 15309 15310 /* Xft } */ 15311 15312 class XDisconnectException : Exception { 15313 bool userRequested; 15314 this(bool userRequested = true) { 15315 this.userRequested = userRequested; 15316 super("X disconnected"); 15317 } 15318 } 15319 15320 /++ 15321 Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this. 15322 15323 Please note that it returns 15324 +/ 15325 XErrorEvent[] trapXErrors(scope void delegate() dg) { 15326 15327 static XErrorEvent[] errorBuffer; 15328 15329 static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow { 15330 errorBuffer ~= *evt; 15331 return 0; 15332 } 15333 15334 auto savedErrorHandler = XSetErrorHandler(&handler); 15335 15336 try { 15337 dg(); 15338 } finally { 15339 XSync(XDisplayConnection.get, 0/*False*/); 15340 XSetErrorHandler(savedErrorHandler); 15341 } 15342 15343 auto bfr = errorBuffer; 15344 errorBuffer = null; 15345 15346 return bfr; 15347 } 15348 15349 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`. 15350 class XDisplayConnection { 15351 private __gshared Display* display; 15352 private __gshared XIM xim; 15353 private __gshared char* displayName; 15354 15355 private __gshared int connectionSequence_; 15356 private __gshared bool isLocal_; 15357 15358 /// use this for lazy caching when reconnection 15359 static int connectionSequenceNumber() { return connectionSequence_; } 15360 15361 /++ 15362 Guesses if the connection appears to be local. 15363 15364 History: 15365 Added June 3, 2021 15366 +/ 15367 static @property bool isLocal() nothrow @trusted @nogc { 15368 return isLocal_; 15369 } 15370 15371 /// Attempts recreation of state, may require application assistance 15372 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 15373 /// then call this, and if successful, reenter the loop. 15374 static void discardAndRecreate(string newDisplayString = null) { 15375 if(insideXEventLoop) 15376 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 15377 15378 // 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 15379 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 15380 15381 foreach(handle; chnenhm) { 15382 handle.discardConnectionState(); 15383 } 15384 15385 discardState(); 15386 15387 if(newDisplayString !is null) 15388 setDisplayName(newDisplayString); 15389 15390 auto display = get(); 15391 15392 foreach(handle; chnenhm) { 15393 handle.recreateAfterDisconnect(); 15394 } 15395 } 15396 15397 private __gshared EventMask rootEventMask; 15398 15399 /++ 15400 Requests the specified input from the root window on the connection, in addition to any other request. 15401 15402 15403 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. 15404 15405 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 15406 +/ 15407 static void addRootInput(EventMask mask) { 15408 auto old = rootEventMask; 15409 rootEventMask |= mask; 15410 get(); // to ensure display connected 15411 if(display !is null && rootEventMask != old) 15412 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 15413 } 15414 15415 static void discardState() { 15416 freeImages(); 15417 15418 foreach(atomPtr; interredAtoms) 15419 *atomPtr = 0; 15420 interredAtoms = null; 15421 interredAtoms.assumeSafeAppend(); 15422 15423 ScreenPainterImplementation.fontAttempted = false; 15424 ScreenPainterImplementation.defaultfont = null; 15425 ScreenPainterImplementation.defaultfontset = null; 15426 15427 Image.impl.xshmQueryCompleted = false; 15428 Image.impl._xshmAvailable = false; 15429 15430 SimpleWindow.nativeMapping = null; 15431 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 15432 // GlobalHotkeyManager 15433 15434 display = null; 15435 xim = null; 15436 } 15437 15438 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 15439 private static void createXIM () { 15440 import core.stdc.locale : setlocale, LC_ALL; 15441 import core.stdc.stdio : stderr, fprintf; 15442 import core.stdc.stdlib : free; 15443 import core.stdc.string : strdup; 15444 15445 static immutable string[3] mtry = [ "", "@im=local", "@im=" ]; 15446 15447 auto olocale = strdup(setlocale(LC_ALL, null)); 15448 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 15449 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 15450 15451 //fprintf(stderr, "opening IM...\n"); 15452 foreach (string s; mtry) { 15453 XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 15454 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 15455 } 15456 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 15457 } 15458 15459 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 15460 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 15461 static struct ImgList { 15462 size_t img; // class; hide it from GC 15463 ImgList* next; 15464 } 15465 15466 static __gshared ImgList* imglist = null; 15467 static __gshared bool imglistLocked = false; // true: don't register and unregister images 15468 15469 static void registerImage (Image img) { 15470 if (!imglistLocked && img !is null) { 15471 import core.stdc.stdlib : malloc; 15472 auto it = cast(ImgList*)malloc(ImgList.sizeof); 15473 assert(it !is null); // do proper checks 15474 it.img = cast(size_t)cast(void*)img; 15475 it.next = imglist; 15476 imglist = it; 15477 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 15478 } 15479 } 15480 15481 static void unregisterImage (Image img) { 15482 if (!imglistLocked && img !is null) { 15483 import core.stdc.stdlib : free; 15484 ImgList* prev = null; 15485 ImgList* cur = imglist; 15486 while (cur !is null) { 15487 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 15488 prev = cur; 15489 cur = cur.next; 15490 } 15491 if (cur !is null) { 15492 if (prev is null) imglist = cur.next; else prev.next = cur.next; 15493 free(cur); 15494 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 15495 } else { 15496 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 15497 } 15498 } 15499 } 15500 15501 static void freeImages () { // needed for discardAndRecreate 15502 imglistLocked = true; 15503 scope(exit) imglistLocked = false; 15504 ImgList* cur = imglist; 15505 ImgList* next = null; 15506 while (cur !is null) { 15507 import core.stdc.stdlib : free; 15508 next = cur.next; 15509 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 15510 (cast(Image)cast(void*)cur.img).dispose(); 15511 free(cur); 15512 cur = next; 15513 } 15514 imglist = null; 15515 } 15516 15517 /// can be used to override normal handling of display name 15518 /// from environment and/or command line 15519 static setDisplayName(string newDisplayName) { 15520 displayName = cast(char*) (newDisplayName ~ '\0'); 15521 } 15522 15523 /// resets to the default display string 15524 static resetDisplayName() { 15525 displayName = null; 15526 } 15527 15528 /// 15529 static Display* get() { 15530 if(display is null) { 15531 if(!librariesSuccessfullyLoaded) 15532 throw new Exception("Unable to load X11 client libraries"); 15533 display = XOpenDisplay(displayName); 15534 15535 isLocal_ = false; 15536 15537 connectionSequence_++; 15538 if(display is null) 15539 throw new Exception("Unable to open X display"); 15540 15541 auto str = display.display_name; 15542 // this is a bit of a hack but like if it looks like a unix socket we assume it is local 15543 // and otherwise it probably isn't 15544 if(str is null || (str[0] != ':' && str[0] != '/')) 15545 isLocal_ = false; 15546 else 15547 isLocal_ = true; 15548 15549 XSetErrorHandler(&adrlogger); 15550 15551 debug(sdpy_x_errors) { 15552 XSynchronize(display, true); 15553 15554 extern(C) int wtf() { 15555 if(errorHappened) { 15556 asm { int 3; } 15557 errorHappened = false; 15558 } 15559 return 0; 15560 } 15561 XSetAfterFunction(display, &wtf); 15562 } 15563 15564 15565 XSetIOErrorHandler(&x11ioerrCB); 15566 Bool sup; 15567 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 15568 createXIM(); 15569 version(with_eventloop) { 15570 import arsd.eventloop; 15571 addFileEventListeners(display.fd, &eventListener, null, null); 15572 } 15573 } 15574 15575 return display; 15576 } 15577 15578 extern(C) 15579 static int x11ioerrCB(Display* dpy) { 15580 throw new XDisconnectException(false); 15581 } 15582 15583 version(with_eventloop) { 15584 import arsd.eventloop; 15585 static void eventListener(OsFileHandle fd) { 15586 //this.mtLock(); 15587 //scope(exit) this.mtUnlock(); 15588 while(XPending(display)) 15589 doXNextEvent(display); 15590 } 15591 } 15592 15593 // close connection on program exit -- we need this to properly free all images 15594 static ~this () { 15595 // the gui thread must clean up after itself or else Xlib might deadlock 15596 // using this flag on any thread destruction is the easiest way i know of 15597 // (shared static this is run by the LAST thread to exit, which may not be 15598 // the gui thread, and normal static this run by ALL threads, so we gotta check.) 15599 if(thisIsGuiThread) 15600 close(); 15601 } 15602 15603 /// 15604 static void close() { 15605 if(display is null) 15606 return; 15607 15608 version(with_eventloop) { 15609 import arsd.eventloop; 15610 removeFileEventListeners(display.fd); 15611 } 15612 15613 // now remove all registered images to prevent shared memory leaks 15614 freeImages(); 15615 15616 // tbh I don't know why it is doing this but like if this happens to run 15617 // from the other thread there's frequent hanging inside here. 15618 if(thisIsGuiThread) 15619 XCloseDisplay(display); 15620 display = null; 15621 } 15622 } 15623 15624 mixin template NativeImageImplementation() { 15625 XImage* handle; 15626 ubyte* rawData; 15627 15628 XShmSegmentInfo shminfo; 15629 bool premultiply = true; 15630 15631 __gshared bool xshmQueryCompleted; 15632 __gshared bool _xshmAvailable; 15633 public static @property bool xshmAvailable() { 15634 if(!xshmQueryCompleted) { 15635 int i1, i2, i3; 15636 xshmQueryCompleted = true; 15637 15638 if(!XDisplayConnection.isLocal) 15639 _xshmAvailable = false; 15640 else 15641 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 15642 } 15643 return _xshmAvailable; 15644 } 15645 15646 bool usingXshm; 15647 final: 15648 15649 private __gshared bool xshmfailed; 15650 15651 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 15652 auto display = XDisplayConnection.get(); 15653 assert(display !is null); 15654 auto screen = DefaultScreen(display); 15655 15656 // it will only use shared memory for somewhat largish images, 15657 // since otherwise we risk wasting shared memory handles on a lot of little ones 15658 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 15659 15660 15661 // it is possible for the query extension to return true, the DISPLAY check to pass, yet 15662 // the actual use still fails. For example, if the program is in a container and permission denied 15663 // on shared memory, or if it is a local thing forwarded to a remote server, etc. 15664 // 15665 // If it does fail, we need to detect it now, abort the xshm and fall back to core protocol. 15666 15667 15668 // synchronize so preexisting buffers are clear 15669 XSync(display, false); 15670 xshmfailed = false; 15671 15672 auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler); 15673 15674 15675 usingXshm = true; 15676 handle = XShmCreateImage( 15677 display, 15678 DefaultVisual(display, screen), 15679 enableAlpha ? 32: 24, 15680 ImageFormat.ZPixmap, 15681 null, 15682 &shminfo, 15683 width, height); 15684 if(handle is null) 15685 goto abortXshm1; 15686 15687 if(handle.bytes_per_line != 4 * width) 15688 goto abortXshm2; 15689 15690 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 15691 if(shminfo.shmid < 0) 15692 goto abortXshm3; 15693 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 15694 if(rawData == cast(ubyte*) -1) 15695 goto abortXshm4; 15696 shminfo.readOnly = 0; 15697 XShmAttach(display, &shminfo); 15698 15699 // and now to the final error check to ensure it actually worked. 15700 XSync(display, false); 15701 if(xshmfailed) 15702 goto abortXshm5; 15703 15704 XSetErrorHandler(oldErrorHandler); 15705 15706 XDisplayConnection.registerImage(this); 15707 // if I don't flush here there's a chance the dtor will run before the 15708 // ctor and lead to a bad value X error. While this hurts the efficiency 15709 // it is local anyway so prolly better to keep it simple 15710 XFlush(display); 15711 15712 return; 15713 15714 abortXshm5: 15715 shmdt(shminfo.shmaddr); 15716 rawData = null; 15717 15718 abortXshm4: 15719 shmctl(shminfo.shmid, IPC_RMID, null); 15720 15721 abortXshm3: 15722 // nothing needed, the shmget failed so there's nothing to free 15723 15724 abortXshm2: 15725 XDestroyImage(handle); 15726 handle = null; 15727 15728 abortXshm1: 15729 XSetErrorHandler(oldErrorHandler); 15730 usingXshm = false; 15731 handle = null; 15732 15733 shminfo = typeof(shminfo).init; 15734 15735 _xshmAvailable = false; // don't try again in the future 15736 15737 // writeln("fallingback"); 15738 15739 goto fallback; 15740 15741 } else { 15742 fallback: 15743 15744 if (forcexshm) throw new Exception("can't create XShm Image"); 15745 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 15746 import core.stdc.stdlib : malloc; 15747 rawData = cast(ubyte*) malloc(width * height * 4); 15748 15749 handle = XCreateImage( 15750 display, 15751 DefaultVisual(display, screen), 15752 enableAlpha ? 32 : 24, // bpp 15753 ImageFormat.ZPixmap, 15754 0, // offset 15755 rawData, 15756 width, height, 15757 enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line 15758 } 15759 } 15760 15761 void dispose() { 15762 // note: this calls free(rawData) for us 15763 if(handle) { 15764 if (usingXshm) { 15765 XDisplayConnection.unregisterImage(this); 15766 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 15767 } 15768 XDestroyImage(handle); 15769 if(usingXshm) { 15770 shmdt(shminfo.shmaddr); 15771 shmctl(shminfo.shmid, IPC_RMID, null); 15772 } 15773 handle = null; 15774 } 15775 } 15776 15777 Color getPixel(int x, int y) @system { 15778 auto offset = (y * width + x) * 4; 15779 Color c; 15780 c.a = enableAlpha ? rawData[offset + 3] : 255; 15781 c.b = rawData[offset + 0]; 15782 c.g = rawData[offset + 1]; 15783 c.r = rawData[offset + 2]; 15784 if(enableAlpha && premultiply) 15785 c.unPremultiply; 15786 return c; 15787 } 15788 15789 void setPixel(int x, int y, Color c) @system { 15790 if(enableAlpha && premultiply) 15791 c.premultiply(); 15792 auto offset = (y * width + x) * 4; 15793 rawData[offset + 0] = c.b; 15794 rawData[offset + 1] = c.g; 15795 rawData[offset + 2] = c.r; 15796 if(enableAlpha) 15797 rawData[offset + 3] = c.a; 15798 } 15799 15800 void convertToRgbaBytes(ubyte[] where) @system { 15801 assert(where.length == this.width * this.height * 4); 15802 15803 // if rawData had a length.... 15804 //assert(rawData.length == where.length); 15805 for(int idx = 0; idx < where.length; idx += 4) { 15806 where[idx + 0] = rawData[idx + 2]; // r 15807 where[idx + 1] = rawData[idx + 1]; // g 15808 where[idx + 2] = rawData[idx + 0]; // b 15809 where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a 15810 15811 if(enableAlpha && premultiply) 15812 unPremultiplyRgba(where[idx .. idx + 4]); 15813 } 15814 } 15815 15816 void setFromRgbaBytes(in ubyte[] where) @system { 15817 assert(where.length == this.width * this.height * 4); 15818 15819 // if rawData had a length.... 15820 //assert(rawData.length == where.length); 15821 for(int idx = 0; idx < where.length; idx += 4) { 15822 rawData[idx + 2] = where[idx + 0]; // r 15823 rawData[idx + 1] = where[idx + 1]; // g 15824 rawData[idx + 0] = where[idx + 2]; // b 15825 if(enableAlpha) { 15826 rawData[idx + 3] = where[idx + 3]; // a 15827 if(premultiply) 15828 premultiplyBgra(rawData[idx .. idx + 4]); 15829 } 15830 } 15831 } 15832 15833 } 15834 15835 mixin template NativeSimpleWindowImplementation() { 15836 GC gc; 15837 Window window; 15838 Display* display; 15839 15840 Pixmap buffer; 15841 int bufferw, bufferh; // size of the buffer; can be bigger than window 15842 XIC xic; // input context 15843 int curHidden = 0; // counter 15844 Cursor blankCurPtr = 0; 15845 int cursorSequenceNumber = 0; 15846 int warpEventCount = 0; // number of mouse movement events to eat 15847 15848 __gshared X11SetSelectionHandler[Atom] setSelectionHandlers; // FIXME: make sure this is not accessed from other threads. it might be ok to make it TLS 15849 X11GetSelectionHandler[Atom] getSelectionHandlers; 15850 15851 version(without_opengl) {} else 15852 GLXContext glc; 15853 15854 private void fixFixedSize(bool forced=false) (int width, int height) { 15855 if (forced || this.resizability == Resizability.fixedSize) { 15856 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 15857 XSizeHints sh; 15858 static if (!forced) { 15859 c_long spr; 15860 XGetWMNormalHints(display, window, &sh, &spr); 15861 sh.flags |= PMaxSize | PMinSize; 15862 } else { 15863 sh.flags = PMaxSize | PMinSize; 15864 } 15865 sh.min_width = width; 15866 sh.min_height = height; 15867 sh.max_width = width; 15868 sh.max_height = height; 15869 XSetWMNormalHints(display, window, &sh); 15870 //XFlush(display); 15871 } 15872 } 15873 15874 ScreenPainter getPainter(bool manualInvalidations) { 15875 return ScreenPainter(this, window, manualInvalidations); 15876 } 15877 15878 void move(int x, int y) { 15879 XMoveWindow(display, window, x, y); 15880 } 15881 15882 void resize(int w, int h) { 15883 if (w < 1) w = 1; 15884 if (h < 1) h = 1; 15885 XResizeWindow(display, window, w, h); 15886 15887 // calling this now to avoid waiting for the server to 15888 // acknowledge the resize; draws without returning to the 15889 // event loop will thus actually work. the server's event 15890 // btw might overrule this and resize it again 15891 recordX11Resize(display, this, w, h); 15892 15893 updateOpenglViewportIfNeeded(w, h); 15894 } 15895 15896 void moveResize (int x, int y, int w, int h) { 15897 if (w < 1) w = 1; 15898 if (h < 1) h = 1; 15899 XMoveResizeWindow(display, window, x, y, w, h); 15900 updateOpenglViewportIfNeeded(w, h); 15901 } 15902 15903 void hideCursor () { 15904 if (curHidden++ == 0) { 15905 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 15906 static const(char)[1] cmbmp = 0; 15907 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 15908 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 15909 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 15910 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 15911 XFreePixmap(display, pm); 15912 } 15913 XDefineCursor(display, window, blankCurPtr); 15914 } 15915 } 15916 15917 void showCursor () { 15918 if (--curHidden == 0) XUndefineCursor(display, window); 15919 } 15920 15921 void warpMouse (int x, int y) { 15922 // here i will send dummy "ignore next mouse motion" event, 15923 // 'cause `XWarpPointer()` sends synthesised mouse motion, 15924 // and we don't need to report it to the user (as warping is 15925 // used when the user needs movement deltas). 15926 //XClientMessageEvent xclient; 15927 XEvent e; 15928 e.xclient.type = EventType.ClientMessage; 15929 e.xclient.window = window; 15930 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 15931 e.xclient.format = 32; 15932 e.xclient.data.l[0] = 0; 15933 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 15934 //{ 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]); } 15935 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 15936 // now warp pointer... 15937 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 15938 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 15939 // ...and flush 15940 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 15941 XFlush(display); 15942 } 15943 15944 void sendDummyEvent () { 15945 // here i will send dummy event to ping event queue 15946 XEvent e; 15947 e.xclient.type = EventType.ClientMessage; 15948 e.xclient.window = window; 15949 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 15950 e.xclient.format = 32; 15951 e.xclient.data.l[0] = 0; 15952 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 15953 XFlush(display); 15954 } 15955 15956 void setTitle(string title) { 15957 if (title.ptr is null) title = ""; 15958 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 15959 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 15960 XTextProperty windowName; 15961 windowName.value = title.ptr; 15962 windowName.encoding = XA_UTF8; //XA_STRING; 15963 windowName.format = 8; 15964 windowName.nitems = cast(uint)title.length; 15965 XSetWMName(display, window, &windowName); 15966 char[1024] namebuf = 0; 15967 auto maxlen = namebuf.length-1; 15968 if (maxlen > title.length) maxlen = title.length; 15969 namebuf[0..maxlen] = title[0..maxlen]; 15970 XStoreName(display, window, namebuf.ptr); 15971 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 15972 flushGui(); // without this OpenGL windows has a LONG delay before changing title 15973 } 15974 15975 string[] getTitles() { 15976 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 15977 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 15978 XTextProperty textProp; 15979 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 15980 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 15981 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 15982 } else 15983 return []; 15984 } else 15985 return null; 15986 } 15987 15988 string getTitle() { 15989 auto titles = getTitles(); 15990 return titles.length ? titles[0] : null; 15991 } 15992 15993 void setMinSize (int minwidth, int minheight) { 15994 import core.stdc.config : c_long; 15995 if (minwidth < 1) minwidth = 1; 15996 if (minheight < 1) minheight = 1; 15997 XSizeHints sh; 15998 c_long spr; 15999 XGetWMNormalHints(display, window, &sh, &spr); 16000 sh.min_width = minwidth; 16001 sh.min_height = minheight; 16002 sh.flags |= PMinSize; 16003 XSetWMNormalHints(display, window, &sh); 16004 flushGui(); 16005 } 16006 16007 void setMaxSize (int maxwidth, int maxheight) { 16008 import core.stdc.config : c_long; 16009 if (maxwidth < 1) maxwidth = 1; 16010 if (maxheight < 1) maxheight = 1; 16011 XSizeHints sh; 16012 c_long spr; 16013 XGetWMNormalHints(display, window, &sh, &spr); 16014 sh.max_width = maxwidth; 16015 sh.max_height = maxheight; 16016 sh.flags |= PMaxSize; 16017 XSetWMNormalHints(display, window, &sh); 16018 flushGui(); 16019 } 16020 16021 void setResizeGranularity (int granx, int grany) { 16022 import core.stdc.config : c_long; 16023 if (granx < 1) granx = 1; 16024 if (grany < 1) grany = 1; 16025 XSizeHints sh; 16026 c_long spr; 16027 XGetWMNormalHints(display, window, &sh, &spr); 16028 sh.width_inc = granx; 16029 sh.height_inc = grany; 16030 sh.flags |= PResizeInc; 16031 XSetWMNormalHints(display, window, &sh); 16032 flushGui(); 16033 } 16034 16035 void setOpacity (uint opacity) { 16036 arch_ulong o = opacity; 16037 if (opacity == uint.max) 16038 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 16039 else 16040 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 16041 XA_CARDINAL, 32, PropModeReplace, &o, 1); 16042 } 16043 16044 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) @trusted { 16045 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 16046 display = XDisplayConnection.get(); 16047 auto screen = DefaultScreen(display); 16048 16049 bool overrideRedirect = false; 16050 if( 16051 windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || 16052 windowType == WindowTypes.tooltip || 16053 windowType == WindowTypes.notification || 16054 windowType == WindowTypes.dnd || 16055 windowType == WindowTypes.comboBoxDropdown || 16056 (customizationFlags & WindowFlags.overrideRedirect) 16057 )// || windowType == WindowTypes.nestedChild) 16058 overrideRedirect = true; 16059 16060 version(without_opengl) {} 16061 else { 16062 if(opengl == OpenGlOptions.yes) { 16063 GLXFBConfig fbconf = null; 16064 XVisualInfo* vi = null; 16065 bool useLegacy = false; 16066 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 16067 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 16068 int[23] visualAttribs = [ 16069 GLX_X_RENDERABLE , 1/*True*/, 16070 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 16071 GLX_RENDER_TYPE , GLX_RGBA_BIT, 16072 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 16073 GLX_RED_SIZE , 8, 16074 GLX_GREEN_SIZE , 8, 16075 GLX_BLUE_SIZE , 8, 16076 GLX_ALPHA_SIZE , 8, 16077 GLX_DEPTH_SIZE , 24, 16078 GLX_STENCIL_SIZE , 8, 16079 GLX_DOUBLEBUFFER , 1/*True*/, 16080 0/*None*/, 16081 ]; 16082 int fbcount; 16083 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 16084 if (fbcount == 0) { 16085 useLegacy = true; // try to do at least something 16086 } else { 16087 // pick the FB config/visual with the most samples per pixel 16088 int bestidx = -1, bestns = -1; 16089 foreach (int fbi; 0..fbcount) { 16090 int sb, samples; 16091 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 16092 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 16093 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 16094 } 16095 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 16096 fbconf = fbc[bestidx]; 16097 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 16098 XFree(fbc); 16099 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 16100 } 16101 } 16102 if (vi is null || useLegacy) { 16103 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 16104 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 16105 useLegacy = true; 16106 } 16107 if (vi is null) throw new Exception("no open gl visual found"); 16108 16109 XSetWindowAttributes swa; 16110 auto root = RootWindow(display, screen); 16111 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 16112 16113 swa.override_redirect = overrideRedirect; 16114 16115 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 16116 0, 0, width, height, 16117 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa); 16118 16119 // now try to use `glXCreateContextAttribsARB()` if it's here 16120 if (!useLegacy) { 16121 // request fairly advanced context, even with stencil buffer! 16122 int[9] contextAttribs = [ 16123 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 16124 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 16125 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 16126 // for modern context, set "forward compatibility" flag too 16127 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 16128 0/*None*/, 16129 ]; 16130 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 16131 if (glc is null && sdpyOpenGLContextAllowFallback) { 16132 sdpyOpenGLContextVersion = 0; 16133 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 16134 } 16135 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 16136 } else { 16137 // fallback to old GLX call 16138 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 16139 sdpyOpenGLContextVersion = 0; 16140 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 16141 } 16142 } 16143 // sync to ensure any errors generated are processed 16144 XSync(display, 0/*False*/); 16145 //{ import core.stdc.stdio; printf("ogl is here\n"); } 16146 if(glc is null) 16147 throw new Exception("glc"); 16148 } 16149 } 16150 16151 if(opengl == OpenGlOptions.no) { 16152 16153 XSetWindowAttributes swa; 16154 swa.background_pixel = WhitePixel(display, screen); 16155 swa.border_pixel = BlackPixel(display, screen); 16156 swa.override_redirect = overrideRedirect; 16157 auto root = RootWindow(display, screen); 16158 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 16159 16160 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 16161 0, 0, width, height, 16162 // I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit. 16163 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa); 16164 16165 16166 16167 /* 16168 window = XCreateSimpleWindow( 16169 display, 16170 parent is null ? RootWindow(display, screen) : parent.impl.window, 16171 0, 0, // x, y 16172 width, height, 16173 1, // border width 16174 BlackPixel(display, screen), // border 16175 WhitePixel(display, screen)); // background 16176 */ 16177 16178 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 16179 bufferw = width; 16180 bufferh = height; 16181 16182 gc = DefaultGC(display, screen); 16183 16184 // clear out the buffer to get us started... 16185 XSetForeground(display, gc, WhitePixel(display, screen)); 16186 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 16187 XSetForeground(display, gc, BlackPixel(display, screen)); 16188 } 16189 16190 // input context 16191 //TODO: create this only for top-level windows, and reuse that? 16192 populateXic(); 16193 16194 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 16195 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 16196 // window class 16197 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 16198 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 16199 XClassHint klass; 16200 XWMHints wh; 16201 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 16202 wh.input = true; 16203 wh.flags |= InputHint; 16204 } 16205 XSizeHints size; 16206 klass.res_name = sdpyWindowClassStr; 16207 klass.res_class = sdpyWindowClassStr; 16208 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 16209 } 16210 16211 setTitle(title); 16212 SimpleWindow.nativeMapping[window] = this; 16213 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 16214 16215 // This gives our window a close button 16216 if (windowType != WindowTypes.eventOnly) { 16217 Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)]; 16218 int useAtoms; 16219 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 16220 useAtoms = 2; 16221 } else { 16222 useAtoms = 1; 16223 } 16224 assert(useAtoms <= atoms.length); 16225 XSetWMProtocols(display, window, atoms.ptr, useAtoms); 16226 } 16227 16228 // FIXME: windowType and customizationFlags 16229 Atom[8] wsatoms; // here, due to goto 16230 int wmsacount = 0; // here, due to goto 16231 16232 try 16233 final switch(windowType) { 16234 case WindowTypes.normal: 16235 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 16236 break; 16237 case WindowTypes.undecorated: 16238 motifHideDecorations(); 16239 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 16240 break; 16241 case WindowTypes.eventOnly: 16242 _hidden = true; 16243 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 16244 goto hiddenWindow; 16245 //break; 16246 case WindowTypes.nestedChild: 16247 // handled in XCreateWindow calls 16248 break; 16249 16250 case WindowTypes.dropdownMenu: 16251 motifHideDecorations(); 16252 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 16253 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 16254 break; 16255 case WindowTypes.popupMenu: 16256 motifHideDecorations(); 16257 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 16258 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 16259 break; 16260 case WindowTypes.notification: 16261 motifHideDecorations(); 16262 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 16263 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 16264 break; 16265 case WindowTypes.minimallyWrapped: 16266 assert(0, "don't create a minimallyWrapped thing explicitly!"); 16267 16268 case WindowTypes.dialog: 16269 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display)); 16270 break; 16271 case WindowTypes.comboBoxDropdown: 16272 motifHideDecorations(); 16273 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display)); 16274 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 16275 break; 16276 case WindowTypes.tooltip: 16277 motifHideDecorations(); 16278 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display)); 16279 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 16280 break; 16281 case WindowTypes.dnd: 16282 motifHideDecorations(); 16283 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display)); 16284 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 16285 break; 16286 /+ 16287 case WindowTypes.menu: 16288 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 16289 motifHideDecorations(); 16290 break; 16291 case WindowTypes.desktop: 16292 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 16293 break; 16294 case WindowTypes.dock: 16295 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 16296 break; 16297 case WindowTypes.toolbar: 16298 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 16299 break; 16300 case WindowTypes.menu: 16301 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 16302 break; 16303 case WindowTypes.utility: 16304 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 16305 break; 16306 case WindowTypes.splash: 16307 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 16308 break; 16309 case WindowTypes.notification: 16310 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 16311 break; 16312 +/ 16313 } 16314 catch(Exception e) { 16315 // XInternAtom failed, prolly a WM 16316 // that doesn't support these things 16317 } 16318 16319 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 16320 // the two following flags may be ignored by WM 16321 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 16322 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 16323 16324 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 16325 16326 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 16327 16328 // What would be ideal here is if they only were 16329 // selected if there was actually an event handler 16330 // for them... 16331 16332 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 16333 16334 hiddenWindow: 16335 16336 // set the pid property for lookup later by window managers 16337 // a standard convenience 16338 import core.sys.posix.unistd; 16339 arch_ulong pid = getpid(); 16340 16341 XChangeProperty( 16342 display, 16343 impl.window, 16344 GetAtom!("_NET_WM_PID", true)(display), 16345 XA_CARDINAL, 16346 32 /* bits */, 16347 0 /*PropModeReplace*/, 16348 &pid, 16349 1); 16350 16351 if(isTransient && parent) { // customizationFlags & WindowFlags.transient) { 16352 if(parent is null) assert(0); 16353 // sdpyPrintDebugString("transient"); 16354 XChangeProperty( 16355 display, 16356 impl.window, 16357 GetAtom!("WM_TRANSIENT_FOR", true)(display), 16358 XA_WINDOW, 16359 32 /* bits */, 16360 0 /*PropModeReplace*/, 16361 &parent.impl.window, 16362 1); 16363 16364 } 16365 16366 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 16367 XMapWindow(display, window); 16368 } else { 16369 _hidden = true; 16370 } 16371 } 16372 16373 void populateXic() { 16374 if (XDisplayConnection.xim !is null) { 16375 xic = XCreateIC(XDisplayConnection.xim, 16376 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 16377 /*XNClientWindow*/"clientWindow".ptr, window, 16378 /*XNFocusWindow*/"focusWindow".ptr, window, 16379 null); 16380 if (xic is null) { 16381 import core.stdc.stdio : stderr, fprintf; 16382 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 16383 } 16384 } 16385 } 16386 16387 void selectDefaultInput(bool forceIncludeMouseMotion) { 16388 auto mask = EventMask.ExposureMask | 16389 EventMask.KeyPressMask | 16390 EventMask.KeyReleaseMask | 16391 EventMask.PropertyChangeMask | 16392 EventMask.FocusChangeMask | 16393 EventMask.StructureNotifyMask | 16394 EventMask.SubstructureNotifyMask | 16395 EventMask.VisibilityChangeMask 16396 | EventMask.ButtonPressMask 16397 | EventMask.ButtonReleaseMask 16398 ; 16399 16400 // xshm is our shortcut for local connections 16401 if(XDisplayConnection.isLocal || forceIncludeMouseMotion) 16402 mask |= EventMask.PointerMotionMask; 16403 else 16404 mask |= EventMask.ButtonMotionMask; 16405 16406 XSelectInput(display, window, mask); 16407 } 16408 16409 16410 void setNetWMWindowType(Atom type) { 16411 Atom[2] atoms; 16412 16413 atoms[0] = type; 16414 // generic fallback 16415 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 16416 16417 XChangeProperty( 16418 display, 16419 impl.window, 16420 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 16421 XA_ATOM, 16422 32 /* bits */, 16423 0 /*PropModeReplace*/, 16424 atoms.ptr, 16425 cast(int) atoms.length); 16426 } 16427 16428 void motifHideDecorations(bool hide = true) { 16429 MwmHints hints; 16430 hints.flags = MWM_HINTS_DECORATIONS; 16431 hints.decorations = hide ? 0 : 1; 16432 16433 XChangeProperty( 16434 display, 16435 impl.window, 16436 GetAtom!"_MOTIF_WM_HINTS"(display), 16437 GetAtom!"_MOTIF_WM_HINTS"(display), 16438 32 /* bits */, 16439 0 /*PropModeReplace*/, 16440 &hints, 16441 hints.sizeof / 4); 16442 } 16443 16444 /*k8: unused 16445 void createOpenGlContext() { 16446 16447 } 16448 */ 16449 16450 void closeWindow() { 16451 // I can't close this or a child window closing will 16452 // break events for everyone. So I'm just leaking it right 16453 // now and that is probably perfectly fine... 16454 version(none) 16455 if (customEventFDRead != -1) { 16456 import core.sys.posix.unistd : close; 16457 auto same = customEventFDRead == customEventFDWrite; 16458 16459 close(customEventFDRead); 16460 if(!same) 16461 close(customEventFDWrite); 16462 customEventFDRead = -1; 16463 customEventFDWrite = -1; 16464 } 16465 16466 version(without_opengl) {} else 16467 if(glc !is null) { 16468 glXDestroyContext(display, glc); 16469 glc = null; 16470 } 16471 16472 if(buffer) 16473 XFreePixmap(display, buffer); 16474 bufferw = bufferh = 0; 16475 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 16476 XDestroyWindow(display, window); 16477 XFlush(display); 16478 } 16479 16480 void dispose() { 16481 } 16482 16483 bool destroyed = false; 16484 } 16485 16486 bool insideXEventLoop; 16487 } 16488 16489 version(X11) { 16490 16491 int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this. 16492 16493 private class ResizeEvent { 16494 int width, height; 16495 } 16496 16497 void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) { 16498 if(win.windowType == WindowTypes.minimallyWrapped) 16499 return; 16500 16501 if(win.pendingResizeEvent is null) { 16502 win.pendingResizeEvent = new ResizeEvent(); 16503 win.addEventListener((ResizeEvent re) { 16504 recordX11Resize(XDisplayConnection.get, win, re.width, re.height); 16505 }); 16506 } 16507 win.pendingResizeEvent.width = width; 16508 win.pendingResizeEvent.height = height; 16509 if(!win.eventQueued!ResizeEvent) { 16510 win.postEvent(win.pendingResizeEvent); 16511 } 16512 } 16513 16514 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 16515 if(win.windowType == WindowTypes.minimallyWrapped) 16516 return; 16517 if(win.closed) 16518 return; 16519 16520 if(width != win.width || height != win.height) { 16521 16522 // writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window); 16523 win._width = width; 16524 win._height = height; 16525 16526 if(win.openglMode == OpenGlOptions.no) { 16527 // FIXME: could this be more efficient? 16528 16529 if (win.bufferw < width || win.bufferh < height) { 16530 //{ 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); } 16531 // grow the internal buffer to match the window... 16532 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 16533 { 16534 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 16535 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 16536 scope(exit) XFreeGC(win.display, xgc); 16537 XSetClipMask(win.display, xgc, None); 16538 XSetForeground(win.display, xgc, 0); 16539 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 16540 } 16541 XCopyArea(display, 16542 cast(Drawable) win.buffer, 16543 cast(Drawable) newPixmap, 16544 win.gc, 0, 0, 16545 win.bufferw < width ? win.bufferw : win.width, 16546 win.bufferh < height ? win.bufferh : win.height, 16547 0, 0); 16548 16549 XFreePixmap(display, win.buffer); 16550 win.buffer = newPixmap; 16551 win.bufferw = width; 16552 win.bufferh = height; 16553 } 16554 16555 // clear unused parts of the buffer 16556 if (win.bufferw > width || win.bufferh > height) { 16557 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 16558 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 16559 scope(exit) XFreeGC(win.display, xgc); 16560 XSetClipMask(win.display, xgc, None); 16561 XSetForeground(win.display, xgc, 0); 16562 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 16563 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 16564 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 16565 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 16566 } 16567 16568 } 16569 16570 win.updateOpenglViewportIfNeeded(width, height); 16571 16572 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 16573 16574 if(win.resizability != Resizability.automaticallyScaleIfPossible) 16575 if(win.windowResized !is null) { 16576 XUnlockDisplay(display); 16577 scope(exit) XLockDisplay(display); 16578 win.windowResized(width, height); 16579 } 16580 } 16581 } 16582 16583 16584 /// Platform-specific, you might use it when doing a custom event loop. 16585 bool doXNextEvent(Display* display) { 16586 bool done; 16587 XEvent e; 16588 XNextEvent(display, &e); 16589 version(sddddd) { 16590 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 16591 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 16592 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 16593 } 16594 } 16595 16596 // filter out compose events 16597 if (XFilterEvent(&e, None)) { 16598 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 16599 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 16600 return false; 16601 } 16602 // process keyboard mapping changes 16603 if (e.type == EventType.KeymapNotify) { 16604 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 16605 XRefreshKeyboardMapping(&e.xmapping); 16606 return false; 16607 } 16608 16609 version(with_eventloop) 16610 import arsd.eventloop; 16611 16612 if(SimpleWindow.handleNativeGlobalEvent !is null) { 16613 // see windows impl's comments 16614 XUnlockDisplay(display); 16615 scope(exit) XLockDisplay(display); 16616 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 16617 if(ret == 0) 16618 return done; 16619 } 16620 16621 16622 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 16623 if(win.getNativeEventHandler !is null) { 16624 XUnlockDisplay(display); 16625 scope(exit) XLockDisplay(display); 16626 auto ret = win.getNativeEventHandler()(e); 16627 if(ret == 0) 16628 return done; 16629 } 16630 } 16631 16632 if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) { 16633 if(auto win = e.xany.window in SimpleWindow.nativeMapping) { 16634 // we get this because of the RRScreenChangeNotifyMask 16635 16636 // this isn't actually an ideal way to do it since it wastes time 16637 // but meh it is simple and it works. 16638 win.actualDpiLoadAttempted = false; 16639 SimpleWindow.xRandrInfoLoadAttemped = false; 16640 win.updateActualDpi(); // trigger a reload 16641 } 16642 } 16643 16644 switch(e.type) { 16645 case EventType.SelectionClear: 16646 // writeln("SelectionClear"); 16647 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) { 16648 // FIXME so it is supposed to finish any in progress transfers... but idk... 16649 // writeln("SelectionClear"); 16650 } 16651 SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection); 16652 mightShortCircuitClipboard = false; 16653 break; 16654 case EventType.SelectionRequest: 16655 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 16656 if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) { 16657 // printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target)); 16658 XUnlockDisplay(display); 16659 scope(exit) XLockDisplay(display); 16660 (*ssh).handleRequest(e); 16661 } 16662 break; 16663 case EventType.PropertyNotify: 16664 // import core.stdc.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state); 16665 16666 foreach(ssh; SimpleWindow.impl.setSelectionHandlers) { 16667 if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete) 16668 ssh.sendMoreIncr(&e.xproperty); 16669 } 16670 16671 16672 if(auto win = e.xproperty.window in SimpleWindow.nativeMapping) 16673 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 16674 if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) { 16675 Atom target; 16676 int format; 16677 arch_ulong bytesafter, length; 16678 void* value; 16679 16680 ubyte[] s; 16681 Atom targetToKeep; 16682 16683 XGetWindowProperty( 16684 e.xproperty.display, 16685 e.xproperty.window, 16686 e.xproperty.atom, 16687 0, 16688 100000 /* length */, 16689 true, /* erase it to signal we got it and want more */ 16690 0 /*AnyPropertyType*/, 16691 &target, &format, &length, &bytesafter, &value); 16692 16693 if(!targetToKeep) 16694 targetToKeep = target; 16695 16696 auto id = (cast(ubyte*) value)[0 .. length]; 16697 16698 handler.handleIncrData(targetToKeep, id); 16699 if(length == 0) { 16700 win.getSelectionHandlers.remove(e.xproperty.atom); 16701 } 16702 16703 XFree(value); 16704 } 16705 } 16706 break; 16707 case EventType.SelectionNotify: 16708 // import std.stdio; writefln("SelectionNotify %06x %06x", e.xselection.requestor, e.xproperty.atom); 16709 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 16710 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 16711 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 16712 XUnlockDisplay(display); 16713 scope(exit) XLockDisplay(display); 16714 handler.handleData(None, null); 16715 win.getSelectionHandlers.remove(e.xproperty.atom); 16716 } else { 16717 Atom target; 16718 int format; 16719 arch_ulong bytesafter, length; 16720 void* value; 16721 XGetWindowProperty( 16722 e.xselection.display, 16723 e.xselection.requestor, 16724 e.xselection.property, 16725 0, 16726 100000 /* length */, 16727 //false, /* don't erase it */ 16728 true, /* do erase it lol */ 16729 0 /*AnyPropertyType*/, 16730 &target, &format, &length, &bytesafter, &value); 16731 16732 // FIXME: I don't have to copy it now since it is in char[] instead of string 16733 16734 { 16735 XUnlockDisplay(display); 16736 scope(exit) XLockDisplay(display); 16737 16738 if(target == XA_ATOM) { 16739 // initial request, see what they are able to work with and request the best one 16740 // we can handle, if available 16741 16742 Atom[] answer = (cast(Atom*) value)[0 .. length]; 16743 Atom best = handler.findBestFormat(answer); 16744 16745 /+ 16746 writeln("got ", answer); 16747 foreach(a; answer) 16748 writeln(XGetAtomName(display, a).stringz); 16749 writeln("best ", best); 16750 +/ 16751 16752 if(best != None) { 16753 // actually request the best format 16754 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 16755 } 16756 } else if(target == GetAtom!"INCR"(display)) { 16757 // incremental 16758 16759 handler.prepareIncremental(e.xselection.requestor, e.xselection.property); 16760 16761 // signal the sending program that we see 16762 // the incr and are ready to receive more. 16763 XDeleteProperty( 16764 e.xselection.display, 16765 e.xselection.requestor, 16766 e.xselection.property); 16767 } else { 16768 // unsupported type... maybe, forward, then we done with it 16769 if(target != None) { 16770 handler.handleData(target, cast(ubyte[]) value[0 .. length]); 16771 win.getSelectionHandlers.remove(e.xproperty.atom); 16772 } 16773 } 16774 } 16775 XFree(value); 16776 /* 16777 XDeleteProperty( 16778 e.xselection.display, 16779 e.xselection.requestor, 16780 e.xselection.property); 16781 */ 16782 } 16783 } 16784 break; 16785 case EventType.ConfigureNotify: 16786 auto event = e.xconfigure; 16787 if(auto win = event.window in SimpleWindow.nativeMapping) { 16788 if(win.windowType == WindowTypes.minimallyWrapped) 16789 break; 16790 //version(sdddd) { writeln(" w=", event.width, "; h=", event.height); } 16791 16792 /+ 16793 The ICCCM says window managers must send a synthetic event when the window 16794 is moved but NOT when it is resized. In the resize case, an event is sent 16795 with position (0, 0) which can be wrong and break the dpi calculations. 16796 16797 So we only consider the synthetic events from the WM and otherwise 16798 need to wait for some other event to get the position which... sucks. 16799 16800 I'd rather not have windows changing their layout on mouse motion after 16801 switching monitors... might be forced to but for now just ignoring it. 16802 16803 Easiest way to switch monitors without sending a size position is by 16804 maximize or fullscreen in a setup like mine, but on most setups those 16805 work on the monitor it is already living on, so it should be ok most the 16806 time. 16807 +/ 16808 if(event.send_event) { 16809 win.screenPositionKnown = true; 16810 win.screenPositionX = event.x; 16811 win.screenPositionY = event.y; 16812 win.updateActualDpi(); 16813 } 16814 16815 win.updateIMEPopupLocation(); 16816 recordX11ResizeAsync(display, *win, event.width, event.height); 16817 } 16818 break; 16819 case EventType.Expose: 16820 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 16821 if(win.windowType == WindowTypes.minimallyWrapped) 16822 break; 16823 // if it is closing from a popup menu, it can get 16824 // an Expose event right by the end and trigger a 16825 // BadDrawable error ... we'll just check 16826 // closed to handle that. 16827 if((*win).closed) break; 16828 if((*win).openglMode == OpenGlOptions.no) { 16829 bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh 16830 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 16831 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); 16832 } else { 16833 // need to redraw the scene somehow 16834 if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all 16835 XUnlockDisplay(display); 16836 scope(exit) XLockDisplay(display); 16837 version(without_opengl) {} else 16838 win.redrawOpenGlSceneSoon(); 16839 } 16840 } 16841 } 16842 break; 16843 case EventType.FocusIn: 16844 case EventType.FocusOut: 16845 mightShortCircuitClipboard = false; // if the focus has changed, good chance the clipboard cache is invalidated too, kinda hacky but always better to skip when unnecessary than use when we shouldn't have 16846 16847 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 16848 /+ 16849 16850 void info(string detail) { 16851 string s; 16852 // import std.conv; 16853 // import std.datetime; 16854 s ~= to!string(Clock.currTime); 16855 s ~= " "; 16856 s ~= e.type == EventType.FocusIn ? "in " : "out"; 16857 s ~= " "; 16858 s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main "; 16859 s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed "; 16860 s ~= detail; 16861 s ~= " "; 16862 16863 sdpyPrintDebugString(s); 16864 16865 } 16866 16867 switch(e.xfocus.detail) { 16868 case NotifyDetail.NotifyAncestor: info("Ancestor"); break; 16869 case NotifyDetail.NotifyVirtual: info("Virtual"); break; 16870 case NotifyDetail.NotifyInferior: info("Inferior"); break; 16871 case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break; 16872 case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break; 16873 case NotifyDetail.NotifyPointer: info("pointer"); break; 16874 case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break; 16875 case NotifyDetail.NotifyDetailNone: info("none"); break; 16876 default: 16877 16878 } 16879 +/ 16880 16881 16882 if(e.xfocus.detail == NotifyDetail.NotifyPointer) 16883 break; // just ignore these they seem irrelevant 16884 16885 auto old = win._focused; 16886 win._focused = e.type == EventType.FocusIn; 16887 16888 // yes, we are losing the focus, but to our own child. that's actually kinda keeping it. 16889 if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior) 16890 win._focused = true; 16891 16892 if(win.demandingAttention) 16893 demandAttention(*win, false); 16894 16895 win.updateIMEFocused(); 16896 16897 if(old != win._focused && win.onFocusChange) { 16898 XUnlockDisplay(display); 16899 scope(exit) XLockDisplay(display); 16900 win.onFocusChange(win._focused); 16901 } 16902 } 16903 break; 16904 case EventType.VisibilityNotify: 16905 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 16906 auto before = (*win)._visible; 16907 (*win)._visible = (e.xvisibility.state != VisibilityNotify.VisibilityFullyObscured); 16908 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 16909 if (win.visibilityChanged !is null && before == true) { 16910 XUnlockDisplay(display); 16911 scope(exit) XLockDisplay(display); 16912 win.visibilityChanged(false); 16913 } 16914 } else { 16915 if (win.visibilityChanged !is null && before == false) { 16916 XUnlockDisplay(display); 16917 scope(exit) XLockDisplay(display); 16918 win.visibilityChanged(true); 16919 } 16920 } 16921 } 16922 break; 16923 case EventType.ClientMessage: 16924 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 16925 // "ignore next mouse motion" event, increment ignore counter for teh window 16926 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16927 ++(*win).warpEventCount; 16928 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 16929 } else { 16930 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 16931 } 16932 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 16933 // user clicked the close button on the window manager 16934 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16935 XUnlockDisplay(display); 16936 scope(exit) XLockDisplay(display); 16937 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 16938 } 16939 16940 } else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) { 16941 // writeln("HAPPENED"); 16942 // user clicked the close button on the window manager 16943 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16944 XUnlockDisplay(display); 16945 scope(exit) XLockDisplay(display); 16946 16947 auto setTo = *win; 16948 16949 if(win.setRequestedInputFocus !is null) { 16950 auto s = win.setRequestedInputFocus(); 16951 if(s !is null) { 16952 setTo = s; 16953 } 16954 } 16955 16956 assert(setTo !is null); 16957 16958 // FIXME: so this is actually supposed to focus to a relevant child window if appropriate 16959 16960 // sdpyPrintDebugString("WM_TAKE_FOCUS ", setTo.impl.window); 16961 XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]); 16962 } 16963 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 16964 foreach(nai; NotificationAreaIcon.activeIcons) 16965 nai.newManager(); 16966 } else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16967 16968 bool xDragWindow = true; 16969 if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) { 16970 //XDefineCursor(display, xDragWindow.impl.window, 16971 //writeln("XdndStatus ", e.xclient.data.l); 16972 } 16973 if(auto dh = win.dropHandler) { 16974 16975 static Atom[3] xFormatsBuffer; 16976 static Atom[] xFormats; 16977 16978 void resetXFormats() { 16979 xFormatsBuffer[] = 0; 16980 xFormats = xFormatsBuffer[]; 16981 } 16982 16983 if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) { 16984 // on Windows it is supposed to return the effect you actually do FIXME 16985 16986 auto sourceWindow = e.xclient.data.l[0]; 16987 16988 xFormatsBuffer[0] = e.xclient.data.l[2]; 16989 xFormatsBuffer[1] = e.xclient.data.l[3]; 16990 xFormatsBuffer[2] = e.xclient.data.l[4]; 16991 16992 if(e.xclient.data.l[1] & 1) { 16993 // can just grab it all but like we don't necessarily need them... 16994 xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM); 16995 } else { 16996 int len; 16997 foreach(fmt; xFormatsBuffer) 16998 if(fmt) len++; 16999 xFormats = xFormatsBuffer[0 .. len]; 17000 } 17001 17002 auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats); 17003 17004 dh.dragEnter(&pkg); 17005 } else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) { 17006 17007 auto pack = e.xclient.data.l[2]; 17008 17009 auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords 17010 17011 17012 XClientMessageEvent xclient; 17013 17014 xclient.type = EventType.ClientMessage; 17015 xclient.window = e.xclient.data.l[0]; 17016 xclient.message_type = GetAtom!"XdndStatus"(display); 17017 xclient.format = 32; 17018 xclient.data.l[0] = win.impl.window; 17019 xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept 17020 auto r = result.consistentWithin; 17021 xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top); 17022 xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height); 17023 xclient.data.l[4] = dndActionAtom(e.xany.display, result.action); 17024 17025 XSendEvent( 17026 display, 17027 e.xclient.data.l[0], 17028 false, 17029 EventMask.NoEventMask, 17030 cast(XEvent*) &xclient 17031 ); 17032 17033 17034 } else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) { 17035 //writeln("XdndLeave"); 17036 // drop cancelled. 17037 // data.l[0] is the source window 17038 dh.dragLeave(); 17039 17040 resetXFormats(); 17041 } else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) { 17042 // drop happening, should fetch data, then send finished 17043 // writeln("XdndDrop"); 17044 17045 auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats); 17046 17047 dh.drop(&pkg); 17048 17049 resetXFormats(); 17050 } else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) { 17051 // writeln("XdndFinished"); 17052 17053 dh.finish(); 17054 } 17055 17056 } 17057 } 17058 break; 17059 case EventType.MapNotify: 17060 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 17061 auto before = (*win)._visible; 17062 (*win)._visible = true; 17063 if (!(*win)._visibleForTheFirstTimeCalled) { 17064 (*win)._visibleForTheFirstTimeCalled = true; 17065 if ((*win).visibleForTheFirstTime !is null) { 17066 XUnlockDisplay(display); 17067 scope(exit) XLockDisplay(display); 17068 (*win).visibleForTheFirstTime(); 17069 } 17070 } 17071 if ((*win).visibilityChanged !is null && before == false) { 17072 XUnlockDisplay(display); 17073 scope(exit) XLockDisplay(display); 17074 (*win).visibilityChanged(true); 17075 } 17076 } 17077 break; 17078 case EventType.UnmapNotify: 17079 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 17080 auto before = (*win)._visible; 17081 win._visible = false; 17082 if (win.visibilityChanged !is null && before == true) { 17083 XUnlockDisplay(display); 17084 scope(exit) XLockDisplay(display); 17085 win.visibilityChanged(false); 17086 } 17087 } 17088 break; 17089 case EventType.DestroyNotify: 17090 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 17091 if(win.destroyed) 17092 break; // might get a notification both for itself and from its parent 17093 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 17094 win._closed = true; // just in case 17095 win.destroyed = true; 17096 if (win.xic !is null) { 17097 XDestroyIC(win.xic); 17098 win.xic = null; // just in case 17099 } 17100 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 17101 bool anyImportant = false; 17102 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 17103 if(w.beingOpenKeepsAppOpen) { 17104 anyImportant = true; 17105 break; 17106 } 17107 if(!anyImportant) { 17108 EventLoop.quitApplication(); 17109 done = true; 17110 } 17111 } 17112 auto window = e.xdestroywindow.window; 17113 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 17114 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 17115 17116 version(with_eventloop) { 17117 if(done) exit(); 17118 } 17119 break; 17120 17121 case EventType.MotionNotify: 17122 MouseEvent mouse; 17123 auto event = e.xmotion; 17124 17125 mouse.type = MouseEventType.motion; 17126 mouse.x = event.x; 17127 mouse.y = event.y; 17128 mouse.modifierState = event.state; 17129 17130 mouse.timestamp = event.time; 17131 17132 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 17133 mouse.window = *win; 17134 if (win.warpEventCount > 0) { 17135 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 17136 --(*win).warpEventCount; 17137 (*win).mdx(mouse); // so deltas will be correctly updated 17138 } else { 17139 win.warpEventCount = 0; // just in case 17140 (*win).mdx(mouse); 17141 if((*win).handleMouseEvent) { 17142 XUnlockDisplay(display); 17143 scope(exit) XLockDisplay(display); 17144 (*win).handleMouseEvent(mouse); 17145 } 17146 } 17147 } 17148 17149 version(with_eventloop) 17150 send(mouse); 17151 break; 17152 case EventType.ButtonPress: 17153 case EventType.ButtonRelease: 17154 MouseEvent mouse; 17155 auto event = e.xbutton; 17156 17157 mouse.timestamp = event.time; 17158 17159 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 17160 mouse.x = event.x; 17161 mouse.y = event.y; 17162 17163 static Time lastMouseDownTime = 0; 17164 static int lastMouseDownButton = -1; 17165 17166 mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 17167 if(e.type == EventType.ButtonPress) { 17168 lastMouseDownTime = event.time; 17169 lastMouseDownButton = event.button; 17170 } 17171 17172 switch(event.button) { 17173 case 1: mouse.button = MouseButton.left; break; // left 17174 case 2: mouse.button = MouseButton.middle; break; // middle 17175 case 3: mouse.button = MouseButton.right; break; // right 17176 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 17177 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 17178 case 6: mouse.button = MouseButton.wheelLeft; break; // scroll left 17179 case 7: mouse.button = MouseButton.wheelRight; break; // scroll right 17180 case 8: mouse.button = MouseButton.backButton; break; 17181 case 9: mouse.button = MouseButton.forwardButton; break; 17182 default: 17183 } 17184 17185 // FIXME: double check this 17186 mouse.modifierState = event.state; 17187 17188 //mouse.modifierState = event.detail; 17189 17190 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 17191 mouse.window = *win; 17192 (*win).mdx(mouse); 17193 if((*win).handleMouseEvent) { 17194 XUnlockDisplay(display); 17195 scope(exit) XLockDisplay(display); 17196 (*win).handleMouseEvent(mouse); 17197 } 17198 } 17199 version(with_eventloop) 17200 send(mouse); 17201 break; 17202 17203 case EventType.GenericEvent: 17204 import arsd.core; 17205 17206 auto cookie = &e.xcookie; 17207 if(XGetEventData(XDisplayConnection.get, cookie)) { 17208 scope(exit) 17209 XFreeEventData(XDisplayConnection.get, cookie); 17210 17211 // should only happen if it was already loaded since otherwise we wouldn't have subscribed to the events 17212 if(xi2.loadAttempted && cookie.extension == xi_opcode) { 17213 // if(cookie.evtype == XIEventType.XI_Motion) 17214 17215 auto deviceEvent = cast(XIDeviceEvent*) cookie.data; 17216 17217 static void interpretXI2MaskThing(ubyte* mask, int mask_len, scope void delegate(int idx, bool value) inspector) { 17218 foreach(idx, by; mask[0 .. mask_len]) { 17219 foreach(bitIdx; 0 .. 8) { 17220 inspector(cast(int) (idx * 8 + bitIdx), (by & (1 << bitIdx)) ? true : false); 17221 } 17222 } 17223 } 17224 17225 InputDeviceEvent ide; 17226 17227 ide.event = cookie.evtype; 17228 ide.deviceId = deviceEvent.deviceid; 17229 // deviceEvent.sourceid is the physical device an event came from 17230 17231 // for a touchscreen, detail is the touch id 17232 ide.detail = deviceEvent.detail; 17233 ide.flags = deviceEvent.flags; 17234 ide.rootX = deviceEvent.root_x; 17235 ide.rootY = deviceEvent.root_y; 17236 ide.windowX = deviceEvent.event_x; 17237 ide.windowY = deviceEvent.event_y; 17238 ide.buttons = 0; 17239 ide.valuators[] = double.nan; 17240 17241 interpretXI2MaskThing(deviceEvent.buttons.mask, deviceEvent.buttons.mask_len, (idx, value) { 17242 if(idx < 64) { 17243 if(value) 17244 ide.buttons |= 1 << idx; 17245 17246 } 17247 }); 17248 17249 auto values = deviceEvent.valuators.values; 17250 interpretXI2MaskThing(deviceEvent.valuators.mask, deviceEvent.valuators.mask_len, (idx, value) { 17251 if(value && idx < ide.valuators.length) { 17252 ide.valuators[idx] = *values; 17253 values++; 17254 } 17255 }); 17256 17257 if(auto win = deviceEvent.event in SimpleWindow.nativeMapping) { 17258 ide.window = *win; 17259 XUnlockDisplay(display); 17260 scope(exit) XLockDisplay(display); 17261 (*win).dispatchXInputEvent(ide); 17262 } 17263 } 17264 } 17265 break; 17266 17267 case EventType.KeyPress: 17268 case EventType.KeyRelease: 17269 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 17270 KeyEvent ke; 17271 ke.pressed = e.type == EventType.KeyPress; 17272 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 17273 17274 auto sym = XKeycodeToKeysym( 17275 XDisplayConnection.get(), 17276 e.xkey.keycode, 17277 0); 17278 17279 ke.key = cast(Key) sym;//e.xkey.keycode; 17280 17281 ke.modifierState = e.xkey.state; 17282 17283 // writefln("%x", sym); 17284 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 17285 int charbuflen = 0; // return value of XwcLookupString 17286 if (ke.pressed) { 17287 auto win = e.xkey.window in SimpleWindow.nativeMapping; 17288 if (win !is null && win.xic !is null) { 17289 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 17290 Status status; 17291 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 17292 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 17293 } else { 17294 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 17295 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 17296 char[16] buffer; 17297 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 17298 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 17299 } 17300 } 17301 17302 // if there's no char, subst one 17303 if (charbuflen == 0) { 17304 switch (sym) { 17305 case 0xff09: charbuf[charbuflen++] = '\t'; break; 17306 case 0xff8d: // keypad enter 17307 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 17308 default : // ignore 17309 } 17310 } 17311 17312 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 17313 ke.window = *win; 17314 17315 17316 if(win.inputProxy) 17317 win = &win.inputProxy; 17318 17319 // char events are separate since they are on Windows too 17320 // also, xcompose can generate long char sequences 17321 // don't send char events if Meta and/or Hyper is pressed 17322 // TODO: ctrl+char should only send control chars; not yet 17323 if ((e.xkey.state&ModifierState.ctrl) != 0) { 17324 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 17325 } 17326 17327 dchar[32] charsComingBuffer; 17328 int charsComingPosition; 17329 dchar[] charsComing = charsComingBuffer[]; 17330 17331 if (ke.pressed && charbuflen > 0) { 17332 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 17333 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 17334 if(charsComingPosition >= charsComing.length) 17335 charsComing.length = charsComingPosition + 8; 17336 17337 charsComing[charsComingPosition++] = ch; 17338 } 17339 17340 charsComing = charsComing[0 .. charsComingPosition]; 17341 } else { 17342 charsComing = null; 17343 } 17344 17345 ke.charsPossible = charsComing; 17346 17347 if (win.handleKeyEvent) { 17348 XUnlockDisplay(display); 17349 scope(exit) XLockDisplay(display); 17350 win.handleKeyEvent(ke); 17351 } 17352 17353 // Super and alt modifier keys never actually send the chars, they are assumed to be special. 17354 if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) { 17355 XUnlockDisplay(display); 17356 scope(exit) XLockDisplay(display); 17357 foreach(ch; charsComing) 17358 win.handleCharEvent(ch); 17359 } 17360 } 17361 17362 version(with_eventloop) 17363 send(ke); 17364 break; 17365 default: 17366 } 17367 17368 return done; 17369 } 17370 } 17371 17372 /* *************************************** */ 17373 /* Done with simpledisplay stuff */ 17374 /* *************************************** */ 17375 17376 // Necessary C library bindings follow 17377 version(Windows) {} else 17378 version(Emscripten) {} else 17379 version(X11) { 17380 17381 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 17382 17383 // X11 bindings needed here 17384 /* 17385 A little of this is from the bindings project on 17386 D Source and some of it is copy/paste from the C 17387 header. 17388 17389 The DSource listing consistently used D's long 17390 where C used long. That's wrong - C long is 32 bit, so 17391 it should be int in D. I changed that here. 17392 17393 Note: 17394 This isn't complete, just took what I needed for myself. 17395 */ 17396 17397 import core.stdc.stddef : wchar_t; 17398 17399 interface XLib { 17400 extern(C) nothrow @nogc { 17401 char* XResourceManagerString(Display*); 17402 void XrmInitialize(); 17403 XrmDatabase XrmGetStringDatabase(char* data); 17404 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 17405 17406 Cursor XCreateFontCursor(Display*, uint shape); 17407 int XDefineCursor(Display* display, Window w, Cursor cursor); 17408 int XUndefineCursor(Display* display, Window w); 17409 17410 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 17411 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 17412 int XFreeCursor(Display* display, Cursor cursor); 17413 17414 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 17415 17416 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 17417 17418 XVaNestedList XVaCreateNestedList(int unused, ...); 17419 17420 char *XKeysymToString(KeySym keysym); 17421 KeySym XKeycodeToKeysym( 17422 Display* /* display */, 17423 KeyCode /* keycode */, 17424 int /* index */ 17425 ); 17426 17427 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 17428 17429 int XFree(void*); 17430 int XDeleteProperty(Display *display, Window w, Atom property); 17431 17432 // int XSetCommand(Display*, Window, const char**, int); 17433 17434 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements); 17435 17436 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 17437 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 17438 *actual_type_return, int *actual_format_return, arch_ulong 17439 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 17440 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 17441 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 17442 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 17443 17444 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 17445 17446 Window XGetSelectionOwner(Display *display, Atom selection); 17447 17448 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 17449 17450 char** XListFonts(Display*, const char*, int, int*); 17451 void XFreeFontNames(char**); 17452 17453 Display* XOpenDisplay(const char*); 17454 int XCloseDisplay(Display*); 17455 17456 int function() XSynchronize(Display*, bool); 17457 int function() XSetAfterFunction(Display*, int function() proc); 17458 17459 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 17460 17461 Bool XSupportsLocale(); 17462 char* XSetLocaleModifiers(const(char)* modifier_list); 17463 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 17464 Status XCloseOM(XOM om); 17465 17466 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 17467 Status XCloseIM(XIM im); 17468 17469 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 17470 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 17471 Display* XDisplayOfIM(XIM im); 17472 char* XLocaleOfIM(XIM im); 17473 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 17474 void XDestroyIC(XIC ic); 17475 void XSetICFocus(XIC ic); 17476 void XUnsetICFocus(XIC ic); 17477 //wchar_t* XwcResetIC(XIC ic); 17478 char* XmbResetIC(XIC ic); 17479 char* Xutf8ResetIC(XIC ic); 17480 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 17481 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 17482 XIM XIMOfIC(XIC ic); 17483 17484 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 17485 17486 17487 XFontStruct *XLoadQueryFont(Display *display, scope const char *name); 17488 int XFreeFont(Display *display, XFontStruct *font_struct); 17489 int XSetFont(Display* display, GC gc, Font font); 17490 int XTextWidth(XFontStruct*, scope const char*, int); 17491 17492 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 17493 int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n); 17494 17495 Window XCreateSimpleWindow( 17496 Display* /* display */, 17497 Window /* parent */, 17498 int /* x */, 17499 int /* y */, 17500 uint /* width */, 17501 uint /* height */, 17502 uint /* border_width */, 17503 uint /* border */, 17504 uint /* background */ 17505 ); 17506 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); 17507 17508 int XReparentWindow(Display*, Window, Window, int, int); 17509 int XClearWindow(Display*, Window); 17510 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 17511 int XMoveWindow(Display*, Window, int, int); 17512 int XResizeWindow(Display *display, Window w, uint width, uint height); 17513 17514 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 17515 17516 Status XMatchVisualInfo(Display *display, int screen, int depth, int class_, XVisualInfo *vinfo_return); 17517 17518 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 17519 17520 XImage *XCreateImage( 17521 Display* /* display */, 17522 Visual* /* visual */, 17523 uint /* depth */, 17524 int /* format */, 17525 int /* offset */, 17526 ubyte* /* data */, 17527 uint /* width */, 17528 uint /* height */, 17529 int /* bitmap_pad */, 17530 int /* bytes_per_line */ 17531 ); 17532 17533 Status XInitImage (XImage* image); 17534 17535 Atom XInternAtom( 17536 Display* /* display */, 17537 const char* /* atom_name */, 17538 Bool /* only_if_exists */ 17539 ); 17540 17541 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 17542 char* XGetAtomName(Display*, Atom); 17543 Status XGetAtomNames(Display*, Atom*, int count, char**); 17544 17545 int XPutImage( 17546 Display* /* display */, 17547 Drawable /* d */, 17548 GC /* gc */, 17549 XImage* /* image */, 17550 int /* src_x */, 17551 int /* src_y */, 17552 int /* dest_x */, 17553 int /* dest_y */, 17554 uint /* width */, 17555 uint /* height */ 17556 ); 17557 17558 XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format); 17559 17560 17561 int XDestroyWindow( 17562 Display* /* display */, 17563 Window /* w */ 17564 ); 17565 17566 int XDestroyImage(XImage*); 17567 17568 int XSelectInput( 17569 Display* /* display */, 17570 Window /* w */, 17571 EventMask /* event_mask */ 17572 ); 17573 17574 int XMapWindow( 17575 Display* /* display */, 17576 Window /* w */ 17577 ); 17578 17579 Status XIconifyWindow(Display*, Window, int); 17580 int XMapRaised(Display*, Window); 17581 int XMapSubwindows(Display*, Window); 17582 17583 int XNextEvent( 17584 Display* /* display */, 17585 XEvent* /* event_return */ 17586 ); 17587 17588 int XMaskEvent(Display*, arch_long, XEvent*); 17589 17590 Bool XFilterEvent(XEvent *event, Window window); 17591 int XRefreshKeyboardMapping(XMappingEvent *event_map); 17592 17593 Status XSetWMProtocols( 17594 Display* /* display */, 17595 Window /* w */, 17596 Atom* /* protocols */, 17597 int /* count */ 17598 ); 17599 17600 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 17601 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 17602 17603 17604 Status XInitThreads(); 17605 void XLockDisplay (Display* display); 17606 void XUnlockDisplay (Display* display); 17607 17608 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 17609 17610 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 17611 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 17612 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 17613 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 17614 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 17615 17616 17617 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 17618 int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int); 17619 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 17620 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 17621 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 17622 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 17623 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 17624 int XDrawPoint(Display*, Drawable, GC, int, int); 17625 int XSetForeground(Display*, GC, uint); 17626 int XSetBackground(Display*, GC, uint); 17627 17628 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 17629 void XFreeFontSet(Display*, XFontSet); 17630 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int); 17631 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 17632 17633 int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 17634 17635 17636 //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); 17637 17638 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 17639 int XSetFunction(Display*, GC, int); 17640 17641 GC XCreateGC(Display*, Drawable, uint, void*); 17642 int XCopyGC(Display*, GC, uint, GC); 17643 int XFreeGC(Display*, GC); 17644 17645 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 17646 bool XCheckMaskEvent(Display*, int, XEvent*); 17647 17648 int XPending(Display*); 17649 int XEventsQueued(Display* display, int mode); 17650 17651 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 17652 int XFreePixmap(Display*, Pixmap); 17653 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 17654 int XFlush(Display*); 17655 int XBell(Display*, int); 17656 int XSync(Display*, bool); 17657 17658 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 17659 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 17660 17661 int XGrabKeyboard(Display*, Window, Bool, int, int, Time); 17662 int XUngrabKeyboard(Display*, Time); 17663 17664 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 17665 17666 KeySym XStringToKeysym(const char *string); 17667 17668 Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return); 17669 17670 Window XDefaultRootWindow(Display*); 17671 17672 int XGrabButton(Display *display, uint button, uint modifiers, Window grab_window, Bool owner_events, uint event_mask, int pointer_mode, int keyboard_mode, Window confine_to, Cursor cursor); 17673 17674 int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 17675 17676 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 17677 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 17678 17679 Status XAllocColor(Display*, Colormap, XColor*); 17680 17681 int XWithdrawWindow(Display*, Window, int); 17682 int XUnmapWindow(Display*, Window); 17683 int XLowerWindow(Display*, Window); 17684 int XRaiseWindow(Display*, Window); 17685 17686 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); 17687 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); 17688 17689 int XGetInputFocus(Display*, Window*, int*); 17690 int XSetInputFocus(Display*, Window, int, Time); 17691 17692 XErrorHandler XSetErrorHandler(XErrorHandler); 17693 17694 int XGetErrorText(Display*, int, char*, int); 17695 17696 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 17697 17698 17699 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); 17700 int XUngrabPointer(Display *display, Time time); 17701 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 17702 17703 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 17704 17705 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 17706 int XSetClipMask(Display*, GC, Pixmap); 17707 int XSetClipOrigin(Display*, GC, int, int); 17708 17709 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 17710 17711 void XSetWMName(Display*, Window, XTextProperty*); 17712 Status XGetWMName(Display*, Window, XTextProperty*); 17713 int XStoreName(Display* display, Window w, const(char)* window_name); 17714 17715 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 17716 17717 Bool XGetEventData(Display* dpy, XGenericEventCookie* cookie); 17718 void XFreeEventData(Display* dpy, XGenericEventCookie* cookie); 17719 17720 } 17721 } 17722 17723 interface Xext { 17724 extern(C) nothrow @nogc { 17725 Status XShmAttach(Display*, XShmSegmentInfo*); 17726 Status XShmDetach(Display*, XShmSegmentInfo*); 17727 Status XShmPutImage( 17728 Display* /* dpy */, 17729 Drawable /* d */, 17730 GC /* gc */, 17731 XImage* /* image */, 17732 int /* src_x */, 17733 int /* src_y */, 17734 int /* dst_x */, 17735 int /* dst_y */, 17736 uint /* src_width */, 17737 uint /* src_height */, 17738 Bool /* send_event */ 17739 ); 17740 17741 Status XShmQueryExtension(Display*); 17742 17743 XImage *XShmCreateImage( 17744 Display* /* dpy */, 17745 Visual* /* visual */, 17746 uint /* depth */, 17747 int /* format */, 17748 char* /* data */, 17749 XShmSegmentInfo* /* shminfo */, 17750 uint /* width */, 17751 uint /* height */ 17752 ); 17753 17754 Pixmap XShmCreatePixmap( 17755 Display* /* dpy */, 17756 Drawable /* d */, 17757 char* /* data */, 17758 XShmSegmentInfo* /* shminfo */, 17759 uint /* width */, 17760 uint /* height */, 17761 uint /* depth */ 17762 ); 17763 17764 } 17765 } 17766 17767 // this requires -lXpm 17768 //int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 17769 17770 17771 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib; 17772 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext; 17773 shared static this() { 17774 xlib.loadDynamicLibrary(); 17775 xext.loadDynamicLibrary(); 17776 } 17777 17778 // See WM_POINTERDOWN and friends for Windows 17779 interface XI2 { extern(C) nothrow @nogc: 17780 Status XIQueryVersion(Display*, int*, int*); 17781 XIDeviceInfo* XIQueryDevice(Display*, int deviceId, int* ndev_return); 17782 void XIFreeDeviceInfo(XIDeviceInfo* info); 17783 int XISelectEvents(Display* dpy, Window win, XIEventMask* mask, int num_masks); 17784 17785 Status XISetFocus(Display* dpy, int deviceid, Window focus, Time time); 17786 Status XIGetFocus(Display* dpy, int deviceid, Window *focus_return); 17787 17788 Status XIGrabDevice( 17789 Display* dpy, 17790 int deviceid, 17791 Window grab_window, 17792 Time time, 17793 Cursor cursor, 17794 int grab_mode, 17795 int paired_device_mode, 17796 Bool owner_events, 17797 XIEventMask *mask 17798 ); 17799 17800 Status XIUngrabDevice(Display* dpy, int deviceid, Time time); 17801 17802 // FIXME: i might want the ability to float a device and maybe reattach it 17803 // and there's even more in there 17804 // see /usr/include/X11/extensions/XInput2.h and /usr/include/X11/extensions/XI2.h 17805 } 17806 __gshared bool xi2SuccessfullyLoaded = true; 17807 mixin DynamicLoad!(XI2, "Xi", 6, xi2SuccessfullyLoaded) xi2; 17808 17809 int xi_opcode() @system { 17810 __gshared int code; 17811 __gshared int status; 17812 // FIXME: disconnect and reconnect 17813 17814 if(code && status == 3) 17815 return code; 17816 if(status) { 17817 throw new Exception("XInput failed previously"); 17818 } 17819 17820 if(!xi2.loadAttempted) { 17821 xi2.loadDynamicLibrary(); 17822 } 17823 if(!xi2SuccessfullyLoaded) { 17824 throw new Exception("XInput2 library load failure"); 17825 } 17826 17827 auto dpy = XDisplayConnection.get; 17828 17829 int ev, err; 17830 if(!XQueryExtension(dpy, "XInputExtension", &code, &ev, &err)) { 17831 status = 1; 17832 throw new Exception("XInputExtension not supported by server"); 17833 } 17834 17835 int major = 2; 17836 int minor = 4; 17837 17838 if(XIQueryVersion(dpy, &major, &minor) != Success) { 17839 status = 2; 17840 throw new Exception("XInput2 not supported by server"); 17841 } 17842 17843 status = 3; 17844 17845 return code; 17846 } 17847 17848 void XISetMask(ubyte[] mask, int event) { 17849 mask[event >> 3] |= (1 << (event & 7)); 17850 } 17851 17852 int XIMaskLen(int event) { 17853 return (event >> 3) + 1; 17854 } 17855 17856 struct XIEventMask { 17857 int deviceid; 17858 int mask_len; 17859 ubyte* mask_ptr; 17860 17861 // my extensions 17862 ubyte[XIMaskLen(XIEventType.max)] buffer; 17863 this(int deviceid) { 17864 this.deviceid = deviceid; 17865 this.mask_len = cast(int) buffer.length; 17866 this.mask_ptr = buffer.ptr; 17867 } 17868 17869 void set(XIEventType[] val...) { 17870 foreach(item; val) 17871 XISetMask(buffer[], item); 17872 } 17873 } 17874 17875 /+ 17876 // this can be sent for logical devices almost randomly just as the user switches which physical device they use... 17877 struct XIDeviceChangedEvent { 17878 int type; /* GenericEvent */ 17879 unsigned long serial; /* # of last request processed by server */ 17880 Bool send_event; /* true if this came from a SendEvent request */ 17881 Display *display; /* Display the event was read from */ 17882 int extension; /* XI extension offset */ 17883 int evtype; /* XI_DeviceChanged */ 17884 Time time; 17885 int deviceid; /* id of the device that changed */ 17886 int sourceid; /* Source for the new classes. */ 17887 int reason; /* Reason for the change */ 17888 int num_classes; 17889 XIAnyClassInfo **classes; /* same as in XIDeviceInfo */ 17890 } 17891 +/ 17892 17893 struct XIDeviceEvent { 17894 int type; /* GenericEvent */ 17895 arch_ulong serial; /* # of last request processed by server */ 17896 Bool send_event; /* true if this came from a SendEvent request */ 17897 Display *display; /* Display the event was read from */ 17898 int extension; /* XI extension offset */ 17899 XIEventType evtype; 17900 Time time; 17901 int deviceid; 17902 int sourceid; 17903 int detail; 17904 Window root; 17905 Window event; 17906 Window child; 17907 double root_x; 17908 double root_y; 17909 double event_x; 17910 double event_y; 17911 int flags; 17912 XIButtonState buttons; 17913 XIValuatorState valuators; 17914 XIModifierState mods; 17915 XIGroupState group; 17916 } 17917 17918 struct XIModifierState { 17919 int base; 17920 int latched; 17921 int locked; 17922 int effective; 17923 } 17924 17925 alias XIGroupState = XIModifierState; 17926 17927 enum XIAllDevices = 0; 17928 struct XIDeviceInfo { 17929 int deviceid; 17930 char* name; 17931 int use; 17932 int attachment; 17933 Bool enabled; 17934 int num_classes; 17935 XIAnyClassInfo **classes; 17936 } 17937 // device classes 17938 enum XIDeviceClass : int { 17939 XIKeyClass = 0, 17940 XIButtonClass = 1, 17941 XIValuatorClass = 2, 17942 XIScrollClass = 3, 17943 XITouchClass = 8, 17944 XIGestureClass = 9, 17945 } 17946 17947 enum XIDeviceType : int { 17948 XIMasterPointer = 1, 17949 XIMasterKeyboard = 2, 17950 XISlavePointer = 3, 17951 XISlaveKeyboard = 4, 17952 XIFloatingSlave = 5, 17953 } 17954 17955 enum XIValuatorMode : int { 17956 XIModeRelative = 0, 17957 XIModeAbsolute = 1, 17958 } 17959 17960 enum XIScrollType : int { 17961 XIScrollTypeVertical = 1, 17962 XIScrollTypeHorizontal = 2, 17963 } 17964 17965 enum XIEventType : int { 17966 XI_DeviceChanged = 1, 17967 XI_KeyPress = 2, 17968 XI_KeyRelease = 3, 17969 XI_ButtonPress = 4, 17970 XI_ButtonRelease = 5, 17971 XI_Motion = 6, 17972 XI_Enter = 7, 17973 XI_Leave = 8, 17974 XI_FocusIn = 9, 17975 XI_FocusOut = 10, 17976 XI_HierarchyChanged = 11, 17977 XI_PropertyEvent = 12, 17978 XI_RawKeyPress = 13, 17979 XI_RawKeyRelease = 14, 17980 XI_RawButtonPress = 15, 17981 XI_RawButtonRelease = 16, 17982 XI_RawMotion = 17, 17983 XI_TouchBegin = 18, /* XI 2.2 */ 17984 XI_TouchUpdate = 19, 17985 XI_TouchEnd = 20, 17986 XI_TouchOwnership = 21, 17987 XI_RawTouchBegin = 22, 17988 XI_RawTouchUpdate = 23, 17989 XI_RawTouchEnd = 24, 17990 XI_BarrierHit = 25, /* XI 2.3 */ 17991 XI_BarrierLeave = 26, 17992 XI_GesturePinchBegin = 27, /* XI 2.4 */ 17993 XI_GesturePinchUpdate = 28, 17994 XI_GesturePinchEnd = 29, 17995 XI_GestureSwipeBegin = 30, 17996 XI_GestureSwipeUpdate = 31, 17997 XI_GestureSwipeEnd = 32, 17998 } 17999 18000 enum XIEventMaskValues : int { 18001 XI_DeviceChangedMask = (1 << XIEventType.XI_DeviceChanged), 18002 XI_KeyPressMask = (1 << XIEventType.XI_KeyPress), 18003 XI_KeyReleaseMask = (1 << XIEventType.XI_KeyRelease), 18004 XI_ButtonPressMask = (1 << XIEventType.XI_ButtonPress), 18005 XI_ButtonReleaseMask = (1 << XIEventType.XI_ButtonRelease), 18006 XI_MotionMask = (1 << XIEventType.XI_Motion), 18007 XI_EnterMask = (1 << XIEventType.XI_Enter), 18008 XI_LeaveMask = (1 << XIEventType.XI_Leave), 18009 XI_FocusInMask = (1 << XIEventType.XI_FocusIn), 18010 XI_FocusOutMask = (1 << XIEventType.XI_FocusOut), 18011 XI_HierarchyChangedMask = (1 << XIEventType.XI_HierarchyChanged), 18012 XI_PropertyEventMask = (1 << XIEventType.XI_PropertyEvent), 18013 XI_RawKeyPressMask = (1 << XIEventType.XI_RawKeyPress), 18014 XI_RawKeyReleaseMask = (1 << XIEventType.XI_RawKeyRelease), 18015 XI_RawButtonPressMask = (1 << XIEventType.XI_RawButtonPress), 18016 XI_RawButtonReleaseMask = (1 << XIEventType.XI_RawButtonRelease), 18017 XI_RawMotionMask = (1 << XIEventType.XI_RawMotion), 18018 XI_TouchBeginMask = (1 << XIEventType.XI_TouchBegin), 18019 XI_TouchEndMask = (1 << XIEventType.XI_TouchEnd), 18020 XI_TouchOwnershipChangedMask = (1 << XIEventType.XI_TouchOwnership), 18021 XI_TouchUpdateMask = (1 << XIEventType.XI_TouchUpdate), 18022 XI_RawTouchBeginMask = (1 << XIEventType.XI_RawTouchBegin), 18023 XI_RawTouchEndMask = (1 << XIEventType.XI_RawTouchEnd), 18024 XI_RawTouchUpdateMask = (1 << XIEventType.XI_RawTouchUpdate), 18025 XI_BarrierHitMask = (1 << XIEventType.XI_BarrierHit), 18026 XI_BarrierLeaveMask = (1 << XIEventType.XI_BarrierLeave), 18027 } 18028 18029 struct XIAnyClassInfo { 18030 XIDeviceClass type; 18031 int sourceid; 18032 } 18033 struct XIKeyClassInfo { 18034 XIAnyClassInfo any; 18035 alias any this; 18036 int num_keycodes; 18037 int* keycodes; 18038 } 18039 struct XIButtonState { 18040 int mask_len; 18041 ubyte* mask; 18042 } 18043 18044 // https://who-t.blogspot.com/2009/07/xi2-recipes-part-4.html 18045 struct XIValuatorState { 18046 int mask_len; 18047 ubyte* mask; 18048 double* values; 18049 } 18050 struct XIButtonClassInfo { 18051 XIAnyClassInfo any; 18052 alias any this; 18053 int num_buttons; 18054 Atom* labels; 18055 XIButtonState state; 18056 } 18057 struct XIValuatorClassInfo { 18058 XIAnyClassInfo any; 18059 alias any this; 18060 18061 int number; 18062 Atom label; 18063 double min; 18064 double max; 18065 double value; 18066 int resolution; 18067 int mode; 18068 } 18069 struct XIScrollClassInfo { 18070 XIAnyClassInfo any; 18071 alias any this; 18072 18073 int number; 18074 int scroll_type; 18075 double increment; 18076 int flags; 18077 } 18078 struct XITouchClassInfo { 18079 XIAnyClassInfo any; 18080 alias any this; 18081 int mode; 18082 int num_touches; 18083 } 18084 struct XIGestureClassInfo { 18085 XIAnyClassInfo any; 18086 alias any this; 18087 int num_touches; 18088 } 18089 18090 extern(C) nothrow @nogc { 18091 18092 alias XrmDatabase = void*; 18093 struct XrmValue { 18094 uint size; 18095 void* addr; 18096 } 18097 18098 struct XVisualInfo { 18099 Visual* visual; 18100 VisualID visualid; 18101 int screen; 18102 uint depth; 18103 int c_class; 18104 c_ulong red_mask; 18105 c_ulong green_mask; 18106 c_ulong blue_mask; 18107 int colormap_size; 18108 int bits_per_rgb; 18109 } 18110 18111 enum VisualNoMask= 0x0; 18112 enum VisualIDMask= 0x1; 18113 enum VisualScreenMask=0x2; 18114 enum VisualDepthMask= 0x4; 18115 enum VisualClassMask= 0x8; 18116 enum VisualRedMaskMask=0x10; 18117 enum VisualGreenMaskMask=0x20; 18118 enum VisualBlueMaskMask=0x40; 18119 enum VisualColormapSizeMask=0x80; 18120 enum VisualBitsPerRGBMask=0x100; 18121 enum VisualAllMask= 0x1FF; 18122 18123 enum AnyKey = 0; 18124 enum AnyModifier = 1 << 15; 18125 18126 // XIM and other crap 18127 struct _XOM {} 18128 struct _XIM {} 18129 struct _XIC {} 18130 alias XOM = _XOM*; 18131 alias XIM = _XIM*; 18132 alias XIC = _XIC*; 18133 18134 alias XVaNestedList = void*; 18135 18136 alias XIMStyle = arch_ulong; 18137 enum : arch_ulong { 18138 XIMPreeditArea = 0x0001, 18139 XIMPreeditCallbacks = 0x0002, 18140 XIMPreeditPosition = 0x0004, 18141 XIMPreeditNothing = 0x0008, 18142 XIMPreeditNone = 0x0010, 18143 XIMStatusArea = 0x0100, 18144 XIMStatusCallbacks = 0x0200, 18145 XIMStatusNothing = 0x0400, 18146 XIMStatusNone = 0x0800, 18147 } 18148 18149 18150 /* X Shared Memory Extension functions */ 18151 //pragma(lib, "Xshm"); 18152 alias arch_ulong ShmSeg; 18153 struct XShmSegmentInfo { 18154 ShmSeg shmseg; 18155 int shmid; 18156 ubyte* shmaddr; 18157 Bool readOnly; 18158 } 18159 18160 // and the necessary OS functions 18161 int shmget(int, size_t, int); 18162 void* shmat(int, scope const void*, int); 18163 int shmdt(scope const void*); 18164 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 18165 18166 enum IPC_PRIVATE = 0; 18167 enum IPC_CREAT = 512; 18168 enum IPC_RMID = 0; 18169 18170 /* MIT-SHM end */ 18171 18172 18173 enum MappingType:int { 18174 MappingModifier =0, 18175 MappingKeyboard =1, 18176 MappingPointer =2 18177 } 18178 18179 /* ImageFormat -- PutImage, GetImage */ 18180 enum ImageFormat:int { 18181 XYBitmap =0, /* depth 1, XYFormat */ 18182 XYPixmap =1, /* depth == drawable depth */ 18183 ZPixmap =2 /* depth == drawable depth */ 18184 } 18185 18186 enum ModifierName:int { 18187 ShiftMapIndex =0, 18188 LockMapIndex =1, 18189 ControlMapIndex =2, 18190 Mod1MapIndex =3, 18191 Mod2MapIndex =4, 18192 Mod3MapIndex =5, 18193 Mod4MapIndex =6, 18194 Mod5MapIndex =7 18195 } 18196 18197 enum ButtonMask:int { 18198 Button1Mask =1<<8, 18199 Button2Mask =1<<9, 18200 Button3Mask =1<<10, 18201 Button4Mask =1<<11, 18202 Button5Mask =1<<12, 18203 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 18204 } 18205 18206 enum KeyOrButtonMask:uint { 18207 ShiftMask =1<<0, 18208 LockMask =1<<1, 18209 ControlMask =1<<2, 18210 Mod1Mask =1<<3, 18211 Mod2Mask =1<<4, 18212 Mod3Mask =1<<5, 18213 Mod4Mask =1<<6, 18214 Mod5Mask =1<<7, 18215 Button1Mask =1<<8, 18216 Button2Mask =1<<9, 18217 Button3Mask =1<<10, 18218 Button4Mask =1<<11, 18219 Button5Mask =1<<12, 18220 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 18221 } 18222 18223 enum ButtonName:int { 18224 Button1 =1, 18225 Button2 =2, 18226 Button3 =3, 18227 Button4 =4, 18228 Button5 =5 18229 } 18230 18231 /* Notify modes */ 18232 enum NotifyModes:int 18233 { 18234 NotifyNormal =0, 18235 NotifyGrab =1, 18236 NotifyUngrab =2, 18237 NotifyWhileGrabbed =3 18238 } 18239 enum NotifyHint = 1; /* for MotionNotify events */ 18240 18241 /* Notify detail */ 18242 enum NotifyDetail:int 18243 { 18244 NotifyAncestor =0, 18245 NotifyVirtual =1, 18246 NotifyInferior =2, 18247 NotifyNonlinear =3, 18248 NotifyNonlinearVirtual =4, 18249 NotifyPointer =5, 18250 NotifyPointerRoot =6, 18251 NotifyDetailNone =7 18252 } 18253 18254 /* Visibility notify */ 18255 18256 enum VisibilityNotify:int 18257 { 18258 VisibilityUnobscured =0, 18259 VisibilityPartiallyObscured =1, 18260 VisibilityFullyObscured =2 18261 } 18262 18263 18264 enum WindowStackingMethod:int 18265 { 18266 Above =0, 18267 Below =1, 18268 TopIf =2, 18269 BottomIf =3, 18270 Opposite =4 18271 } 18272 18273 /* Circulation request */ 18274 enum CirculationRequest:int 18275 { 18276 PlaceOnTop =0, 18277 PlaceOnBottom =1 18278 } 18279 18280 enum PropertyNotification:int 18281 { 18282 PropertyNewValue =0, 18283 PropertyDelete =1 18284 } 18285 18286 enum ColorMapNotification:int 18287 { 18288 ColormapUninstalled =0, 18289 ColormapInstalled =1 18290 } 18291 18292 18293 struct _XPrivate {} 18294 struct _XrmHashBucketRec {} 18295 18296 alias void* XPointer; 18297 alias void* XExtData; 18298 18299 version( X86_64 ) { 18300 alias ulong XID; 18301 alias ulong arch_ulong; 18302 alias long arch_long; 18303 } else version (AArch64) { 18304 alias ulong XID; 18305 alias ulong arch_ulong; 18306 alias long arch_long; 18307 } else { 18308 alias uint XID; 18309 alias uint arch_ulong; 18310 alias int arch_long; 18311 } 18312 18313 alias XID Window; 18314 alias XID Drawable; 18315 alias XID Pixmap; 18316 18317 alias arch_ulong Atom; 18318 alias int Bool; 18319 alias Display XDisplay; 18320 18321 alias int ByteOrder; 18322 alias arch_ulong Time; 18323 alias void ScreenFormat; 18324 18325 struct XImage { 18326 int width, height; /* size of image */ 18327 int xoffset; /* number of pixels offset in X direction */ 18328 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 18329 void *data; /* pointer to image data */ 18330 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 18331 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 18332 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 18333 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 18334 int depth; /* depth of image */ 18335 int bytes_per_line; /* accelarator to next line */ 18336 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 18337 arch_ulong red_mask; /* bits in z arrangment */ 18338 arch_ulong green_mask; 18339 arch_ulong blue_mask; 18340 XPointer obdata; /* hook for the object routines to hang on */ 18341 static struct F { /* image manipulation routines */ 18342 XImage* function( 18343 XDisplay* /* display */, 18344 Visual* /* visual */, 18345 uint /* depth */, 18346 int /* format */, 18347 int /* offset */, 18348 ubyte* /* data */, 18349 uint /* width */, 18350 uint /* height */, 18351 int /* bitmap_pad */, 18352 int /* bytes_per_line */) create_image; 18353 int function(XImage *) destroy_image; 18354 arch_ulong function(XImage *, int, int) get_pixel; 18355 int function(XImage *, int, int, arch_ulong) put_pixel; 18356 XImage* function(XImage *, int, int, uint, uint) sub_image; 18357 int function(XImage *, arch_long) add_pixel; 18358 } 18359 F f; 18360 } 18361 version(X86_64) static assert(XImage.sizeof == 136); 18362 else version(X86) static assert(XImage.sizeof == 88); 18363 18364 struct XCharStruct { 18365 short lbearing; /* origin to left edge of raster */ 18366 short rbearing; /* origin to right edge of raster */ 18367 short width; /* advance to next char's origin */ 18368 short ascent; /* baseline to top edge of raster */ 18369 short descent; /* baseline to bottom edge of raster */ 18370 ushort attributes; /* per char flags (not predefined) */ 18371 } 18372 18373 /* 18374 * To allow arbitrary information with fonts, there are additional properties 18375 * returned. 18376 */ 18377 struct XFontProp { 18378 Atom name; 18379 arch_ulong card32; 18380 } 18381 18382 alias Atom Font; 18383 18384 struct XFontStruct { 18385 XExtData *ext_data; /* Hook for extension to hang data */ 18386 Font fid; /* Font ID for this font */ 18387 uint direction; /* Direction the font is painted */ 18388 uint min_char_or_byte2; /* First character */ 18389 uint max_char_or_byte2; /* Last character */ 18390 uint min_byte1; /* First row that exists (for two-byte fonts) */ 18391 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 18392 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 18393 uint default_char; /* Char to print for undefined character */ 18394 int n_properties; /* How many properties there are */ 18395 XFontProp *properties; /* Pointer to array of additional properties*/ 18396 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 18397 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 18398 XCharStruct *per_char; /* first_char to last_char information */ 18399 int ascent; /* Max extent above baseline for spacing */ 18400 int descent; /* Max descent below baseline for spacing */ 18401 } 18402 18403 18404 /* 18405 * Definitions of specific events. 18406 */ 18407 struct XKeyEvent 18408 { 18409 int type; /* of event */ 18410 arch_ulong serial; /* # of last request processed by server */ 18411 Bool send_event; /* true if this came from a SendEvent request */ 18412 Display *display; /* Display the event was read from */ 18413 Window window; /* "event" window it is reported relative to */ 18414 Window root; /* root window that the event occurred on */ 18415 Window subwindow; /* child window */ 18416 Time time; /* milliseconds */ 18417 int x, y; /* pointer x, y coordinates in event window */ 18418 int x_root, y_root; /* coordinates relative to root */ 18419 KeyOrButtonMask state; /* key or button mask */ 18420 uint keycode; /* detail */ 18421 Bool same_screen; /* same screen flag */ 18422 } 18423 version(X86_64) static assert(XKeyEvent.sizeof == 96); 18424 alias XKeyEvent XKeyPressedEvent; 18425 alias XKeyEvent XKeyReleasedEvent; 18426 18427 struct XButtonEvent 18428 { 18429 int type; /* of event */ 18430 arch_ulong serial; /* # of last request processed by server */ 18431 Bool send_event; /* true if this came from a SendEvent request */ 18432 Display *display; /* Display the event was read from */ 18433 Window window; /* "event" window it is reported relative to */ 18434 Window root; /* root window that the event occurred on */ 18435 Window subwindow; /* child window */ 18436 Time time; /* milliseconds */ 18437 int x, y; /* pointer x, y coordinates in event window */ 18438 int x_root, y_root; /* coordinates relative to root */ 18439 KeyOrButtonMask state; /* key or button mask */ 18440 uint button; /* detail */ 18441 Bool same_screen; /* same screen flag */ 18442 } 18443 alias XButtonEvent XButtonPressedEvent; 18444 alias XButtonEvent XButtonReleasedEvent; 18445 18446 struct XMotionEvent{ 18447 int type; /* of event */ 18448 arch_ulong serial; /* # of last request processed by server */ 18449 Bool send_event; /* true if this came from a SendEvent request */ 18450 Display *display; /* Display the event was read from */ 18451 Window window; /* "event" window reported relative to */ 18452 Window root; /* root window that the event occurred on */ 18453 Window subwindow; /* child window */ 18454 Time time; /* milliseconds */ 18455 int x, y; /* pointer x, y coordinates in event window */ 18456 int x_root, y_root; /* coordinates relative to root */ 18457 KeyOrButtonMask state; /* key or button mask */ 18458 byte is_hint; /* detail */ 18459 Bool same_screen; /* same screen flag */ 18460 } 18461 alias XMotionEvent XPointerMovedEvent; 18462 18463 struct XCrossingEvent{ 18464 int type; /* of event */ 18465 arch_ulong serial; /* # of last request processed by server */ 18466 Bool send_event; /* true if this came from a SendEvent request */ 18467 Display *display; /* Display the event was read from */ 18468 Window window; /* "event" window reported relative to */ 18469 Window root; /* root window that the event occurred on */ 18470 Window subwindow; /* child window */ 18471 Time time; /* milliseconds */ 18472 int x, y; /* pointer x, y coordinates in event window */ 18473 int x_root, y_root; /* coordinates relative to root */ 18474 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 18475 NotifyDetail detail; 18476 /* 18477 * NotifyAncestor, NotifyVirtual, NotifyInferior, 18478 * NotifyNonlinear,NotifyNonlinearVirtual 18479 */ 18480 Bool same_screen; /* same screen flag */ 18481 Bool focus; /* Boolean focus */ 18482 KeyOrButtonMask state; /* key or button mask */ 18483 } 18484 alias XCrossingEvent XEnterWindowEvent; 18485 alias XCrossingEvent XLeaveWindowEvent; 18486 18487 struct XFocusChangeEvent{ 18488 int type; /* FocusIn or FocusOut */ 18489 arch_ulong serial; /* # of last request processed by server */ 18490 Bool send_event; /* true if this came from a SendEvent request */ 18491 Display *display; /* Display the event was read from */ 18492 Window window; /* window of event */ 18493 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 18494 NotifyGrab, NotifyUngrab */ 18495 NotifyDetail detail; 18496 /* 18497 * NotifyAncestor, NotifyVirtual, NotifyInferior, 18498 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 18499 * NotifyPointerRoot, NotifyDetailNone 18500 */ 18501 } 18502 alias XFocusChangeEvent XFocusInEvent; 18503 alias XFocusChangeEvent XFocusOutEvent; 18504 18505 enum CWBackPixmap = (1L<<0); 18506 enum CWBackPixel = (1L<<1); 18507 enum CWBorderPixmap = (1L<<2); 18508 enum CWBorderPixel = (1L<<3); 18509 enum CWBitGravity = (1L<<4); 18510 enum CWWinGravity = (1L<<5); 18511 enum CWBackingStore = (1L<<6); 18512 enum CWBackingPlanes = (1L<<7); 18513 enum CWBackingPixel = (1L<<8); 18514 enum CWOverrideRedirect = (1L<<9); 18515 enum CWSaveUnder = (1L<<10); 18516 enum CWEventMask = (1L<<11); 18517 enum CWDontPropagate = (1L<<12); 18518 enum CWColormap = (1L<<13); 18519 enum CWCursor = (1L<<14); 18520 18521 struct XWindowAttributes { 18522 int x, y; /* location of window */ 18523 int width, height; /* width and height of window */ 18524 int border_width; /* border width of window */ 18525 int depth; /* depth of window */ 18526 Visual *visual; /* the associated visual structure */ 18527 Window root; /* root of screen containing window */ 18528 int class_; /* InputOutput, InputOnly*/ 18529 int bit_gravity; /* one of the bit gravity values */ 18530 int win_gravity; /* one of the window gravity values */ 18531 int backing_store; /* NotUseful, WhenMapped, Always */ 18532 arch_ulong backing_planes; /* planes to be preserved if possible */ 18533 arch_ulong backing_pixel; /* value to be used when restoring planes */ 18534 Bool save_under; /* boolean, should bits under be saved? */ 18535 Colormap colormap; /* color map to be associated with window */ 18536 Bool map_installed; /* boolean, is color map currently installed*/ 18537 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 18538 arch_long all_event_masks; /* set of events all people have interest in*/ 18539 arch_long your_event_mask; /* my event mask */ 18540 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 18541 Bool override_redirect; /* boolean value for override-redirect */ 18542 Screen *screen; /* back pointer to correct screen */ 18543 } 18544 18545 enum IsUnmapped = 0; 18546 enum IsUnviewable = 1; 18547 enum IsViewable = 2; 18548 18549 struct XSetWindowAttributes { 18550 Pixmap background_pixmap;/* background, None, or ParentRelative */ 18551 arch_ulong background_pixel;/* background pixel */ 18552 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 18553 arch_ulong border_pixel;/* border pixel value */ 18554 int bit_gravity; /* one of bit gravity values */ 18555 int win_gravity; /* one of the window gravity values */ 18556 int backing_store; /* NotUseful, WhenMapped, Always */ 18557 arch_ulong backing_planes;/* planes to be preserved if possible */ 18558 arch_ulong backing_pixel;/* value to use in restoring planes */ 18559 Bool save_under; /* should bits under be saved? (popups) */ 18560 arch_long event_mask; /* set of events that should be saved */ 18561 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 18562 Bool override_redirect; /* boolean value for override_redirect */ 18563 Colormap colormap; /* color map to be associated with window */ 18564 Cursor cursor; /* cursor to be displayed (or None) */ 18565 } 18566 18567 18568 alias int Status; 18569 18570 18571 enum EventMask:int 18572 { 18573 NoEventMask =0, 18574 KeyPressMask =1<<0, 18575 KeyReleaseMask =1<<1, 18576 ButtonPressMask =1<<2, 18577 ButtonReleaseMask =1<<3, 18578 EnterWindowMask =1<<4, 18579 LeaveWindowMask =1<<5, 18580 PointerMotionMask =1<<6, 18581 PointerMotionHintMask =1<<7, 18582 Button1MotionMask =1<<8, 18583 Button2MotionMask =1<<9, 18584 Button3MotionMask =1<<10, 18585 Button4MotionMask =1<<11, 18586 Button5MotionMask =1<<12, 18587 ButtonMotionMask =1<<13, 18588 KeymapStateMask =1<<14, 18589 ExposureMask =1<<15, 18590 VisibilityChangeMask =1<<16, 18591 StructureNotifyMask =1<<17, 18592 ResizeRedirectMask =1<<18, 18593 SubstructureNotifyMask =1<<19, 18594 SubstructureRedirectMask=1<<20, 18595 FocusChangeMask =1<<21, 18596 PropertyChangeMask =1<<22, 18597 ColormapChangeMask =1<<23, 18598 OwnerGrabButtonMask =1<<24 18599 } 18600 18601 struct MwmHints { 18602 c_ulong flags; 18603 c_ulong functions; 18604 c_ulong decorations; 18605 c_long input_mode; 18606 c_ulong status; 18607 } 18608 18609 enum { 18610 MWM_HINTS_FUNCTIONS = (1L << 0), 18611 MWM_HINTS_DECORATIONS = (1L << 1), 18612 18613 MWM_FUNC_ALL = (1L << 0), 18614 MWM_FUNC_RESIZE = (1L << 1), 18615 MWM_FUNC_MOVE = (1L << 2), 18616 MWM_FUNC_MINIMIZE = (1L << 3), 18617 MWM_FUNC_MAXIMIZE = (1L << 4), 18618 MWM_FUNC_CLOSE = (1L << 5), 18619 18620 MWM_DECOR_ALL = (1L << 0), 18621 MWM_DECOR_BORDER = (1L << 1), 18622 MWM_DECOR_RESIZEH = (1L << 2), 18623 MWM_DECOR_TITLE = (1L << 3), 18624 MWM_DECOR_MENU = (1L << 4), 18625 MWM_DECOR_MINIMIZE = (1L << 5), 18626 MWM_DECOR_MAXIMIZE = (1L << 6), 18627 } 18628 18629 import core.stdc.config : c_long, c_ulong; 18630 18631 /* Size hints mask bits */ 18632 18633 enum USPosition = (1L << 0) /* user specified x, y */; 18634 enum USSize = (1L << 1) /* user specified width, height */; 18635 enum PPosition = (1L << 2) /* program specified position */; 18636 enum PSize = (1L << 3) /* program specified size */; 18637 enum PMinSize = (1L << 4) /* program specified minimum size */; 18638 enum PMaxSize = (1L << 5) /* program specified maximum size */; 18639 enum PResizeInc = (1L << 6) /* program specified resize increments */; 18640 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 18641 enum PBaseSize = (1L << 8); 18642 enum PWinGravity = (1L << 9); 18643 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 18644 struct XSizeHints { 18645 arch_long flags; /* marks which fields in this structure are defined */ 18646 int x, y; /* Obsolete */ 18647 int width, height; /* Obsolete */ 18648 int min_width, min_height; 18649 int max_width, max_height; 18650 int width_inc, height_inc; 18651 struct Aspect { 18652 int x; /* numerator */ 18653 int y; /* denominator */ 18654 } 18655 18656 Aspect min_aspect; 18657 Aspect max_aspect; 18658 int base_width, base_height; 18659 int win_gravity; 18660 /* this structure may be extended in the future */ 18661 } 18662 18663 18664 18665 enum EventType:int 18666 { 18667 KeyPress =2, 18668 KeyRelease =3, 18669 ButtonPress =4, 18670 ButtonRelease =5, 18671 MotionNotify =6, 18672 EnterNotify =7, 18673 LeaveNotify =8, 18674 FocusIn =9, 18675 FocusOut =10, 18676 KeymapNotify =11, 18677 Expose =12, 18678 GraphicsExpose =13, 18679 NoExpose =14, 18680 VisibilityNotify =15, 18681 CreateNotify =16, 18682 DestroyNotify =17, 18683 UnmapNotify =18, 18684 MapNotify =19, 18685 MapRequest =20, 18686 ReparentNotify =21, 18687 ConfigureNotify =22, 18688 ConfigureRequest =23, 18689 GravityNotify =24, 18690 ResizeRequest =25, 18691 CirculateNotify =26, 18692 CirculateRequest =27, 18693 PropertyNotify =28, 18694 SelectionClear =29, 18695 SelectionRequest =30, 18696 SelectionNotify =31, 18697 ColormapNotify =32, 18698 ClientMessage =33, 18699 MappingNotify =34, 18700 GenericEvent = 35, 18701 LASTEvent =36 /* must be bigger than any event # */ 18702 } 18703 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 18704 struct XKeymapEvent 18705 { 18706 int type; 18707 arch_ulong serial; /* # of last request processed by server */ 18708 Bool send_event; /* true if this came from a SendEvent request */ 18709 Display *display; /* Display the event was read from */ 18710 Window window; 18711 byte[32] key_vector; 18712 } 18713 18714 struct XExposeEvent 18715 { 18716 int type; 18717 arch_ulong serial; /* # of last request processed by server */ 18718 Bool send_event; /* true if this came from a SendEvent request */ 18719 Display *display; /* Display the event was read from */ 18720 Window window; 18721 int x, y; 18722 int width, height; 18723 int count; /* if non-zero, at least this many more */ 18724 } 18725 18726 struct XGraphicsExposeEvent{ 18727 int type; 18728 arch_ulong serial; /* # of last request processed by server */ 18729 Bool send_event; /* true if this came from a SendEvent request */ 18730 Display *display; /* Display the event was read from */ 18731 Drawable drawable; 18732 int x, y; 18733 int width, height; 18734 int count; /* if non-zero, at least this many more */ 18735 int major_code; /* core is CopyArea or CopyPlane */ 18736 int minor_code; /* not defined in the core */ 18737 } 18738 18739 struct XNoExposeEvent{ 18740 int type; 18741 arch_ulong serial; /* # of last request processed by server */ 18742 Bool send_event; /* true if this came from a SendEvent request */ 18743 Display *display; /* Display the event was read from */ 18744 Drawable drawable; 18745 int major_code; /* core is CopyArea or CopyPlane */ 18746 int minor_code; /* not defined in the core */ 18747 } 18748 18749 struct XVisibilityEvent{ 18750 int type; 18751 arch_ulong serial; /* # of last request processed by server */ 18752 Bool send_event; /* true if this came from a SendEvent request */ 18753 Display *display; /* Display the event was read from */ 18754 Window window; 18755 VisibilityNotify state; /* Visibility state */ 18756 } 18757 18758 struct XCreateWindowEvent{ 18759 int type; 18760 arch_ulong serial; /* # of last request processed by server */ 18761 Bool send_event; /* true if this came from a SendEvent request */ 18762 Display *display; /* Display the event was read from */ 18763 Window parent; /* parent of the window */ 18764 Window window; /* window id of window created */ 18765 int x, y; /* window location */ 18766 int width, height; /* size of window */ 18767 int border_width; /* border width */ 18768 Bool override_redirect; /* creation should be overridden */ 18769 } 18770 18771 struct XDestroyWindowEvent 18772 { 18773 int type; 18774 arch_ulong serial; /* # of last request processed by server */ 18775 Bool send_event; /* true if this came from a SendEvent request */ 18776 Display *display; /* Display the event was read from */ 18777 Window event; 18778 Window window; 18779 } 18780 18781 struct XUnmapEvent 18782 { 18783 int type; 18784 arch_ulong serial; /* # of last request processed by server */ 18785 Bool send_event; /* true if this came from a SendEvent request */ 18786 Display *display; /* Display the event was read from */ 18787 Window event; 18788 Window window; 18789 Bool from_configure; 18790 } 18791 18792 struct XMapEvent 18793 { 18794 int type; 18795 arch_ulong serial; /* # of last request processed by server */ 18796 Bool send_event; /* true if this came from a SendEvent request */ 18797 Display *display; /* Display the event was read from */ 18798 Window event; 18799 Window window; 18800 Bool override_redirect; /* Boolean, is override set... */ 18801 } 18802 18803 struct XMapRequestEvent 18804 { 18805 int type; 18806 arch_ulong serial; /* # of last request processed by server */ 18807 Bool send_event; /* true if this came from a SendEvent request */ 18808 Display *display; /* Display the event was read from */ 18809 Window parent; 18810 Window window; 18811 } 18812 18813 struct XReparentEvent 18814 { 18815 int type; 18816 arch_ulong serial; /* # of last request processed by server */ 18817 Bool send_event; /* true if this came from a SendEvent request */ 18818 Display *display; /* Display the event was read from */ 18819 Window event; 18820 Window window; 18821 Window parent; 18822 int x, y; 18823 Bool override_redirect; 18824 } 18825 18826 struct XConfigureEvent 18827 { 18828 int type; 18829 arch_ulong serial; /* # of last request processed by server */ 18830 Bool send_event; /* true if this came from a SendEvent request */ 18831 Display *display; /* Display the event was read from */ 18832 Window event; 18833 Window window; 18834 int x, y; 18835 int width, height; 18836 int border_width; 18837 Window above; 18838 Bool override_redirect; 18839 } 18840 18841 struct XGravityEvent 18842 { 18843 int type; 18844 arch_ulong serial; /* # of last request processed by server */ 18845 Bool send_event; /* true if this came from a SendEvent request */ 18846 Display *display; /* Display the event was read from */ 18847 Window event; 18848 Window window; 18849 int x, y; 18850 } 18851 18852 struct XResizeRequestEvent 18853 { 18854 int type; 18855 arch_ulong serial; /* # of last request processed by server */ 18856 Bool send_event; /* true if this came from a SendEvent request */ 18857 Display *display; /* Display the event was read from */ 18858 Window window; 18859 int width, height; 18860 } 18861 18862 struct XConfigureRequestEvent 18863 { 18864 int type; 18865 arch_ulong serial; /* # of last request processed by server */ 18866 Bool send_event; /* true if this came from a SendEvent request */ 18867 Display *display; /* Display the event was read from */ 18868 Window parent; 18869 Window window; 18870 int x, y; 18871 int width, height; 18872 int border_width; 18873 Window above; 18874 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 18875 arch_ulong value_mask; 18876 } 18877 18878 struct XCirculateEvent 18879 { 18880 int type; 18881 arch_ulong serial; /* # of last request processed by server */ 18882 Bool send_event; /* true if this came from a SendEvent request */ 18883 Display *display; /* Display the event was read from */ 18884 Window event; 18885 Window window; 18886 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 18887 } 18888 18889 struct XCirculateRequestEvent 18890 { 18891 int type; 18892 arch_ulong serial; /* # of last request processed by server */ 18893 Bool send_event; /* true if this came from a SendEvent request */ 18894 Display *display; /* Display the event was read from */ 18895 Window parent; 18896 Window window; 18897 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 18898 } 18899 18900 struct XPropertyEvent 18901 { 18902 int type; 18903 arch_ulong serial; /* # of last request processed by server */ 18904 Bool send_event; /* true if this came from a SendEvent request */ 18905 Display *display; /* Display the event was read from */ 18906 Window window; 18907 Atom atom; 18908 Time time; 18909 PropertyNotification state; /* NewValue, Deleted */ 18910 } 18911 18912 struct XSelectionClearEvent 18913 { 18914 int type; 18915 arch_ulong serial; /* # of last request processed by server */ 18916 Bool send_event; /* true if this came from a SendEvent request */ 18917 Display *display; /* Display the event was read from */ 18918 Window window; 18919 Atom selection; 18920 Time time; 18921 } 18922 18923 struct XSelectionRequestEvent 18924 { 18925 int type; 18926 arch_ulong serial; /* # of last request processed by server */ 18927 Bool send_event; /* true if this came from a SendEvent request */ 18928 Display *display; /* Display the event was read from */ 18929 Window owner; 18930 Window requestor; 18931 Atom selection; 18932 Atom target; 18933 Atom property; 18934 Time time; 18935 } 18936 18937 struct XSelectionEvent 18938 { 18939 int type; 18940 arch_ulong serial; /* # of last request processed by server */ 18941 Bool send_event; /* true if this came from a SendEvent request */ 18942 Display *display; /* Display the event was read from */ 18943 Window requestor; 18944 Atom selection; 18945 Atom target; 18946 Atom property; /* ATOM or None */ 18947 Time time; 18948 } 18949 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 18950 18951 struct XColormapEvent 18952 { 18953 int type; 18954 arch_ulong serial; /* # of last request processed by server */ 18955 Bool send_event; /* true if this came from a SendEvent request */ 18956 Display *display; /* Display the event was read from */ 18957 Window window; 18958 Colormap colormap; /* COLORMAP or None */ 18959 Bool new_; /* C++ */ 18960 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 18961 } 18962 version(X86_64) static assert(XColormapEvent.sizeof == 56); 18963 18964 struct XClientMessageEvent 18965 { 18966 int type; 18967 arch_ulong serial; /* # of last request processed by server */ 18968 Bool send_event; /* true if this came from a SendEvent request */ 18969 Display *display; /* Display the event was read from */ 18970 Window window; 18971 Atom message_type; 18972 int format; 18973 union Data{ 18974 byte[20] b; 18975 short[10] s; 18976 arch_ulong[5] l; 18977 } 18978 Data data; 18979 18980 } 18981 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 18982 18983 struct XMappingEvent 18984 { 18985 int type; 18986 arch_ulong serial; /* # of last request processed by server */ 18987 Bool send_event; /* true if this came from a SendEvent request */ 18988 Display *display; /* Display the event was read from */ 18989 Window window; /* unused */ 18990 MappingType request; /* one of MappingModifier, MappingKeyboard, 18991 MappingPointer */ 18992 int first_keycode; /* first keycode */ 18993 int count; /* defines range of change w. first_keycode*/ 18994 } 18995 18996 struct XErrorEvent 18997 { 18998 int type; 18999 Display *display; /* Display the event was read from */ 19000 XID resourceid; /* resource id */ 19001 arch_ulong serial; /* serial number of failed request */ 19002 ubyte error_code; /* error code of failed request */ 19003 ubyte request_code; /* Major op-code of failed request */ 19004 ubyte minor_code; /* Minor op-code of failed request */ 19005 } 19006 19007 struct XAnyEvent 19008 { 19009 int type; 19010 arch_ulong serial; /* # of last request processed by server */ 19011 Bool send_event; /* true if this came from a SendEvent request */ 19012 Display *display;/* Display the event was read from */ 19013 Window window; /* window on which event was requested in event mask */ 19014 } 19015 19016 union XEvent{ 19017 int type; /* must not be changed; first element */ 19018 XAnyEvent xany; 19019 XKeyEvent xkey; 19020 XButtonEvent xbutton; 19021 XMotionEvent xmotion; 19022 XCrossingEvent xcrossing; 19023 XFocusChangeEvent xfocus; 19024 XExposeEvent xexpose; 19025 XGraphicsExposeEvent xgraphicsexpose; 19026 XNoExposeEvent xnoexpose; 19027 XVisibilityEvent xvisibility; 19028 XCreateWindowEvent xcreatewindow; 19029 XDestroyWindowEvent xdestroywindow; 19030 XUnmapEvent xunmap; 19031 XMapEvent xmap; 19032 XMapRequestEvent xmaprequest; 19033 XReparentEvent xreparent; 19034 XConfigureEvent xconfigure; 19035 XGravityEvent xgravity; 19036 XResizeRequestEvent xresizerequest; 19037 XConfigureRequestEvent xconfigurerequest; 19038 XCirculateEvent xcirculate; 19039 XCirculateRequestEvent xcirculaterequest; 19040 XPropertyEvent xproperty; 19041 XSelectionClearEvent xselectionclear; 19042 XSelectionRequestEvent xselectionrequest; 19043 XSelectionEvent xselection; 19044 XColormapEvent xcolormap; 19045 XClientMessageEvent xclient; 19046 XMappingEvent xmapping; 19047 XErrorEvent xerror; 19048 XKeymapEvent xkeymap; 19049 XGenericEvent xgeneric; 19050 XGenericEventCookie xcookie; 19051 arch_ulong[24] pad; 19052 } 19053 struct XGenericEvent { 19054 int type; 19055 arch_ulong serial; 19056 Bool send_event; 19057 Display *display; 19058 int extension; 19059 int evtype; 19060 } 19061 19062 struct XGenericEventCookie { 19063 int type; 19064 arch_ulong serial; 19065 Bool send_event; 19066 Display *display; 19067 int extension; 19068 int evtype; 19069 uint cookie; 19070 void *data; 19071 } 19072 19073 struct Display { 19074 XExtData *ext_data; /* hook for extension to hang data */ 19075 _XPrivate *private1; 19076 int fd; /* Network socket. */ 19077 int private2; 19078 int proto_major_version;/* major version of server's X protocol */ 19079 int proto_minor_version;/* minor version of servers X protocol */ 19080 char *vendor; /* vendor of the server hardware */ 19081 XID private3; 19082 XID private4; 19083 XID private5; 19084 int private6; 19085 XID function(Display*)resource_alloc;/* allocator function */ 19086 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 19087 int bitmap_unit; /* padding and data requirements */ 19088 int bitmap_pad; /* padding requirements on bitmaps */ 19089 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 19090 int nformats; /* number of pixmap formats in list */ 19091 ScreenFormat *pixmap_format; /* pixmap format list */ 19092 int private8; 19093 int release; /* release of the server */ 19094 _XPrivate *private9; 19095 _XPrivate *private10; 19096 int qlen; /* Length of input event queue */ 19097 arch_ulong last_request_read; /* seq number of last event read */ 19098 arch_ulong request; /* sequence number of last request. */ 19099 XPointer private11; 19100 XPointer private12; 19101 XPointer private13; 19102 XPointer private14; 19103 uint max_request_size; /* maximum number 32 bit words in request*/ 19104 _XrmHashBucketRec *db; 19105 int function (Display*)private15; 19106 char *display_name; /* "host:display" string used on this connect*/ 19107 int default_screen; /* default screen for operations */ 19108 int nscreens; /* number of screens on this server*/ 19109 Screen *screens; /* pointer to list of screens */ 19110 arch_ulong motion_buffer; /* size of motion buffer */ 19111 arch_ulong private16; 19112 int min_keycode; /* minimum defined keycode */ 19113 int max_keycode; /* maximum defined keycode */ 19114 XPointer private17; 19115 XPointer private18; 19116 int private19; 19117 byte *xdefaults; /* contents of defaults from server */ 19118 /* there is more to this structure, but it is private to Xlib */ 19119 } 19120 19121 // I got these numbers from a C program as a sanity test 19122 version(X86_64) { 19123 static assert(Display.sizeof == 296); 19124 static assert(XPointer.sizeof == 8); 19125 static assert(XErrorEvent.sizeof == 40); 19126 static assert(XAnyEvent.sizeof == 40); 19127 static assert(XMappingEvent.sizeof == 56); 19128 static assert(XEvent.sizeof == 192); 19129 } else version (AArch64) { 19130 // omit check for aarch64 19131 } else { 19132 static assert(Display.sizeof == 176); 19133 static assert(XPointer.sizeof == 4); 19134 static assert(XEvent.sizeof == 96); 19135 } 19136 19137 struct Depth 19138 { 19139 int depth; /* this depth (Z) of the depth */ 19140 int nvisuals; /* number of Visual types at this depth */ 19141 Visual *visuals; /* list of visuals possible at this depth */ 19142 } 19143 19144 alias void* GC; 19145 alias c_ulong VisualID; 19146 alias XID Colormap; 19147 alias XID Cursor; 19148 alias XID KeySym; 19149 alias uint KeyCode; 19150 enum None = 0; 19151 } 19152 19153 version(without_opengl) {} 19154 else { 19155 extern(C) nothrow @nogc { 19156 19157 19158 static if(!SdpyIsUsingIVGLBinds) { 19159 enum GLX_USE_GL= 1; /* support GLX rendering */ 19160 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 19161 enum GLX_LEVEL= 3; /* level in plane stacking */ 19162 enum GLX_RGBA= 4; /* true if RGBA mode */ 19163 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 19164 enum GLX_STEREO= 6; /* stereo buffering supported */ 19165 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 19166 enum GLX_RED_SIZE= 8; /* number of red component bits */ 19167 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 19168 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 19169 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 19170 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 19171 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 19172 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 19173 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 19174 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 19175 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 19176 19177 19178 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 19179 19180 19181 19182 enum GL_TRUE = 1; 19183 enum GL_FALSE = 0; 19184 } 19185 19186 alias XID GLXContextID; 19187 alias XID GLXPixmap; 19188 alias XID GLXDrawable; 19189 alias XID GLXPbuffer; 19190 alias XID GLXWindow; 19191 alias XID GLXFBConfigID; 19192 alias void* GLXContext; 19193 19194 } 19195 } 19196 19197 enum AllocNone = 0; 19198 19199 extern(C) { 19200 /* WARNING, this type not in Xlib spec */ 19201 extern(C) alias XIOErrorHandler = int function (Display* display); 19202 } 19203 19204 extern(C) nothrow 19205 alias XErrorHandler = int function(Display*, XErrorEvent*); 19206 19207 extern(C) nothrow @nogc { 19208 struct Screen{ 19209 XExtData *ext_data; /* hook for extension to hang data */ 19210 Display *display; /* back pointer to display structure */ 19211 Window root; /* Root window id. */ 19212 int width, height; /* width and height of screen */ 19213 int mwidth, mheight; /* width and height of in millimeters */ 19214 int ndepths; /* number of depths possible */ 19215 Depth *depths; /* list of allowable depths on the screen */ 19216 int root_depth; /* bits per pixel */ 19217 Visual *root_visual; /* root visual */ 19218 GC default_gc; /* GC for the root root visual */ 19219 Colormap cmap; /* default color map */ 19220 uint white_pixel; 19221 uint black_pixel; /* White and Black pixel values */ 19222 int max_maps, min_maps; /* max and min color maps */ 19223 int backing_store; /* Never, WhenMapped, Always */ 19224 bool save_unders; 19225 int root_input_mask; /* initial root input mask */ 19226 } 19227 19228 struct Visual 19229 { 19230 XExtData *ext_data; /* hook for extension to hang data */ 19231 VisualID visualid; /* visual id of this visual */ 19232 int class_; /* class of screen (monochrome, etc.) */ 19233 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 19234 int bits_per_rgb; /* log base 2 of distinct color values */ 19235 int map_entries; /* color map entries */ 19236 } 19237 19238 alias Display* _XPrivDisplay; 19239 19240 extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) @system { 19241 assert(dpy !is null); 19242 return &dpy.screens[scr]; 19243 } 19244 19245 extern(D) Window RootWindow(Display *dpy,int scr) { 19246 return ScreenOfDisplay(dpy,scr).root; 19247 } 19248 19249 struct XWMHints { 19250 arch_long flags; 19251 Bool input; 19252 int initial_state; 19253 Pixmap icon_pixmap; 19254 Window icon_window; 19255 int icon_x, icon_y; 19256 Pixmap icon_mask; 19257 XID window_group; 19258 } 19259 19260 struct XClassHint { 19261 char* res_name; 19262 char* res_class; 19263 } 19264 19265 extern(D) int DefaultScreen(Display *dpy) { 19266 return dpy.default_screen; 19267 } 19268 19269 extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 19270 extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 19271 extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 19272 extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 19273 extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 19274 extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 19275 19276 extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; } 19277 19278 enum int AnyPropertyType = 0; 19279 enum int Success = 0; 19280 19281 enum int RevertToNone = None; 19282 enum int PointerRoot = 1; 19283 enum Time CurrentTime = 0; 19284 enum int RevertToPointerRoot = PointerRoot; 19285 enum int RevertToParent = 2; 19286 19287 extern(D) int DefaultDepthOfDisplay(Display* dpy) { 19288 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 19289 } 19290 19291 extern(D) Visual* DefaultVisual(Display *dpy,int scr) { 19292 return ScreenOfDisplay(dpy,scr).root_visual; 19293 } 19294 19295 extern(D) GC DefaultGC(Display *dpy,int scr) { 19296 return ScreenOfDisplay(dpy,scr).default_gc; 19297 } 19298 19299 extern(D) uint BlackPixel(Display *dpy,int scr) { 19300 return ScreenOfDisplay(dpy,scr).black_pixel; 19301 } 19302 19303 extern(D) uint WhitePixel(Display *dpy,int scr) { 19304 return ScreenOfDisplay(dpy,scr).white_pixel; 19305 } 19306 19307 alias void* XFontSet; // i think 19308 struct XmbTextItem { 19309 char* chars; 19310 int nchars; 19311 int delta; 19312 XFontSet font_set; 19313 } 19314 19315 struct XTextItem { 19316 char* chars; 19317 int nchars; 19318 int delta; 19319 Font font; 19320 } 19321 19322 enum { 19323 GXclear = 0x0, /* 0 */ 19324 GXand = 0x1, /* src AND dst */ 19325 GXandReverse = 0x2, /* src AND NOT dst */ 19326 GXcopy = 0x3, /* src */ 19327 GXandInverted = 0x4, /* NOT src AND dst */ 19328 GXnoop = 0x5, /* dst */ 19329 GXxor = 0x6, /* src XOR dst */ 19330 GXor = 0x7, /* src OR dst */ 19331 GXnor = 0x8, /* NOT src AND NOT dst */ 19332 GXequiv = 0x9, /* NOT src XOR dst */ 19333 GXinvert = 0xa, /* NOT dst */ 19334 GXorReverse = 0xb, /* src OR NOT dst */ 19335 GXcopyInverted = 0xc, /* NOT src */ 19336 GXorInverted = 0xd, /* NOT src OR dst */ 19337 GXnand = 0xe, /* NOT src OR NOT dst */ 19338 GXset = 0xf, /* 1 */ 19339 } 19340 enum QueueMode : int { 19341 QueuedAlready, 19342 QueuedAfterReading, 19343 QueuedAfterFlush 19344 } 19345 19346 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 19347 19348 struct XPoint { 19349 short x; 19350 short y; 19351 } 19352 19353 enum CoordMode:int { 19354 CoordModeOrigin = 0, 19355 CoordModePrevious = 1 19356 } 19357 19358 enum PolygonShape:int { 19359 Complex = 0, 19360 Nonconvex = 1, 19361 Convex = 2 19362 } 19363 19364 struct XTextProperty { 19365 const(char)* value; /* same as Property routines */ 19366 Atom encoding; /* prop type */ 19367 int format; /* prop data format: 8, 16, or 32 */ 19368 arch_ulong nitems; /* number of data items in value */ 19369 } 19370 19371 version( X86_64 ) { 19372 static assert(XTextProperty.sizeof == 32); 19373 } 19374 19375 19376 struct XGCValues { 19377 int function_; /* logical operation */ 19378 arch_ulong plane_mask;/* plane mask */ 19379 arch_ulong foreground;/* foreground pixel */ 19380 arch_ulong background;/* background pixel */ 19381 int line_width; /* line width */ 19382 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 19383 int cap_style; /* CapNotLast, CapButt, 19384 CapRound, CapProjecting */ 19385 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 19386 int fill_style; /* FillSolid, FillTiled, 19387 FillStippled, FillOpaeueStippled */ 19388 int fill_rule; /* EvenOddRule, WindingRule */ 19389 int arc_mode; /* ArcChord, ArcPieSlice */ 19390 Pixmap tile; /* tile pixmap for tiling operations */ 19391 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 19392 int ts_x_origin; /* offset for tile or stipple operations */ 19393 int ts_y_origin; 19394 Font font; /* default text font for text operations */ 19395 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 19396 Bool graphics_exposures;/* boolean, should exposures be generated */ 19397 int clip_x_origin; /* origin for clipping */ 19398 int clip_y_origin; 19399 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 19400 int dash_offset; /* patterned/dashed line information */ 19401 char dashes; 19402 } 19403 19404 struct XColor { 19405 arch_ulong pixel; 19406 ushort red, green, blue; 19407 byte flags; 19408 byte pad; 19409 } 19410 19411 struct XRectangle { 19412 short x; 19413 short y; 19414 ushort width; 19415 ushort height; 19416 } 19417 19418 enum ClipByChildren = 0; 19419 enum IncludeInferiors = 1; 19420 19421 enum Atom XA_PRIMARY = 1; 19422 enum Atom XA_SECONDARY = 2; 19423 enum Atom XA_STRING = 31; 19424 enum Atom XA_CARDINAL = 6; 19425 enum Atom XA_WM_NAME = 39; 19426 enum Atom XA_ATOM = 4; 19427 enum Atom XA_WINDOW = 33; 19428 enum Atom XA_WM_HINTS = 35; 19429 enum int PropModeAppend = 2; 19430 enum int PropModeReplace = 0; 19431 enum int PropModePrepend = 1; 19432 19433 enum int CopyFromParent = 0; 19434 enum int InputOutput = 1; 19435 19436 // XWMHints 19437 enum InputHint = 1 << 0; 19438 enum StateHint = 1 << 1; 19439 enum IconPixmapHint = (1L << 2); 19440 enum IconWindowHint = (1L << 3); 19441 enum IconPositionHint = (1L << 4); 19442 enum IconMaskHint = (1L << 5); 19443 enum WindowGroupHint = (1L << 6); 19444 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 19445 enum XUrgencyHint = (1L << 8); 19446 19447 // GC Components 19448 enum GCFunction = (1L<<0); 19449 enum GCPlaneMask = (1L<<1); 19450 enum GCForeground = (1L<<2); 19451 enum GCBackground = (1L<<3); 19452 enum GCLineWidth = (1L<<4); 19453 enum GCLineStyle = (1L<<5); 19454 enum GCCapStyle = (1L<<6); 19455 enum GCJoinStyle = (1L<<7); 19456 enum GCFillStyle = (1L<<8); 19457 enum GCFillRule = (1L<<9); 19458 enum GCTile = (1L<<10); 19459 enum GCStipple = (1L<<11); 19460 enum GCTileStipXOrigin = (1L<<12); 19461 enum GCTileStipYOrigin = (1L<<13); 19462 enum GCFont = (1L<<14); 19463 enum GCSubwindowMode = (1L<<15); 19464 enum GCGraphicsExposures= (1L<<16); 19465 enum GCClipXOrigin = (1L<<17); 19466 enum GCClipYOrigin = (1L<<18); 19467 enum GCClipMask = (1L<<19); 19468 enum GCDashOffset = (1L<<20); 19469 enum GCDashList = (1L<<21); 19470 enum GCArcMode = (1L<<22); 19471 enum GCLastBit = 22; 19472 19473 19474 enum int WithdrawnState = 0; 19475 enum int NormalState = 1; 19476 enum int IconicState = 3; 19477 19478 } 19479 } else version (OSXCocoa) { 19480 19481 /+ 19482 DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do. 19483 +/ 19484 19485 import core.attribute; 19486 19487 private __gshared AppDelegate globalAppDelegate; 19488 19489 extern(Objective-C) 19490 class AppDelegate : NSObject, NSApplicationDelegate { 19491 override static AppDelegate alloc() @selector("alloc"); 19492 19493 19494 void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") { 19495 SimpleWindow.processAllCustomEvents(); 19496 } 19497 19498 override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") { 19499 immutable style = NSWindowStyleMask.resizable | 19500 NSWindowStyleMask.closable | 19501 NSWindowStyleMask.miniaturizable | 19502 NSWindowStyleMask.titled; 19503 19504 NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow); 19505 19506 { 19507 auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow); 19508 auto menu = NSMenu.alloc.init(MacString("Test2").borrow); 19509 mainMenu.setSubmenu(menu, item); 19510 19511 auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow); 19512 newItem.target = NSApp; 19513 auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow); 19514 newItem2.target = NSApp; 19515 } 19516 19517 { 19518 auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow); 19519 auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used 19520 mainMenu.setSubmenu(menu, item); 19521 19522 auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow); 19523 menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow); 19524 } 19525 19526 19527 NSApp.menu = mainMenu; 19528 19529 19530 // auto controller = ViewController.alloc.init; 19531 19532 // auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true); 19533 19534 /+ 19535 this.window = window; 19536 this.controller = controller; 19537 +/ 19538 } 19539 19540 override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") { 19541 NSApplication.shared_.activateIgnoringOtherApps(false); 19542 19543 sdpyPrintDebugString("before"); 19544 NSApp.stop(cast(void*) NSApp); // stop NSApp.run and let arsd.core event loop take over... 19545 sdpyPrintDebugString("after"); 19546 } 19547 override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") { 19548 return true; 19549 } 19550 } 19551 19552 extern(Objective-C) 19553 class SDWindowDelegate : NSObject, NSWindowDelegate { 19554 override static SDWindowDelegate alloc() @selector("alloc"); 19555 override SDWindowDelegate init() @selector("init"); 19556 19557 SimpleWindow simpleWindow; 19558 19559 override void windowWillClose(NSNotification notification) @selector("windowWillClose:") { 19560 auto window = cast(void*) notification.object; 19561 19562 // FIXME: do i need to release it? 19563 if(auto swp = window in SimpleWindow.nativeMapping) { 19564 auto sw = *swp; 19565 19566 sw._closed = true; 19567 19568 if (sw.visibilityChanged !is null && sw._visible) sw.visibilityChanged(false); 19569 19570 if (sw.onDestroyed !is null) try { sw.onDestroyed(); } catch (Exception e) {} // sorry 19571 SimpleWindow.nativeMapping.remove(window); 19572 // FIXME: this makes a ref to typeinfo apparently 19573 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 19574 19575 bool anyImportant = false; 19576 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 19577 if(w.beingOpenKeepsAppOpen) { 19578 anyImportant = true; 19579 break; 19580 } 19581 if(!anyImportant) { 19582 EventLoop.quitApplication(); 19583 } 19584 19585 } 19586 } 19587 19588 override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") { 19589 if(simpleWindow.windowResized) { 19590 // FIXME: automaticallyScaleIfPossible behaviors 19591 19592 simpleWindow._width = cast(int) frameSize.width; 19593 simpleWindow._height = cast(int) frameSize.height; 19594 19595 simpleWindow.view.setFrameSize(frameSize); 19596 19597 /+ 19598 auto size = simpleWindow.view.frame.size; 19599 writeln(cast(int) size.width, "x", cast(int) size.height); 19600 +/ 19601 19602 simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height); 19603 19604 simpleWindow.windowResized(simpleWindow._width, simpleWindow._height); 19605 19606 // simpleWindow.view.setNeedsDisplay(true); 19607 } 19608 19609 return frameSize; 19610 } 19611 19612 /+ 19613 override void windowDidResize(NSNotification notification) @selector("windowDidResize:") { 19614 if(simpleWindow.windowResized) { 19615 auto window = simpleWindow.window; 19616 auto rect = window.contentRectForFrameRect(window.frame); 19617 import std.stdio; writeln(window.frame.size); 19618 simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height); 19619 } 19620 } 19621 +/ 19622 } 19623 19624 extern(Objective-C) 19625 class SDGraphicsView : NSView { 19626 SimpleWindow simpleWindow; 19627 19628 override static SDGraphicsView alloc() @selector("alloc"); 19629 override SDGraphicsView init() @selector("init");/* { 19630 super.init(); 19631 return this; 19632 }*/ 19633 19634 override void drawRect(NSRect rect) @selector("drawRect:") { 19635 auto curCtx = NSGraphicsContext.currentContext.graphicsPort; 19636 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 19637 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext)); 19638 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 19639 CGImageRelease(cgImage); 19640 } 19641 19642 extern(D) 19643 private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) { 19644 MouseEvent me; 19645 me.type = type; 19646 19647 auto pos = event.locationInWindow; 19648 19649 me.x = cast(int) pos.x; 19650 19651 // FIXME: 1-based things here might need fixup 19652 me.y = cast(int) (simpleWindow.height - pos.y); 19653 19654 me.dx = 0; // FIXME 19655 me.dy = 0; // FIXME 19656 19657 me.button = button; 19658 me.modifierState = cast(uint) event.modifierFlags; 19659 me.window = simpleWindow; 19660 19661 me.doubleClick = false; 19662 19663 if(simpleWindow && simpleWindow.handleMouseEvent) 19664 simpleWindow.handleMouseEvent(me); 19665 } 19666 19667 override void mouseDown(NSEvent event) @selector("mouseDown:") { 19668 // writeln(event.pressedMouseButtons); 19669 19670 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 19671 } 19672 override void mouseDragged(NSEvent event) @selector("mouseDragged:") { 19673 mouseHelper(event, MouseEventType.motion, MouseButton.left); 19674 } 19675 override void mouseUp(NSEvent event) @selector("mouseUp:") { 19676 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left); 19677 } 19678 override void mouseMoved(NSEvent event) @selector("mouseMoved:") { 19679 mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly 19680 } 19681 /+ 19682 // FIXME 19683 override void mouseEntered(NSEvent event) @selector("mouseEntered:") { 19684 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 19685 } 19686 override void mouseExited(NSEvent event) @selector("mouseExited:") { 19687 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 19688 } 19689 +/ 19690 19691 override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") { 19692 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right); 19693 } 19694 override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") { 19695 mouseHelper(event, MouseEventType.motion, MouseButton.right); 19696 } 19697 override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") { 19698 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right); 19699 } 19700 19701 override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") { 19702 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle); 19703 } 19704 override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") { 19705 mouseHelper(event, MouseEventType.motion, MouseButton.middle); 19706 } 19707 override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") { 19708 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle); 19709 } 19710 19711 override void scrollWheel(NSEvent event) @selector("scrollWheel:") { 19712 // import std.stdio; writeln(event.deltaY); 19713 } 19714 19715 override void keyDown(NSEvent event) @selector("keyDown:") { 19716 // the event may have multiple characters, and we send them all at once. 19717 if (simpleWindow.handleCharEvent) { 19718 auto chars = DeifiedNSString(event.characters); 19719 foreach (dchar dc; chars.str) 19720 simpleWindow.handleCharEvent(dc); 19721 } 19722 19723 keyHelper(event, true); 19724 } 19725 19726 override void keyUp(NSEvent event) @selector("keyUp:") { 19727 keyHelper(event, false); 19728 } 19729 19730 extern(D) 19731 private void keyHelper(NSEvent event, bool pressed) { 19732 if(simpleWindow.handleKeyEvent) { 19733 KeyEvent ev; 19734 ev.key = cast(Key) event.keyCode;// (event.specialKey ? event.specialKey : event.keyCode); 19735 ev.pressed = pressed; 19736 ev.hardwareCode = cast(ubyte) event.keyCode; 19737 ev.modifierState = cast(uint) event.modifierFlags; 19738 ev.window = simpleWindow; 19739 19740 simpleWindow.handleKeyEvent(ev); 19741 } 19742 } 19743 19744 override bool isFlipped() @selector("isFlipped") { 19745 return true; 19746 } 19747 override bool acceptsFirstResponder() @selector("acceptsFirstResponder") { 19748 return true; 19749 } 19750 19751 void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") { 19752 if(simpleWindow && simpleWindow.handlePulse) 19753 simpleWindow.handlePulse(); 19754 /+ 19755 setNeedsDisplay = true; 19756 +/ 19757 } 19758 } 19759 19760 private: 19761 alias const(void)* CFStringRef; 19762 alias const(void)* CFAllocatorRef; 19763 alias const(void)* CFTypeRef; 19764 alias const(void)* CGColorSpaceRef; 19765 alias const(void)* CGImageRef; 19766 alias ulong CGBitmapInfo; 19767 alias NSGraphicsContext CGContextRef; // actually CGContextRef should be a subclass... 19768 19769 alias NSPoint CGPoint; 19770 alias NSSize CGSize; 19771 alias NSRect CGRect; 19772 19773 struct CGAffineTransform { 19774 double a, b, c, d, tx, ty; 19775 } 19776 19777 enum NSApplicationActivationPolicyRegular = 0; 19778 enum NSBackingStoreBuffered = 2; 19779 enum kCFStringEncodingUTF8 = 0x08000100; 19780 19781 enum : size_t { 19782 NSBorderlessWindowMask = 0, 19783 NSTitledWindowMask = 1 << 0, 19784 NSClosableWindowMask = 1 << 1, 19785 NSMiniaturizableWindowMask = 1 << 2, 19786 NSResizableWindowMask = 1 << 3, 19787 NSTexturedBackgroundWindowMask = 1 << 8 19788 } 19789 19790 enum : ulong { 19791 kCGImageAlphaNone, 19792 kCGImageAlphaPremultipliedLast, 19793 kCGImageAlphaPremultipliedFirst, 19794 kCGImageAlphaLast, 19795 kCGImageAlphaFirst, 19796 kCGImageAlphaNoneSkipLast, 19797 kCGImageAlphaNoneSkipFirst 19798 } 19799 enum : ulong { 19800 kCGBitmapAlphaInfoMask = 0x1F, 19801 kCGBitmapFloatComponents = (1 << 8), 19802 kCGBitmapByteOrderMask = 0x7000, 19803 kCGBitmapByteOrderDefault = (0 << 12), 19804 kCGBitmapByteOrder16Little = (1 << 12), 19805 kCGBitmapByteOrder32Little = (2 << 12), 19806 kCGBitmapByteOrder16Big = (3 << 12), 19807 kCGBitmapByteOrder32Big = (4 << 12) 19808 } 19809 enum CGPathDrawingMode { 19810 kCGPathFill, 19811 kCGPathEOFill, 19812 kCGPathStroke, 19813 kCGPathFillStroke, 19814 kCGPathEOFillStroke 19815 } 19816 enum objc_AssociationPolicy : size_t { 19817 OBJC_ASSOCIATION_ASSIGN = 0, 19818 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 19819 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 19820 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 19821 OBJC_ASSOCIATION_COPY = 0x303 //01403 19822 } 19823 19824 extern(C) { 19825 CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo); 19826 void CGContextRelease(CGContextRef c); 19827 ubyte* CGBitmapContextGetData(CGContextRef c); 19828 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 19829 size_t CGBitmapContextGetWidth(CGContextRef c); 19830 size_t CGBitmapContextGetHeight(CGContextRef c); 19831 19832 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 19833 void CGColorSpaceRelease(CGColorSpaceRef cs); 19834 19835 alias void* CGFontRef; 19836 alias CTFontRef = NSFont; 19837 CGFontRef CTFontCopyGraphicsFont(CTFontRef font, void /*CTFontDescriptorRef*/ * attributes); 19838 19839 19840 void CGContextSetFont(CGContextRef c, CGFontRef font); 19841 void CGContextSetFontSize(CGContextRef c, CGFloat size); 19842 19843 void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha); 19844 void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha); 19845 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 19846 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length); 19847 void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count); 19848 void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat *lengths, size_t count); 19849 19850 void CGContextBeginPath(CGContextRef c); 19851 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 19852 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 19853 void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise); 19854 void CGContextAddRect(CGContextRef c, CGRect rect); 19855 void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count); 19856 void CGContextSaveGState(CGContextRef c); 19857 void CGContextRestoreGState(CGContextRef c); 19858 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding); 19859 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 19860 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 19861 19862 void CGImageRelease(CGImageRef image); 19863 } 19864 } else static assert(0, "Unsupported operating system"); 19865 19866 19867 version(OSXCocoa) { 19868 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 19869 // 19870 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 19871 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 19872 // 19873 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 19874 // Probably won't even fully compile right now 19875 19876 private enum double PI = 3.14159265358979323; 19877 19878 alias NSWindow NativeWindowHandle; 19879 alias void delegate(NSid) NativeEventHandler; 19880 19881 enum KEY_ESCAPE = 27; 19882 19883 mixin template NativeImageImplementation() { 19884 CGContextRef context; 19885 ubyte* rawData; 19886 19887 final: 19888 19889 void convertToRgbaBytes(ubyte[] where) @system { 19890 assert(where.length == this.width * this.height * 4); 19891 19892 // if rawData had a length.... 19893 //assert(rawData.length == where.length); 19894 for(long idx = 0; idx < where.length; idx += 4) { 19895 auto alpha = rawData[idx + 3]; 19896 if(alpha == 255) { 19897 where[idx + 0] = rawData[idx + 0]; // r 19898 where[idx + 1] = rawData[idx + 1]; // g 19899 where[idx + 2] = rawData[idx + 2]; // b 19900 where[idx + 3] = rawData[idx + 3]; // a 19901 } else { 19902 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 19903 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 19904 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 19905 where[idx + 3] = rawData[idx + 3]; // a 19906 19907 } 19908 } 19909 } 19910 19911 void setFromRgbaBytes(in ubyte[] where) @system { 19912 // FIXME: this is probably wrong 19913 assert(where.length == this.width * this.height * 4); 19914 19915 // if rawData had a length.... 19916 //assert(rawData.length == where.length); 19917 for(long idx = 0; idx < where.length; idx += 4) { 19918 auto alpha = where[idx + 3]; 19919 if(alpha == 255) { 19920 rawData[idx + 0] = where[idx + 0]; // r 19921 rawData[idx + 1] = where[idx + 1]; // g 19922 rawData[idx + 2] = where[idx + 2]; // b 19923 rawData[idx + 3] = where[idx + 3]; // a 19924 } else if(alpha == 0) { 19925 rawData[idx + 0] = 0; 19926 rawData[idx + 1] = 0; 19927 rawData[idx + 2] = 0; 19928 rawData[idx + 3] = 0; 19929 } else { 19930 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 19931 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 19932 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 19933 rawData[idx + 3] = where[idx + 3]; // a 19934 } 19935 } 19936 } 19937 19938 19939 void createImage(int width, int height, bool forcexshm=false, bool ignored = false) { 19940 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 19941 context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 19942 CGColorSpaceRelease(colorSpace); 19943 rawData = CGBitmapContextGetData(context); 19944 } 19945 void dispose() { 19946 CGContextRelease(context); 19947 } 19948 19949 void setPixel(int x, int y, Color c) @system { 19950 auto offset = (y * width + x) * 4; 19951 if (c.a == 255) { 19952 rawData[offset + 0] = c.r; 19953 rawData[offset + 1] = c.g; 19954 rawData[offset + 2] = c.b; 19955 rawData[offset + 3] = c.a; 19956 } else { 19957 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 19958 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 19959 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 19960 rawData[offset + 3] = c.a; 19961 } 19962 } 19963 } 19964 19965 mixin template NativeScreenPainterImplementation() { 19966 CGContextRef context; 19967 ubyte[4] _outlineComponents; 19968 NSView view; 19969 19970 Pen _activePen; 19971 Color _fillColor; 19972 Rectangle _clipRectangle; 19973 OperatingSystemFont _font; 19974 19975 OperatingSystemFont getFont() { 19976 if(_font is null) { 19977 static OperatingSystemFont _defaultFont; 19978 if(_defaultFont is null) { 19979 _defaultFont = new OperatingSystemFont(); 19980 _defaultFont.loadDefault(); 19981 } 19982 _font = _defaultFont; 19983 } 19984 19985 return _font; 19986 } 19987 19988 void create(PaintingHandle window) { 19989 // this.destiny = window; 19990 if(auto sw = cast(SimpleWindow) this.window) { 19991 context = sw.drawingContext; 19992 view = sw.view; 19993 } else { 19994 throw new NotYetImplementedException(); 19995 } 19996 } 19997 19998 void dispose() { 19999 view.setNeedsDisplay(true); 20000 } 20001 20002 bool manualInvalidations; 20003 void invalidateRect(Rectangle invalidRect) { } 20004 20005 // NotYetImplementedException 20006 void rasterOp(RasterOp op) { 20007 } 20008 void setClipRectangle(int, int, int, int) { 20009 } 20010 Size textSize(in char[] txt) { 20011 auto font = getFont(); 20012 return Size(castFnumToCnum(font.stringWidth(txt)), castFnumToCnum(font.height())); 20013 } 20014 20015 void setFont(OperatingSystemFont font) { 20016 _font = font; 20017 // font.font.setInContext(context); 20018 if(font) { 20019 // FIXME: should i free this thing? 20020 /+ 20021 auto f = CTFontCopyGraphicsFont(font.font, null); 20022 if(font.font is null) 20023 sdpyPrintDebugString("input is null"); 20024 if(f is null) 20025 sdpyPrintDebugString("f is null"); 20026 CGContextSetFont(context, f); 20027 +/ 20028 // CGContextSetFontSize(context, font.size); 20029 20030 // FIXME kinda hacky 20031 CGContextSelectFont(context, (font.loadedInfo.name ~ "\0").ptr, font.loadedInfo.size, 1); 20032 } else {} // FIMXE 20033 } 20034 int fontHeight() { 20035 auto font = getFont(); 20036 return castFnumToCnum(font.height); 20037 } 20038 20039 // end 20040 20041 void pen(Pen pen) { 20042 _activePen = pen; 20043 auto color = pen.color; // FIXME 20044 double alphaComponent = color.a/255.0f; 20045 CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 20046 20047 double[2] patternBuffer; 20048 double[] pattern; 20049 final switch(pen.style) { 20050 case Pen.Style.Solid: 20051 pattern = null; 20052 break; 20053 case Pen.Style.Dashed: 20054 patternBuffer[0] = 4; 20055 patternBuffer[1] = 1; 20056 pattern = patternBuffer[]; 20057 break; 20058 case Pen.Style.Dotted: 20059 patternBuffer[0] = 1; 20060 patternBuffer[1] = 1; 20061 pattern = patternBuffer[]; 20062 break; 20063 } 20064 20065 CGContextSetLineDash(context, 0, pattern.ptr, pattern.length); 20066 20067 if (color.a != 255) { 20068 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 20069 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 20070 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 20071 _outlineComponents[3] = color.a; 20072 } else { 20073 _outlineComponents[0] = color.r; 20074 _outlineComponents[1] = color.g; 20075 _outlineComponents[2] = color.b; 20076 _outlineComponents[3] = color.a; 20077 } 20078 } 20079 20080 @property void fillColor(Color color) { 20081 CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 20082 } 20083 20084 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 20085 // NotYetImplementedException for upper left/width/height 20086 auto cgImage = CGBitmapContextCreateImage(image.context); 20087 auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context)); 20088 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 20089 CGImageRelease(cgImage); 20090 } 20091 20092 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 20093 // FIXME: is this efficient? 20094 auto cgImage = CGBitmapContextCreateImage(s.handle); 20095 auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle)); 20096 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 20097 CGImageRelease(cgImage); 20098 } 20099 20100 20101 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 20102 // FIXME: alignment 20103 if (_outlineComponents[3] != 0) { 20104 CGContextSaveGState(context); 20105 auto invAlpha = 1.0f/_outlineComponents[3]; 20106 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 20107 _outlineComponents[1]*invAlpha, 20108 _outlineComponents[2]*invAlpha, 20109 _outlineComponents[3]/255.0f); 20110 20111 20112 20113 // FIXME: should we clip it to the bounding box? 20114 int textHeight = fontHeight; 20115 20116 auto lines = text.split('\n'); 20117 20118 const lineHeight = textHeight; 20119 textHeight *= lines.length; 20120 20121 int cy = y; 20122 20123 if(alignment & TextAlignment.VerticalBottom) { 20124 if(y2 <= 0) 20125 return; 20126 auto h = y2 - y; 20127 if(h > textHeight) { 20128 cy += h - textHeight; 20129 cy -= lineHeight / 2; 20130 } 20131 } else if(alignment & TextAlignment.VerticalCenter) { 20132 if(y2 <= 0) 20133 return; 20134 auto h = y2 - y; 20135 if(textHeight < h) { 20136 cy += (h - textHeight) / 2; 20137 //cy -= lineHeight / 4; 20138 } 20139 } 20140 20141 foreach(line; text.split('\n')) { 20142 int textWidth = this.textSize(line).width; 20143 20144 int px = x, py = cy; 20145 20146 if(alignment & TextAlignment.Center) { 20147 if(x2 <= 0) 20148 return; 20149 auto w = x2 - x; 20150 if(w > textWidth) 20151 px += (w - textWidth) / 2; 20152 } else if(alignment & TextAlignment.Right) { 20153 if(x2 <= 0) 20154 return; 20155 auto pos = x2 - textWidth; 20156 if(pos > x) 20157 px = pos; 20158 } 20159 20160 CGContextShowTextAtPoint(context, px, py + getFont.ascent /* this is cuz this picks baseline but i want bounding box */, line.ptr, line.length); 20161 20162 carry_on: 20163 cy += lineHeight + 4; 20164 } 20165 20166 // auto cfstr = cast(NSid)createCFString(text); 20167 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 20168 // NSPoint(x, y), null); 20169 // CFRelease(cfstr); 20170 CGContextRestoreGState(context); 20171 } 20172 } 20173 20174 void drawPixel(int x, int y) { 20175 auto rawData = CGBitmapContextGetData(context); 20176 auto width = CGBitmapContextGetWidth(context); 20177 auto height = CGBitmapContextGetHeight(context); 20178 auto offset = ((height - y - 1) * width + x) * 4; 20179 rawData[offset .. offset+4] = _outlineComponents; 20180 } 20181 20182 void drawLine(int x1, int y1, int x2, int y2) { 20183 CGPoint[2] linePoints; 20184 linePoints[0] = CGPoint(x1, y1); 20185 linePoints[1] = CGPoint(x2, y2); 20186 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 20187 } 20188 20189 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 20190 drawRectangle(upperLeft.x, upperLeft.y, lowerRight.x - upperLeft.x, lowerRight.y - upperLeft.y); // FIXME not rounded 20191 } 20192 20193 void drawRectangle(int x, int y, int width, int height) { 20194 CGContextBeginPath(context); 20195 // trying to align with actual pixels... 20196 auto rect = CGRect(CGPoint(x + 0.5, y + 0.5), CGSize(width - 1, height - 1)); 20197 CGContextAddRect(context, rect); 20198 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 20199 } 20200 20201 void drawEllipse(int x1, int y1, int x2, int y2) { 20202 CGContextBeginPath(context); 20203 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 20204 CGContextAddEllipseInRect(context, rect); 20205 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 20206 } 20207 20208 void drawArc(int x1, int y1, int width, int height, int start, int length) { 20209 // @@@BUG@@@ Does not support elliptic arc (width != height). 20210 CGContextBeginPath(context); 20211 int clockwise = 0; 20212 if(length < 0) { 20213 clockwise = 1; 20214 length = -length; 20215 } 20216 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 20217 start*PI/(180*64), (start+length)*PI/(180*64), clockwise); 20218 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 20219 } 20220 20221 void drawPolygon(Point[] intPoints) { 20222 CGContextBeginPath(context); 20223 CGPoint[16] pointsBuffer; 20224 CGPoint[] points; 20225 if(intPoints.length <= pointsBuffer.length) 20226 points = pointsBuffer[0 .. intPoints.length]; 20227 else 20228 points = new CGPoint[](intPoints.length); 20229 20230 foreach(idx, pt; intPoints) 20231 points[idx] = CGPoint(pt.x, pt.y); 20232 20233 CGContextAddLines(context, points.ptr, points.length); 20234 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 20235 } 20236 } 20237 20238 private bool appInitialized = false; 20239 void initializeApp() { 20240 if(appInitialized) 20241 return; 20242 synchronized { 20243 if(appInitialized) 20244 return; 20245 20246 auto app = NSApp(); // ensure the is initialized 20247 20248 auto dg = AppDelegate.alloc; 20249 globalAppDelegate = dg; 20250 NSApp.delegate_ = dg; 20251 20252 NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular); 20253 20254 appInitialized = true; 20255 } 20256 } 20257 20258 mixin template NativeSimpleWindowImplementation() { 20259 void setTitle(string title) { 20260 window.title = MacString(title).borrow; 20261 } 20262 20263 void moveResize (int x, int y, int w, int h) { 20264 //auto f = window.frame; 20265 // FIXME: finish 20266 sdpyPrintDebugString("moveResize not implemented"); 20267 } 20268 20269 void resize(int w, int h) { 20270 // FIXME: finish 20271 sdpyPrintDebugString("resize not implemented"); 20272 } 20273 20274 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 20275 initializeApp(); 20276 20277 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 20278 20279 window = NSWindow.alloc.initWithContentRect( 20280 contentRect, 20281 NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled, 20282 NSBackingStoreType.buffered, 20283 true 20284 ); 20285 20286 SimpleWindow.nativeMapping[cast(void*) window] = this; 20287 20288 window.title = MacString(title).borrow; 20289 20290 auto dg = SDWindowDelegate.alloc.init; 20291 dg.simpleWindow = this; 20292 window.delegate_ = dg; 20293 20294 auto view = SDGraphicsView.alloc.init; 20295 assert(view !is null); 20296 window.contentView = view; 20297 this.view = view; 20298 view.simpleWindow = this; 20299 20300 window.center(); 20301 20302 window.makeKeyAndOrderFront(null); 20303 20304 // no need to make a bitmap on mac since everything is double buffered already 20305 20306 // create area to draw on. 20307 createNewDrawingContext(width, height); 20308 20309 window.setBackgroundColor(NSColor.whiteColor); 20310 20311 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 20312 // show it 20313 view.setNeedsDisplay(true); 20314 } else { 20315 20316 view.setNeedsDisplay(true); 20317 // hide it 20318 //window.setIsVisible = false; 20319 } 20320 } 20321 20322 void createNewDrawingContext(int width, int height) { 20323 // FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay 20324 if(this.drawingContext) 20325 CGContextRelease(this.drawingContext); 20326 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 20327 this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 20328 CGColorSpaceRelease(colorSpace); 20329 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 20330 auto matrix = CGContextGetTextMatrix(drawingContext); 20331 matrix.c = -matrix.c; 20332 matrix.d = -matrix.d; 20333 CGContextSetTextMatrix(drawingContext, matrix); 20334 20335 } 20336 20337 void dispose() { 20338 closeWindow(); 20339 // window.release(); // closing the window does this automatically i think 20340 } 20341 void closeWindow() { 20342 if(window) 20343 window.close(); 20344 } 20345 20346 ScreenPainter getPainter(bool manualInvalidations) { 20347 return ScreenPainter(this, this.window, manualInvalidations); 20348 } 20349 20350 NSWindow window; 20351 NSView view; 20352 CGContextRef drawingContext; 20353 } 20354 } 20355 20356 version(without_opengl) {} else 20357 extern(System) nothrow @nogc { 20358 //enum uint GL_VERSION = 0x1F02; 20359 //const(char)* glGetString (/*GLenum*/uint); 20360 version(X11) { 20361 static if (!SdpyIsUsingIVGLBinds) { 20362 20363 enum GLX_X_RENDERABLE = 0x8012; 20364 enum GLX_DRAWABLE_TYPE = 0x8010; 20365 enum GLX_RENDER_TYPE = 0x8011; 20366 enum GLX_X_VISUAL_TYPE = 0x22; 20367 enum GLX_TRUE_COLOR = 0x8002; 20368 enum GLX_WINDOW_BIT = 0x00000001; 20369 enum GLX_RGBA_BIT = 0x00000001; 20370 enum GLX_COLOR_INDEX_BIT = 0x00000002; 20371 enum GLX_SAMPLE_BUFFERS = 0x186a0; 20372 enum GLX_SAMPLES = 0x186a1; 20373 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 20374 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 20375 } 20376 20377 // GLX_EXT_swap_control 20378 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 20379 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 20380 20381 //k8: ugly code to prevent warnings when sdpy is compiled into .a 20382 extern(System) { 20383 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 20384 } 20385 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 20386 20387 // this made public so we don't have to get it again and again 20388 public bool glXCreateContextAttribsARB_present () @system { 20389 if (glXCreateContextAttribsARBFn is cast(void*)1) { 20390 // get it 20391 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 20392 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 20393 } 20394 return (glXCreateContextAttribsARBFn !is null); 20395 } 20396 20397 // this made public so we don't have to get it again and again 20398 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) @system { 20399 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 20400 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 20401 } 20402 20403 // extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers 20404 extern(C) private __gshared int function(int) glXSwapIntervalMESA; 20405 20406 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 20407 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 20408 if (_glx_swapInterval_fn is null) { 20409 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 20410 if (_glx_swapInterval_fn is null) { 20411 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 20412 return; 20413 } 20414 version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); } 20415 } 20416 20417 if(glXSwapIntervalMESA is null) { 20418 // it seems to require both to actually take effect on many computers 20419 // idk why 20420 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA"); 20421 if(glXSwapIntervalMESA is null) 20422 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1; 20423 } 20424 20425 if(cast(void*) glXSwapIntervalMESA > cast(void*) 1) 20426 glXSwapIntervalMESA(wait ? 1 : 0); 20427 20428 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 20429 } 20430 } else version(Windows) { 20431 static if (!SdpyIsUsingIVGLBinds) { 20432 enum GL_TRUE = 1; 20433 enum GL_FALSE = 0; 20434 20435 public void* glbindGetProcAddress (const(char)* name) { 20436 void* res = wglGetProcAddress(name); 20437 if (res is null) { 20438 /+ 20439 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 20440 import core.sys.windows.windef, core.sys.windows.winbase; 20441 __gshared HINSTANCE dll = null; 20442 if (dll is null) { 20443 dll = LoadLibraryA("opengl32.dll"); 20444 if (dll is null) return null; // <32, but idc 20445 } 20446 res = GetProcAddress(dll, name); 20447 +/ 20448 res = GetProcAddress(gl.libHandle, name); 20449 } 20450 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 20451 return res; 20452 } 20453 } 20454 20455 20456 private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT; 20457 void wglSetVSync(bool wait) { 20458 if(wglSwapIntervalEXT is null) { 20459 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT"); 20460 if(wglSwapIntervalEXT is null) 20461 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1; 20462 } 20463 if(cast(void*) wglSwapIntervalEXT is cast(void*) 1) 20464 return; 20465 20466 wglSwapIntervalEXT(wait ? 1 : 0); 20467 } 20468 20469 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 20470 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 20471 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 20472 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 20473 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 20474 20475 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 20476 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 20477 20478 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 20479 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 20480 20481 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 20482 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 20483 20484 void wglInitOtherFunctions () { 20485 if (wglCreateContextAttribsARB is null) { 20486 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 20487 } 20488 } 20489 } 20490 20491 static if (!SdpyIsUsingIVGLBinds) { 20492 20493 interface GL { 20494 extern(System) @nogc nothrow: 20495 20496 void glGetIntegerv(int, void*); 20497 void glMatrixMode(int); 20498 void glPushMatrix(); 20499 void glLoadIdentity(); 20500 void glOrtho(double, double, double, double, double, double); 20501 void glFrustum(double, double, double, double, double, double); 20502 20503 void glPopMatrix(); 20504 void glEnable(int); 20505 void glDisable(int); 20506 void glClear(int); 20507 void glBegin(int); 20508 void glVertex2f(float, float); 20509 void glVertex3f(float, float, float); 20510 void glEnd(); 20511 void glColor3b(byte, byte, byte); 20512 void glColor3ub(ubyte, ubyte, ubyte); 20513 void glColor4b(byte, byte, byte, byte); 20514 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 20515 void glColor3i(int, int, int); 20516 void glColor3ui(uint, uint, uint); 20517 void glColor4i(int, int, int, int); 20518 void glColor4ui(uint, uint, uint, uint); 20519 void glColor3f(float, float, float); 20520 void glColor4f(float, float, float, float); 20521 void glTranslatef(float, float, float); 20522 void glScalef(float, float, float); 20523 version(X11) { 20524 void glSecondaryColor3b(byte, byte, byte); 20525 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 20526 void glSecondaryColor3i(int, int, int); 20527 void glSecondaryColor3ui(uint, uint, uint); 20528 void glSecondaryColor3f(float, float, float); 20529 } 20530 20531 void glDrawElements(int, int, int, void*); 20532 20533 void glRotatef(float, float, float, float); 20534 20535 uint glGetError(); 20536 20537 void glDeleteTextures(int, uint*); 20538 20539 20540 void glRasterPos2i(int, int); 20541 void glDrawPixels(int, int, uint, uint, void*); 20542 void glClearColor(float, float, float, float); 20543 20544 20545 void glPixelStorei(uint, int); 20546 20547 void glGenTextures(uint, uint*); 20548 void glBindTexture(int, int); 20549 void glTexParameteri(uint, uint, int); 20550 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 20551 void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*); 20552 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 20553 /*GLsizei*/int width, /*GLsizei*/int height, 20554 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 20555 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 20556 20557 void glLineWidth(int); 20558 20559 20560 void glTexCoord2f(float, float); 20561 void glVertex2i(int, int); 20562 void glBlendFunc (int, int); 20563 void glDepthFunc (int); 20564 void glViewport(int, int, int, int); 20565 20566 void glClearDepth(double); 20567 20568 void glReadBuffer(uint); 20569 void glReadPixels(int, int, int, int, int, int, void*); 20570 20571 void glScissor(GLint x, GLint y, GLsizei width, GLsizei height); 20572 20573 void glFlush(); 20574 void glFinish(); 20575 20576 version(Windows) { 20577 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 20578 HGLRC wglCreateContext(HDC); 20579 HGLRC wglCreateLayerContext(HDC, int); 20580 BOOL wglDeleteContext(HGLRC); 20581 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 20582 HGLRC wglGetCurrentContext(); 20583 HDC wglGetCurrentDC(); 20584 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 20585 PROC wglGetProcAddress(LPCSTR); 20586 BOOL wglMakeCurrent(HDC, HGLRC); 20587 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 20588 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 20589 BOOL wglShareLists(HGLRC, HGLRC); 20590 BOOL wglSwapLayerBuffers(HDC, UINT); 20591 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 20592 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 20593 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 20594 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 20595 } 20596 20597 } 20598 20599 interface GL3 { 20600 extern(System) @nogc nothrow: 20601 20602 void glGenVertexArrays(GLsizei, GLuint*); 20603 void glBindVertexArray(GLuint); 20604 void glDeleteVertexArrays(GLsizei, const(GLuint)*); 20605 void glGenerateMipmap(GLenum); 20606 void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 20607 void glStencilMask(GLuint); 20608 void glStencilFunc(GLenum, GLint, GLuint); 20609 void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 20610 void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 20611 GLuint glCreateProgram(); 20612 GLuint glCreateShader(GLenum); 20613 void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 20614 void glCompileShader(GLuint); 20615 void glGetShaderiv(GLuint, GLenum, GLint*); 20616 void glAttachShader(GLuint, GLuint); 20617 void glBindAttribLocation(GLuint, GLuint, const(GLchar)*); 20618 void glLinkProgram(GLuint); 20619 void glGetProgramiv(GLuint, GLenum, GLint*); 20620 void glDeleteProgram(GLuint); 20621 void glDeleteShader(GLuint); 20622 GLint glGetUniformLocation(GLuint, const(GLchar)*); 20623 void glGenBuffers(GLsizei, GLuint*); 20624 20625 void glUniform1f(GLint location, GLfloat v0); 20626 void glUniform2f(GLint location, GLfloat v0, GLfloat v1); 20627 void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); 20628 void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); 20629 void glUniform1i(GLint location, GLint v0); 20630 void glUniform2i(GLint location, GLint v0, GLint v1); 20631 void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2); 20632 void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); 20633 void glUniform1ui(GLint location, GLuint v0); 20634 void glUniform2ui(GLint location, GLuint v0, GLuint v1); 20635 void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2); 20636 void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); 20637 void glUniform1fv(GLint location, GLsizei count, const GLfloat *value); 20638 void glUniform2fv(GLint location, GLsizei count, const GLfloat *value); 20639 void glUniform3fv(GLint location, GLsizei count, const GLfloat *value); 20640 void glUniform4fv(GLint location, GLsizei count, const GLfloat *value); 20641 void glUniform1iv(GLint location, GLsizei count, const GLint *value); 20642 void glUniform2iv(GLint location, GLsizei count, const GLint *value); 20643 void glUniform3iv(GLint location, GLsizei count, const GLint *value); 20644 void glUniform4iv(GLint location, GLsizei count, const GLint *value); 20645 void glUniform1uiv(GLint location, GLsizei count, const GLuint *value); 20646 void glUniform2uiv(GLint location, GLsizei count, const GLuint *value); 20647 void glUniform3uiv(GLint location, GLsizei count, const GLuint *value); 20648 void glUniform4uiv(GLint location, GLsizei count, const GLuint *value); 20649 void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 20650 void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 20651 void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 20652 void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 20653 void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 20654 void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 20655 void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 20656 void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 20657 void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 20658 20659 void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); 20660 void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); 20661 void glDrawArrays(GLenum, GLint, GLsizei); 20662 void glStencilOp(GLenum, GLenum, GLenum); 20663 void glUseProgram(GLuint); 20664 void glCullFace(GLenum); 20665 void glFrontFace(GLenum); 20666 void glActiveTexture(GLenum); 20667 void glBindBuffer(GLenum, GLuint); 20668 void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum); 20669 void glEnableVertexAttribArray(GLuint); 20670 void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 20671 void glUniform1i(GLint, GLint); 20672 void glUniform2fv(GLint, GLsizei, const(GLfloat)*); 20673 void glDisableVertexAttribArray(GLuint); 20674 void glDeleteBuffers(GLsizei, const(GLuint)*); 20675 void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); 20676 void glLogicOp (GLenum opcode); 20677 void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 20678 void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers); 20679 void glGenFramebuffers (GLsizei n, GLuint* framebuffers); 20680 GLenum glCheckFramebufferStatus (GLenum target); 20681 void glBindFramebuffer (GLenum target, GLuint framebuffer); 20682 } 20683 20684 interface GL4 { 20685 extern(System) @nogc nothrow: 20686 20687 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 20688 /*GLsizei*/int width, /*GLsizei*/int height, 20689 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 20690 } 20691 20692 interface GLU { 20693 extern(System) @nogc nothrow: 20694 20695 void gluLookAt(double, double, double, double, double, double, double, double, double); 20696 void gluPerspective(double, double, double, double); 20697 20698 char* gluErrorString(uint); 20699 } 20700 20701 20702 enum GL_RED = 0x1903; 20703 enum GL_ALPHA = 0x1906; 20704 20705 enum uint GL_FRONT = 0x0404; 20706 20707 enum uint GL_BLEND = 0x0be2; 20708 enum uint GL_LEQUAL = 0x0203; 20709 20710 20711 enum uint GL_RGB = 0x1907; 20712 enum uint GL_BGRA = 0x80e1; 20713 enum uint GL_RGBA = 0x1908; 20714 enum uint GL_RGBA8 = 0x8058; 20715 enum uint GL_TEXTURE_2D = 0x0DE1; 20716 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 20717 enum uint GL_NEAREST = 0x2600; 20718 enum uint GL_LINEAR = 0x2601; 20719 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 20720 enum uint GL_TEXTURE_WRAP_S = 0x2802; 20721 enum uint GL_TEXTURE_WRAP_T = 0x2803; 20722 enum uint GL_REPEAT = 0x2901; 20723 enum uint GL_CLAMP = 0x2900; 20724 enum uint GL_CLAMP_TO_EDGE = 0x812F; 20725 enum uint GL_CLAMP_TO_BORDER = 0x812D; 20726 enum uint GL_DECAL = 0x2101; 20727 enum uint GL_MODULATE = 0x2100; 20728 enum uint GL_TEXTURE_ENV = 0x2300; 20729 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 20730 enum uint GL_REPLACE = 0x1E01; 20731 enum uint GL_LIGHTING = 0x0B50; 20732 enum uint GL_DITHER = 0x0BD0; 20733 20734 enum uint GL_NO_ERROR = 0; 20735 20736 20737 20738 enum int GL_VIEWPORT = 0x0BA2; 20739 enum int GL_MODELVIEW = 0x1700; 20740 enum int GL_TEXTURE = 0x1702; 20741 enum int GL_PROJECTION = 0x1701; 20742 enum int GL_DEPTH_TEST = 0x0B71; 20743 20744 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 20745 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 20746 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 20747 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 20748 20749 enum int GL_POINTS = 0x0000; 20750 enum int GL_LINES = 0x0001; 20751 enum int GL_LINE_LOOP = 0x0002; 20752 enum int GL_LINE_STRIP = 0x0003; 20753 enum int GL_TRIANGLES = 0x0004; 20754 enum int GL_TRIANGLE_STRIP = 5; 20755 enum int GL_TRIANGLE_FAN = 6; 20756 enum int GL_QUADS = 7; 20757 enum int GL_QUAD_STRIP = 8; 20758 enum int GL_POLYGON = 9; 20759 20760 alias GLvoid = void; 20761 alias GLboolean = ubyte; 20762 alias GLint = int; 20763 alias GLuint = uint; 20764 alias GLenum = uint; 20765 alias GLchar = char; 20766 alias GLsizei = int; 20767 alias GLfloat = float; 20768 alias GLintptr = size_t; 20769 alias GLsizeiptr = ptrdiff_t; 20770 20771 20772 enum uint GL_INVALID_ENUM = 0x0500; 20773 20774 enum uint GL_ZERO = 0; 20775 enum uint GL_ONE = 1; 20776 20777 enum uint GL_BYTE = 0x1400; 20778 enum uint GL_UNSIGNED_BYTE = 0x1401; 20779 enum uint GL_SHORT = 0x1402; 20780 enum uint GL_UNSIGNED_SHORT = 0x1403; 20781 enum uint GL_INT = 0x1404; 20782 enum uint GL_UNSIGNED_INT = 0x1405; 20783 enum uint GL_FLOAT = 0x1406; 20784 enum uint GL_2_BYTES = 0x1407; 20785 enum uint GL_3_BYTES = 0x1408; 20786 enum uint GL_4_BYTES = 0x1409; 20787 enum uint GL_DOUBLE = 0x140A; 20788 20789 enum uint GL_STREAM_DRAW = 0x88E0; 20790 20791 enum uint GL_CCW = 0x0901; 20792 20793 enum uint GL_STENCIL_TEST = 0x0B90; 20794 enum uint GL_SCISSOR_TEST = 0x0C11; 20795 20796 enum uint GL_EQUAL = 0x0202; 20797 enum uint GL_NOTEQUAL = 0x0205; 20798 20799 enum uint GL_ALWAYS = 0x0207; 20800 enum uint GL_KEEP = 0x1E00; 20801 20802 enum uint GL_INCR = 0x1E02; 20803 20804 enum uint GL_INCR_WRAP = 0x8507; 20805 enum uint GL_DECR_WRAP = 0x8508; 20806 20807 enum uint GL_CULL_FACE = 0x0B44; 20808 enum uint GL_BACK = 0x0405; 20809 20810 enum uint GL_FRAGMENT_SHADER = 0x8B30; 20811 enum uint GL_VERTEX_SHADER = 0x8B31; 20812 20813 enum uint GL_COMPILE_STATUS = 0x8B81; 20814 enum uint GL_LINK_STATUS = 0x8B82; 20815 20816 enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893; 20817 20818 enum uint GL_STATIC_DRAW = 0x88E4; 20819 20820 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 20821 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 20822 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 20823 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 20824 20825 enum uint GL_GENERATE_MIPMAP = 0x8191; 20826 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 20827 20828 enum uint GL_TEXTURE0 = 0x84C0U; 20829 enum uint GL_TEXTURE1 = 0x84C1U; 20830 20831 enum uint GL_ARRAY_BUFFER = 0x8892; 20832 20833 enum uint GL_SRC_COLOR = 0x0300; 20834 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 20835 enum uint GL_SRC_ALPHA = 0x0302; 20836 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 20837 enum uint GL_DST_ALPHA = 0x0304; 20838 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 20839 enum uint GL_DST_COLOR = 0x0306; 20840 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 20841 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 20842 20843 enum uint GL_INVERT = 0x150AU; 20844 20845 enum uint GL_DEPTH_STENCIL = 0x84F9U; 20846 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 20847 20848 enum uint GL_FRAMEBUFFER = 0x8D40U; 20849 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 20850 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 20851 20852 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 20853 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 20854 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 20855 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 20856 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 20857 20858 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 20859 enum uint GL_CLEAR = 0x1500U; 20860 enum uint GL_COPY = 0x1503U; 20861 enum uint GL_XOR = 0x1506U; 20862 20863 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 20864 20865 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 20866 20867 } 20868 } 20869 20870 /++ 20871 History: 20872 Added September 10, 2021. Previously it would have listed openGlLibrariesSuccessfullyLoaded as false if it couldn't find GLU but really opengl3 works fine without it so I didn't want to keep it required anymore. 20873 +/ 20874 __gshared bool gluSuccessfullyLoaded = true; 20875 20876 version(without_opengl) {} else { 20877 static if(!SdpyIsUsingIVGLBinds) { 20878 version(Windows) { 20879 mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl; 20880 mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu; 20881 } else { 20882 mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl; 20883 mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu; 20884 } 20885 mixin DynamicLoadSupplementalOpenGL!(GL3) gl3; 20886 20887 20888 shared static this() { 20889 gl.loadDynamicLibrary(); 20890 20891 // FIXME: this is NOT actually required and should NOT fail if it is not loaded 20892 // unless those functions are actually used 20893 // go to mark b openGlLibrariesSuccessfullyLoaded = false; 20894 glu.loadDynamicLibrary(); 20895 } 20896 } 20897 } 20898 20899 /++ 20900 Convenience method for converting D arrays to opengl buffer data 20901 20902 I would LOVE to overload it with the original glBufferData, but D won't 20903 let me since glBufferData is a function pointer :( 20904 20905 Added: August 25, 2020 (version 8.5) 20906 +/ 20907 version(without_opengl) {} else 20908 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) { 20909 glBufferData(target, data.length, data.ptr, usage); 20910 } 20911 20912 /++ 20913 History: 20914 Added September 1, 2024 20915 +/ 20916 version(without_opengl) {} else 20917 void glBufferSubDataSlice(GLenum target, size_t offset, const(void[]) data, GLenum usage) { 20918 glBufferSubData(target, offset, data.length, data.ptr); 20919 } 20920 20921 /++ 20922 Convenience class for using opengl shaders. 20923 20924 Ensure that you've loaded opengl 3+ and set your active 20925 context before trying to use this. 20926 20927 Added: August 25, 2020 (version 8.5) 20928 +/ 20929 version(without_opengl) {} else 20930 final class OpenGlShader { 20931 private int shaderProgram_; 20932 private @property void shaderProgram(int a) { 20933 shaderProgram_ = a; 20934 } 20935 /// Get the program ID for use in OpenGL functions. 20936 public @property int shaderProgram() { 20937 return shaderProgram_; 20938 } 20939 20940 /++ 20941 20942 +/ 20943 static struct Source { 20944 uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc. 20945 string code; /// 20946 } 20947 20948 /++ 20949 Helper method to just compile some shader code and check for errors 20950 while you do glCreateShader, etc. on the outside yourself. 20951 20952 This just does `glShaderSource` and `glCompileShader` for the given code. 20953 20954 If you the OpenGlShader class constructor, you never need to call this yourself. 20955 +/ 20956 static void compile(int sid, Source code) { 20957 const(char)*[1] buffer; 20958 int[1] lengthBuffer; 20959 20960 buffer[0] = code.code.ptr; 20961 lengthBuffer[0] = cast(int) code.code.length; 20962 20963 glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr); 20964 glCompileShader(sid); 20965 20966 int success; 20967 glGetShaderiv(sid, GL_COMPILE_STATUS, &success); 20968 if(!success) { 20969 char[512] info; 20970 int len; 20971 glGetShaderInfoLog(sid, info.length, &len, info.ptr); 20972 20973 throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]); 20974 } 20975 } 20976 20977 /++ 20978 Calls `glLinkProgram` and throws if error a occurs. 20979 20980 If you the OpenGlShader class constructor, you never need to call this yourself. 20981 +/ 20982 static void link(int shaderProgram) { 20983 glLinkProgram(shaderProgram); 20984 int success; 20985 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 20986 if(!success) { 20987 char[512] info; 20988 int len; 20989 glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr); 20990 20991 throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]); 20992 } 20993 } 20994 20995 /++ 20996 Constructs the shader object by calling `glCreateProgram`, then 20997 compiling each given [Source], and finally, linking them together. 20998 20999 Throws: on compile or link failure. 21000 +/ 21001 this(Source[] codes...) { 21002 shaderProgram = glCreateProgram(); 21003 21004 int[16] shadersBufferStack; 21005 21006 int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 21007 shadersBufferStack[0 .. codes.length] : 21008 new int[](codes.length); 21009 21010 foreach(idx, code; codes) { 21011 shadersBuffer[idx] = glCreateShader(code.type); 21012 21013 compile(shadersBuffer[idx], code); 21014 21015 glAttachShader(shaderProgram, shadersBuffer[idx]); 21016 } 21017 21018 link(shaderProgram); 21019 21020 foreach(s; shadersBuffer) 21021 glDeleteShader(s); 21022 } 21023 21024 /// Calls `glUseProgram(this.shaderProgram)` 21025 void use() { 21026 glUseProgram(this.shaderProgram); 21027 } 21028 21029 /// Deletes the program. 21030 void delete_() { 21031 glDeleteProgram(shaderProgram); 21032 shaderProgram = 0; 21033 } 21034 21035 /++ 21036 [OpenGlShader.uniforms].name gives you one of these. 21037 21038 You can get the id out of it or just assign 21039 +/ 21040 static struct Uniform { 21041 /// the id passed to glUniform* 21042 int id; 21043 21044 /// Assigns the 4 floats. You will probably have to call this via the .opAssign name 21045 void opAssign(float x, float y, float z, float w) { 21046 if(id != -1) 21047 glUniform4f(id, x, y, z, w); 21048 } 21049 21050 void opAssign(float x) { 21051 if(id != -1) 21052 glUniform1f(id, x); 21053 } 21054 21055 void opAssign(float x, float y) { 21056 if(id != -1) 21057 glUniform2f(id, x, y); 21058 } 21059 21060 void opAssign(T)(T t) { 21061 t.glUniform(id); 21062 } 21063 } 21064 21065 static struct UniformsHelper { 21066 OpenGlShader _shader; 21067 21068 @property Uniform opDispatch(string name)() { 21069 auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); 21070 // FIXME: decide what to do here; the exception is liable to be swallowed by the event syste 21071 //if(i == -1) 21072 //throw new Exception("Could not find uniform " ~ name); 21073 return Uniform(i); 21074 } 21075 21076 @property void opDispatch(string name, T)(T t) { 21077 Uniform f = this.opDispatch!name; 21078 t.glUniform(f); 21079 } 21080 } 21081 21082 /++ 21083 Gives access to the uniforms through dot access. 21084 `OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo"); 21085 +/ 21086 @property UniformsHelper uniforms() { return UniformsHelper(this); } 21087 } 21088 21089 version(without_opengl) {} else { 21090 /++ 21091 A static container of experimental types and value constructors for opengl 3+ shaders. 21092 21093 21094 You can declare variables like: 21095 21096 ``` 21097 OGL.vec3f something; 21098 ``` 21099 21100 But generally it would be used with [OpenGlShader]'s uniform helpers like 21101 21102 ``` 21103 shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific 21104 ``` 21105 21106 This is still extremely experimental, not very useful at this point, and thus subject to change at random. 21107 21108 21109 History: 21110 Added December 7, 2021. Not yet stable. 21111 +/ 21112 final class OGL { 21113 static: 21114 21115 private template typeFromSpecifier(string specifier) { 21116 static if(specifier == "f") 21117 alias typeFromSpecifier = GLfloat; 21118 else static if(specifier == "i") 21119 alias typeFromSpecifier = GLint; 21120 else static if(specifier == "ui") 21121 alias typeFromSpecifier = GLuint; 21122 else static assert(0, "I don't know this ogl type suffix " ~ specifier); 21123 } 21124 21125 private template CommonType(T...) { 21126 static if(T.length == 1) 21127 alias CommonType = T[0]; 21128 else static if(is(typeof(true ? T[0].init : T[1].init) C)) 21129 alias CommonType = CommonType!(C, T[2 .. $]); 21130 } 21131 21132 private template typesToSpecifier(T...) { 21133 static if(is(CommonType!T == float)) 21134 enum typesToSpecifier = "f"; 21135 else static if(is(CommonType!T == int)) 21136 enum typesToSpecifier = "i"; 21137 else static if(is(CommonType!T == uint)) 21138 enum typesToSpecifier = "ui"; 21139 else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof); 21140 } 21141 21142 private template genNames(size_t dim, size_t dim2 = 0) { 21143 string helper() { 21144 string s; 21145 if(dim2) { 21146 static if(__VERSION__ < 2102) 21147 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = void;"; // stupid compiler bug 21148 else 21149 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = 0;"; 21150 } else { 21151 if(dim > 0) s ~= "type x = 0;"; 21152 if(dim > 1) s ~= "type y = 0;"; 21153 if(dim > 2) s ~= "type z = 0;"; 21154 if(dim > 3) s ~= "type w = 0;"; 21155 } 21156 21157 s ~= "this(typeof(this.tupleof) args) { this.tupleof = args; }"; 21158 if(dim2) 21159 s ~= "this(type["~(dim*dim2).stringof~"] t) { (cast(typeof(t)) this.matrix)[] = t[]; }"; 21160 21161 return s; 21162 } 21163 21164 enum genNames = helper(); 21165 } 21166 21167 // there's vec, arrays of vec, mat, and arrays of mat 21168 template opDispatch(string name) 21169 if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat")) 21170 { 21171 static if(name[4] == 'x') { 21172 enum dimX = cast(int) (name[3] - '0'); 21173 static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]); 21174 21175 enum dimY = cast(int) (name[5] - '0'); 21176 static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]); 21177 21178 enum isArray = name[$ - 1] == 'v'; 21179 enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $]; 21180 alias type = typeFromSpecifier!typeSpecifier; 21181 } else { 21182 enum dim = cast(int) (name[3] - '0'); 21183 static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]); 21184 enum isArray = name[$ - 1] == 'v'; 21185 enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $]; 21186 alias type = typeFromSpecifier!typeSpecifier; 21187 } 21188 21189 align(1) 21190 struct opDispatch { 21191 align(1): 21192 static if(name[4] == 'x') 21193 mixin(genNames!(dimX, dimY)); 21194 else 21195 mixin(genNames!dim); 21196 21197 private void glUniform(OpenGlShader.Uniform assignTo) { 21198 glUniform(assignTo.id); 21199 } 21200 private void glUniform(int assignTo) { 21201 static if(name[4] == 'x') { 21202 static if(name[3] == name[5]) { 21203 // import std.stdio; writeln(name, " ", this.matrix, dimX, " ", dimY); 21204 mixin("glUniformMatrix" ~ name[5 .. $] ~ "v")(assignTo, 1, true, &this.matrix[0][0]); 21205 } else { 21206 mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, 1, false, this.matrix.ptr); 21207 } 21208 } else 21209 mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof); 21210 } 21211 } 21212 } 21213 21214 auto vec(T...)(T members) { 21215 return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members); 21216 } 21217 } 21218 21219 void checkGlError() { 21220 auto error = glGetError(); 21221 int[] errors; 21222 string[] errorStrings; 21223 while(error != GL_NO_ERROR) { 21224 errors ~= error; 21225 switch(error) { 21226 case 0x0500: errorStrings ~= "GL_INVALID_ENUM"; break; 21227 case 0x0501: errorStrings ~= "GL_INVALID_VALUE"; break; 21228 case 0x0502: errorStrings ~= "GL_INVALID_OPERATION"; break; 21229 case 0x0503: errorStrings ~= "GL_STACK_OVERFLOW"; break; 21230 case 0x0504: errorStrings ~= "GL_STACK_UNDERFLOW"; break; 21231 case 0x0505: errorStrings ~= "GL_OUT_OF_MEMORY"; break; 21232 default: errorStrings ~= "idk"; 21233 } 21234 error = glGetError(); 21235 } 21236 if(errors.length) 21237 throw ArsdException!"glGetError"(errors, errorStrings); 21238 } 21239 21240 /++ 21241 A matrix for simple uses that easily integrates with [OpenGlShader]. 21242 21243 Might not be useful to you since it only as some simple functions and 21244 probably isn't that fast. 21245 21246 Note it uses an inline static array for its storage, so copying it 21247 may be expensive. 21248 +/ 21249 struct BasicMatrix(int columns, int rows, T = float) { 21250 static import core.stdc.math; 21251 static if(is(T == float)) { 21252 alias cos = core.stdc.math.cosf; 21253 alias sin = core.stdc.math.sinf; 21254 } else { 21255 alias cos = core.stdc.math.cos; 21256 alias sin = core.stdc.math.sin; 21257 } 21258 21259 T[columns * rows] data = 0.0; 21260 21261 /++ 21262 21263 +/ 21264 this(T[columns * rows] data) { 21265 this.data = data; 21266 } 21267 21268 /++ 21269 Basic operations that operate *in place*. 21270 +/ 21271 static if(columns == 4 && rows == 4) 21272 void translate(T x, T y, T z) { 21273 BasicMatrix m = [ 21274 1, 0, 0, x, 21275 0, 1, 0, y, 21276 0, 0, 1, z, 21277 0, 0, 0, 1 21278 ]; 21279 21280 this *= m; 21281 } 21282 21283 /// ditto 21284 static if(columns == 4 && rows == 4) 21285 void scale(T x, T y, T z) { 21286 BasicMatrix m = [ 21287 x, 0, 0, 0, 21288 0, y, 0, 0, 21289 0, 0, z, 0, 21290 0, 0, 0, 1 21291 ]; 21292 21293 this *= m; 21294 } 21295 21296 /// ditto 21297 static if(columns == 4 && rows == 4) 21298 void rotateX(T theta) { 21299 BasicMatrix m = [ 21300 1, 0, 0, 0, 21301 0, cos(theta), -sin(theta), 0, 21302 0, sin(theta), cos(theta), 0, 21303 0, 0, 0, 1 21304 ]; 21305 21306 this *= m; 21307 } 21308 21309 /// ditto 21310 static if(columns == 4 && rows == 4) 21311 void rotateY(T theta) { 21312 BasicMatrix m = [ 21313 cos(theta), 0, sin(theta), 0, 21314 0, 1, 0, 0, 21315 -sin(theta), 0, cos(theta), 0, 21316 0, 0, 0, 1 21317 ]; 21318 21319 this *= m; 21320 } 21321 21322 /// ditto 21323 static if(columns == 4 && rows == 4) 21324 void rotateZ(T theta) { 21325 BasicMatrix m = [ 21326 cos(theta), -sin(theta), 0, 0, 21327 sin(theta), cos(theta), 0, 0, 21328 0, 0, 1, 0, 21329 0, 0, 0, 1 21330 ]; 21331 21332 this *= m; 21333 } 21334 21335 /++ 21336 21337 +/ 21338 static if(columns == rows) 21339 static BasicMatrix identity() { 21340 BasicMatrix m; 21341 foreach(i; 0 .. columns) 21342 m.data[0 + i + i * columns] = 1.0; 21343 return m; 21344 } 21345 21346 static if(columns == rows) 21347 void loadIdentity() { 21348 this = identity(); 21349 } 21350 21351 static if(columns == 4 && rows == 4) 21352 static BasicMatrix ortho(T l, T r, T b, T t, T n, T f) { 21353 return BasicMatrix([ 21354 2/(r-l), 0, 0, -(r+l)/(r-l), 21355 0, 2/(t-b), 0, -(t+b)/(t-b), 21356 0, 0, -2/(f-n), -(f+n)/(f-n), 21357 0, 0, 0, 1 21358 ]); 21359 } 21360 21361 static if(columns == 4 && rows == 4) 21362 void loadOrtho(T l, T r, T b, T t, T n, T f) { 21363 this = ortho(l, r, b, t, n, f); 21364 } 21365 21366 void opOpAssign(string op : "+")(const BasicMatrix rhs) { 21367 this.data[] += rhs.data; 21368 } 21369 void opOpAssign(string op : "-")(const BasicMatrix rhs) { 21370 this.data[] -= rhs.data; 21371 } 21372 void opOpAssign(string op : "*")(const T rhs) { 21373 this.data[] *= rhs; 21374 } 21375 void opOpAssign(string op : "/")(const T rhs) { 21376 this.data[] /= rhs; 21377 } 21378 void opOpAssign(string op : "*", BM : BasicMatrix!(rhsColumns, rhsRows, rhsT), int rhsColumns, int rhsRows, rhsT)(const BM rhs) { 21379 static assert(columns == rhsRows); 21380 auto multiplySize = columns; 21381 21382 auto tmp = this.data; // copy cuz it is a value type 21383 21384 int idx = 0; 21385 foreach(r; 0 .. rows) 21386 foreach(c; 0 .. columns) { 21387 T sum = 0.0; 21388 21389 foreach(i; 0 .. multiplySize) 21390 sum += this.data[r * columns + i] * rhs.data[i * rhsColumns + c]; 21391 21392 tmp[idx++] = sum; 21393 } 21394 21395 this.data = tmp; 21396 } 21397 } 21398 21399 unittest { 21400 auto m = BasicMatrix!(2, 2)([ 21401 1, 2, 21402 3, 4 21403 ]); 21404 21405 auto m2 = BasicMatrix!(2, 2)([ 21406 5, 6, 21407 7, 8 21408 ]); 21409 21410 //import std.conv; 21411 m *= m2; 21412 assert(m.data == [ 21413 19, 22, 21414 43, 50 21415 ]);//, to!string(m.data)); 21416 } 21417 21418 21419 21420 class GlObjectBase { 21421 protected uint _vao; 21422 protected uint _elementsCount; 21423 21424 protected uint element_buffer; 21425 21426 void gen() { 21427 glGenVertexArrays(1, &_vao); 21428 } 21429 21430 void bind() { 21431 glBindVertexArray(_vao); 21432 } 21433 21434 void dispose() { 21435 glDeleteVertexArrays(1, &_vao); 21436 } 21437 21438 void draw() { 21439 bind(); 21440 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer); 21441 glDrawElements(GL_TRIANGLES, _elementsCount, GL_UNSIGNED_INT, null); 21442 } 21443 } 21444 21445 /++ 21446 21447 +/ 21448 class GlObject(T) : GlObjectBase { 21449 protected uint VBO; 21450 21451 this(T[] arr, uint[] indices) { 21452 gen(); 21453 bind(); 21454 21455 glGenBuffers(1, &VBO); 21456 glGenBuffers(1, &element_buffer); 21457 21458 glBindBuffer(GL_ARRAY_BUFFER, VBO); 21459 glBufferDataSlice(GL_ARRAY_BUFFER, arr, GL_STATIC_DRAW); 21460 21461 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer); 21462 glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); 21463 _elementsCount = cast(int) indices.length; 21464 21465 foreach(int idx, memberName; __traits(allMembers, T)) { 21466 static if(memberName != "__ctor") { 21467 static if(is(typeof(__traits(getMember, T, memberName)) == float[N], size_t N)) { 21468 glVertexAttribPointer(idx, N, GL_FLOAT, GL_FALSE, T.sizeof, cast(void*) __traits(getMember, T, memberName).offsetof); 21469 glEnableVertexAttribArray(idx); 21470 } else static assert(0); } 21471 } 21472 } 21473 21474 static string generateShaderDefinitions() { 21475 string code; 21476 21477 foreach(idx, memberName; __traits(allMembers, T)) { 21478 // never use stringof ladies and gents it has a LU thing at the end of it 21479 static if(memberName != "__ctor") 21480 code ~= "layout (location = " ~ idx.stringof[0..$-2] ~ ") in " ~ typeToGl!(typeof(__traits(getMember, T, memberName))) ~ " " ~ memberName ~ ";\n"; 21481 } 21482 21483 return code; 21484 } 21485 } 21486 21487 private string typeToGl(T)() { 21488 static if(is(T == float[4])) 21489 return "vec4"; 21490 else static if(is(T == float[3])) 21491 return "vec3"; 21492 else static if(is(T == float[2])) 21493 return "vec2"; 21494 else static assert(0, T.stringof); 21495 } 21496 21497 21498 } 21499 21500 version(Emscripten) { 21501 21502 } else version(linux) { 21503 version(with_eventloop) {} else { 21504 private int epollFd = -1; 21505 void prepareEventLoop() { 21506 if(epollFd != -1) 21507 return; // already initialized, no need to do it again 21508 import ep = core.sys.linux.epoll; 21509 21510 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 21511 if(epollFd == -1) 21512 throw new Exception("epoll create failure"); 21513 } 21514 } 21515 } else version(Posix) { 21516 void prepareEventLoop() {} 21517 } 21518 21519 version(X11) { 21520 import core.stdc.locale : LC_ALL; // rdmd fix 21521 __gshared bool sdx_isUTF8Locale; 21522 21523 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 21524 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 21525 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 21526 // anal magic is here. I (Ketmar) hope you like it. 21527 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 21528 // always return correct unicode symbols. The detection is here 'cause user can change locale 21529 // later. 21530 21531 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 21532 shared static this () @system { 21533 if(!librariesSuccessfullyLoaded) 21534 return; 21535 21536 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 21537 21538 // this doesn't hurt; it may add some locking, but the speed is still 21539 // allows doing 60 FPS videogames; also, ignore the result, as most 21540 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 21541 // never seen this failing). 21542 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 21543 21544 setlocale(LC_ALL, ""); 21545 // check if out locale is UTF-8 21546 auto lct = setlocale(LC_CTYPE, null); 21547 if (lct is null) { 21548 sdx_isUTF8Locale = false; 21549 } else { 21550 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 21551 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 21552 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 21553 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 21554 { 21555 sdx_isUTF8Locale = true; 21556 break; 21557 } 21558 } 21559 } 21560 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 21561 } 21562 } 21563 21564 /++ 21565 $(PITFALL This is not yet stable and may break in future versions without notice.) 21566 21567 History: 21568 Added February 19, 2021 21569 +/ 21570 /// Group: drag_and_drop 21571 interface DropHandler { 21572 /++ 21573 Called when the drag enters the handler's area. 21574 +/ 21575 DragAndDropAction dragEnter(DropPackage*); 21576 /++ 21577 Called when the drag leaves the handler's area or is 21578 cancelled. You should free your resources when this is called. 21579 +/ 21580 void dragLeave(); 21581 /++ 21582 Called continually as the drag moves over the handler's area. 21583 21584 Returns: feedback to the dragger 21585 +/ 21586 DropParameters dragOver(Point pt); 21587 /++ 21588 The user dropped the data and you should process it now. You can 21589 access the data through the given [DropPackage]. 21590 +/ 21591 void drop(scope DropPackage*); 21592 /++ 21593 Called when the drop is complete. You should free whatever temporary 21594 resources you were using. It is often reasonable to simply forward 21595 this call to [dragLeave]. 21596 +/ 21597 void finish(); 21598 21599 /++ 21600 Parameters returned by [DropHandler.drop]. 21601 +/ 21602 static struct DropParameters { 21603 /++ 21604 Acceptable action over this area. 21605 +/ 21606 DragAndDropAction action; 21607 /++ 21608 Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again. 21609 21610 If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources. 21611 +/ 21612 Rectangle consistentWithin; 21613 } 21614 } 21615 21616 /++ 21617 History: 21618 Added February 19, 2021 21619 +/ 21620 /// Group: drag_and_drop 21621 enum DragAndDropAction { 21622 none = 0, 21623 copy, 21624 move, 21625 link, 21626 ask, 21627 custom 21628 } 21629 21630 /++ 21631 An opaque structure representing dropped data. It contains 21632 private, platform-specific data that your `drop` function 21633 should simply forward. 21634 21635 $(PITFALL This is not yet stable and may break in future versions without notice.) 21636 21637 History: 21638 Added February 19, 2021 21639 +/ 21640 /// Group: drag_and_drop 21641 struct DropPackage { 21642 /++ 21643 Lists the available formats as magic numbers. You should compare these 21644 against looked-up formats (see [DraggableData.getFormatId]) you know you support and can 21645 understand the passed data. 21646 +/ 21647 DraggableData.FormatId[] availableFormats() { 21648 version(X11) { 21649 return xFormats; 21650 } else version(Windows) { 21651 if(pDataObj is null) 21652 return null; 21653 21654 typeof(return) ret; 21655 21656 IEnumFORMATETC ef; 21657 if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) { 21658 FORMATETC fmt; 21659 ULONG fetched; 21660 while(ef.Next(1, &fmt, &fetched) == S_OK) { 21661 if(fetched == 0) 21662 break; 21663 21664 if(fmt.lindex != -1) 21665 continue; 21666 if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT) 21667 continue; 21668 if(!(fmt.tymed & TYMED.TYMED_HGLOBAL)) 21669 continue; 21670 21671 ret ~= fmt.cfFormat; 21672 } 21673 } 21674 21675 return ret; 21676 } else throw new NotYetImplementedException(); 21677 } 21678 21679 /++ 21680 Gets data from the drop and optionally accepts it. 21681 21682 Returns: 21683 void because the data is fed asynchronously through the `dg` parameter. 21684 21685 Params: 21686 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. 21687 21688 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. 21689 21690 Calling `getData` again after accepting a drop is not permitted. 21691 21692 format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format. 21693 21694 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. 21695 21696 Throws: 21697 if `format` was not compatible with the [availableFormats] or if the drop has already been accepted. 21698 21699 History: 21700 Included in first release of [DropPackage]. 21701 +/ 21702 void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) { 21703 version(X11) { 21704 21705 auto display = XDisplayConnection.get(); 21706 auto selectionAtom = GetAtom!"XdndSelection"(display); 21707 auto best = format; 21708 21709 static class X11GetSelectionHandler_Drop : X11GetSelectionHandler { 21710 21711 XDisplay* display; 21712 Atom selectionAtom; 21713 DraggableData.FormatId best; 21714 DraggableData.FormatId format; 21715 void delegate(scope ubyte[] data) dg; 21716 DragAndDropAction acceptedAction; 21717 Window sourceWindow; 21718 SimpleWindow win; 21719 this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) { 21720 this.display = display; 21721 this.win = win; 21722 this.sourceWindow = sourceWindow; 21723 this.format = format; 21724 this.selectionAtom = selectionAtom; 21725 this.best = best; 21726 this.dg = dg; 21727 this.acceptedAction = acceptedAction; 21728 } 21729 21730 21731 mixin X11GetSelectionHandler_Basics; 21732 21733 void handleData(Atom target, in ubyte[] data) { 21734 //if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 21735 21736 dg(cast(ubyte[]) data); 21737 21738 if(acceptedAction != DragAndDropAction.none) { 21739 auto display = XDisplayConnection.get; 21740 21741 XClientMessageEvent xclient; 21742 21743 xclient.type = EventType.ClientMessage; 21744 xclient.window = sourceWindow; 21745 xclient.message_type = GetAtom!"XdndFinished"(display); 21746 xclient.format = 32; 21747 xclient.data.l[0] = win.impl.window; 21748 xclient.data.l[1] = 1; // drop successful 21749 xclient.data.l[2] = dndActionAtom(display, acceptedAction); 21750 21751 XSendEvent( 21752 display, 21753 sourceWindow, 21754 false, 21755 EventMask.NoEventMask, 21756 cast(XEvent*) &xclient 21757 ); 21758 21759 XFlush(display); 21760 } 21761 } 21762 21763 Atom findBestFormat(Atom[] answer) { 21764 Atom best = None; 21765 foreach(option; answer) { 21766 if(option == format) { 21767 best = option; 21768 break; 21769 } 21770 /* 21771 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 21772 best = option; 21773 break; 21774 } else if(option == XA_STRING) { 21775 best = option; 21776 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 21777 best = option; 21778 } 21779 */ 21780 } 21781 return best; 21782 } 21783 } 21784 21785 win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction); 21786 21787 XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp); 21788 21789 } else version(Windows) { 21790 21791 // clean up like DragLeave 21792 // pass effect back up 21793 21794 FORMATETC t; 21795 assert(format >= 0 && format <= ushort.max); 21796 t.cfFormat = cast(ushort) format; 21797 t.lindex = -1; 21798 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 21799 t.tymed = TYMED.TYMED_HGLOBAL; 21800 21801 STGMEDIUM m; 21802 21803 if(pDataObj.GetData(&t, &m) != S_OK) { 21804 // fail 21805 } else { 21806 // succeed, take the data and clean up 21807 21808 // FIXME: ensure it is legit HGLOBAL 21809 auto handle = m.hGlobal; 21810 21811 if(handle) { 21812 auto sz = GlobalSize(handle); 21813 if(auto ptr = cast(ubyte*) GlobalLock(handle)) { 21814 scope(exit) GlobalUnlock(handle); 21815 scope(exit) GlobalFree(handle); 21816 21817 auto data = ptr[0 .. sz]; 21818 21819 dg(data); 21820 } 21821 } 21822 } 21823 } 21824 } 21825 21826 private: 21827 21828 version(X11) { 21829 SimpleWindow win; 21830 Window sourceWindow; 21831 Time dataTimestamp; 21832 21833 Atom[] xFormats; 21834 } 21835 version(Windows) { 21836 IDataObject pDataObj; 21837 } 21838 } 21839 21840 /++ 21841 A generic helper base class for making a drop handler with a preference list of custom types. 21842 This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own 21843 droppers too. 21844 21845 It assumes the whole window it used, but you can subclass to change that. 21846 21847 $(PITFALL This is not yet stable and may break in future versions without notice.) 21848 21849 History: 21850 Added February 19, 2021 21851 +/ 21852 /// Group: drag_and_drop 21853 class GenericDropHandlerBase : DropHandler { 21854 // no fancy state here so no need to do anything here 21855 void finish() { } 21856 void dragLeave() { } 21857 21858 private DragAndDropAction acceptedAction; 21859 private DraggableData.FormatId acceptedFormat; 21860 private void delegate(scope ubyte[]) acceptedHandler; 21861 21862 struct FormatHandler { 21863 DraggableData.FormatId format; 21864 void delegate(scope ubyte[]) handler; 21865 } 21866 21867 protected abstract FormatHandler[] formatHandlers(); 21868 21869 DragAndDropAction dragEnter(DropPackage* pkg) { 21870 debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); } 21871 foreach(fmt; formatHandlers()) 21872 foreach(f; pkg.availableFormats()) 21873 if(f == fmt.format) { 21874 acceptedFormat = f; 21875 acceptedHandler = fmt.handler; 21876 return acceptedAction = DragAndDropAction.copy; 21877 } 21878 return acceptedAction = DragAndDropAction.none; 21879 } 21880 DropParameters dragOver(Point pt) { 21881 return DropParameters(acceptedAction); 21882 } 21883 21884 void drop(scope DropPackage* dropPackage) { 21885 if(!acceptedFormat || acceptedHandler is null) { 21886 debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); } 21887 return; // prolly shouldn't happen anyway... 21888 } 21889 21890 dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler); 21891 } 21892 } 21893 21894 /++ 21895 A simple handler for making your window accept drops of plain text. 21896 21897 $(PITFALL This is not yet stable and may break in future versions without notice.) 21898 21899 History: 21900 Added February 22, 2021 21901 +/ 21902 /// Group: drag_and_drop 21903 class TextDropHandler : GenericDropHandlerBase { 21904 private void delegate(in char[] text) dg; 21905 21906 /++ 21907 21908 +/ 21909 this(void delegate(in char[] text) dg) { 21910 this.dg = dg; 21911 } 21912 21913 protected override FormatHandler[] formatHandlers() { 21914 version(X11) 21915 return [ 21916 FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator), 21917 FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator), 21918 ]; 21919 else version(Windows) 21920 return [ 21921 FormatHandler(CF_UNICODETEXT, &translator), 21922 ]; 21923 else throw new NotYetImplementedException(); 21924 } 21925 21926 private void translator(scope ubyte[] data) { 21927 version(X11) 21928 dg(cast(char[]) data); 21929 else version(Windows) 21930 dg(makeUtf8StringFromWindowsString(cast(wchar[]) data)); 21931 } 21932 } 21933 21934 /++ 21935 A simple handler for making your window accept drops of files, issued to you as file names. 21936 21937 $(PITFALL This is not yet stable and may break in future versions without notice.) 21938 21939 History: 21940 Added February 22, 2021 21941 +/ 21942 /// Group: drag_and_drop 21943 21944 class FilesDropHandler : GenericDropHandlerBase { 21945 private void delegate(in char[][]) dg; 21946 21947 /++ 21948 21949 +/ 21950 this(void delegate(in char[][] fileNames) dg) { 21951 this.dg = dg; 21952 } 21953 21954 protected override FormatHandler[] formatHandlers() { 21955 version(X11) 21956 return [ 21957 FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator), 21958 ]; 21959 else version(Windows) 21960 return [ 21961 FormatHandler(CF_HDROP, &translator), 21962 ]; 21963 else throw new NotYetImplementedException(); 21964 } 21965 21966 private void translator(scope ubyte[] data) @system { 21967 version(X11) { 21968 char[] listString = cast(char[]) data; 21969 char[][16] buffer; 21970 int count; 21971 char[][] result = buffer[]; 21972 21973 void commit(char[] s) { 21974 if(count == result.length) 21975 result.length += 16; 21976 if(s.length > 7 && s[0 ..7] == "file://") 21977 s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding 21978 result[count++] = s; 21979 } 21980 21981 size_t last; 21982 foreach(idx, char c; listString) { 21983 if(c == '\n') { 21984 commit(listString[last .. idx - 1]); // a \r 21985 last = idx + 1; // a \n 21986 } 21987 } 21988 21989 if(last < listString.length) { 21990 commit(listString[last .. $]); 21991 } 21992 21993 // FIXME: they are uris now, should I translate it to local file names? 21994 // of course the host name is supposed to be there cuz of X rokking... 21995 21996 dg(result[0 .. count]); 21997 } else version(Windows) { 21998 21999 static struct DROPFILES { 22000 DWORD pFiles; 22001 POINT pt; 22002 BOOL fNC; 22003 BOOL fWide; 22004 } 22005 22006 22007 const(char)[][16] buffer; 22008 int count; 22009 const(char)[][] result = buffer[]; 22010 size_t last; 22011 22012 void commitA(in char[] stuff) { 22013 if(count == result.length) 22014 result.length += 16; 22015 result[count++] = stuff; 22016 } 22017 22018 void commitW(in wchar[] stuff) { 22019 commitA(makeUtf8StringFromWindowsString(stuff)); 22020 } 22021 22022 void magic(T)(T chars) { 22023 size_t idx; 22024 while(chars[idx]) { 22025 last = idx; 22026 while(chars[idx]) { 22027 idx++; 22028 } 22029 static if(is(T == char*)) 22030 commitA(chars[last .. idx]); 22031 else 22032 commitW(chars[last .. idx]); 22033 idx++; 22034 } 22035 } 22036 22037 auto df = cast(DROPFILES*) data.ptr; 22038 if(df.fWide) { 22039 wchar* chars = cast(wchar*) (data.ptr + df.pFiles); 22040 magic(chars); 22041 } else { 22042 char* chars = cast(char*) (data.ptr + df.pFiles); 22043 magic(chars); 22044 } 22045 dg(result[0 .. count]); 22046 } 22047 else throw new NotYetImplementedException(); 22048 } 22049 } 22050 22051 /++ 22052 Interface to describe data being dragged. See also [draggable] helper function. 22053 22054 $(PITFALL This is not yet stable and may break in future versions without notice.) 22055 22056 History: 22057 Added February 19, 2021 22058 +/ 22059 interface DraggableData { 22060 version(X11) 22061 alias FormatId = Atom; 22062 else 22063 alias FormatId = uint; 22064 /++ 22065 Gets the platform-specific FormatId associated with the given named format. 22066 22067 This may be a MIME type, but may also be other various strings defined by the 22068 programs you want to interoperate with. 22069 22070 FIXME: sdpy needs to offer data adapter things that look for compatible formats 22071 and convert it to some particular type for you. 22072 +/ 22073 static FormatId getFormatId(string name)() { 22074 version(X11) 22075 return GetAtom!name(XDisplayConnection.get); 22076 else version(Windows) { 22077 static UINT cache; 22078 if(!cache) 22079 cache = RegisterClipboardFormatA(name); 22080 return cache; 22081 } else 22082 throw new NotYetImplementedException(); 22083 } 22084 22085 /++ 22086 Looks up a string to represent the name for the given format, if there is one. 22087 22088 You should avoid using this function because it is slow. It is provided more for 22089 debugging than for primary use. 22090 +/ 22091 static string getFormatName(FormatId format) { 22092 version(X11) { 22093 if(format == 0) 22094 return "None"; 22095 else 22096 return getAtomName(format, XDisplayConnection.get); 22097 } else version(Windows) { 22098 switch(format) { 22099 case CF_UNICODETEXT: return "CF_UNICODETEXT"; 22100 case CF_DIBV5: return "CF_DIBV5"; 22101 case CF_RIFF: return "CF_RIFF"; 22102 case CF_WAVE: return "CF_WAVE"; 22103 case CF_HDROP: return "CF_HDROP"; 22104 default: 22105 char[1024] name; 22106 auto count = GetClipboardFormatNameA(format, name.ptr, name.length); 22107 return name[0 .. count].idup; 22108 } 22109 } else throw new NotYetImplementedException(); 22110 } 22111 22112 FormatId[] availableFormats(); 22113 // Return the slice of data you filled, empty slice if done. 22114 // this is to support the incremental thing 22115 ubyte[] getData(FormatId format, return scope ubyte[] data); 22116 22117 size_t dataLength(FormatId format); 22118 } 22119 22120 /++ 22121 $(PITFALL This is not yet stable and may break in future versions without notice.) 22122 22123 History: 22124 Added February 19, 2021 22125 +/ 22126 DraggableData draggable(string s) { 22127 version(X11) 22128 return new class X11SetSelectionHandler_Text, DraggableData { 22129 this() { 22130 super(s); 22131 } 22132 22133 override FormatId[] availableFormats() { 22134 return X11SetSelectionHandler_Text.availableFormats(); 22135 } 22136 22137 override ubyte[] getData(FormatId format, return scope ubyte[] data) { 22138 return X11SetSelectionHandler_Text.getData(format, data); 22139 } 22140 22141 size_t dataLength(FormatId format) { 22142 return s.length; 22143 } 22144 }; 22145 else version(Windows) 22146 return new class DraggableData { 22147 FormatId[] availableFormats() { 22148 return [CF_UNICODETEXT]; 22149 } 22150 22151 ubyte[] getData(FormatId format, return scope ubyte[] data) { 22152 return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 22153 } 22154 22155 size_t dataLength(FormatId format) { 22156 return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof; 22157 } 22158 }; 22159 else 22160 throw new NotYetImplementedException(); 22161 } 22162 22163 /++ 22164 $(PITFALL This is not yet stable and may break in future versions without notice.) 22165 22166 History: 22167 Added February 19, 2021 22168 +/ 22169 /// Group: drag_and_drop 22170 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) 22171 in { 22172 assert(window !is null); 22173 assert(handler !is null); 22174 } 22175 do 22176 { 22177 version(X11) { 22178 auto sh = cast(X11SetSelectionHandler) handler; 22179 if(sh is null) { 22180 // gotta make my own adapter. 22181 sh = new class X11SetSelectionHandler { 22182 mixin X11SetSelectionHandler_Basics; 22183 22184 Atom[] availableFormats() { return handler.availableFormats(); } 22185 ubyte[] getData(Atom format, return scope ubyte[] data) { 22186 return handler.getData(format, data); 22187 } 22188 22189 // since the drop selection is only ever used once it isn't important 22190 // to reset it. 22191 void done() {} 22192 }; 22193 } 22194 return doDragDropX11(window, sh, action); 22195 } else version(Windows) { 22196 return doDragDropWindows(window, handler, action); 22197 } else throw new NotYetImplementedException(); 22198 } 22199 22200 version(Windows) 22201 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) { 22202 IDataObject obj = new class IDataObject { 22203 ULONG refCount; 22204 ULONG AddRef() { 22205 return ++refCount; 22206 } 22207 ULONG Release() { 22208 return --refCount; 22209 } 22210 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22211 if (IID_IUnknown == *riid) { 22212 *ppv = cast(void*) cast(IUnknown) this; 22213 } 22214 else if (IID_IDataObject == *riid) { 22215 *ppv = cast(void*) cast(IDataObject) this; 22216 } 22217 else { 22218 *ppv = null; 22219 return E_NOINTERFACE; 22220 } 22221 22222 AddRef(); 22223 return NOERROR; 22224 } 22225 22226 HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) { 22227 // writeln("Advise"); 22228 return E_NOTIMPL; 22229 } 22230 HRESULT DUnadvise(DWORD dwConnection) { 22231 return E_NOTIMPL; 22232 } 22233 HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) { 22234 // writeln("EnumDAdvise"); 22235 return OLE_E_ADVISENOTSUPPORTED; 22236 } 22237 // tell what formats it supports 22238 22239 FORMATETC[] types; 22240 this() { 22241 FORMATETC t; 22242 foreach(ty; handler.availableFormats()) { 22243 assert(ty <= ushort.max && ty >= 0); 22244 t.cfFormat = cast(ushort) ty; 22245 t.lindex = -1; 22246 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 22247 t.tymed = TYMED.TYMED_HGLOBAL; 22248 } 22249 types ~= t; 22250 } 22251 HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) { 22252 if(dwDirection == DATADIR.DATADIR_GET) { 22253 *ppenumFormatEtc = new class IEnumFORMATETC { 22254 ULONG refCount; 22255 ULONG AddRef() { 22256 return ++refCount; 22257 } 22258 ULONG Release() { 22259 return --refCount; 22260 } 22261 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22262 if (IID_IUnknown == *riid) { 22263 *ppv = cast(void*) cast(IUnknown) this; 22264 } 22265 else if (IID_IEnumFORMATETC == *riid) { 22266 *ppv = cast(void*) cast(IEnumFORMATETC) this; 22267 } 22268 else { 22269 *ppv = null; 22270 return E_NOINTERFACE; 22271 } 22272 22273 AddRef(); 22274 return NOERROR; 22275 } 22276 22277 22278 int pos; 22279 this() { 22280 pos = 0; 22281 } 22282 22283 HRESULT Clone(IEnumFORMATETC* ppenum) { 22284 // writeln("clone"); 22285 return E_NOTIMPL; // FIXME 22286 } 22287 22288 // Caller is responsible for freeing memory 22289 HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) { 22290 // fetched may be null if celt is one 22291 if(celt != 1) 22292 return E_NOTIMPL; // FIXME 22293 22294 if(celt + pos > types.length) 22295 return S_FALSE; 22296 22297 *rgelt = types[pos++]; 22298 22299 if(pceltFetched !is null) 22300 *pceltFetched = 1; 22301 22302 // writeln("ok celt ", celt); 22303 return S_OK; 22304 } 22305 22306 HRESULT Reset() { 22307 pos = 0; 22308 return S_OK; 22309 } 22310 22311 HRESULT Skip(ULONG celt) { 22312 if(celt + pos <= types.length) { 22313 pos += celt; 22314 return S_OK; 22315 } 22316 return S_FALSE; 22317 } 22318 }; 22319 22320 return S_OK; 22321 } else 22322 return E_NOTIMPL; 22323 } 22324 // given a format, return the format you'd prefer to use cuz it is identical 22325 HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) { 22326 // FIXME: prolly could be better but meh 22327 // writeln("gcf: ", *pformatectIn); 22328 *pformatetcOut = *pformatectIn; 22329 return S_OK; 22330 } 22331 HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 22332 foreach(ty; types) { 22333 if(ty == *pformatetcIn) { 22334 auto format = ty.cfFormat; 22335 // writeln("A: ", *pformatetcIn, "\nB: ", ty); 22336 STGMEDIUM medium; 22337 medium.tymed = TYMED.TYMED_HGLOBAL; 22338 22339 auto sz = handler.dataLength(format); 22340 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz); 22341 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 22342 if(auto data = cast(wchar*) GlobalLock(handle)) { 22343 auto slice = data[0 .. sz]; 22344 scope(exit) 22345 GlobalUnlock(handle); 22346 22347 handler.getData(format, cast(ubyte[]) slice[]); 22348 } 22349 22350 22351 medium.hGlobal = handle; // FIXME 22352 *pmedium = medium; 22353 return S_OK; 22354 } 22355 } 22356 return DV_E_FORMATETC; 22357 } 22358 HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 22359 // writeln("GDH: ", *pformatetcIn); 22360 return E_NOTIMPL; // FIXME 22361 } 22362 HRESULT QueryGetData(FORMATETC* pformatetc) { 22363 auto search = *pformatetc; 22364 search.tymed &= TYMED.TYMED_HGLOBAL; 22365 foreach(ty; types) 22366 if(ty == search) { 22367 // writeln("QueryGetData ", search, " ", types[0]); 22368 return S_OK; 22369 } 22370 if(pformatetc.cfFormat==CF_UNICODETEXT) { 22371 //writeln("QueryGetData FALSE ", search, " ", types[0]); 22372 } 22373 return S_FALSE; 22374 } 22375 HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) { 22376 // writeln("SetData: "); 22377 return E_NOTIMPL; 22378 } 22379 }; 22380 22381 22382 IDropSource src = new class IDropSource { 22383 ULONG refCount; 22384 ULONG AddRef() { 22385 return ++refCount; 22386 } 22387 ULONG Release() { 22388 return --refCount; 22389 } 22390 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22391 if (IID_IUnknown == *riid) { 22392 *ppv = cast(void*) cast(IUnknown) this; 22393 } 22394 else if (IID_IDropSource == *riid) { 22395 *ppv = cast(void*) cast(IDropSource) this; 22396 } 22397 else { 22398 *ppv = null; 22399 return E_NOINTERFACE; 22400 } 22401 22402 AddRef(); 22403 return NOERROR; 22404 } 22405 22406 int QueryContinueDrag(int fEscapePressed, uint grfKeyState) { 22407 if(fEscapePressed) 22408 return DRAGDROP_S_CANCEL; 22409 if(!(grfKeyState & MK_LBUTTON)) 22410 return DRAGDROP_S_DROP; 22411 return S_OK; 22412 } 22413 22414 int GiveFeedback(uint dwEffect) { 22415 return DRAGDROP_S_USEDEFAULTCURSORS; 22416 } 22417 }; 22418 22419 DWORD effect; 22420 22421 if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect."); 22422 22423 DROPEFFECT de = win32DragAndDropAction(action); 22424 22425 // I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time 22426 // but still prolly a FIXME 22427 22428 auto ret = DoDragDrop(obj, src, de, &effect); 22429 /+ 22430 if(ret == DRAGDROP_S_DROP) 22431 writeln("drop ", effect); 22432 else if(ret == DRAGDROP_S_CANCEL) 22433 writeln("cancel"); 22434 else if(ret == S_OK) 22435 writeln("ok"); 22436 else writeln(ret); 22437 +/ 22438 22439 return ret; 22440 } 22441 22442 version(Windows) 22443 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) { 22444 DROPEFFECT de; 22445 22446 with(DragAndDropAction) 22447 with(DROPEFFECT) 22448 final switch(action) { 22449 case none: de = DROPEFFECT_NONE; break; 22450 case copy: de = DROPEFFECT_COPY; break; 22451 case move: de = DROPEFFECT_MOVE; break; 22452 case link: de = DROPEFFECT_LINK; break; 22453 case ask: throw new Exception("ask not implemented yet"); 22454 case custom: throw new Exception("custom not implemented yet"); 22455 } 22456 22457 return de; 22458 } 22459 22460 22461 /++ 22462 History: 22463 Added February 19, 2021 22464 +/ 22465 /// Group: drag_and_drop 22466 void enableDragAndDrop(SimpleWindow window, DropHandler handler) { 22467 version(X11) { 22468 auto display = XDisplayConnection.get; 22469 22470 Atom atom = 5; // right??? 22471 22472 XChangeProperty( 22473 display, 22474 window.impl.window, 22475 GetAtom!"XdndAware"(display), 22476 XA_ATOM, 22477 32 /* bits */, 22478 PropModeReplace, 22479 &atom, 22480 1); 22481 22482 window.dropHandler = handler; 22483 } else version(Windows) { 22484 22485 initDnd(); 22486 22487 auto dropTarget = new class (handler) IDropTarget { 22488 DropHandler handler; 22489 this(DropHandler handler) { 22490 this.handler = handler; 22491 } 22492 ULONG refCount; 22493 ULONG AddRef() { 22494 return ++refCount; 22495 } 22496 ULONG Release() { 22497 return --refCount; 22498 } 22499 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22500 if (IID_IUnknown == *riid) { 22501 *ppv = cast(void*) cast(IUnknown) this; 22502 } 22503 else if (IID_IDropTarget == *riid) { 22504 *ppv = cast(void*) cast(IDropTarget) this; 22505 } 22506 else { 22507 *ppv = null; 22508 return E_NOINTERFACE; 22509 } 22510 22511 AddRef(); 22512 return NOERROR; 22513 } 22514 22515 22516 // /////////////////// 22517 22518 HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 22519 DropPackage dropPackage = DropPackage(pDataObj); 22520 *pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage)); 22521 return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter 22522 } 22523 22524 HRESULT DragLeave() { 22525 handler.dragLeave(); 22526 // release the IDataObject if needed 22527 return S_OK; 22528 } 22529 22530 HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 22531 auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates 22532 22533 *pdwEffect = win32DragAndDropAction(res.action); 22534 // same as DragEnter basically 22535 return S_OK; 22536 } 22537 22538 HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 22539 DropPackage pkg = DropPackage(pDataObj); 22540 handler.drop(&pkg); 22541 22542 return S_OK; 22543 } 22544 }; 22545 // Windows can hold on to the handler and try to call it 22546 // during which time the GC can't see it. so important to 22547 // manually manage this. At some point i'll FIXME and make 22548 // all my com instances manually managed since they supposed 22549 // to respect the refcount. 22550 import core.memory; 22551 GC.addRoot(cast(void*) dropTarget); 22552 22553 if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK) 22554 throw new WindowsApiException("RegisterDragDrop", GetLastError()); 22555 22556 window.dropHandler = handler; 22557 } else throw new NotYetImplementedException(); 22558 } 22559 22560 22561 22562 static if(UsingSimpledisplayX11) { 22563 22564 enum _NET_WM_STATE_ADD = 1; 22565 enum _NET_WM_STATE_REMOVE = 0; 22566 enum _NET_WM_STATE_TOGGLE = 2; 22567 22568 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases. 22569 void demandAttention(SimpleWindow window, bool needs = true) { 22570 demandAttention(window.impl.window, needs); 22571 } 22572 22573 /// ditto 22574 void demandAttention(Window window, bool needs = true) { 22575 setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs); 22576 } 22577 22578 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) { 22579 auto display = XDisplayConnection.get(); 22580 if(atom == None) 22581 return; // non-failure error 22582 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 22583 22584 XClientMessageEvent xclient; 22585 22586 xclient.type = EventType.ClientMessage; 22587 xclient.window = window; 22588 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 22589 xclient.format = 32; 22590 xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 22591 xclient.data.l[1] = atom; 22592 xclient.data.l[2] = atom2; 22593 xclient.data.l[3] = 1; 22594 // [3] == source. 0 == unknown, 1 == app, 2 == else 22595 22596 XSendEvent( 22597 display, 22598 RootWindow(display, DefaultScreen(display)), 22599 false, 22600 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 22601 cast(XEvent*) &xclient 22602 ); 22603 22604 /+ 22605 XChangeProperty( 22606 display, 22607 window.impl.window, 22608 GetAtom!"_NET_WM_STATE"(display), 22609 XA_ATOM, 22610 32 /* bits */, 22611 PropModeAppend, 22612 &atom, 22613 1); 22614 +/ 22615 } 22616 22617 private Atom dndActionAtom(Display* display, DragAndDropAction action) { 22618 Atom actionAtom; 22619 with(DragAndDropAction) 22620 final switch(action) { 22621 case none: actionAtom = None; break; 22622 case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break; 22623 case move: actionAtom = GetAtom!"XdndActionMove"(display); break; 22624 case link: actionAtom = GetAtom!"XdndActionLink"(display); break; 22625 case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break; 22626 case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break; 22627 } 22628 22629 return actionAtom; 22630 } 22631 22632 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) { 22633 // FIXME: I need to show user feedback somehow. 22634 auto display = XDisplayConnection.get; 22635 22636 auto actionAtom = dndActionAtom(display, action); 22637 assert(actionAtom, "Don't use action none to accept a drop"); 22638 22639 setX11Selection!"XdndSelection"(window, handler, null); 22640 22641 auto oldKeyHandler = window.handleKeyEvent; 22642 scope(exit) window.handleKeyEvent = oldKeyHandler; 22643 22644 auto oldCharHandler = window.handleCharEvent; 22645 scope(exit) window.handleCharEvent = oldCharHandler; 22646 22647 auto oldMouseHandler = window.handleMouseEvent; 22648 scope(exit) window.handleMouseEvent = oldMouseHandler; 22649 22650 Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child 22651 22652 import core.sys.posix.sys.time; 22653 timeval tv; 22654 gettimeofday(&tv, null); 22655 22656 Time dataTimestamp = cast(Time) ( tv.tv_sec * 1000 + tv.tv_usec / 1000 ); 22657 22658 Time lastMouseTimestamp; 22659 22660 bool dnding = true; 22661 Window lastIn = None; 22662 22663 void leave() { 22664 if(lastIn == None) 22665 return; 22666 22667 XEvent ev; 22668 ev.xclient.type = EventType.ClientMessage; 22669 ev.xclient.window = lastIn; 22670 ev.xclient.message_type = GetAtom!("XdndLeave", true)(display); 22671 ev.xclient.format = 32; 22672 ev.xclient.data.l[0] = window.impl.window; 22673 22674 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 22675 XFlush(display); 22676 22677 lastIn = None; 22678 } 22679 22680 void enter(Window w) { 22681 assert(lastIn == None); 22682 22683 lastIn = w; 22684 22685 XEvent ev; 22686 ev.xclient.type = EventType.ClientMessage; 22687 ev.xclient.window = lastIn; 22688 ev.xclient.message_type = GetAtom!("XdndEnter", true)(display); 22689 ev.xclient.format = 32; 22690 ev.xclient.data.l[0] = window.impl.window; 22691 ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types 22692 22693 auto types = handler.availableFormats(); 22694 assert(types.length > 0); 22695 22696 ev.xclient.data.l[2] = types[0]; 22697 if(types.length > 1) 22698 ev.xclient.data.l[3] = types[1]; 22699 if(types.length > 2) 22700 ev.xclient.data.l[4] = types[2]; 22701 22702 // FIXME: other types?!?!? and make sure we skip TARGETS 22703 22704 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 22705 XFlush(display); 22706 } 22707 22708 void position(int rootX, int rootY) { 22709 assert(lastIn != None); 22710 22711 XEvent ev; 22712 ev.xclient.type = EventType.ClientMessage; 22713 ev.xclient.window = lastIn; 22714 ev.xclient.message_type = GetAtom!("XdndPosition", true)(display); 22715 ev.xclient.format = 32; 22716 ev.xclient.data.l[0] = window.impl.window; 22717 ev.xclient.data.l[1] = 0; // reserved 22718 ev.xclient.data.l[2] = (rootX << 16) | rootY; 22719 ev.xclient.data.l[3] = dataTimestamp; 22720 ev.xclient.data.l[4] = actionAtom; 22721 22722 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 22723 XFlush(display); 22724 22725 } 22726 22727 void drop() { 22728 XEvent ev; 22729 ev.xclient.type = EventType.ClientMessage; 22730 ev.xclient.window = lastIn; 22731 ev.xclient.message_type = GetAtom!("XdndDrop", true)(display); 22732 ev.xclient.format = 32; 22733 ev.xclient.data.l[0] = window.impl.window; 22734 ev.xclient.data.l[1] = 0; // reserved 22735 ev.xclient.data.l[2] = dataTimestamp; 22736 22737 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 22738 XFlush(display); 22739 22740 lastIn = None; 22741 dnding = false; 22742 } 22743 22744 // fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler 22745 // but idk if i should... 22746 22747 window.setEventHandlers( 22748 delegate(KeyEvent ev) { 22749 if(ev.pressed == true && ev.key == Key.Escape) { 22750 // cancel 22751 dnding = false; 22752 } 22753 }, 22754 delegate(MouseEvent ev) { 22755 if(ev.timestamp < lastMouseTimestamp) 22756 return; 22757 22758 lastMouseTimestamp = ev.timestamp; 22759 22760 if(ev.type == MouseEventType.motion) { 22761 auto display = XDisplayConnection.get; 22762 auto root = RootWindow(display, DefaultScreen(display)); 22763 22764 Window topWindow; 22765 int rootX, rootY; 22766 22767 XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow); 22768 22769 if(topWindow == None) 22770 return; 22771 22772 top: 22773 if(auto result = topWindow in eligibility) { 22774 auto dropWindow = *result; 22775 if(dropWindow == None) { 22776 leave(); 22777 return; 22778 } 22779 22780 if(dropWindow != lastIn) { 22781 leave(); 22782 enter(dropWindow); 22783 position(rootX, rootY); 22784 } else { 22785 position(rootX, rootY); 22786 } 22787 } else { 22788 // determine eligibility 22789 auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM); 22790 if(data.length == 1) { 22791 // in case there is no WM or it isn't reparenting 22792 eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh 22793 } else { 22794 22795 Window tryScanChildren(Window search, int maxRecurse) { 22796 // could be reparenting window manager, so gotta check the next few children too 22797 Window child; 22798 int x; 22799 int y; 22800 XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child); 22801 22802 if(child == None) 22803 return None; 22804 auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM); 22805 if(data.length == 1) { 22806 return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh 22807 } else { 22808 if(maxRecurse) 22809 return tryScanChildren(child, maxRecurse - 1); 22810 else 22811 return None; 22812 } 22813 22814 } 22815 22816 // if a WM puts more than 3 layers on it, like wtf is it doing, screw that. 22817 auto topResult = tryScanChildren(topWindow, 3); 22818 // it is easy to have a false negative due to the mouse going over a WM 22819 // child window like the close button if separate from the frame... so I 22820 // can't really cache negatives, :( 22821 if(topResult != None) { 22822 eligibility[topWindow] = topResult; 22823 goto top; // reload to do the positioning iff eligibility changed lest we endless loop 22824 } 22825 } 22826 22827 } 22828 22829 } else if(ev.type == MouseEventType.buttonReleased) { 22830 drop(); 22831 dnding = false; 22832 } 22833 } 22834 ); 22835 22836 window.grabInput(); 22837 scope(exit) 22838 window.releaseInputGrab(); 22839 22840 22841 EventLoop.get.run(() => dnding); 22842 22843 return 0; 22844 } 22845 22846 /// X-specific 22847 TrueColorImage getWindowNetWmIcon(Window window) { 22848 try { 22849 auto display = XDisplayConnection.get; 22850 22851 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 22852 22853 if (data.length > arch_ulong.sizeof * 2) { 22854 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 22855 // these are an array of rgba images that we have to convert into pixmaps ourself 22856 22857 int width = cast(int) meta[0]; 22858 int height = cast(int) meta[1]; 22859 22860 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 22861 22862 static if(arch_ulong.sizeof == 4) { 22863 bytes = bytes[0 .. width * height * 4]; 22864 alias imageData = bytes; 22865 } else static if(arch_ulong.sizeof == 8) { 22866 bytes = bytes[0 .. width * height * 8]; 22867 auto imageData = new ubyte[](4 * width * height); 22868 } else static assert(0); 22869 22870 22871 22872 // this returns ARGB. Remember it is little-endian so 22873 // we have BGRA 22874 // our thing uses RGBA, which in little endian, is ABGR 22875 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 22876 auto r = bytes[idx + 2]; 22877 auto g = bytes[idx + 1]; 22878 auto b = bytes[idx + 0]; 22879 auto a = bytes[idx + 3]; 22880 22881 imageData[idx2 + 0] = r; 22882 imageData[idx2 + 1] = g; 22883 imageData[idx2 + 2] = b; 22884 imageData[idx2 + 3] = a; 22885 } 22886 22887 return new TrueColorImage(width, height, imageData); 22888 } 22889 22890 return null; 22891 } catch(Exception e) { 22892 return null; 22893 } 22894 } 22895 22896 } /* UsingSimpledisplayX11 */ 22897 22898 22899 void loadBinNameToWindowClassName () { 22900 import core.stdc.stdlib : realloc; 22901 version(linux) { 22902 // args[0] MAY be empty, so we'll just use this 22903 import core.sys.posix.unistd : readlink; 22904 char[1024] ebuf = void; // 1KB should be enough for everyone! 22905 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 22906 if (len < 1) return; 22907 } else /*version(Windows)*/ { 22908 import core.runtime : Runtime; 22909 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 22910 auto ebuf = Runtime.args[0]; 22911 auto len = ebuf.length; 22912 } 22913 auto pos = len; 22914 while (pos > 0 && ebuf[pos-1] != '/') --pos; 22915 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 22916 if (sdpyWindowClassStr is null) return; // oops 22917 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 22918 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 22919 } 22920 22921 /++ 22922 An interface representing a font that is drawn with custom facilities. 22923 22924 You might want [OperatingSystemFont] instead, which represents 22925 a font loaded and drawn by functions native to the operating system. 22926 22927 WARNING: I might still change this. 22928 +/ 22929 interface DrawableFont : MeasurableFont { 22930 /++ 22931 Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string. 22932 22933 Implementations must use the painter's fillColor to draw a rectangle behind the string, 22934 then use the outlineColor to draw the string. It might alpha composite if there's a transparent 22935 fill color, but that's up to the implementation. 22936 +/ 22937 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 22938 22939 /++ 22940 Requests that the given string is added to the image cache. You should only do this rarely, but 22941 if you have a string that you know will be used over and over again, adding it to a cache can 22942 improve things (assuming the implementation actually has a cache; it is also valid for an implementation 22943 to implement this as a do-nothing method). 22944 +/ 22945 void cacheString(SimpleWindow window, Color foreground, Color background, string text); 22946 } 22947 22948 /++ 22949 Loads a true type font using [arsd.ttf] that can be drawn as images on windows 22950 through a [ScreenPainter]. That module must be compiled in if you choose to use this function. 22951 22952 You should also consider [OperatingSystemFont], which loads and draws a font with 22953 facilities native to the user's operating system. You might also consider 22954 [arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind 22955 of game, as they have their own ways to draw text too. 22956 22957 Be warned: this can be slow, especially on remote connections to the X server, since 22958 it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface 22959 offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to 22960 experiment in your specific case. 22961 22962 Please note that the return type of [DrawableFont] also includes an implementation of 22963 [MeasurableFont]. 22964 +/ 22965 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 22966 import arsd.ttf; 22967 static class ArsdTtfFont : DrawableFont { 22968 TtfFont font; 22969 int size; 22970 this(in ubyte[] data, int size) { 22971 font = TtfFont(data); 22972 this.size = size; 22973 22974 22975 auto scale = stbtt_ScaleForPixelHeight(&font.font, size); 22976 int ascent_, descent_, line_gap; 22977 stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap); 22978 22979 int advance, lsb; 22980 stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb); 22981 xWidth = cast(int) (advance * scale); 22982 stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb); 22983 MWidth = cast(int) (advance * scale); 22984 } 22985 22986 private int ascent_; 22987 private int descent_; 22988 private int xWidth; 22989 private int MWidth; 22990 22991 bool isMonospace() { 22992 return xWidth == MWidth; 22993 } 22994 int averageWidth() { 22995 return xWidth; 22996 } 22997 int height() { 22998 return size; 22999 } 23000 int ascent() { 23001 return ascent_; 23002 } 23003 int descent() { 23004 return descent_; 23005 } 23006 23007 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 23008 int width, height; 23009 font.getStringSize(s, size, width, height); 23010 return width; 23011 } 23012 23013 23014 23015 Sprite[string] cache; 23016 23017 void cacheString(SimpleWindow window, Color foreground, Color background, string text) { 23018 auto sprite = new Sprite(window, stringToImage(foreground, background, text)); 23019 cache[text] = sprite; 23020 } 23021 23022 Image stringToImage(Color fg, Color bg, in char[] text) { 23023 int width, height; 23024 auto data = font.renderString(text, size, width, height); 23025 auto image = new TrueColorImage(width, height); 23026 int pos = 0; 23027 foreach(y; 0 .. height) 23028 foreach(x; 0 .. width) { 23029 fg.a = data[0]; 23030 bg.a = 255; 23031 auto color = alphaBlend(fg, bg); 23032 image.imageData.bytes[pos++] = color.r; 23033 image.imageData.bytes[pos++] = color.g; 23034 image.imageData.bytes[pos++] = color.b; 23035 image.imageData.bytes[pos++] = data[0]; 23036 data = data[1 .. $]; 23037 } 23038 assert(data.length == 0); 23039 23040 return Image.fromMemoryImage(image); 23041 } 23042 23043 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 23044 Sprite sprite = (text in cache) ? *(text in cache) : null; 23045 23046 auto fg = painter.impl._outlineColor; 23047 auto bg = painter.impl._fillColor; 23048 23049 if(sprite !is null) { 23050 auto w = cast(SimpleWindow) painter.window; 23051 assert(w !is null); 23052 23053 sprite.drawAt(painter, upperLeft); 23054 } else { 23055 painter.drawImage(upperLeft, stringToImage(fg, bg, text)); 23056 } 23057 } 23058 } 23059 23060 return new ArsdTtfFont(data, size); 23061 } 23062 23063 class NotYetImplementedException : Exception { 23064 this(string file = __FILE__, size_t line = __LINE__) { 23065 super("Not yet implemented", file, line); 23066 } 23067 } 23068 23069 /// 23070 __gshared bool librariesSuccessfullyLoaded = true; 23071 /// 23072 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 23073 23074 private mixin template DynamicLoadSupplementalOpenGL(Iface) { 23075 // mixin(staticForeachReplacement!Iface); 23076 static foreach(name; __traits(derivedMembers, Iface)) 23077 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 23078 23079 void loadDynamicLibrary() @nogc { 23080 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 23081 } 23082 23083 void loadDynamicLibraryForReal() { 23084 foreach(name; __traits(derivedMembers, Iface)) { 23085 mixin("alias tmp = " ~ name ~ ";"); 23086 tmp = cast(typeof(tmp)) glbindGetProcAddress(name); 23087 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL"); 23088 } 23089 } 23090 } 23091 23092 /+ 23093 private const(char)[] staticForeachReplacement(Iface)() pure { 23094 /* 23095 // just this for gdc 9.... 23096 // when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease 23097 23098 static foreach(name; __traits(derivedMembers, Iface)) 23099 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 23100 */ 23101 23102 char[] code = new char[](__traits(derivedMembers, Iface).length * 64); 23103 size_t pos; 23104 23105 void append(in char[] what) { 23106 if(pos + what.length > code.length) 23107 code.length = (code.length * 3) / 2; 23108 code[pos .. pos + what.length] = what[]; 23109 pos += what.length; 23110 } 23111 23112 foreach(name; __traits(derivedMembers, Iface)) { 23113 append(`__gshared typeof(&__traits(getMember, Iface, "`); 23114 append(name); 23115 append(`")) `); 23116 append(name); 23117 append(";"); 23118 } 23119 23120 return code[0 .. pos]; 23121 } 23122 +/ 23123 23124 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) { 23125 //mixin(staticForeachReplacement!Iface); 23126 static foreach(name; __traits(derivedMembers, Iface)) 23127 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 23128 23129 private __gshared void* libHandle; 23130 private __gshared bool attempted; 23131 23132 void loadDynamicLibrary() @nogc { 23133 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 23134 } 23135 23136 bool loadAttempted() { 23137 return attempted; 23138 } 23139 bool loadSuccessful() { 23140 return libHandle !is null; 23141 } 23142 23143 void loadDynamicLibraryForReal() { 23144 attempted = true; 23145 version(Posix) { 23146 import core.sys.posix.dlfcn; 23147 version(OSX) { 23148 version(X11) 23149 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 23150 else 23151 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 23152 } else { 23153 version(apitrace) { 23154 if(library == "GL" || library == "GLX") { 23155 libHandle = dlopen("glxtrace.so", RTLD_NOW); 23156 if(libHandle is null) { 23157 assert(false, "Failed to load `glxtrace.so`."); 23158 } 23159 } 23160 else { 23161 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 23162 } 23163 } 23164 else { 23165 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 23166 } 23167 if(libHandle is null) { 23168 libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW); 23169 } 23170 } 23171 23172 static void* loadsym(void* l, const char* name) { 23173 import core.stdc.stdlib; 23174 if(l is null) 23175 return &abort; 23176 return dlsym(l, name); 23177 } 23178 } else version(Windows) { 23179 import core.sys.windows.winbase; 23180 libHandle = LoadLibrary(library ~ ".dll"); 23181 static void* loadsym(void* l, const char* name) { 23182 import core.stdc.stdlib; 23183 if(l is null) 23184 return &abort; 23185 return GetProcAddress(l, name); 23186 } 23187 } 23188 if(libHandle is null) { 23189 success = false; 23190 //throw new Exception("load failure of library " ~ library); 23191 } 23192 foreach(name; __traits(derivedMembers, Iface)) { 23193 mixin("alias tmp = " ~ name ~ ";"); 23194 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 23195 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 23196 } 23197 } 23198 23199 void unloadDynamicLibrary() { 23200 version(Posix) { 23201 import core.sys.posix.dlfcn; 23202 dlclose(libHandle); 23203 } else version(Windows) { 23204 import core.sys.windows.winbase; 23205 FreeLibrary(libHandle); 23206 } 23207 foreach(name; __traits(derivedMembers, Iface)) 23208 mixin(name ~ " = null;"); 23209 } 23210 } 23211 23212 // version(X11) 23213 /++ 23214 Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"]. 23215 23216 $(WARNING 23217 This function is exempted from stability guarantees. 23218 ) 23219 +/ 23220 float customScalingFactorForMonitor(int monitorNumber) @system { 23221 import core.stdc.stdlib; 23222 auto val = getenv("ARSD_SCALING_FACTOR"); 23223 23224 // FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given 23225 if(val is null) 23226 return 1.0; 23227 23228 char[16] buffer = 0; 23229 int pos; 23230 23231 const(char)* at = val; 23232 23233 foreach(item; 0 .. monitorNumber + 1) { 23234 if(*at == 0) 23235 break; // reuse the last number when we at the end of the string 23236 pos = 0; 23237 while(pos + 1 < buffer.length && *at && *at != ';') { 23238 buffer[pos++] = *at; 23239 at++; 23240 } 23241 if(*at) 23242 at++; // skip the semicolon 23243 buffer[pos] = 0; 23244 } 23245 23246 //sdpyPrintDebugString(buffer[0 .. pos]); 23247 23248 import core.stdc.math; 23249 auto f = atof(buffer.ptr); 23250 23251 if(f <= 0.0 || isnan(f) || isinf(f)) 23252 return 1.0; 23253 23254 return f; 23255 } 23256 23257 void guiAbortProcess(string msg) { 23258 import core.stdc.stdlib; 23259 version(Windows) { 23260 WCharzBuffer t = WCharzBuffer(msg); 23261 MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0); 23262 } else { 23263 import core.stdc.stdio; 23264 fwrite(msg.ptr, 1, msg.length, stderr); 23265 msg = "\n"; 23266 fwrite(msg.ptr, 1, msg.length, stderr); 23267 fflush(stderr); 23268 } 23269 23270 abort(); 23271 } 23272 23273 private int minInternal(int a, int b) { 23274 return (a < b) ? a : b; 23275 } 23276 23277 private alias scriptable = arsd_jsvar_compatible; 23278 23279 23280 version(Windows) { 23281 enum POINTER_FLAGS : DWORD { 23282 LOL 23283 } 23284 23285 enum POINTER_INPUT_TYPE { 23286 PT_POINTER = 1, 23287 PT_TOUCH = 2, 23288 PT_PEN = 3, 23289 PT_MOUSE = 4, 23290 PT_TOUCHPAD = 5, 23291 } 23292 23293 enum POINTER_BUTTON_CHANGE_TYPE { 23294 POINTER_CHANGE_NONE, 23295 POINTER_CHANGE_FIRSTBUTTON_DOWN, 23296 POINTER_CHANGE_FIRSTBUTTON_UP, 23297 POINTER_CHANGE_SECONDBUTTON_DOWN, 23298 POINTER_CHANGE_SECONDBUTTON_UP, 23299 POINTER_CHANGE_THIRDBUTTON_DOWN, 23300 POINTER_CHANGE_THIRDBUTTON_UP, 23301 POINTER_CHANGE_FOURTHBUTTON_DOWN, 23302 POINTER_CHANGE_FOURTHBUTTON_UP, 23303 POINTER_CHANGE_FIFTHBUTTON_DOWN, 23304 POINTER_CHANGE_FIFTHBUTTON_UP 23305 } 23306 23307 struct POINTER_INFO { 23308 POINTER_INPUT_TYPE pointerType; 23309 UINT32 pointerId; 23310 UINT32 frameId; 23311 POINTER_FLAGS pointerFlags; 23312 HANDLE sourceDevice; 23313 HWND hwndTarget; 23314 POINT ptPixelLocation; 23315 POINT ptHimetricLocation; 23316 POINT ptPixelLocationRaw; 23317 POINT ptHimetricLocationRaw; 23318 DWORD dwTime; 23319 UINT32 historyCount; 23320 INT32 InputData; 23321 DWORD dwKeyStates; 23322 UINT64 PerformanceCount; 23323 POINTER_BUTTON_CHANGE_TYPE ButtonChangeType; 23324 } 23325 23326 enum PEN_FLAGS : DWORD { 23327 PEN_FLAG_NONE = 0, 23328 PEN_FLAG_BARREL = 1, 23329 PEN_FLAG_INVERTED = 2, 23330 PEN_FLAG_ERASER = 4 23331 } 23332 enum PEN_MASK : DWORD { 23333 PEN_MASK_NONE = 0, 23334 PEN_MASK_PRESSURE = 1, 23335 PEN_MASK_ROTATION = 2, 23336 PEN_MASK_TILT_X = 4, 23337 PEN_MASK_TILT_Y = 8, 23338 } 23339 struct POINTER_PEN_INFO { 23340 POINTER_INFO pointerInfo; 23341 PEN_FLAGS penFlags; 23342 PEN_MASK penMask; 23343 UINT32 pressure; 23344 UINT32 rotation; 23345 INT32 tiltX; 23346 INT32 tiltY; 23347 } 23348 23349 enum TOUCH_FLAGS : DWORD { 23350 none 23351 } 23352 23353 enum TOUCH_MASK : DWORD { 23354 TOUCH_MASK_NONE = 0, 23355 TOUCH_MASK_CONTACTAREA = 1, 23356 TOUCH_MASK_ORIENTATION = 2, 23357 TOUCH_MASK_PRESSURE = 4, 23358 } 23359 23360 struct POINTER_TOUCH_INFO { 23361 POINTER_INFO pointerInfo; 23362 TOUCH_FLAGS touchFlags; 23363 TOUCH_MASK touchMask; 23364 RECT rcContact; 23365 RECT rcContactRaw; 23366 UINT32 orientation; 23367 UINT32 pressure; 23368 } 23369 23370 enum POINTER_DEVICE_TYPE : DWORD { 23371 POINTER_DEVICE_TYPE_INTEGRATED_PEN = 0x00000001, 23372 POINTER_DEVICE_TYPE_EXTERNAL_PEN = 0x00000002, 23373 POINTER_DEVICE_TYPE_TOUCH = 0x00000003, 23374 POINTER_DEVICE_TYPE_TOUCH_PAD = 0x00000004, 23375 } 23376 23377 struct POINTER_DEVICE_INFO { 23378 DWORD displayOrientation; 23379 HANDLE device; 23380 POINTER_DEVICE_TYPE pointerDeviceType; 23381 HMONITOR monitor; 23382 ULONG startingCursorId; 23383 USHORT maxActiveContacts; 23384 WCHAR[520] productString; 23385 } 23386 23387 extern(Windows) { 23388 BOOL GetPointerType(UINT32 pointerId, POINTER_INPUT_TYPE* type); 23389 BOOL GetPointerInfo(UINT32 pointerId, POINTER_INFO* info); 23390 BOOL GetPointerFramePenInfo(UINT32 pointerId, UINT32* pointerCount, POINTER_PEN_INFO *penInfo); 23391 BOOL GetPointerFrameTouchInfo(UINT32 pointerId, UINT32* pointerCount, POINTER_TOUCH_INFO *touchInfo); 23392 BOOL GetPointerDevices(UINT32* deviceCount, POINTER_DEVICE_INFO* pointerDevices); 23393 } 23394 }