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(D_OpenD) { 1168 version(OSX) 1169 version=OSXCocoa; 1170 version(iOS) 1171 version=OSXCocoa; 1172 } else { 1173 version(OSX) version(DigitalMars) version=OSXCocoa; 1174 } 1175 1176 1177 1178 version(Emscripten) { 1179 version=allow_unimplemented_features; 1180 version=without_opengl; 1181 } 1182 1183 1184 version(OSXCocoa) { 1185 version=without_opengl; 1186 version=allow_unimplemented_features; 1187 // version=OSXCocoa; 1188 // pragma(linkerDirective, "-framework Cocoa"); 1189 } 1190 1191 version(without_opengl) { 1192 enum SdpyIsUsingIVGLBinds = false; 1193 } else /*version(Posix)*/ { 1194 static if (__traits(compiles, (){import iv.glbinds;})) { 1195 enum SdpyIsUsingIVGLBinds = true; 1196 public import iv.glbinds; 1197 //pragma(msg, "SDPY: using iv.glbinds"); 1198 } else { 1199 enum SdpyIsUsingIVGLBinds = false; 1200 } 1201 //} else { 1202 // enum SdpyIsUsingIVGLBinds = false; 1203 } 1204 1205 version(Windows) { 1206 //import core.sys.windows.windows; 1207 import core.sys.windows.winnls; 1208 import core.sys.windows.windef; 1209 import core.sys.windows.basetyps; 1210 import core.sys.windows.winbase; 1211 import core.sys.windows.winuser; 1212 import core.sys.windows.shellapi; 1213 import core.sys.windows.wingdi; 1214 static import gdi = core.sys.windows.wingdi; // so i 1215 1216 pragma(lib, "gdi32"); 1217 pragma(lib, "user32"); 1218 1219 // for AlphaBlend... a breaking change.... 1220 version(CRuntime_DigitalMars) { } else 1221 pragma(lib, "msimg32"); 1222 1223 // core.sys.windows.dwmapi 1224 private { 1225 /++ 1226 See_also: 1227 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmgetwindowattribute 1228 +/ 1229 extern extern(Windows) HRESULT DwmGetWindowAttribute( 1230 HWND hwnd, 1231 DWORD dwAttribute, 1232 PVOID pvAttribute, 1233 DWORD cbAttribute 1234 ) nothrow @nogc; 1235 1236 /++ 1237 See_also: 1238 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute 1239 +/ 1240 extern extern(Windows) HRESULT DwmSetWindowAttribute( 1241 HWND hwnd, 1242 DWORD dwAttribute, 1243 LPCVOID pvAttribute, 1244 DWORD cbAttribute 1245 ) nothrow @nogc; 1246 1247 /++ 1248 See_also: 1249 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute 1250 +/ 1251 enum DWMWINDOWATTRIBUTE { 1252 // incomplete, only declare what we need 1253 1254 /++ 1255 Usage: 1256 pvAttribute → `DWM_WINDOW_CORNER_PREFERENCE*` 1257 1258 $(NOTE 1259 Requires Windows 11 or later. 1260 ) 1261 +/ 1262 DWMWA_WINDOW_CORNER_PREFERENCE = 33, 1263 } 1264 1265 /++ 1266 See_also: 1267 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference 1268 +/ 1269 enum DWM_WINDOW_CORNER_PREFERENCE { 1270 /// System decision 1271 DWMWCP_DEFAULT = 0, 1272 1273 /// Never 1274 DWMWCP_DONOTROUND = 1, 1275 1276 // If "appropriate" 1277 DWMWCP_ROUND = 2, 1278 1279 // If "appropriate", but smaller radius 1280 DWMWCP_ROUNDSMALL = 3 1281 } 1282 1283 bool fromDWM( 1284 DWM_WINDOW_CORNER_PREFERENCE value, 1285 out CornerStyle result, 1286 ) @safe pure nothrow @nogc { 1287 switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) { 1288 case DWMWCP_DEFAULT: 1289 result = CornerStyle.automatic; 1290 return true; 1291 case DWMWCP_DONOTROUND: 1292 result = CornerStyle.rectangular; 1293 return true; 1294 case DWMWCP_ROUND: 1295 result = CornerStyle.rounded; 1296 return true; 1297 case DWMWCP_ROUNDSMALL: 1298 result = CornerStyle.roundedSlightly; 1299 return true; 1300 default: 1301 return false; 1302 } 1303 } 1304 1305 bool toDWM( 1306 CornerStyle value, 1307 out DWM_WINDOW_CORNER_PREFERENCE result, 1308 ) @safe pure nothrow @nogc { 1309 final switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) { 1310 case CornerStyle.automatic: 1311 result = DWMWCP_DEFAULT; 1312 return true; 1313 case CornerStyle.rectangular: 1314 result = DWMWCP_DONOTROUND; 1315 return true; 1316 case CornerStyle.rounded: 1317 result = DWMWCP_ROUND; 1318 return true; 1319 case CornerStyle.roundedSlightly: 1320 result = DWMWCP_ROUNDSMALL; 1321 return true; 1322 } 1323 } 1324 1325 pragma(lib, "dwmapi"); 1326 } 1327 } else version(Emscripten) { 1328 } else version (linux) { 1329 //k8: this is hack for rdmd. sorry. 1330 static import core.sys.linux.epoll; 1331 static import core.sys.linux.timerfd; 1332 } 1333 1334 1335 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off. 1336 1337 // http://wiki.dlang.org/Simpledisplay.d 1338 1339 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led 1340 1341 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl 1342 // but can i control the scroll lock led 1343 1344 1345 // Note: if you are using Image on X, you might want to do: 1346 /* 1347 static if(UsingSimpledisplayX11) { 1348 if(!Image.impl.xshmAvailable) { 1349 // the images will use the slower XPutImage, you might 1350 // want to consider an alternative method to get better speed 1351 } 1352 } 1353 1354 If the shared memory extension is available though, simpledisplay uses it 1355 for a significant speed boost whenever you draw large Images. 1356 */ 1357 1358 // 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. 1359 1360 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()! 1361 1362 /* 1363 Biggest FIXME: 1364 make sure the key event numbers match between X and Windows OR provide symbolic constants on each system 1365 1366 clean up opengl contexts when their windows close 1367 1368 fix resizing the bitmaps/pixmaps 1369 */ 1370 1371 // BTW on Windows: 1372 // -L/SUBSYSTEM:WINDOWS:5.0 1373 // to dmd will make a nice windows binary w/o a console if you want that. 1374 1375 /* 1376 Stuff to add: 1377 1378 use multibyte functions everywhere we can 1379 1380 OpenGL windows 1381 more event stuff 1382 extremely basic windows w/ no decoration for tooltips, splash screens, etc. 1383 1384 1385 resizeEvent 1386 and make the windows non-resizable by default, 1387 or perhaps stretched (if I can find something in X like StretchBlt) 1388 1389 take a screenshot function! 1390 1391 Pens and brushes? 1392 Maybe a global event loop? 1393 1394 Mouse deltas 1395 Key items 1396 */ 1397 1398 /* 1399 From MSDN: 1400 1401 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate. 1402 1403 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. 1404 1405 */ 1406 1407 version(Emscripten) { 1408 1409 } else version(linux) { 1410 version = X11; 1411 version(without_libnotify) { 1412 // we cool 1413 } 1414 else 1415 version = libnotify; 1416 } 1417 1418 version(libnotify) { 1419 pragma(lib, "dl"); 1420 import core.sys.posix.dlfcn; 1421 1422 void delegate()[int] libnotify_action_delegates; 1423 int libnotify_action_delegates_count; 1424 extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) { 1425 auto idx = cast(int) user_data; 1426 if(auto dgptr = idx in libnotify_action_delegates) { 1427 (*dgptr)(); 1428 libnotify_action_delegates.remove(idx); 1429 } 1430 } 1431 1432 struct C_DynamicLibrary { 1433 void* handle; 1434 this(string name) { 1435 handle = dlopen((name ~ "\0").ptr, RTLD_NOW); 1436 if(handle is null) 1437 throw new Exception("dlopen"); 1438 } 1439 1440 void close() { 1441 dlclose(handle); 1442 } 1443 1444 ~this() { 1445 // close 1446 } 1447 1448 // FIXME: this looks up by name every time.... 1449 template call(string func, Ret, Args...) { 1450 extern(C) Ret function(Args) fptr; 1451 typeof(fptr) call() { 1452 fptr = cast(typeof(fptr)) dlsym(handle, func); 1453 return fptr; 1454 } 1455 } 1456 } 1457 1458 C_DynamicLibrary* libnotify; 1459 } 1460 1461 version(OSX) { 1462 version(OSXCocoa) {} 1463 else { version = X11; } 1464 } 1465 //version = OSXCocoa; // this was written by KennyTM 1466 version(FreeBSD) 1467 version = X11; 1468 version(Solaris) 1469 version = X11; 1470 1471 version(X11) { 1472 version(without_xft) {} 1473 else version=with_xft; 1474 } 1475 1476 void featureNotImplemented()() { 1477 version(allow_unimplemented_features) 1478 throw new NotYetImplementedException(); 1479 else 1480 static assert(0); 1481 } 1482 1483 // these are so the static asserts don't trigger unless you want to 1484 // add support to it for an OS 1485 version(Windows) 1486 version = with_timer; 1487 version(linux) 1488 version = with_timer; 1489 version(OSXCocoa) 1490 version = with_timer; 1491 1492 version(with_timer) 1493 enum bool SimpledisplayTimerAvailable = true; 1494 else 1495 enum bool SimpledisplayTimerAvailable = false; 1496 1497 /// 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. 1498 version(Windows) 1499 enum bool UsingSimpledisplayWindows = true; 1500 else 1501 enum bool UsingSimpledisplayWindows = false; 1502 1503 /// 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. 1504 version(X11) 1505 enum bool UsingSimpledisplayX11 = true; 1506 else 1507 enum bool UsingSimpledisplayX11 = false; 1508 1509 /// 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. 1510 version(OSXCocoa) 1511 enum bool UsingSimpledisplayCocoa = true; 1512 else 1513 enum bool UsingSimpledisplayCocoa = false; 1514 1515 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception. 1516 version(Windows) 1517 enum multipleWindowsSupported = true; 1518 else version(Emscripten) 1519 enum multipleWindowsSupported = false; 1520 else version(X11) 1521 enum multipleWindowsSupported = true; 1522 else version(OSXCocoa) 1523 enum multipleWindowsSupported = true; 1524 else 1525 static assert(0); 1526 1527 version(without_opengl) 1528 enum bool OpenGlEnabled = false; 1529 else 1530 enum bool OpenGlEnabled = true; 1531 1532 /++ 1533 Adds the necessary pragmas to your application to use the Windows gui subsystem. 1534 If you mix this in above your `main` function, you no longer need to use the linker 1535 flags explicitly. It does the necessary version blocks for various compilers and runtimes. 1536 1537 It does nothing if not compiling for Windows, so you need not version it out yourself. 1538 1539 Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and 1540 stderr writeln. It will fail and throw an exception. 1541 1542 This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`. 1543 1544 History: 1545 Added November 24, 2021 (dub v10.4) 1546 +/ 1547 mixin template EnableWindowsSubsystem() { 1548 version(Windows) 1549 version(CRuntime_Microsoft) { 1550 pragma(linkerDirective, "/subsystem:windows"); 1551 version(LDC) 1552 pragma(linkerDirective, "/entry:wmainCRTStartup"); 1553 else 1554 pragma(linkerDirective, "/entry:mainCRTStartup"); 1555 } 1556 } 1557 1558 1559 /++ 1560 After selecting a type from [WindowTypes], you may further customize 1561 its behavior by setting one or more of these flags. 1562 1563 1564 The different window types have different meanings of `normal`. If the 1565 window type already is a good match for what you want to do, you should 1566 just use [WindowFlags.normal], the default, which will do the right thing 1567 for your users. 1568 1569 The window flags will not always be honored by the operating system 1570 and window managers; they are hints, not commands. 1571 +/ 1572 enum WindowFlags : int { 1573 normal = 0, /// 1574 skipTaskbar = 1, /// 1575 alwaysOnTop = 2, /// 1576 alwaysOnBottom = 4, /// 1577 cannotBeActivated = 8, /// 1578 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. 1579 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. 1580 /++ 1581 Sets the window as a short-lived child of its parent, but unlike an ordinary child, 1582 it is still a top-level window. This should NOT be set separately for most window types. 1583 1584 A transient window will not keep the application open if its main window closes. 1585 1586 $(PITFALL This may not be correctly implemented and its behavior is subject to change.) 1587 1588 1589 From the ICCM: 1590 1591 $(BLOCKQUOTE 1592 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. 1593 1594 $(CITE https://tronche.com/gui/x/icccm/sec-4.html) 1595 ) 1596 1597 So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag. 1598 1599 History: 1600 Added February 23, 2021 but not yet stabilized. 1601 +/ 1602 transient = 64, 1603 /++ 1604 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. 1605 1606 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 1607 1608 History: 1609 Added April 1, 2022 1610 +/ 1611 managesChildWindowFocus = 128, 1612 1613 /++ 1614 +/ 1615 overrideRedirect = 256, 1616 1617 dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually. 1618 } 1619 1620 /++ 1621 When creating a window, you can pass a type to SimpleWindow's constructor, 1622 then further customize the window by changing `WindowFlags`. 1623 1624 1625 You should mostly only need [normal], [undecorated], and [eventOnly] for normal 1626 use. The others are there to build a foundation for a higher level GUI toolkit, 1627 but are themselves not as high level as you might think from their names. 1628 1629 This list is based on the EMWH spec for X11. 1630 http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896 1631 +/ 1632 enum WindowTypes : int { 1633 /// An ordinary application window. 1634 normal, 1635 /// 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. 1636 undecorated, 1637 /// 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. 1638 eventOnly, 1639 /// A drop down menu, such as from a menu bar 1640 dropdownMenu, 1641 /// A popup menu, such as from a right click 1642 popupMenu, 1643 /// A popup bubble notification 1644 notification, 1645 /* 1646 menu, /// a tearable menu bar (not override-redirect - contrast to popups) 1647 toolbar, /// a tearable menu bar (not override-redirect) 1648 splashScreen, /// a loading splash screen for your application 1649 desktop, /// 1650 dockOrPanel, /// think taskbar 1651 utility, /// a palette or something 1652 */ 1653 /// A tiny window showing temporary help text or something. 1654 tooltip, 1655 /// only supported on X; will assert fail elsewhere 1656 dnd, 1657 /// can also be used for other auto-complete presentations 1658 comboBoxDropdown, 1659 /// a dialog box of some sort 1660 dialog, 1661 /// a child nested inside the parent. You must pass a parent window to the ctor 1662 nestedChild, 1663 1664 /++ 1665 The type you get when you pass in an existing browser handle, which means most 1666 of simpledisplay's fancy things will not be done since they were never set up. 1667 1668 Using this to the main SimpleWindow constructor explicitly will trigger an assertion 1669 failure; you should use the existing handle constructor. 1670 1671 History: 1672 Added November 17, 2022 (previously it would have type `normal`) 1673 +/ 1674 minimallyWrapped 1675 } 1676 1677 1678 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call 1679 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features 1680 private __gshared char* sdpyWindowClassStr = null; 1681 private __gshared bool sdpyOpenGLContextAllowFallback = false; 1682 1683 /** 1684 Set OpenGL context version to use. This has no effect on non-OpenGL windows. 1685 You may want to change context version if you want to use advanced shaders or 1686 other modern OpenGL techinques. This setting doesn't affect already created 1687 windows. You may use version 2.1 as your default, which should be supported 1688 by any box since 2006, so seems to be a reasonable choice. 1689 1690 Note that by default version is set to `0`, which forces SimpleDisplay to use 1691 old context creation code without any version specified. This is the safest 1692 way to init OpenGL, but it may not give you access to advanced features. 1693 1694 See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL 1695 */ 1696 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); } 1697 1698 /** 1699 Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed 1700 pipeline functions, and without "compatible" mode you won't be able to use 1701 your old non-shader-based code with such contexts. By default SimpleDisplay 1702 creates compatible context, so you can gradually upgrade your OpenGL code if 1703 you want to (or leave it as is, as it should "just work"). 1704 */ 1705 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; } 1706 1707 /** 1708 Set to `true` to allow creating OpenGL context with lower version than requested 1709 instead of throwing. If fallback was activated (or legacy OpenGL was requested), 1710 `openGLContextFallbackActivated()` will return `true`. 1711 */ 1712 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; } 1713 1714 /** 1715 After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context. 1716 */ 1717 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); } 1718 1719 /++ 1720 History: 1721 Added April 24, 2023 (dub v11.0) 1722 +/ 1723 version(without_opengl) {} else 1724 auto openGLCurrentContext() { 1725 version(Windows) 1726 return wglGetCurrentContext(); 1727 else 1728 return glXGetCurrentContext(); 1729 } 1730 1731 1732 /** 1733 Set window class name for all following `new SimpleWindow()` calls. 1734 1735 WARNING! For Windows, you should set your class name before creating any 1736 window, and NEVER change it after that! 1737 */ 1738 void sdpyWindowClass (const(char)[] v) { 1739 import core.stdc.stdlib : realloc; 1740 if (v.length == 0) v = "SimpleDisplayWindow"; 1741 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1); 1742 if (sdpyWindowClassStr is null) return; // oops 1743 sdpyWindowClassStr[0..v.length+1] = 0; 1744 sdpyWindowClassStr[0..v.length] = v[]; 1745 } 1746 1747 /** 1748 Get current window class name. 1749 */ 1750 string sdpyWindowClass () @trusted { 1751 if (sdpyWindowClassStr is null) return null; 1752 foreach (immutable idx; 0..size_t.max-1) { 1753 if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup; 1754 } 1755 return null; 1756 } 1757 1758 /++ 1759 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. 1760 1761 If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0. 1762 +/ 1763 float[2] getDpi() { 1764 float[2] dpi; 1765 version(Windows) { 1766 HDC screen = GetDC(null); 1767 dpi[0] = GetDeviceCaps(screen, LOGPIXELSX); 1768 dpi[1] = GetDeviceCaps(screen, LOGPIXELSY); 1769 } else version(X11) { 1770 auto display = XDisplayConnection.get; 1771 auto screen = DefaultScreen(display); 1772 1773 void fallback() { 1774 /+ 1775 // 25.4 millimeters in an inch... 1776 dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4; 1777 dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4; 1778 +/ 1779 1780 // the physical size isn't actually as important as the logical size since this is 1781 // all about scaling really 1782 dpi[0] = 96; 1783 dpi[1] = 96; 1784 } 1785 1786 auto xft = getXftDpi(); 1787 if(xft is float.init) 1788 fallback(); 1789 else { 1790 dpi[0] = xft; 1791 dpi[1] = xft; 1792 } 1793 } 1794 1795 return dpi; 1796 } 1797 1798 version(X11) 1799 float getXftDpi() { 1800 auto display = XDisplayConnection.get; 1801 1802 char* resourceString = XResourceManagerString(display); 1803 XrmInitialize(); 1804 1805 if (resourceString) { 1806 auto db = XrmGetStringDatabase(resourceString); 1807 XrmValue value; 1808 char* type; 1809 if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) { 1810 if (value.addr) { 1811 import core.stdc.stdlib; 1812 return atof(cast(char*) value.addr); 1813 } 1814 } 1815 } 1816 1817 return float.init; 1818 } 1819 1820 /++ 1821 Implementation used by [SimpleWindow.takeScreenshot]. 1822 1823 Params: 1824 handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen. 1825 width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target. 1826 height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target. 1827 x = the x-offset of the image to capture, from the left. 1828 y = the y-offset of the image to capture, from the top. 1829 1830 History: 1831 Added on March 14, 2021 1832 1833 Documented public on September 23, 2021 with full support for null params (dub 10.3) 1834 1835 +/ 1836 TrueColorImage trueColorImageFromNativeHandle(PaintingHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) { 1837 TrueColorImage got; 1838 version(X11) { 1839 auto display = XDisplayConnection.get; 1840 if(handle == 0) 1841 handle = RootWindow(display, DefaultScreen(display)); 1842 1843 if(width == 0 || height == 0) { 1844 Window root; 1845 int xpos, ypos; 1846 uint widthret, heightret, borderret, depthret; 1847 XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret); 1848 1849 if(width == 0) 1850 width = widthret; 1851 if(height == 0) 1852 height = heightret; 1853 } 1854 1855 auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap); 1856 1857 // https://github.com/adamdruppe/arsd/issues/98 1858 1859 auto i = new Image(image); 1860 got = i.toTrueColorImage(); 1861 1862 XDestroyImage(image); 1863 } else version(Windows) { 1864 auto hdc = GetDC(handle); 1865 scope(exit) ReleaseDC(handle, hdc); 1866 1867 if(width == 0 || height == 0) { 1868 BITMAP bmHeader; 1869 auto bm = GetCurrentObject(hdc, OBJ_BITMAP); 1870 GetObject(bm, BITMAP.sizeof, &bmHeader); 1871 if(width == 0) 1872 width = bmHeader.bmWidth; 1873 if(height == 0) 1874 height = bmHeader.bmHeight; 1875 } 1876 1877 auto i = new Image(width, height); 1878 HDC hdcMem = CreateCompatibleDC(hdc); 1879 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 1880 BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY); 1881 SelectObject(hdcMem, hbmOld); 1882 DeleteDC(hdcMem); 1883 1884 got = i.toTrueColorImage(); 1885 } else featureNotImplemented(); 1886 1887 return got; 1888 } 1889 1890 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE); 1891 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow; 1892 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi; 1893 1894 version(Windows) 1895 shared static this() { 1896 auto lib = LoadLibrary("User32.dll"); 1897 if(lib is null) 1898 return; 1899 //scope(exit) 1900 //FreeLibrary(lib); 1901 1902 SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext"); 1903 1904 if(SetProcessDpiAwarenessContext is null) 1905 return; 1906 1907 enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4; 1908 if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { 1909 //writeln(GetLastError()); 1910 } 1911 1912 GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow"); 1913 SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi"); 1914 } 1915 1916 /++ 1917 Blocking mode for event loop calls associated with a window instance. 1918 1919 History: 1920 Added December 8, 2021 (dub v10.5). Prior to that, all calls to 1921 `window.eventLoop` were the same as calls to `EventLoop.get.run`; that 1922 is, all would block until the application quit. 1923 1924 That behavior can still be achieved here with `untilApplicationQuits`, 1925 or explicitly calling the top-level `EventLoop.get.run` function. 1926 +/ 1927 enum BlockingMode { 1928 /++ 1929 The event loop call will block until the whole application is ready 1930 to quit if it is the only one running, but if it is nested inside 1931 another one, it will only block until the window you're calling it on 1932 closes. 1933 +/ 1934 automatic = 0x00, 1935 /++ 1936 The event loop call will only return when the whole application 1937 is ready to quit. This usually means all windows have been closed. 1938 1939 This is appropriate for your main application event loop. 1940 +/ 1941 untilApplicationQuits = 0x01, 1942 /++ 1943 The event loop will return when the window you're calling it on 1944 closes. If there are other windows still open, they may be destroyed 1945 unless you have another event loop running later. 1946 1947 This might be appropriate for a modal dialog box loop. Remember that 1948 other windows are still processing input though, so you can end up 1949 with a lengthy call stack if this happens in a loop, similar to a 1950 recursive function (well, it literally is a recursive function, just 1951 not an obvious looking one). 1952 +/ 1953 untilWindowCloses = 0x02, 1954 /++ 1955 If an event loop is already running, this call will immediately 1956 return, allowing the existing loop to handle it. If not, this call 1957 will block until the condition you bitwise-or into the flag. 1958 1959 The default is to block until the application quits, same as with 1960 the `automatic` setting (since if it were nested, which triggers until 1961 window closes in automatic, this flag would instead not block at all), 1962 but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`, 1963 it will only nest until the window closes. You might want that if you are 1964 going to open two windows simultaneously and want closing just one of them 1965 to trigger the event loop return. 1966 +/ 1967 onlyIfNotNested = 0x10, 1968 } 1969 1970 /++ 1971 Window corner visuals preference 1972 +/ 1973 enum CornerStyle { 1974 /++ 1975 Use the default style automatically applied by the system or its window manager/compositor. 1976 +/ 1977 automatic, 1978 1979 /++ 1980 Prefer rectangular window corners 1981 +/ 1982 rectangular, 1983 1984 /++ 1985 Prefer rounded window corners 1986 +/ 1987 rounded, 1988 1989 /++ 1990 Prefer slightly-rounded window corners 1991 +/ 1992 roundedSlightly, 1993 } 1994 1995 /++ 1996 The flagship window class. 1997 1998 1999 SimpleWindow tries to make ordinary windows very easy to create and use without locking you 2000 out of more advanced or complex features of the underlying windowing system. 2001 2002 For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")` 2003 and get a suitable window to work with. 2004 2005 From there, you can opt into additional features, like custom resizability and OpenGL support 2006 with the next two constructor arguments. Or, if you need even more, you can set a window type 2007 and customization flags with the final two constructor arguments. 2008 2009 If none of that works for you, you can also create a window using native function calls, then 2010 wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember, 2011 though, if you do this, managing the window is still your own responsibility! Notably, you 2012 will need to destroy it yourself. 2013 +/ 2014 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { 2015 2016 /++ 2017 Copies the window's current state into a [TrueColorImage]. 2018 2019 Be warned: this can be a very slow operation 2020 2021 History: 2022 Actually implemented on March 14, 2021 2023 +/ 2024 TrueColorImage takeScreenshot() { 2025 version(Windows) 2026 return trueColorImageFromNativeHandle(impl.hwnd, _width, _height); 2027 else version(OSXCocoa) 2028 throw new NotYetImplementedException(); 2029 else 2030 return trueColorImageFromNativeHandle(impl.window, _width, _height); 2031 } 2032 2033 /++ 2034 Returns the actual logical DPI for the window on its current display monitor. If the window 2035 straddles monitors, it will return the value of one or the other in a platform-defined manner. 2036 2037 Please note this function may return zero if it doesn't know the answer! 2038 2039 2040 On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10), 2041 or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up). 2042 2043 On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems, 2044 this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the 2045 window primarily resides on by checking the center point of the window against the monitor map. 2046 2047 Returns: 2048 0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It 2049 assumes the X and Y dpi are the same. 2050 2051 History: 2052 Added November 26, 2021 (dub v10.4) 2053 2054 It said "physical dpi" in the description prior to July 29, 2022, but the behavior was 2055 always a logical value on Windows and usually one on Linux too, so now the docs reflect 2056 that. 2057 2058 Bugs: 2059 Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically 2060 just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to 2061 set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor 2062 and 1.5 on the secondary monitor. 2063 2064 The local dpi is not necessarily related to the physical dpi of the monitor. The name 2065 is a historical misnomer - the real thing of interest is the scale factor and due to 2066 compatibility concerns the scale would modify dpi values to trick applications. But since 2067 that's the terminology common out there, I used it too. 2068 2069 See_Also: 2070 [getDpi] gives the value provided for the default monitor. Not necessarily the same 2071 as this since the window many be on a different monitor, but it is a reasonable fallback 2072 to use if `actualDpi` returns 0. 2073 2074 [onDpiChanged] is changed when `actualDpi` has changed. 2075 +/ 2076 int actualDpi() { 2077 version(X11) bool useFallbackDpi = false; 2078 if(!actualDpiLoadAttempted) { 2079 // FIXME: do the actual monitor we are on 2080 // and on X this is a good chance to load the monitor map. 2081 version(Windows) { 2082 if(GetDpiForWindow) 2083 actualDpi_ = GetDpiForWindow(impl.hwnd); 2084 } else version(X11) { 2085 if(!xRandrInfoLoadAttemped) { 2086 xRandrInfoLoadAttemped = true; 2087 if(!XRandrLibrary.attempted) { 2088 XRandrLibrary.loadDynamicLibrary(); 2089 } 2090 2091 if(XRandrLibrary.loadSuccessful) { 2092 auto display = XDisplayConnection.get; 2093 int scratch; 2094 int major, minor; 2095 if(!XRRQueryExtension(display, &xrrEventBase, &scratch)) 2096 goto fallback; 2097 2098 XRRQueryVersion(display, &major, &minor); 2099 if(major <= 1 && minor < 5) 2100 goto fallback; 2101 2102 int count; 2103 XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count); 2104 if(monitors is null) 2105 goto fallback; 2106 scope(exit) XRRFreeMonitors(monitors); 2107 2108 MonitorInfo.info = MonitorInfo.info[0 .. 0]; 2109 MonitorInfo.info.assumeSafeAppend(); 2110 foreach(idx, monitor; monitors[0 .. count]) { 2111 MonitorInfo.info ~= MonitorInfo( 2112 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 2113 Size(monitor.mwidth, monitor.mheight), 2114 cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0]) 2115 ); 2116 2117 /+ 2118 if(monitor.mwidth == 0 || monitor.mheight == 0) 2119 // unknown physical size, just guess 96 to avoid divide by zero 2120 MonitorInfo.info ~= MonitorInfo( 2121 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 2122 Size(monitor.mwidth, monitor.mheight), 2123 96 2124 ); 2125 else 2126 // and actual thing 2127 MonitorInfo.info ~= MonitorInfo( 2128 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 2129 Size(monitor.mwidth, monitor.mheight), 2130 minInternal( 2131 // millimeter to int then rounding up. 2132 cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5), 2133 cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5) 2134 ) 2135 ); 2136 +/ 2137 } 2138 // writeln("Here", MonitorInfo.info); 2139 } 2140 } 2141 2142 if(XRandrLibrary.loadSuccessful) { 2143 updateActualDpi(true); 2144 // writeln("updated"); 2145 2146 if(!requestedInput) { 2147 // this is what requests live updates should the configuration change 2148 // each time you select input, it sends an initial event, so very important 2149 // to not get into a loop of selecting input, getting event, updating data, 2150 // and reselecting input... 2151 requestedInput = true; 2152 XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask); 2153 // writeln("requested input"); 2154 } 2155 } else { 2156 fallback: 2157 // make sure we disable events that aren't coming 2158 xrrEventBase = -1; 2159 // best guess... respect the custom scaling user command to some extent at least though 2160 useFallbackDpi = true; 2161 } 2162 } else version(OSXCocoa) { 2163 actualDpi_ = cast(int)(96 * customScalingFactorForMonitor(0)); // FIXME 2164 } 2165 actualDpiLoadAttempted = true; 2166 } else version(X11) if(MonitorInfo.info.length == 0) { 2167 useFallbackDpi = true; 2168 } 2169 2170 version(X11) 2171 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... 2172 actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0)); 2173 return actualDpi_; 2174 } 2175 2176 private int actualDpi_; 2177 private bool actualDpiLoadAttempted; 2178 2179 version(X11) private { 2180 bool requestedInput; 2181 static bool xRandrInfoLoadAttemped; 2182 struct MonitorInfo { 2183 Rectangle position; 2184 Size size; 2185 int dpi; 2186 2187 static MonitorInfo[] info; 2188 } 2189 bool screenPositionKnown; 2190 int screenPositionX; 2191 int screenPositionY; 2192 void updateActualDpi(bool loadingNow = false) { 2193 if(!loadingNow && !actualDpiLoadAttempted) 2194 actualDpi(); // just to make it do the load 2195 foreach(idx, m; MonitorInfo.info) { 2196 if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) { 2197 bool changed = actualDpi_ && actualDpi_ != m.dpi; 2198 actualDpi_ = m.dpi; 2199 // writeln("monitor ", idx); 2200 if(changed && onDpiChanged) 2201 onDpiChanged(); 2202 break; 2203 } 2204 } 2205 } 2206 } 2207 2208 /++ 2209 Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors 2210 or if the window is moved to a new remote connection or a monitor is hot-swapped. 2211 2212 History: 2213 Added November 26, 2021 (dub v10.4) 2214 2215 See_Also: 2216 [actualDpi] 2217 +/ 2218 void delegate() onDpiChanged; 2219 2220 version(X11) { 2221 void recreateAfterDisconnect() { 2222 if(!stateDiscarded) return; 2223 2224 if(_parent !is null && _parent.stateDiscarded) 2225 _parent.recreateAfterDisconnect(); 2226 2227 bool wasHidden = hidden; 2228 2229 activeScreenPainter = null; // should already be done but just to confirm 2230 2231 actualDpi_ = 0; 2232 actualDpiLoadAttempted = false; 2233 xRandrInfoLoadAttemped = false; 2234 2235 impl.createWindow(_width, _height, _title, openglMode, _parent); 2236 2237 if(auto dh = dropHandler) { 2238 dropHandler = null; 2239 enableDragAndDrop(this, dh); 2240 } 2241 2242 if(recreateAdditionalConnectionState) 2243 recreateAdditionalConnectionState(); 2244 2245 hidden = wasHidden; 2246 stateDiscarded = false; 2247 } 2248 2249 bool stateDiscarded; 2250 void discardConnectionState() { 2251 if(XDisplayConnection.display) 2252 impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway 2253 if(discardAdditionalConnectionState) 2254 discardAdditionalConnectionState(); 2255 stateDiscarded = true; 2256 } 2257 2258 void delegate() discardAdditionalConnectionState; 2259 void delegate() recreateAdditionalConnectionState; 2260 2261 } 2262 2263 private DropHandler dropHandler; 2264 2265 SimpleWindow _parent; 2266 bool beingOpenKeepsAppOpen = true; 2267 /++ 2268 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. 2269 2270 The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them. 2271 2272 Params: 2273 2274 width = the width of the window's client area, in pixels 2275 height = the height of the window's client area, in pixels 2276 title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property. 2277 opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window. 2278 resizable = [Resizability] has three options: 2279 $(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.) 2280 $(P `fixedSize` will not allow the user to resize the window.) 2281 $(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.) 2282 windowType = The type of window you want to make. 2283 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. 2284 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". 2285 +/ 2286 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) { 2287 claimGuiThread(); 2288 version(sdpy_thread_checks) assert(thisIsGuiThread); 2289 this._width = this._virtualWidth = width; 2290 this._height = this._virtualHeight = height; 2291 this.openglMode = opengl; 2292 version(X11) { 2293 // auto scale not implemented except with opengl and even there it is kinda weird 2294 if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no) 2295 resizable = Resizability.fixedSize; 2296 } 2297 this.resizability = resizable; 2298 this.windowType = windowType; 2299 this.customizationFlags = customizationFlags; 2300 this._title = (title is null ? "D Application" : title); 2301 this._parent = parent; 2302 impl.createWindow(width, height, this._title, opengl, parent); 2303 2304 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient)) 2305 beingOpenKeepsAppOpen = false; 2306 } 2307 2308 /// ditto 2309 this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 2310 this(width, height, title, opengl, resizable, windowType, customizationFlags, parent); 2311 } 2312 2313 /// Same as above, except using the `Size` struct instead of separate width and height. 2314 this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) { 2315 this(size.width, size.height, title, opengl, resizable); 2316 } 2317 2318 /// ditto 2319 this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) { 2320 this(size, title, opengl, resizable); 2321 } 2322 2323 2324 /++ 2325 Creates a window based on the given [Image]. It's client area 2326 width and height is equal to the image. (A window's client area 2327 is the drawable space inside; it excludes the title bar, etc.) 2328 2329 Windows based on images will not be resizable and do not use OpenGL. 2330 2331 It will draw the image in upon creation, but this will be overwritten 2332 upon any draws, including the initial window visible event. 2333 2334 You probably do not want to use this and it may be removed from 2335 the library eventually, or I might change it to be a "permanent" 2336 background image; one that is automatically drawn on it before any 2337 other drawing event. idk. 2338 +/ 2339 this(Image image, string title = null) { 2340 this(image.width, image.height, title); 2341 this.image = image; 2342 } 2343 2344 /++ 2345 Wraps a native window handle with very little additional processing - notably no destruction 2346 this is incomplete so don't use it for much right now. The purpose of this is to make native 2347 windows created through the low level API (so you can use platform-specific options and 2348 other details SimpleWindow does not expose) available to the event loop wrappers. 2349 +/ 2350 this(NativeWindowHandle nativeWindow) { 2351 windowType = WindowTypes.minimallyWrapped; 2352 version(Windows) 2353 impl.hwnd = nativeWindow; 2354 else version(X11) { 2355 impl.window = nativeWindow; 2356 if(nativeWindow) 2357 display = XDisplayConnection.get(); // get initial display to not segfault 2358 } else version(Emscripten) { 2359 // FIXME 2360 } else version(OSXCocoa) { 2361 if(nativeWindow !is NullWindow) throw new NotYetImplementedException(); 2362 } else featureNotImplemented(); 2363 // FIXME: set the size correctly 2364 _width = 1; 2365 _height = 1; 2366 if(nativeWindow) 2367 nativeMapping[cast(void*) nativeWindow] = this; 2368 2369 beingOpenKeepsAppOpen = false; 2370 2371 if(nativeWindow) 2372 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 2373 _suppressDestruction = true; // so it doesn't try to close 2374 } 2375 2376 /++ 2377 Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created. 2378 The delegate will be called when the window manager asks you to take focus. 2379 2380 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 2381 2382 History: 2383 Added April 1, 2022 (dub v10.8) 2384 +/ 2385 SimpleWindow delegate() setRequestedInputFocus; 2386 2387 /// Experimental, do not use yet 2388 /++ 2389 Grabs exclusive input from the user until you release it with 2390 [releaseInputGrab]. 2391 2392 2393 Note: it is extremely rude to do this without good reason. 2394 Reasons may include doing some kind of mouse drag operation 2395 or popping up a temporary menu that should get events and will 2396 be dismissed at ease by the user clicking away. 2397 2398 Params: 2399 keyboard = do you want to grab keyboard input? 2400 mouse = grab mouse input? 2401 confine = confine the mouse cursor to inside this window? 2402 2403 History: 2404 Prior to March 11, 2021, grabbing the keyboard would always also 2405 set the X input focus. Now, it only focuses if it is a non-transient 2406 window and otherwise manages the input direction internally. 2407 2408 This means spurious focus/blur events will no longer be sent and the 2409 application will not steal focus from other applications (which the 2410 window manager may have rejected anyway). 2411 +/ 2412 void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) { 2413 static if(UsingSimpledisplayX11) { 2414 XSync(XDisplayConnection.get, 0); 2415 if(keyboard) { 2416 if(isTransient && _parent) { 2417 /* 2418 FIXME: 2419 setting the keyboard focus is not actually that helpful, what I more likely want 2420 is the events from the parent window to be sent over here if we're transient. 2421 */ 2422 2423 _parent.inputProxy = this; 2424 } else { 2425 2426 SimpleWindow setTo; 2427 if(setRequestedInputFocus !is null) 2428 setTo = setRequestedInputFocus(); 2429 if(setTo is null) 2430 setTo = this; 2431 2432 // sdpyPrintDebugString("grabInput() ", setTo.impl.window; 2433 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2434 } 2435 } 2436 if(mouse) { 2437 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 2438 EventMask.PointerMotionMask // FIXME: not efficient 2439 | EventMask.ButtonPressMask 2440 | EventMask.ButtonReleaseMask 2441 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 2442 ) 2443 { 2444 XSync(XDisplayConnection.get, 0); 2445 import core.stdc.stdio; 2446 printf("Grab input failed %d\n", res); 2447 //throw new Exception("Grab input failed"); 2448 } else { 2449 // cool 2450 } 2451 } 2452 2453 } else version(Windows) { 2454 // FIXME: keyboard? 2455 SetCapture(impl.hwnd); 2456 if(confine) { 2457 RECT rcClip; 2458 //RECT rcOldClip; 2459 //GetClipCursor(&rcOldClip); 2460 GetWindowRect(hwnd, &rcClip); 2461 ClipCursor(&rcClip); 2462 } 2463 } else version(Emscripten) { 2464 // nothing necessary 2465 } else version(OSXCocoa) { 2466 // throw new NotYetImplementedException(); 2467 } else static assert(0); 2468 } 2469 2470 private Point imePopupLocation = Point(0, 0); 2471 2472 /++ 2473 Sets the location for the IME (input method editor) to pop up when the user activates it. 2474 2475 Bugs: 2476 Not implemented outside X11. 2477 +/ 2478 void setIMEPopupLocation(Point location) { 2479 static if(UsingSimpledisplayX11) { 2480 imePopupLocation = location; 2481 updateIMEPopupLocation(); 2482 } else { 2483 // this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least 2484 // throw new NotYetImplementedException(); 2485 } 2486 } 2487 2488 /// ditto 2489 void setIMEPopupLocation(int x, int y) { 2490 return setIMEPopupLocation(Point(x, y)); 2491 } 2492 2493 // we need to remind XIM of where we wanted to place the IME whenever the window moves 2494 // so this function gets called in setIMEPopupLocation as well as whenever the window 2495 // receives a ConfigureNotify event 2496 private void updateIMEPopupLocation() { 2497 static if(UsingSimpledisplayX11) { 2498 if (xic is null) { 2499 return; 2500 } 2501 2502 XPoint nspot; 2503 nspot.x = cast(short) imePopupLocation.x; 2504 nspot.y = cast(short) imePopupLocation.y; 2505 XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null); 2506 XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null); 2507 XFree(preeditAttr); 2508 } 2509 } 2510 2511 private bool imeFocused = true; 2512 2513 /++ 2514 Tells the IME whether or not an input field is currently focused in the window. 2515 2516 Bugs: 2517 Not implemented outside X11. 2518 +/ 2519 void setIMEFocused(bool value) { 2520 imeFocused = value; 2521 updateIMEFocused(); 2522 } 2523 2524 // used to focus/unfocus the IC if necessary when the window gains/loses focus 2525 private void updateIMEFocused() { 2526 static if(UsingSimpledisplayX11) { 2527 if (xic is null) { 2528 return; 2529 } 2530 2531 if (focused && imeFocused) { 2532 XSetICFocus(xic); 2533 } else { 2534 XUnsetICFocus(xic); 2535 } 2536 } 2537 } 2538 2539 /++ 2540 Returns the native window. 2541 2542 History: 2543 Added November 5, 2021 (dub v10.4). Prior to that, you'd have 2544 to access it through the `impl` member (which is semi-supported 2545 but platform specific and here it is simple enough to offer an accessor). 2546 2547 Bugs: 2548 Not implemented outside Windows or X11. 2549 +/ 2550 NativeWindowHandle nativeWindowHandle() { 2551 version(X11) 2552 return impl.window; 2553 else version(Windows) 2554 return impl.hwnd; 2555 else 2556 throw new NotYetImplementedException(); 2557 } 2558 2559 private bool isTransient() { 2560 with(WindowTypes) 2561 final switch(windowType) { 2562 case normal, undecorated, eventOnly: 2563 case nestedChild, minimallyWrapped: 2564 return (customizationFlags & WindowFlags.transient) ? true : false; 2565 case dropdownMenu, popupMenu, notification, dialog, tooltip, dnd, comboBoxDropdown: 2566 return true; 2567 } 2568 } 2569 2570 private SimpleWindow inputProxy; 2571 2572 /++ 2573 Releases the grab acquired by [grabInput]. 2574 +/ 2575 void releaseInputGrab() { 2576 static if(UsingSimpledisplayX11) { 2577 XUngrabPointer(XDisplayConnection.get, CurrentTime); 2578 if(_parent) 2579 _parent.inputProxy = null; 2580 } else version(Windows) { 2581 ReleaseCapture(); 2582 ClipCursor(null); 2583 } else version(OSXCocoa) { 2584 // throw new NotYetImplementedException(); 2585 } else version(Emscripten) { 2586 // nothing needed 2587 } else static assert(0); 2588 } 2589 2590 /++ 2591 Sets the input focus to this window. 2592 2593 You shouldn't call this very often - please let the user control the input focus. 2594 +/ 2595 void focus() { 2596 static if(UsingSimpledisplayX11) { 2597 SimpleWindow setTo; 2598 if(setRequestedInputFocus !is null) 2599 setTo = setRequestedInputFocus(); 2600 if(setTo is null) 2601 setTo = this; 2602 // sdpyPrintDebugString("sdpy.focus() ", setTo.impl.window); 2603 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2604 } else version(Windows) { 2605 SetFocus(this.impl.hwnd); 2606 } else version(Emscripten) { 2607 throw new NotYetImplementedException(); 2608 } else version(OSXCocoa) { 2609 throw new NotYetImplementedException(); 2610 } else static assert(0); 2611 } 2612 2613 /++ 2614 Requests attention from the user for this window. 2615 2616 2617 The typical result of this function is to change the color 2618 of the taskbar icon, though it may be tweaked on specific 2619 platforms. 2620 2621 It is meant to unobtrusively tell the user that something 2622 relevant to them happened in the background and they should 2623 check the window when they get a chance. Upon receiving the 2624 keyboard focus, the window will automatically return to its 2625 natural state. 2626 2627 If the window already has the keyboard focus, this function 2628 may do nothing, because the user is presumed to already be 2629 giving the window attention. 2630 2631 Implementation_note: 2632 2633 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 2634 atom on X11 and the FlashWindow function on Windows. 2635 +/ 2636 void requestAttention() { 2637 if(_focused) 2638 return; 2639 2640 version(Windows) { 2641 FLASHWINFO info; 2642 info.cbSize = info.sizeof; 2643 info.hwnd = impl.hwnd; 2644 info.dwFlags = FLASHW_TRAY; 2645 info.uCount = 1; 2646 2647 FlashWindowEx(&info); 2648 2649 } else version(X11) { 2650 demandingAttention = true; 2651 demandAttention(this, true); 2652 } else version(Emscripten) { 2653 throw new NotYetImplementedException(); 2654 } else version(OSXCocoa) { 2655 throw new NotYetImplementedException(); 2656 } else static assert(0); 2657 } 2658 2659 private bool _focused; 2660 2661 version(X11) private bool demandingAttention; 2662 2663 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 2664 /// You'll have to call `close()` manually if you set this delegate. 2665 void delegate () closeQuery; 2666 2667 /// This will be called when window visibility was changed. 2668 void delegate (bool becomesVisible) visibilityChanged; 2669 2670 /// This will be called when window becomes visible for the first time. 2671 /// You can do OpenGL initialization here. Note that in X11 you can't call 2672 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 2673 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 2674 /// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization. 2675 private bool _visibleForTheFirstTimeCalled; 2676 void delegate () visibleForTheFirstTime; 2677 2678 /// Returns true if the window has been closed. 2679 final @property bool closed() { return _closed; } 2680 2681 private final @property bool notClosed() { return !_closed; } 2682 2683 /// Returns true if the window is focused. 2684 final @property bool focused() { return _focused; } 2685 2686 private bool _visible; 2687 /// Returns true if the window is visible (mapped). 2688 final @property bool visible() { return _visible; } 2689 2690 /// Closes the window. If there are no more open windows, the event loop will terminate. 2691 void close() { 2692 if (!_closed) { 2693 runInGuiThread( { 2694 if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued 2695 if (onClosing !is null) onClosing(); 2696 impl.closeWindow(); 2697 _closed = true; 2698 } ); 2699 } 2700 } 2701 2702 /++ 2703 `close` is one of the few methods that can be called from other threads. This `shared` overload reflects that. 2704 2705 History: 2706 Overload added on March 7, 2021. 2707 +/ 2708 void close() shared { 2709 (cast() this).close(); 2710 } 2711 2712 /++ 2713 2714 +/ 2715 void maximize() { 2716 version(Windows) 2717 ShowWindow(impl.hwnd, SW_MAXIMIZE); 2718 else version(X11) { 2719 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get)); 2720 2721 // also note _NET_WM_STATE_FULLSCREEN 2722 } 2723 2724 } 2725 2726 private bool _fullscreen; 2727 version(Windows) 2728 private WINDOWPLACEMENT g_wpPrev; 2729 2730 /// not fully implemented but planned for a future release 2731 void fullscreen(bool yes) { 2732 version(Windows) { 2733 g_wpPrev.length = WINDOWPLACEMENT.sizeof; 2734 DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE); 2735 if (dwStyle & WS_OVERLAPPEDWINDOW) { 2736 MONITORINFO mi; 2737 mi.cbSize = MONITORINFO.sizeof; 2738 if (GetWindowPlacement(hwnd, &g_wpPrev) && 2739 GetMonitorInfo(MonitorFromWindow(hwnd, 2740 MONITOR_DEFAULTTOPRIMARY), &mi)) { 2741 SetWindowLong(hwnd, GWL_STYLE, 2742 dwStyle & ~WS_OVERLAPPEDWINDOW); 2743 SetWindowPos(hwnd, HWND_TOP, 2744 mi.rcMonitor.left, mi.rcMonitor.top, 2745 mi.rcMonitor.right - mi.rcMonitor.left, 2746 mi.rcMonitor.bottom - mi.rcMonitor.top, 2747 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2748 } 2749 } else { 2750 SetWindowLong(hwnd, GWL_STYLE, 2751 dwStyle | WS_OVERLAPPEDWINDOW); 2752 SetWindowPlacement(hwnd, &g_wpPrev); 2753 SetWindowPos(hwnd, null, 0, 0, 0, 0, 2754 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | 2755 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2756 } 2757 2758 } else version(X11) { 2759 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes); 2760 } 2761 2762 _fullscreen = yes; 2763 2764 } 2765 2766 bool fullscreen() { 2767 return _fullscreen; 2768 } 2769 2770 /++ 2771 Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead. 2772 2773 +/ 2774 void minimize() { 2775 version(Windows) 2776 ShowWindow(impl.hwnd, SW_MINIMIZE); 2777 //else version(X11) 2778 //setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true); 2779 } 2780 2781 /// Alias for `hidden = false` 2782 void show() { 2783 hidden = false; 2784 } 2785 2786 /// Alias for `hidden = true` 2787 void hide() { 2788 hidden = true; 2789 } 2790 2791 /// Hide cursor when it enters the window. 2792 void hideCursor() { 2793 version(OSXCocoa) throw new NotYetImplementedException(); else 2794 if (!_closed) impl.hideCursor(); 2795 } 2796 2797 /// Don't hide cursor when it enters the window. 2798 void showCursor() { 2799 version(OSXCocoa) throw new NotYetImplementedException(); else 2800 if (!_closed) impl.showCursor(); 2801 } 2802 2803 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 2804 * 2805 * Please remember that the cursor is a shared resource that should usually be left to the user's 2806 * control. Try to think for other approaches before using this function. 2807 * 2808 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 2809 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 2810 * receive "mouse moved here" event. 2811 */ 2812 bool warpMouse (int x, int y) { 2813 version(X11) { 2814 if (!_closed) { impl.warpMouse(x, y); return true; } 2815 } else version(Windows) { 2816 if (!_closed) { 2817 POINT point; 2818 point.x = x; 2819 point.y = y; 2820 if(ClientToScreen(impl.hwnd, &point)) { 2821 SetCursorPos(point.x, point.y); 2822 return true; 2823 } 2824 } 2825 } 2826 return false; 2827 } 2828 2829 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 2830 void sendDummyEvent () { 2831 version(X11) { 2832 if (!_closed) { impl.sendDummyEvent(); } 2833 } 2834 } 2835 2836 /// Set window minimal size. 2837 void setMinSize (int minwidth, int minheight) { 2838 version(OSXCocoa) throw new NotYetImplementedException(); else 2839 if (!_closed) impl.setMinSize(minwidth, minheight); 2840 } 2841 2842 /// Set window maximal size. 2843 void setMaxSize (int maxwidth, int maxheight) { 2844 version(OSXCocoa) throw new NotYetImplementedException(); else 2845 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 2846 } 2847 2848 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 2849 /// Currently only supported on X11. 2850 void setResizeGranularity (int granx, int grany) { 2851 version(OSXCocoa) throw new NotYetImplementedException(); else 2852 if (!_closed) impl.setResizeGranularity(granx, grany); 2853 } 2854 2855 /// Move window. 2856 void move(int x, int y) { 2857 version(OSXCocoa) throw new NotYetImplementedException(); else 2858 if (!_closed) impl.move(x, y); 2859 } 2860 2861 /// ditto 2862 void move(Point p) { 2863 version(OSXCocoa) throw new NotYetImplementedException(); else 2864 if (!_closed) impl.move(p.x, p.y); 2865 } 2866 2867 /++ 2868 Resize window. 2869 2870 Note that the width and height of the window are NOT instantly 2871 updated - it waits for the window manager to approve the resize 2872 request, which means you must return to the event loop before the 2873 width and height are actually changed. 2874 +/ 2875 void resize(int w, int h) { 2876 if(!_closed && _fullscreen) fullscreen = false; 2877 if (!_closed) impl.resize(w, h); 2878 } 2879 2880 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 2881 void moveResize (int x, int y, int w, int h) { 2882 if(!_closed && _fullscreen) fullscreen = false; 2883 if (!_closed) impl.moveResize(x, y, w, h); 2884 } 2885 2886 private bool _hidden; 2887 2888 /// Returns true if the window is hidden. 2889 final @property bool hidden() { 2890 return _hidden; 2891 } 2892 2893 /// Shows or hides the window based on the bool argument. 2894 final @property void hidden(bool b) { 2895 _hidden = b; 2896 version(Windows) { 2897 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 2898 } else version(X11) { 2899 if(b) 2900 //XUnmapWindow(impl.display, impl.window); 2901 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 2902 else 2903 XMapWindow(impl.display, impl.window); 2904 } else version(OSXCocoa) { 2905 impl.window.setIsVisible = !b; 2906 if(!hidden) 2907 impl.view.setNeedsDisplay(true); 2908 } else version(Emscripten) { 2909 } else static assert(0); 2910 } 2911 2912 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 2913 void opacity(double opacity) @property 2914 in { 2915 assert(opacity >= 0 && opacity <= 1); 2916 } do { 2917 version (Windows) { 2918 impl.setOpacity(cast(ubyte)(255 * opacity)); 2919 } else version (X11) { 2920 impl.setOpacity(cast(uint)(uint.max * opacity)); 2921 } else throw new NotYetImplementedException(); 2922 } 2923 2924 /++ 2925 Sets your event handlers, without entering the event loop. Useful if you 2926 have multiple windows - set the handlers on each window, then only do 2927 [eventLoop] on your main window or call `EventLoop.get.run();`. 2928 2929 This assigns the given handlers to [handleKeyEvent], [handleCharEvent], 2930 [handlePulse], and [handleMouseEvent] automatically based on the provide 2931 delegate signatures. 2932 +/ 2933 void setEventHandlers(T...)(T eventHandlers) { 2934 // FIXME: add more events 2935 foreach(handler; eventHandlers) { 2936 static if(__traits(compiles, handleKeyEvent = handler)) { 2937 handleKeyEvent = handler; 2938 } else static if(__traits(compiles, handleCharEvent = handler)) { 2939 handleCharEvent = handler; 2940 } else static if(__traits(compiles, handlePulse = handler)) { 2941 handlePulse = handler; 2942 } else static if(__traits(compiles, handleMouseEvent = handler)) { 2943 handleMouseEvent = handler; 2944 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 2945 } 2946 } 2947 2948 /++ 2949 The event loop automatically returns when the window is closed 2950 pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 2951 pulse timer is created. The event loop will block until an event 2952 arrives or the pulse timer goes off. 2953 2954 The given `eventHandlers` are passed to [setEventHandlers], which in turn 2955 assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and 2956 [handleMouseEvent], based on the signature of delegates you provide. 2957 2958 Give one with no parameters to set a timer pulse handler. Give one that 2959 takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler, 2960 and one that takes `dchar` for a char event handler. You can use as many 2961 or as few handlers as you need for your application. 2962 2963 Bugs: 2964 2965 $(PITFALL 2966 You should always have one event loop live for your application. 2967 If you make two windows in sequence, the second call to eventLoop 2968 might fail: 2969 2970 --- 2971 // don't do this! 2972 auto window = new SimpleWindow(); 2973 window.eventLoop(0); 2974 2975 auto window2 = new SimpleWindow(); 2976 window2.eventLoop(0); // problematic! might crash 2977 --- 2978 2979 simpledisplay's current implementation assumes that final cleanup is 2980 done when the event loop refcount reaches zero. So after the first 2981 eventLoop returns, when there isn't already another one active, it assumes 2982 the program will exit soon and cleans up. 2983 2984 This is arguably a bug that it doesn't reinitialize, and I'll probably change 2985 it eventually, but in the mean time, there's an easy solution: 2986 2987 --- 2988 // do this 2989 EventLoop mainEventLoop = EventLoop.get; // just add this line 2990 2991 auto window = new SimpleWindow(); 2992 window.eventLoop(0); 2993 2994 auto window2 = new SimpleWindow(); 2995 window2.eventLoop(0); // perfectly fine since mainEventLoop still alive 2996 --- 2997 2998 By adding a top-level reference to the event loop, it ensures the final cleanup 2999 is not performed until it goes out of scope too, letting the individual window loops 3000 work without trouble despite the bug. 3001 ) 3002 3003 History: 3004 The overload without `pulseTimeout` was added on December 8, 2021. 3005 3006 On December 9, 2021, the default blocking mode (which is now configurable 3007 because [eventLoopWithBlockingMode] was added) switched from 3008 [BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This 3009 should almost never be noticeable to you since the typical simpledisplay 3010 paradigm has been (and I still recommend) to have one `eventLoop` call. 3011 3012 See_Also: 3013 [eventLoopWithBlockingMode] 3014 +/ 3015 final int eventLoop(T...)( 3016 long pulseTimeout, /// set to zero if you don't want a pulse. 3017 T eventHandlers) /// delegate list like std.concurrency.receive 3018 { 3019 return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers); 3020 } 3021 3022 /// ditto 3023 final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate)) 3024 { 3025 return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers); 3026 } 3027 3028 /++ 3029 This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`. 3030 3031 History: 3032 Added December 8, 2021 (dub v10.5) 3033 3034 Previously, this implementation was right inside [eventLoop], but when I wanted 3035 to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I 3036 just renamed it instead of adding as an overload. Besides, the new name makes it 3037 easier to remember the order and avoids ambiguity between two int-like params anyway. 3038 3039 See_Also: 3040 [SimpleWindow.eventLoop], [EventLoop] 3041 3042 Bugs: 3043 The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop. 3044 +/ 3045 final int eventLoopWithBlockingMode(T...)( 3046 BlockingMode blockingMode, /// when you want this function to block until 3047 long pulseTimeout, /// set to zero if you don't want a pulse. 3048 T eventHandlers) /// delegate list like std.concurrency.receive 3049 { 3050 setEventHandlers(eventHandlers); 3051 3052 version(with_eventloop) { 3053 // delegates event loop to my other module 3054 version(X11) 3055 XFlush(display); 3056 3057 import arsd.eventloop; 3058 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 3059 scope(exit) clearInterval(handle); 3060 3061 loop(); 3062 return 0; 3063 } else { 3064 EventLoop el = EventLoop(pulseTimeout, handlePulse); 3065 3066 if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1) 3067 return 0; 3068 3069 return el.run( 3070 ((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ? 3071 null : 3072 &this.notClosed 3073 ); 3074 } 3075 } 3076 3077 /++ 3078 This lets you draw on the window (or its backing buffer) using basic 3079 2D primitives. 3080 3081 Be sure to call this in a limited scope because your changes will not 3082 actually appear on the window until ScreenPainter's destructor runs. 3083 3084 Returns: an instance of [ScreenPainter], which has the drawing methods 3085 on it to draw on this window. 3086 3087 Params: 3088 manualInvalidations = if you set this to true, you will need to 3089 set the invalid rectangle on the painter yourself. If false, it 3090 assumes the whole window has been redrawn each time you draw. 3091 3092 Only invalidated rectangles are blitted back to the window when 3093 the destructor runs. Doing this yourself can reduce flickering 3094 of child windows. 3095 3096 History: 3097 The `manualInvalidations` parameter overload was added on 3098 December 30, 2021 (dub v10.5) 3099 +/ 3100 ScreenPainter draw() { 3101 return draw(false); 3102 } 3103 /// ditto 3104 ScreenPainter draw(bool manualInvalidations) { 3105 return impl.getPainter(manualInvalidations); 3106 } 3107 3108 // This is here to implement the interface we use for various native handlers. 3109 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 3110 3111 // maps native window handles to SimpleWindow instances, if there are any 3112 // you shouldn't need this, but it is public in case you do in a native event handler or something 3113 // mac uses void* cuz NSObject opHash won't pick up in typeinfo 3114 version(OSXCocoa) 3115 public __gshared SimpleWindow[void*] nativeMapping; 3116 else 3117 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 3118 3119 // the size the user requested in the constructor, in automatic scale modes it always pretends to be this size 3120 private int _virtualWidth; 3121 private int _virtualHeight; 3122 3123 /// Width of the window's drawable client area, in pixels. 3124 @scriptable 3125 final @property int width() const pure nothrow @safe @nogc { 3126 if(resizability == Resizability.automaticallyScaleIfPossible) 3127 return _virtualWidth; 3128 else 3129 return _width; 3130 } 3131 3132 /// Height of the window's drawable client area, in pixels. 3133 @scriptable 3134 final @property int height() const pure nothrow @safe @nogc { 3135 if(resizability == Resizability.automaticallyScaleIfPossible) 3136 return _virtualHeight; 3137 else 3138 return _height; 3139 } 3140 3141 /++ 3142 Returns the actual size of the window, bypassing the logical 3143 illusions of [Resizability.automaticallyScaleIfPossible]. 3144 3145 History: 3146 Added November 11, 2022 (dub v10.10) 3147 +/ 3148 final @property Size actualWindowSize() const pure nothrow @safe @nogc { 3149 return Size(_width, _height); 3150 } 3151 3152 3153 private int _width; 3154 private int _height; 3155 3156 // HACK: making the best of some copy constructor woes with refcounting 3157 private ScreenPainterImplementation* activeScreenPainter_; 3158 3159 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 3160 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 3161 3162 private OpenGlOptions openglMode; 3163 private Resizability resizability; 3164 private WindowTypes windowType; 3165 private int customizationFlags; 3166 3167 /// `true` if OpenGL was initialized for this window. 3168 @property bool isOpenGL () const pure nothrow @safe @nogc { 3169 version(without_opengl) 3170 return false; 3171 else 3172 return (openglMode == OpenGlOptions.yes); 3173 } 3174 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 3175 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 3176 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 3177 3178 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 3179 /// to call this, as it's not recommended to share window between threads. 3180 void mtLock () { 3181 version(X11) { 3182 XLockDisplay(this.display); 3183 } 3184 } 3185 3186 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 3187 /// to call this, as it's not recommended to share window between threads. 3188 void mtUnlock () { 3189 version(X11) { 3190 XUnlockDisplay(this.display); 3191 } 3192 } 3193 3194 /// Emit a beep to get user's attention. 3195 void beep () { 3196 version(X11) { 3197 XBell(this.display, 100); 3198 } else version(Windows) { 3199 MessageBeep(0xFFFFFFFF); 3200 } 3201 } 3202 3203 3204 3205 version(without_opengl) {} else { 3206 3207 /// 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`. 3208 void delegate() redrawOpenGlScene; 3209 3210 /// This will allow you to change OpenGL vsync state. 3211 final @property void vsync (bool wait) { 3212 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3213 version(X11) { 3214 setAsCurrentOpenGlContext(); 3215 glxSetVSync(display, impl.window, wait); 3216 } else version(Windows) { 3217 setAsCurrentOpenGlContext(); 3218 wglSetVSync(wait); 3219 } 3220 } 3221 3222 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 3223 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 3224 /// enough without waiting 'em to finish their frame business. 3225 bool useGLFinish = true; 3226 3227 // FIXME: it should schedule it for the end of the current iteration of the event loop... 3228 /// 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. 3229 void redrawOpenGlSceneNow() { 3230 version(X11) if (!this._visible) return; // no need to do this if window is invisible 3231 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3232 if(redrawOpenGlScene is null) 3233 return; 3234 3235 this.mtLock(); 3236 scope(exit) this.mtUnlock(); 3237 3238 this.setAsCurrentOpenGlContext(); 3239 3240 redrawOpenGlScene(); 3241 3242 this.swapOpenGlBuffers(); 3243 // 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. 3244 if (useGLFinish) glFinish(); 3245 } 3246 3247 private bool redrawOpenGlSceneSoonSet = false; 3248 private static class RedrawOpenGlSceneEvent { 3249 SimpleWindow w; 3250 this(SimpleWindow w) { this.w = w; } 3251 } 3252 private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent; 3253 /++ 3254 Queues an opengl redraw as soon as the other pending events are cleared. 3255 +/ 3256 void redrawOpenGlSceneSoon() { 3257 if(redrawOpenGlScene is null) 3258 return; 3259 3260 if(!redrawOpenGlSceneSoonSet) { 3261 redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this); 3262 this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); }); 3263 redrawOpenGlSceneSoonSet = true; 3264 } 3265 this.postEvent(redrawOpenGlSceneEvent, true); 3266 } 3267 3268 3269 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3270 void setAsCurrentOpenGlContext() { 3271 assert(openglMode == OpenGlOptions.yes); 3272 version(X11) { 3273 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 3274 throw new Exception("glXMakeCurrent"); 3275 } else version(Windows) { 3276 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3277 if (!wglMakeCurrent(ghDC, ghRC)) 3278 throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too 3279 } 3280 } 3281 3282 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3283 /// This doesn't throw, returning success flag instead. 3284 bool setAsCurrentOpenGlContextNT() nothrow { 3285 assert(openglMode == OpenGlOptions.yes); 3286 version(X11) { 3287 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 3288 } else version(Windows) { 3289 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3290 return wglMakeCurrent(ghDC, ghRC) ? true : false; 3291 } 3292 } 3293 3294 /// 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. 3295 /// This doesn't throw, returning success flag instead. 3296 bool releaseCurrentOpenGlContext() nothrow { 3297 assert(openglMode == OpenGlOptions.yes); 3298 version(X11) { 3299 return (glXMakeCurrent(display, 0, null) != 0); 3300 } else version(Windows) { 3301 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3302 return wglMakeCurrent(ghDC, null) ? true : false; 3303 } 3304 } 3305 3306 /++ 3307 simpledisplay always uses double buffering, usually automatically. This 3308 manually swaps the OpenGL buffers. You should only use this if you are NOT 3309 using the [redrawOpenGlScene] delegate. 3310 3311 3312 You must not this yourself if you use [redrawOpenGlScene] because simpledisplay will do it 3313 for you after calling your `redrawOpenGlScene`. Please note that once you swap 3314 buffers, the contents become undefined - the implementation, in the OpenGL driver 3315 or the desktop compositor, may not actually just swap two buffers. The back buffer's 3316 contents are $(B undefined) after calling this function. 3317 3318 See: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-swapbuffers 3319 and https://linux.die.net/man/3/glxswapbuffers 3320 3321 Remember that this may throw an exception, which you can catch in a multithreaded 3322 application to keep your thread from dying from an unhandled exception. 3323 +/ 3324 void swapOpenGlBuffers() { 3325 assert(openglMode == OpenGlOptions.yes); 3326 version(X11) { 3327 if (!this._visible) return; // no need to do this if window is invisible 3328 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3329 glXSwapBuffers(display, impl.window); 3330 } else version(Windows) { 3331 SwapBuffers(ghDC); 3332 } 3333 } 3334 } 3335 3336 /++ 3337 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 3338 3339 3340 --- 3341 auto window = new SimpleWindow(100, 100, "First title"); 3342 window.title = "A new title"; 3343 --- 3344 3345 You may call this function at any time. 3346 +/ 3347 @property void title(string title) { 3348 _title = title; 3349 impl.setTitle(title); 3350 } 3351 3352 private string _title; 3353 3354 /// Gets the title 3355 @property string title() { 3356 if(_title is null) 3357 _title = getRealTitle(); 3358 return _title; 3359 } 3360 3361 /++ 3362 Get the title as set by the window manager. 3363 May not match what you attempted to set. 3364 +/ 3365 string getRealTitle() { 3366 static if(is(typeof(impl.getTitle()))) 3367 return impl.getTitle(); 3368 else 3369 return null; 3370 } 3371 3372 // don't use this generally it is not yet really released 3373 version(X11) 3374 @property Image secret_icon() { 3375 return secret_icon_inner; 3376 } 3377 private Image secret_icon_inner; 3378 3379 3380 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing. 3381 @property void icon(MemoryImage icon) { 3382 if(icon is null) 3383 return; 3384 auto tci = icon.getAsTrueColorImage(); 3385 version(Windows) { 3386 winIcon = new WindowsIcon(icon); 3387 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 3388 } else version(X11) { 3389 secret_icon_inner = Image.fromMemoryImage(icon); 3390 // FIXME: ensure this is correct 3391 auto display = XDisplayConnection.get; 3392 arch_ulong[] buffer; 3393 buffer ~= icon.width; 3394 buffer ~= icon.height; 3395 foreach(c; tci.imageData.colors) { 3396 arch_ulong b; 3397 b |= c.a << 24; 3398 b |= c.r << 16; 3399 b |= c.g << 8; 3400 b |= c.b; 3401 buffer ~= b; 3402 } 3403 3404 XChangeProperty( 3405 display, 3406 impl.window, 3407 GetAtom!("_NET_WM_ICON", true)(display), 3408 GetAtom!"CARDINAL"(display), 3409 32 /* bits */, 3410 0 /*PropModeReplace*/, 3411 buffer.ptr, 3412 cast(int) buffer.length); 3413 } else version(OSXCocoa) { 3414 throw new NotYetImplementedException(); 3415 } else version(Emscripten) { 3416 throw new NotYetImplementedException(); 3417 } else static assert(0); 3418 } 3419 3420 version(Windows) 3421 private WindowsIcon winIcon; 3422 3423 bool _suppressDestruction; 3424 3425 ~this() { 3426 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 3427 if(_suppressDestruction) 3428 return; 3429 impl.dispose(); 3430 } 3431 3432 private bool _closed; 3433 3434 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 3435 /* 3436 ScreenPainter drawTransiently() { 3437 return impl.getPainter(); 3438 } 3439 */ 3440 3441 /// Draws an image on the window. This is meant to provide quick look 3442 /// of a static image generated elsewhere. 3443 @property void image(Image i) { 3444 /+ 3445 version(Windows) { 3446 BITMAP bm; 3447 HDC hdc = GetDC(hwnd); 3448 HDC hdcMem = CreateCompatibleDC(hdc); 3449 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 3450 3451 GetObject(i.handle, bm.sizeof, &bm); 3452 3453 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 3454 3455 SelectObject(hdcMem, hbmOld); 3456 DeleteDC(hdcMem); 3457 ReleaseDC(hwnd, hdc); 3458 3459 /* 3460 RECT r; 3461 r.right = i.width; 3462 r.bottom = i.height; 3463 InvalidateRect(hwnd, &r, false); 3464 */ 3465 } else 3466 version(X11) { 3467 if(!destroyed) { 3468 if(i.usingXshm) 3469 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 3470 else 3471 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 3472 } 3473 } else 3474 version(OSXCocoa) { 3475 draw().drawImage(Point(0, 0), i); 3476 setNeedsDisplay(view, true); 3477 } else static assert(0); 3478 +/ 3479 auto painter = this.draw; 3480 painter.drawImage(Point(0, 0), i); 3481 } 3482 3483 /++ 3484 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 3485 3486 --- 3487 window.cursor = GenericCursor.Help; 3488 // now the window mouse cursor is set to a generic help 3489 --- 3490 3491 +/ 3492 @property void cursor(MouseCursor cursor) { 3493 version(OSXCocoa) 3494 {} // featureNotImplemented(); 3495 else 3496 if(this.impl.curHidden <= 0) { 3497 static if(UsingSimpledisplayX11) { 3498 auto ch = cursor.cursorHandle; 3499 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 3500 } else version(Windows) { 3501 auto ch = cursor.cursorHandle; 3502 impl.currentCursor = ch; 3503 SetCursor(ch); // redraw without waiting for mouse movement to update 3504 } else featureNotImplemented(); 3505 } 3506 3507 } 3508 3509 /// What follows are the event handlers. These are set automatically 3510 /// by the eventLoop function, but are still public so you can change 3511 /// them later. wasPressed == true means key down. false == key up. 3512 3513 /// Handles a low-level keyboard event. Settable through setEventHandlers. 3514 void delegate(KeyEvent ke) handleKeyEvent; 3515 3516 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 3517 void delegate(dchar c) handleCharEvent; 3518 3519 /// Handles a timer pulse. Settable through setEventHandlers. 3520 void delegate() handlePulse; 3521 3522 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 3523 void delegate(bool) onFocusChange; 3524 3525 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 3526 * Sometimes it is easier to setup the delegate instead of subclassing. */ 3527 void delegate() onClosing; 3528 3529 /** Called when we received destroy notification. At this stage we cannot do much with our window 3530 * (as it is already dead, and it's native handle cannot be used), but we still can do some 3531 * last minute cleanup. */ 3532 void delegate() onDestroyed; 3533 3534 static if (UsingSimpledisplayX11) 3535 /** Called when Expose event comes. See Xlib manual to understand the arguments. 3536 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 3537 * You will probably never need to setup this handler, it is for very low-level stuff. 3538 * 3539 * WARNING! Xlib is multithread-locked when this handles is called! */ 3540 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 3541 3542 //version(Windows) 3543 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 3544 3545 private { 3546 int lastMouseX = int.min; 3547 int lastMouseY = int.min; 3548 void mdx(ref MouseEvent ev) { 3549 if(lastMouseX == int.min || lastMouseY == int.min) { 3550 ev.dx = 0; 3551 ev.dy = 0; 3552 } else { 3553 ev.dx = ev.x - lastMouseX; 3554 ev.dy = ev.y - lastMouseY; 3555 } 3556 3557 lastMouseX = ev.x; 3558 lastMouseY = ev.y; 3559 } 3560 } 3561 3562 /// Mouse event handler. Settable through setEventHandlers. 3563 void delegate(MouseEvent) handleMouseEvent; 3564 3565 /// use to redraw child widgets if you use system apis to add stuff 3566 void delegate() paintingFinished; 3567 3568 void delegate() paintingFinishedDg() { 3569 return paintingFinished; 3570 } 3571 3572 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 3573 /// for this to ever happen. 3574 void delegate(int width, int height) windowResized; 3575 3576 /++ 3577 Platform specific - handle any native message this window gets. 3578 3579 Note: this is called *in addition to* other event handlers, unless you either: 3580 3581 1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded. 3582 3583 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. 3584 3585 On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`. 3586 3587 On X, it takes the form of `int delegate(XEvent)`. 3588 3589 History: 3590 In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead. 3591 3592 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. 3593 +/ 3594 NativeEventHandler handleNativeEvent_; 3595 3596 @property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe { 3597 return handleNativeEvent_; 3598 } 3599 @property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe { 3600 handleNativeEvent_ = neh; 3601 } 3602 3603 version(Windows) 3604 // compatibility shim with the old deprecated way 3605 // in this one, if you return 0, it means you must return. otherwise the ret value is ignored. 3606 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) { 3607 handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) { 3608 auto ret = dg(h, m, w, l); 3609 if(ret == 0) 3610 r = 1; 3611 return ret; 3612 }; 3613 } 3614 3615 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 3616 /// If you used to use handleNativeEvent depending on it being static, just change it to use 3617 /// this instead and it will work the same way. 3618 __gshared NativeEventHandler handleNativeGlobalEvent; 3619 3620 // private: 3621 /// The native implementation is available, but you shouldn't use it unless you are 3622 /// familiar with the underlying operating system, don't mind depending on it, and 3623 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 3624 /// do what you need to do with handleNativeEvent instead. 3625 /// 3626 /// This is likely to eventually change to be just a struct holding platform-specific 3627 /// handles instead of a template mixin at some point because I'm not happy with the 3628 /// code duplication here (ironically). 3629 mixin NativeSimpleWindowImplementation!() impl; 3630 3631 /** 3632 This is in-process one-way (from anything to window) event sending mechanics. 3633 It is thread-safe, so it can be used in multi-threaded applications to send, 3634 for example, "wake up and repaint" events when thread completed some operation. 3635 This will allow to avoid using timer pulse to check events with synchronization, 3636 'cause event handler will be called in UI thread. You can stop guessing which 3637 pulse frequency will be enough for your app. 3638 Note that events handlers may be called in arbitrary order, i.e. last registered 3639 handler can be called first, and vice versa. 3640 */ 3641 public: 3642 /** Is our custom event queue empty? Can be used in simple cases to prevent 3643 * "spamming" window with events it can't cope with. 3644 * It is safe to call this from non-UI threads. 3645 */ 3646 @property bool eventQueueEmpty() () { 3647 synchronized(this) { 3648 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 3649 } 3650 return true; 3651 } 3652 3653 /** Does our custom event queue contains at least one with the given type? 3654 * Can be used in simple cases to prevent "spamming" window with events 3655 * it can't cope with. 3656 * It is safe to call this from non-UI threads. 3657 */ 3658 @property bool eventQueued(ET:Object) () { 3659 synchronized(this) { 3660 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 3661 if (!o.doProcess) { 3662 if (cast(ET)(o.evt)) return true; 3663 } 3664 } 3665 } 3666 return false; 3667 } 3668 3669 /++ 3670 Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds. 3671 3672 History: 3673 Added May 12, 2021 3674 +/ 3675 void delegate(Exception e) nothrow eventUncaughtException; 3676 3677 /** Add listener for custom event. Can be used like this: 3678 * 3679 * --------------------- 3680 * auto eid = win.addEventListener((MyStruct evt) { ... }); 3681 * ... 3682 * win.removeEventListener(eid); 3683 * --------------------- 3684 * 3685 * Returns: 0 on failure (should never happen, so ignore it) 3686 * 3687 * $(WARNING Don't use this method in object destructors!) 3688 * 3689 * $(WARNING It is better to register all event handlers and don't remove 'em, 3690 * 'cause if event handler id counter will overflow, you won't be able 3691 * to register any more events.) 3692 */ 3693 uint addEventListener(ET:Object) (void delegate (ET) dg) { 3694 if (dg is null) return 0; // ignore empty handlers 3695 synchronized(this) { 3696 //FIXME: abort on overflow? 3697 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 3698 EventHandlerEntry e; 3699 e.dg = delegate (Object o) { 3700 if (auto co = cast(ET)o) { 3701 try { 3702 dg(co); 3703 } catch (Exception e) { 3704 // sorry! 3705 if(eventUncaughtException) 3706 eventUncaughtException(e); 3707 } 3708 return true; 3709 } 3710 return false; 3711 }; 3712 e.id = lastUsedHandlerId; 3713 auto optr = eventHandlers.ptr; 3714 eventHandlers ~= e; 3715 if (eventHandlers.ptr !is optr) { 3716 import core.memory : GC; 3717 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 3718 } 3719 return lastUsedHandlerId; 3720 } 3721 } 3722 3723 /// Remove event listener. It is safe to pass invalid event id here. 3724 /// $(WARNING Don't use this method in object destructors!) 3725 void removeEventListener() (uint id) { 3726 if (id == 0 || id > lastUsedHandlerId) return; 3727 synchronized(this) { 3728 foreach (immutable idx; 0..eventHandlers.length) { 3729 if (eventHandlers[idx].id == id) { 3730 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 3731 eventHandlers[$-1].dg = null; 3732 eventHandlers.length -= 1; 3733 eventHandlers.assumeSafeAppend; 3734 return; 3735 } 3736 } 3737 } 3738 } 3739 3740 /// Post event to queue. It is safe to call this from non-UI threads. 3741 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 3742 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3743 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3744 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) { 3745 if (this.closed) return false; // closed windows can't handle events 3746 3747 // remove all events of type `ET` 3748 void removeAllET () { 3749 uint eidx = 0, ec = eventQueueUsed; 3750 auto eptr = eventQueue.ptr; 3751 while (eidx < ec) { 3752 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 3753 if (cast(ET)eptr.evt !is null) { 3754 // i found her! 3755 if (inCustomEventProcessor) { 3756 // if we're in custom event processing loop, processor will clear it for us 3757 eptr.evt = null; 3758 ++eidx; 3759 ++eptr; 3760 } else { 3761 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3762 ec = --eventQueueUsed; 3763 // clear last event (it is already copied) 3764 eventQueue.ptr[ec].evt = null; 3765 } 3766 } else { 3767 ++eidx; 3768 ++eptr; 3769 } 3770 } 3771 } 3772 3773 if (evt is null) { 3774 if (replace) { synchronized(this) removeAllET(); } 3775 // ignore empty events, they can't be handled anyway 3776 return false; 3777 } 3778 3779 // add events even if no event FD/event object created yet 3780 synchronized(this) { 3781 if (replace) removeAllET(); 3782 if (eventQueueUsed == uint.max) return false; // just in case 3783 if (eventQueueUsed < eventQueue.length) { 3784 eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs); 3785 } else { 3786 if (eventQueue.capacity == eventQueue.length) { 3787 // need to reallocate; do a trick to ensure that old array is cleared 3788 auto oarr = eventQueue; 3789 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3790 // just in case, do yet another check 3791 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 3792 import core.memory : GC; 3793 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 3794 } else { 3795 auto optr = eventQueue.ptr; 3796 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3797 assert(eventQueue.ptr is optr); 3798 } 3799 ++eventQueueUsed; 3800 assert(eventQueueUsed == eventQueue.length); 3801 } 3802 if (!eventWakeUp()) { 3803 // can't wake up event processor, so there is no reason to keep the event 3804 assert(eventQueueUsed > 0); 3805 eventQueue[--eventQueueUsed].evt = null; 3806 return false; 3807 } 3808 return true; 3809 } 3810 } 3811 3812 /// Post event to queue. It is safe to call this from non-UI threads. 3813 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3814 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3815 bool postEvent(ET:Object) (ET evt, bool replace=false) { 3816 return postTimeout!ET(evt, 0, replace); 3817 } 3818 3819 private: 3820 private import core.time : MonoTime; 3821 3822 version(Posix) { 3823 __gshared int customEventFDRead = -1; 3824 __gshared int customEventFDWrite = -1; 3825 __gshared int customSignalFD = -1; 3826 } else version(Windows) { 3827 __gshared HANDLE customEventH = null; 3828 } 3829 3830 // wake up event processor 3831 static bool eventWakeUp () { 3832 version(X11) { 3833 import core.sys.posix.unistd : write; 3834 ulong n = 1; 3835 if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof); 3836 return true; 3837 } else version(Windows) { 3838 if (customEventH !is null) SetEvent(customEventH); 3839 return true; 3840 } else version(OSXCocoa) { 3841 if(globalAppDelegate) 3842 globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false); 3843 return true; 3844 } else { 3845 // not implemented for other OSes 3846 return false; 3847 } 3848 } 3849 3850 static struct QueuedEvent { 3851 Object evt; 3852 bool timed = false; 3853 MonoTime hittime = MonoTime.zero; 3854 bool doProcess = false; // process event at the current iteration (internal flag) 3855 3856 this (Object aevt, uint toutmsecs) { 3857 evt = aevt; 3858 if (toutmsecs > 0) { 3859 import core.time : msecs; 3860 timed = true; 3861 hittime = MonoTime.currTime+toutmsecs.msecs; 3862 } 3863 } 3864 } 3865 3866 alias CustomEventHandler = bool delegate (Object o) nothrow; 3867 static struct EventHandlerEntry { 3868 CustomEventHandler dg; 3869 uint id; 3870 } 3871 3872 uint lastUsedHandlerId; 3873 EventHandlerEntry[] eventHandlers; 3874 QueuedEvent[] eventQueue = null; 3875 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 3876 bool inCustomEventProcessor = false; // required to properly remove events 3877 3878 // process queued events and call custom event handlers 3879 // this will not process events posted from called handlers (such events are postponed for the next iteration) 3880 void processCustomEvents () @system { 3881 bool hasSomethingToDo = false; 3882 uint ecount; 3883 bool ocep; 3884 synchronized(this) { 3885 ocep = inCustomEventProcessor; 3886 inCustomEventProcessor = true; 3887 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 3888 auto ctt = MonoTime.currTime; 3889 bool hasEmpty = false; 3890 // mark events to process (this is required for `eventQueued()`) 3891 foreach (ref qe; eventQueue[0..ecount]) { 3892 if (qe.evt is null) { hasEmpty = true; continue; } 3893 if (qe.timed) { 3894 qe.doProcess = (qe.hittime <= ctt); 3895 } else { 3896 qe.doProcess = true; 3897 } 3898 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 3899 } 3900 if (!hasSomethingToDo) { 3901 // remove empty events 3902 if (hasEmpty) { 3903 uint eidx = 0, ec = eventQueueUsed; 3904 auto eptr = eventQueue.ptr; 3905 while (eidx < ec) { 3906 if (eptr.evt is null) { 3907 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3908 ec = --eventQueueUsed; 3909 eventQueue.ptr[ec].evt = null; // make GC life easier 3910 } else { 3911 ++eidx; 3912 ++eptr; 3913 } 3914 } 3915 } 3916 inCustomEventProcessor = ocep; 3917 return; 3918 } 3919 } 3920 // process marked events 3921 uint efree = 0; // non-processed events will be put at this index 3922 EventHandlerEntry[] eh; 3923 Object evt; 3924 foreach (immutable eidx; 0..ecount) { 3925 synchronized(this) { 3926 if (!eventQueue[eidx].doProcess) { 3927 // skip this event 3928 assert(efree <= eidx); 3929 if (efree != eidx) { 3930 // copy this event to queue start 3931 eventQueue[efree] = eventQueue[eidx]; 3932 eventQueue[eidx].evt = null; // just in case 3933 } 3934 ++efree; 3935 continue; 3936 } 3937 evt = eventQueue[eidx].evt; 3938 eventQueue[eidx].evt = null; // in case event handler will hit GC 3939 if (evt is null) continue; // just in case 3940 // try all handlers; this can be slow, but meh... 3941 eh = eventHandlers; 3942 } 3943 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 3944 evt = null; 3945 eh = null; 3946 } 3947 synchronized(this) { 3948 // move all unprocessed events to queue top; efree holds first "free index" 3949 foreach (immutable eidx; ecount..eventQueueUsed) { 3950 assert(efree <= eidx); 3951 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 3952 ++efree; 3953 } 3954 eventQueueUsed = efree; 3955 // wake up event processor on next event loop iteration if we have more queued events 3956 // also, remove empty events 3957 bool awaken = false; 3958 uint eidx = 0, ec = eventQueueUsed; 3959 auto eptr = eventQueue.ptr; 3960 while (eidx < ec) { 3961 if (eptr.evt is null) { 3962 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3963 ec = --eventQueueUsed; 3964 eventQueue.ptr[ec].evt = null; // make GC life easier 3965 } else { 3966 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 3967 ++eidx; 3968 ++eptr; 3969 } 3970 } 3971 inCustomEventProcessor = ocep; 3972 } 3973 } 3974 3975 // for all windows in nativeMapping 3976 package static void processAllCustomEvents () @system { 3977 3978 cleanupQueue.process(); 3979 3980 justCommunication.processCustomEvents(); 3981 3982 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3983 if (sw is null || sw.closed) continue; 3984 sw.processCustomEvents(); 3985 } 3986 3987 runPendingRunInGuiThreadDelegates(); 3988 } 3989 3990 // 0: infinite (i.e. no scheduled events in queue) 3991 uint eventQueueTimeoutMSecs () { 3992 synchronized(this) { 3993 if (eventQueueUsed == 0) return 0; 3994 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3995 uint res = int.max; 3996 auto ctt = MonoTime.currTime; 3997 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 3998 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3999 if (qe.doProcess) continue; // just in case 4000 if (!qe.timed) return 1; // minimal 4001 if (qe.hittime <= ctt) return 1; // minimal 4002 auto tms = (qe.hittime-ctt).total!"msecs"; 4003 if (tms < 1) tms = 1; // safety net 4004 if (tms >= int.max) tms = int.max-1; // and another safety net 4005 if (res > tms) res = cast(uint)tms; 4006 } 4007 return (res >= int.max ? 0 : res); 4008 } 4009 } 4010 4011 // for all windows in nativeMapping 4012 static uint eventAllQueueTimeoutMSecs () { 4013 uint res = uint.max; 4014 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 4015 if (sw is null || sw.closed) continue; 4016 uint to = sw.eventQueueTimeoutMSecs(); 4017 if (to && to < res) { 4018 res = to; 4019 if (to == 1) break; // can't have less than this 4020 } 4021 } 4022 return (res >= int.max ? 0 : res); 4023 } 4024 4025 version(X11) { 4026 ResizeEvent pendingResizeEvent; 4027 } 4028 4029 /++ 4030 When in opengl mode and automatically resizing, it will set the opengl viewport to stretch. 4031 4032 If you work with multiple opengl contexts and/or threads, this might be more trouble than it is 4033 worth so you can disable it by setting this to `true`. 4034 4035 History: 4036 Added November 13, 2022. 4037 +/ 4038 public bool suppressAutoOpenglViewport = false; 4039 private void updateOpenglViewportIfNeeded(int width, int height) { 4040 if(suppressAutoOpenglViewport) return; 4041 4042 version(without_opengl) {} else 4043 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 4044 // writeln(width, " ", height); 4045 setAsCurrentOpenGlContextNT(); 4046 glViewport(0, 0, width, height); 4047 } 4048 } 4049 4050 // TODO: Implement on non-Windows platforms (where available). 4051 private CornerStyle _fauxCornerStyle = CornerStyle.automatic; 4052 4053 /++ 4054 Style of the window's corners 4055 4056 $(WARNING 4057 Currently only implemented on Windows targets. 4058 Has no visual effect elsewhere. 4059 4060 Windows: Requires Windows 11 or later. 4061 ) 4062 4063 History: 4064 Added September 09, 2024. 4065 +/ 4066 public CornerStyle cornerStyle() @trusted { 4067 version(Windows) { 4068 DWM_WINDOW_CORNER_PREFERENCE dwmCorner; 4069 const apiResult = DwmGetWindowAttribute( 4070 this.hwnd, 4071 DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, 4072 &dwmCorner, 4073 typeof(dwmCorner).sizeof 4074 ); 4075 4076 if (apiResult != S_OK) { 4077 // Unsupported? 4078 if (apiResult == E_INVALIDARG) { 4079 // Feature unsupported; Windows version probably too old. 4080 // Requires Windows 11 (build 22000) or later. 4081 return _fauxCornerStyle; 4082 } 4083 4084 throw new WindowsApiException("DwmGetWindowAttribute", apiResult); 4085 } 4086 4087 CornerStyle corner; 4088 if (!dwmCorner.fromDWM(corner)) { 4089 throw ArsdException!"DwmGetWindowAttribute unfamiliar corner preference"(dwmCorner); 4090 } 4091 return corner; 4092 } else { 4093 return _fauxCornerStyle; 4094 } 4095 } 4096 4097 /// ditto 4098 public void cornerStyle(const CornerStyle corner) @trusted { 4099 version(Windows) { 4100 DWM_WINDOW_CORNER_PREFERENCE dwmCorner; 4101 if (!corner.toDWM(dwmCorner)) { 4102 assert(false, "This should have been impossible because of a final switch."); 4103 } 4104 4105 const apiResult = DwmSetWindowAttribute( 4106 this.hwnd, 4107 DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, 4108 &dwmCorner, 4109 typeof(dwmCorner).sizeof 4110 ); 4111 4112 if (apiResult != S_OK) { 4113 // Unsupported? 4114 if (apiResult == E_INVALIDARG) { 4115 // Feature unsupported; Windows version probably too old. 4116 // Requires Windows 11 (build 22000) or later. 4117 _fauxCornerStyle = corner; 4118 return; 4119 } 4120 4121 throw new WindowsApiException("DwmSetWindowAttribute", apiResult); 4122 } 4123 } else { 4124 _fauxCornerStyle = corner; 4125 } 4126 } 4127 } 4128 4129 version(OSXCocoa) 4130 enum NSWindow NullWindow = null; 4131 else 4132 enum NullWindow = NativeWindowHandle.init; 4133 4134 /++ 4135 Magic pseudo-window for just posting events to a global queue. 4136 4137 Not entirely supported, I might delete it at any time. 4138 4139 Added Nov 5, 2021. 4140 +/ 4141 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow); 4142 4143 /* Drag and drop support { */ 4144 version(X11) { 4145 4146 } else version(Windows) { 4147 import core.sys.windows.uuid; 4148 import core.sys.windows.ole2; 4149 import core.sys.windows.oleidl; 4150 import core.sys.windows.objidl; 4151 import core.sys.windows.wtypes; 4152 4153 pragma(lib, "ole32"); 4154 void initDnd() { 4155 auto err = OleInitialize(null); 4156 if(err != S_OK && err != S_FALSE) 4157 throw new Exception("init");//err); 4158 } 4159 } 4160 /* } End drag and drop support */ 4161 4162 4163 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 4164 /// See [GenericCursor]. 4165 class MouseCursor { 4166 int osId; 4167 bool isStockCursor; 4168 private this(int osId) { 4169 this.osId = osId; 4170 this.isStockCursor = true; 4171 } 4172 4173 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 4174 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 4175 4176 version(Windows) { 4177 HCURSOR cursor_; 4178 HCURSOR cursorHandle() { 4179 if(cursor_ is null) 4180 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 4181 return cursor_; 4182 } 4183 4184 } else static if(UsingSimpledisplayX11) { 4185 Cursor cursor_ = None; 4186 int xDisplaySequence; 4187 4188 Cursor cursorHandle() { 4189 if(this.osId == None) 4190 return None; 4191 4192 // we need to reload if we on a new X connection 4193 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 4194 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 4195 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 4196 } 4197 return cursor_; 4198 } 4199 } 4200 } 4201 4202 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 4203 // https://tronche.com/gui/x/xlib/appendix/b/ 4204 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 4205 /// 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. 4206 enum GenericCursorType { 4207 Default, /// The default arrow pointer. 4208 Wait, /// A cursor indicating something is loading and the user must wait. 4209 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 4210 Help, /// A cursor indicating the user can get help about the pointer location. 4211 Cross, /// A crosshair. 4212 Text, /// An i-beam shape, typically used to indicate text selection is possible. 4213 Move, /// Pointer indicating movement is possible. May also be used as SizeAll. 4214 UpArrow, /// An arrow pointing straight up. 4215 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 4216 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 4217 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator). 4218 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator). 4219 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator). 4220 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator). 4221 4222 } 4223 4224 /* 4225 X_plus == css cell == Windows ? 4226 */ 4227 4228 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 4229 static struct GenericCursor { 4230 static: 4231 /// 4232 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 4233 static MouseCursor mc; 4234 4235 auto type = __traits(getMember, GenericCursorType, str); 4236 4237 if(mc is null) { 4238 4239 version(Windows) { 4240 int osId; 4241 final switch(type) { 4242 case GenericCursorType.Default: osId = IDC_ARROW; break; 4243 case GenericCursorType.Wait: osId = IDC_WAIT; break; 4244 case GenericCursorType.Hand: osId = IDC_HAND; break; 4245 case GenericCursorType.Help: osId = IDC_HELP; break; 4246 case GenericCursorType.Cross: osId = IDC_CROSS; break; 4247 case GenericCursorType.Text: osId = IDC_IBEAM; break; 4248 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 4249 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 4250 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 4251 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 4252 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 4253 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 4254 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 4255 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 4256 } 4257 } else static if(UsingSimpledisplayX11) { 4258 int osId; 4259 final switch(type) { 4260 case GenericCursorType.Default: osId = None; break; 4261 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 4262 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 4263 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 4264 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 4265 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 4266 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 4267 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 4268 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 4269 4270 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 4271 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 4272 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 4273 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 4274 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 4275 } 4276 4277 } else { 4278 int osId; 4279 // featureNotImplemented(); 4280 } 4281 4282 mc = new MouseCursor(osId); 4283 } 4284 return mc; 4285 } 4286 } 4287 4288 4289 /++ 4290 If you want to get more control over the event loop, you can use this. 4291 4292 Typically though, you can just call [SimpleWindow.eventLoop] which forwards 4293 to `EventLoop.get.run`. 4294 +/ 4295 struct EventLoop { 4296 @disable this(); 4297 4298 /// Gets a reference to an existing event loop 4299 static EventLoop get() { 4300 return EventLoop(0, null); 4301 } 4302 4303 static void quitApplication() { 4304 static if(use_arsd_core) { 4305 import arsd.core; 4306 ICoreEventLoop.exitApplication(); 4307 } 4308 EventLoop.get().exit(); 4309 } 4310 4311 private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi 4312 4313 /// Construct an application-global event loop for yourself 4314 /// See_Also: [SimpleWindow.setEventHandlers] 4315 this(long pulseTimeout, void delegate() handlePulse) { 4316 synchronized(monitor) { 4317 if(impl is null) { 4318 claimGuiThread(); 4319 version(sdpy_thread_checks) assert(thisIsGuiThread); 4320 impl = new EventLoopImpl(pulseTimeout, handlePulse); 4321 } else { 4322 if(pulseTimeout) { 4323 impl.pulseTimeout = pulseTimeout; 4324 impl.handlePulse = handlePulse; 4325 } 4326 } 4327 impl.refcount++; 4328 } 4329 } 4330 4331 ~this() { 4332 if(impl is null) 4333 return; 4334 impl.refcount--; 4335 if(impl.refcount == 0) { 4336 impl.dispose(); 4337 if(thisIsGuiThread) 4338 guiThreadFinalize(); 4339 } 4340 4341 } 4342 4343 this(this) { 4344 if(impl is null) 4345 return; 4346 impl.refcount++; 4347 } 4348 4349 /// Runs the event loop until the whileCondition, if present, returns false 4350 int run(bool delegate() whileCondition = null) { 4351 assert(impl !is null); 4352 impl.notExited = true; 4353 return impl.run(whileCondition); 4354 } 4355 4356 /// Exits the event loop, but allows you to reenter it again later (in contrast with quitApplication, which tries to terminate the program) 4357 void exit() { 4358 assert(impl !is null); 4359 impl.notExited = false; 4360 4361 static if(use_arsd_core) { 4362 import arsd.core; 4363 ICoreEventLoop.exitApplication(); 4364 } 4365 } 4366 4367 version(linux) 4368 ref void delegate(int) signalHandler() { 4369 assert(impl !is null); 4370 return impl.signalHandler; 4371 } 4372 4373 __gshared static EventLoopImpl* impl; 4374 } 4375 4376 version(linux) 4377 void delegate(int, int) globalHupHandler; 4378 4379 version(Posix) 4380 void makeNonBlocking(int fd) { 4381 import fcntl = core.sys.posix.fcntl; 4382 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 4383 if(flags == -1) 4384 throw new Exception("fcntl get"); 4385 flags |= fcntl.O_NONBLOCK; 4386 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 4387 if(s == -1) 4388 throw new Exception("fcntl set"); 4389 } 4390 4391 struct EventLoopImpl { 4392 int refcount; 4393 4394 bool notExited = true; 4395 4396 version(Emscripten) { 4397 void delegate(int) signalHandler; 4398 static import unix = core.sys.posix.unistd; 4399 static import err = core.stdc.errno; 4400 } else 4401 version(linux) { 4402 static import ep = core.sys.linux.epoll; 4403 static import unix = core.sys.posix.unistd; 4404 static import err = core.stdc.errno; 4405 import core.sys.linux.timerfd; 4406 4407 void delegate(int) signalHandler; 4408 } 4409 4410 version(X11) { 4411 int pulseFd = -1; 4412 version(Emscripten) {} else 4413 version(linux) ep.epoll_event[16] events = void; 4414 } else version(Windows) { 4415 Timer pulser; 4416 HANDLE[] handles; 4417 } 4418 4419 4420 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 4421 /// to call this, as it's not recommended to share window between threads. 4422 void mtLock () { 4423 version(X11) { 4424 XLockDisplay(this.display); 4425 } 4426 } 4427 4428 version(X11) 4429 auto display() { return XDisplayConnection.get; } 4430 4431 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 4432 /// to call this, as it's not recommended to share window between threads. 4433 void mtUnlock () { 4434 version(X11) { 4435 XUnlockDisplay(this.display); 4436 } 4437 } 4438 4439 version(with_eventloop) 4440 void initialize(long pulseTimeout) {} 4441 else 4442 void initialize(long pulseTimeout) @system { 4443 version(Windows) { 4444 if(pulseTimeout && handlePulse !is null) 4445 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 4446 4447 if (customEventH is null) { 4448 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 4449 if (customEventH !is null) { 4450 handles ~= customEventH; 4451 } else { 4452 // this is something that should not be; better be safe than sorry 4453 throw new Exception("can't create eventfd for custom event processing"); 4454 } 4455 } 4456 4457 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 4458 } 4459 4460 version(Emscripten) { 4461 4462 } else version(linux) { 4463 prepareEventLoop(); 4464 { 4465 auto display = XDisplayConnection.get; 4466 // adding Xlib file 4467 ep.epoll_event ev = void; 4468 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4469 ev.events = ep.EPOLLIN; 4470 ev.data.fd = display.fd; 4471 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 4472 throw new Exception("add x fd");// ~ to!string(epollFd)); 4473 displayFd = display.fd; 4474 } 4475 4476 if(pulseTimeout && handlePulse !is null) { 4477 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 4478 if(pulseFd == -1) 4479 throw new Exception("pulse timer create failed"); 4480 4481 itimerspec value; 4482 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 4483 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4484 4485 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 4486 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4487 4488 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 4489 throw new Exception("couldn't make pulse timer"); 4490 4491 ep.epoll_event ev = void; 4492 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4493 ev.events = ep.EPOLLIN; 4494 ev.data.fd = pulseFd; 4495 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 4496 } 4497 4498 // eventfd for custom events 4499 if (customEventFDWrite == -1) { 4500 customEventFDWrite = eventfd(0, 0); 4501 customEventFDRead = customEventFDWrite; 4502 if (customEventFDRead >= 0) { 4503 ep.epoll_event ev = void; 4504 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4505 ev.events = ep.EPOLLIN; 4506 ev.data.fd = customEventFDRead; 4507 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev); 4508 } else { 4509 // this is something that should not be; better be safe than sorry 4510 throw new Exception("can't create eventfd for custom event processing"); 4511 } 4512 } 4513 4514 if (customSignalFD == -1) { 4515 import core.sys.linux.sys.signalfd; 4516 4517 sigset_t sigset; 4518 auto err = sigemptyset(&sigset); 4519 assert(!err); 4520 err = sigaddset(&sigset, SIGINT); 4521 assert(!err); 4522 err = sigaddset(&sigset, SIGHUP); 4523 assert(!err); 4524 err = sigprocmask(SIG_BLOCK, &sigset, null); 4525 assert(!err); 4526 4527 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 4528 assert(customSignalFD != -1); 4529 4530 ep.epoll_event ev = void; 4531 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4532 ev.events = ep.EPOLLIN; 4533 ev.data.fd = customSignalFD; 4534 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 4535 } 4536 } else version(Posix) { 4537 prepareEventLoop(); 4538 if (customEventFDRead == -1) { 4539 int[2] bfr; 4540 import core.sys.posix.unistd; 4541 auto ret = pipe(bfr); 4542 if(ret == -1) throw new Exception("pipe"); 4543 customEventFDRead = bfr[0]; 4544 customEventFDWrite = bfr[1]; 4545 } 4546 4547 } 4548 4549 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 4550 4551 version(linux) { 4552 this.mtLock(); 4553 scope(exit) this.mtUnlock(); 4554 version(X11) 4555 XPending(display); // no, really 4556 } 4557 4558 disposed = false; 4559 } 4560 4561 bool disposed = true; 4562 version(X11) 4563 int displayFd = -1; 4564 4565 version(with_eventloop) 4566 void dispose() {} 4567 else 4568 void dispose() @system { 4569 disposed = true; 4570 version(X11) { 4571 if(pulseFd != -1) { 4572 import unix = core.sys.posix.unistd; 4573 unix.close(pulseFd); 4574 pulseFd = -1; 4575 } 4576 4577 version(Emscripten) {} else 4578 version(linux) 4579 if(displayFd != -1) { 4580 // 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 4581 ep.epoll_event ev = void; 4582 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4583 ev.events = ep.EPOLLIN; 4584 ev.data.fd = displayFd; 4585 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 4586 displayFd = -1; 4587 } 4588 4589 } else version(Windows) { 4590 if(pulser !is null) { 4591 pulser.destroy(); 4592 pulser = null; 4593 } 4594 if (customEventH !is null) { 4595 CloseHandle(customEventH); 4596 customEventH = null; 4597 } 4598 } 4599 } 4600 4601 this(long pulseTimeout, void delegate() handlePulse) { 4602 this.pulseTimeout = pulseTimeout; 4603 this.handlePulse = handlePulse; 4604 initialize(pulseTimeout); 4605 } 4606 4607 private long pulseTimeout; 4608 void delegate() handlePulse; 4609 4610 ~this() { 4611 dispose(); 4612 } 4613 4614 version(Posix) 4615 ref int customEventFDRead() { return SimpleWindow.customEventFDRead; } 4616 version(Posix) 4617 ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; } 4618 version(linux) 4619 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 4620 version(Windows) 4621 ref auto customEventH() { return SimpleWindow.customEventH; } 4622 4623 version(X11) { 4624 bool doXPending() { 4625 bool done = false; 4626 4627 this.mtLock(); 4628 scope(exit) this.mtUnlock(); 4629 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 4630 while(!done && XPending(display)) { 4631 done = doXNextEvent(this.display); 4632 } 4633 4634 return done; 4635 } 4636 void doXNextEventVoid() { 4637 doXPending(); 4638 } 4639 } 4640 4641 version(with_eventloop) { 4642 int loopHelper(bool delegate() whileCondition) { 4643 // FIXME: whileCondition 4644 import arsd.eventloop; 4645 loop(); 4646 return 0; 4647 } 4648 } else 4649 int loopHelper(bool delegate() whileCondition) { 4650 version(X11) { 4651 bool done = false; 4652 4653 XFlush(display); 4654 insideXEventLoop = true; 4655 scope(exit) insideXEventLoop = false; 4656 4657 static if(use_arsd_core) { 4658 import arsd.core; 4659 auto el = getThisThreadEventLoop(EventLoopType.Ui); 4660 4661 static bool loopInitialized = false; 4662 if(!loopInitialized) { 4663 el.addDelegateOnLoopIteration(&doXNextEventVoid, 3); 4664 el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 3); 4665 4666 if(customSignalFD != -1) 4667 cast(void) el.addCallbackOnFdReadable(customSignalFD, new CallbackHelper(() { 4668 version(linux) { 4669 import core.sys.linux.sys.signalfd; 4670 import core.sys.posix.unistd : read; 4671 signalfd_siginfo info; 4672 read(customSignalFD, &info, info.sizeof); 4673 4674 auto sig = info.ssi_signo; 4675 4676 if(EventLoop.get.signalHandler !is null) { 4677 EventLoop.get.signalHandler()(sig); 4678 } else { 4679 EventLoop.get.exit(); 4680 } 4681 } 4682 })); 4683 4684 if(display.fd != -1) 4685 cast(void) el.addCallbackOnFdReadable(display.fd, new CallbackHelper(() { 4686 this.mtLock(); 4687 scope(exit) this.mtUnlock(); 4688 while(!done && XPending(display)) { 4689 done = doXNextEvent(this.display); 4690 } 4691 })); 4692 4693 if(pulseFd != -1) 4694 cast(void) el.addCallbackOnFdReadable(pulseFd, new CallbackHelper(() { 4695 long expirationCount; 4696 // 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... 4697 4698 handlePulse(); 4699 4700 // read just to clear the buffer so poll doesn't trigger again 4701 // BTW I read AFTER the pulse because if the pulse handler takes 4702 // a lot of time to execute, we don't want the app to get stuck 4703 // in a loop of timer hits without a chance to do anything else 4704 // 4705 // IOW handlePulse happens at most once per pulse interval. 4706 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4707 })); 4708 4709 if(customEventFDRead != -1) 4710 cast(void) el.addCallbackOnFdReadable(customEventFDRead, new CallbackHelper(() { 4711 // we have some custom events; process 'em 4712 import core.sys.posix.unistd : read; 4713 ulong n; 4714 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4715 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4716 //SimpleWindow.processAllCustomEvents(); 4717 })); 4718 4719 // FIXME: posix fds 4720 // FIXME up? 4721 4722 4723 loopInitialized = true; 4724 } 4725 4726 if(whileCondition is null) 4727 whileCondition = () => true; 4728 4729 el.run(() => !whileCondition()); 4730 } else version(linux) { 4731 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 4732 bool forceXPending = false; 4733 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4734 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 4735 { 4736 this.mtLock(); 4737 scope(exit) this.mtUnlock(); 4738 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 4739 } 4740 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 4741 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 4742 if(nfds == -1) { 4743 if(err.errno == err.EINTR) { 4744 //if(forceXPending) goto xpending; 4745 continue; // interrupted by signal, just try again 4746 } 4747 throw new Exception("epoll wait failure"); 4748 } 4749 // writeln(nfds, " ", events[0].data.fd); 4750 4751 SimpleWindow.processAllCustomEvents(); // anyway 4752 //version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 4753 foreach(idx; 0 .. nfds) { 4754 if(done) break; 4755 auto fd = events[idx].data.fd; 4756 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 4757 auto flags = events[idx].events; 4758 if(flags & ep.EPOLLIN) { 4759 if (fd == customSignalFD) { 4760 version(linux) { 4761 import core.sys.linux.sys.signalfd; 4762 import core.sys.posix.unistd : read; 4763 signalfd_siginfo info; 4764 read(customSignalFD, &info, info.sizeof); 4765 4766 auto sig = info.ssi_signo; 4767 4768 if(EventLoop.get.signalHandler !is null) { 4769 EventLoop.get.signalHandler()(sig); 4770 } else { 4771 EventLoop.get.exit(); 4772 } 4773 } 4774 } else if(fd == display.fd) { 4775 version(sdddd) { writeln("X EVENT PENDING!"); } 4776 this.mtLock(); 4777 scope(exit) this.mtUnlock(); 4778 while(!done && XPending(display)) { 4779 done = doXNextEvent(this.display); 4780 } 4781 forceXPending = false; 4782 } else if(fd == pulseFd) { 4783 long expirationCount; 4784 // 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... 4785 4786 handlePulse(); 4787 4788 // read just to clear the buffer so poll doesn't trigger again 4789 // BTW I read AFTER the pulse because if the pulse handler takes 4790 // a lot of time to execute, we don't want the app to get stuck 4791 // in a loop of timer hits without a chance to do anything else 4792 // 4793 // IOW handlePulse happens at most once per pulse interval. 4794 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4795 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 4796 } else if (fd == customEventFDRead) { 4797 // we have some custom events; process 'em 4798 import core.sys.posix.unistd : read; 4799 ulong n; 4800 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4801 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4802 //SimpleWindow.processAllCustomEvents(); 4803 4804 forceXPending = true; 4805 } else { 4806 // some other timer 4807 version(sdddd) { writeln("unknown fd: ", fd); } 4808 4809 if(Timer* t = fd in Timer.mapping) 4810 (*t).trigger(); 4811 4812 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4813 (*pfr).ready(flags); 4814 4815 // we don't know what the user did in this timer, so we need to assume that 4816 // there's X data to be flushed and potentially processed 4817 forceXPending = true; 4818 4819 // or i might add support for other FDs too 4820 // but for now it is just timer 4821 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 4822 } 4823 } 4824 if(flags & ep.EPOLLHUP) { 4825 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4826 (*pfr).hup(flags); 4827 if(globalHupHandler) 4828 globalHupHandler(fd, flags); 4829 } 4830 /+ 4831 } else { 4832 // not interested in OUT, we are just reading here. 4833 // 4834 // error or hup might also be reported 4835 // but it shouldn't here since we are only 4836 // using a few types of FD and Xlib will report 4837 // if it dies. 4838 // so instead of thoughtfully handling it, I'll 4839 // just throw. for now at least 4840 4841 throw new Exception("epoll did something else"); 4842 } 4843 +/ 4844 } 4845 // if we won't call `XPending()` here, libX may delay some internal event delivery. 4846 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 4847 xpending: 4848 if (!done && forceXPending) { 4849 done = doXPending(); 4850 } 4851 } 4852 } else { 4853 // Generic fallback: yes to simple pulse support, 4854 // but NO timer support! 4855 4856 // FIXME: we could probably support the POSIX timer_create 4857 // signal-based option, but I'm in no rush to write it since 4858 // I prefer the fd-based functions. 4859 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 4860 4861 import core.sys.posix.poll; 4862 4863 pollfd[] pfds; 4864 pollfd[32] pfdsBuffer; 4865 auto len = PosixFdReader.mapping.length + 2; 4866 // FIXME: i should just reuse the buffer 4867 if(len < pfdsBuffer.length) 4868 pfds = pfdsBuffer[0 .. len]; 4869 else 4870 pfds = new pollfd[](len); 4871 4872 pfds[0].fd = display.fd; 4873 pfds[0].events = POLLIN; 4874 pfds[0].revents = 0; 4875 4876 int slot = 1; 4877 4878 if(customEventFDRead != -1) { 4879 pfds[slot].fd = customEventFDRead; 4880 pfds[slot].events = POLLIN; 4881 pfds[slot].revents = 0; 4882 4883 slot++; 4884 } 4885 4886 foreach(fd, obj; PosixFdReader.mapping) { 4887 if(!obj.enabled) continue; 4888 pfds[slot].fd = fd; 4889 pfds[slot].events = POLLIN; 4890 pfds[slot].revents = 0; 4891 4892 slot++; 4893 } 4894 4895 auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1); 4896 if(ret == -1) throw new Exception("poll"); 4897 4898 if(ret == 0) { 4899 // FIXME it may not necessarily time out if events keep coming 4900 if(handlePulse !is null) 4901 handlePulse(); 4902 } else { 4903 foreach(s; 0 .. slot) { 4904 if(pfds[s].revents == 0) continue; 4905 4906 if(pfds[s].fd == display.fd) { 4907 while(!done && XPending(display)) { 4908 this.mtLock(); 4909 scope(exit) this.mtUnlock(); 4910 done = doXNextEvent(this.display); 4911 } 4912 } else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) { 4913 4914 import core.sys.posix.unistd : read; 4915 ulong n; 4916 read(customEventFDRead, &n, n.sizeof); 4917 SimpleWindow.processAllCustomEvents(); 4918 } else { 4919 auto obj = PosixFdReader.mapping[pfds[s].fd]; 4920 if(pfds[s].revents & POLLNVAL) { 4921 obj.dispose(); 4922 } else { 4923 obj.ready(pfds[s].revents); 4924 } 4925 } 4926 4927 ret--; 4928 if(ret == 0) break; 4929 } 4930 } 4931 } 4932 } 4933 } else 4934 version(Windows) { 4935 4936 static if(use_arsd_core) { 4937 import arsd.core; 4938 auto el = getThisThreadEventLoop(EventLoopType.Ui); 4939 static bool loopInitialized = false; 4940 if(!loopInitialized) { 4941 el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 3); 4942 el.addDelegateOnLoopIteration(function() { eventLoopRound++; }, 3); 4943 loopInitialized = true; 4944 } 4945 el.run(() => !whileCondition()); 4946 } else { 4947 int ret = -1; 4948 MSG message; 4949 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 4950 eventLoopRound++; 4951 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4952 auto waitResult = MsgWaitForMultipleObjectsEx( 4953 cast(int) handles.length, handles.ptr, 4954 (wto == 0 ? INFINITE : wto), /* timeout */ 4955 0x04FF, /* QS_ALLINPUT */ 4956 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 4957 4958 SimpleWindow.processAllCustomEvents(); // anyway 4959 enum WAIT_OBJECT_0 = 0; 4960 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 4961 auto h = handles[waitResult - WAIT_OBJECT_0]; 4962 if(auto e = h in WindowsHandleReader.mapping) { 4963 (*e).ready(); 4964 } 4965 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 4966 // message ready 4967 int count; 4968 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 4969 ret = GetMessage(&message, null, 0, 0); 4970 if(ret == -1) 4971 throw new WindowsApiException("GetMessage", GetLastError()); 4972 TranslateMessage(&message); 4973 DispatchMessage(&message); 4974 4975 count++; 4976 if(count > 10) 4977 break; // take the opportunity to catch up on other events 4978 4979 if(ret == 0) { // WM_QUIT 4980 EventLoop.quitApplication(); 4981 break; 4982 } 4983 } 4984 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 4985 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 4986 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 4987 // timeout, should never happen since we aren't using it 4988 } else if(waitResult == 0xFFFFFFFF) { 4989 // failed 4990 throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError()); 4991 } else { 4992 // idk.... 4993 } 4994 } 4995 } 4996 4997 // return message.wParam; 4998 return 0; 4999 } version (OSXCocoa) { 5000 5001 static assert(use_arsd_core); 5002 5003 /+ 5004 if (handlePulse !is null && pulseTimeout != 0) { 5005 NSTimer timer = NSTimer.schedule(pulseTimeout*1e-3, 5006 cast(NSid) view, sel_registerName("simpledisplay_pulse:"), 5007 null, true); 5008 5009 5010 if(timer) 5011 timer.invalidate(); 5012 } 5013 +/ 5014 5015 import arsd.core; 5016 auto el = getThisThreadEventLoop(EventLoopType.Ui); 5017 static bool loopInitialized = false; 5018 if(!loopInitialized) { 5019 el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 3); 5020 loopInitialized = true; 5021 sdpyPrintDebugString("one"); 5022 NSApp.run(); 5023 sdpyPrintDebugString("here"); 5024 } 5025 5026 sdpyPrintDebugString("arsd.core loop starting"); 5027 el.run(() => !whileCondition()); 5028 5029 sdpyPrintDebugString("kiio all done"); 5030 5031 return 0; 5032 } else { 5033 return 0; 5034 } 5035 } 5036 5037 int run(bool delegate() whileCondition = null) { 5038 if(disposed) 5039 initialize(this.pulseTimeout); 5040 5041 version(X11) { 5042 try { 5043 return loopHelper(whileCondition); 5044 } catch(XDisconnectException e) { 5045 if(e.userRequested) { 5046 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 5047 item.discardConnectionState(); 5048 XCloseDisplay(XDisplayConnection.display); 5049 } 5050 5051 XDisplayConnection.display = null; 5052 5053 this.dispose(); 5054 5055 throw e; 5056 } 5057 } else { 5058 return loopHelper(whileCondition); 5059 } 5060 } 5061 } 5062 5063 5064 /++ 5065 Provides an icon on the system notification area (also known as the system tray). 5066 5067 5068 If a notification area is not available with the NotificationIcon object is created, 5069 it will silently succeed and simply attempt to create one when an area becomes available. 5070 5071 5072 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for 5073 Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency 5074 with true color was added at that time. I was just too lazy to write the fallback. 5075 5076 If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest 5077 you use arsd 10.x when targeting Windows XP. 5078 +/ 5079 version(Emscripten) {} else 5080 version(OSXCocoa) {} else // NotYetImplementedException 5081 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 5082 5083 version(X11) { 5084 void recreateAfterDisconnect() { 5085 stateDiscarded = false; 5086 clippixmap = None; 5087 throw new Exception("NOT IMPLEMENTED"); 5088 } 5089 5090 bool stateDiscarded; 5091 void discardConnectionState() { 5092 stateDiscarded = true; 5093 } 5094 } 5095 5096 5097 version(X11) { 5098 Image img; 5099 5100 NativeEventHandler getNativeEventHandler() { 5101 return delegate int(XEvent e) { 5102 switch(e.type) { 5103 case EventType.Expose: 5104 //case EventType.VisibilityNotify: 5105 redraw(); 5106 break; 5107 case EventType.ClientMessage: 5108 version(sddddd) { 5109 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 5110 writeln("\t", e.xclient.format); 5111 writeln("\t", e.xclient.data.l); 5112 } 5113 break; 5114 case EventType.ButtonPress: 5115 auto event = e.xbutton; 5116 if (onClick !is null || onClickEx !is null) { 5117 MouseButton mb = cast(MouseButton)0; 5118 switch (event.button) { 5119 case 1: mb = MouseButton.left; break; // left 5120 case 2: mb = MouseButton.middle; break; // middle 5121 case 3: mb = MouseButton.right; break; // right 5122 case 4: mb = MouseButton.wheelUp; break; // scroll up 5123 case 5: mb = MouseButton.wheelDown; break; // scroll down 5124 case 6: break; // scroll left... 5125 case 7: break; // scroll right... 5126 case 8: mb = MouseButton.backButton; break; 5127 case 9: mb = MouseButton.forwardButton; break; 5128 default: 5129 } 5130 if (mb) { 5131 try { onClick()(mb); } catch (Exception) {} 5132 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 5133 } 5134 } 5135 break; 5136 case EventType.EnterNotify: 5137 if (onEnter !is null) { 5138 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 5139 } 5140 break; 5141 case EventType.LeaveNotify: 5142 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 5143 break; 5144 case EventType.DestroyNotify: 5145 active = false; 5146 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 5147 break; 5148 case EventType.ConfigureNotify: 5149 auto event = e.xconfigure; 5150 this.width = event.width; 5151 this.height = event.height; 5152 // writeln(width, " x " , height, " @ ", event.x, " ", event.y); 5153 redraw(); 5154 break; 5155 default: return 1; 5156 } 5157 return 1; 5158 }; 5159 } 5160 5161 /* private */ void hideBalloon() { 5162 balloon.close(); 5163 version(with_timer) 5164 timer.destroy(); 5165 balloon = null; 5166 version(with_timer) 5167 timer = null; 5168 } 5169 5170 void redraw() { 5171 if (!active) return; 5172 5173 auto display = XDisplayConnection.get; 5174 GC gc; 5175 5176 // from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap 5177 5178 int gc_depth(int depth, Display *dpy, Window root, GC *gc) { 5179 Visual *visual; 5180 XVisualInfo vis_info; 5181 XSetWindowAttributes win_attr; 5182 c_ulong win_mask; 5183 5184 if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) { 5185 assert(0); 5186 // return 1; 5187 } 5188 5189 visual = vis_info.visual; 5190 5191 win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone); 5192 win_attr.background_pixel = 0; 5193 win_attr.border_pixel = 0; 5194 5195 win_mask = CWBackPixel | CWColormap | CWBorderPixel; 5196 5197 *gc = XCreateGC(dpy, nativeHandle, 0, null); 5198 5199 return 0; 5200 } 5201 5202 if(useAlpha) 5203 gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc); 5204 else 5205 gc = DefaultGC(display, DefaultScreen(display)); 5206 5207 XClearWindow(display, nativeHandle); 5208 5209 if(!useAlpha && img !is null) 5210 XSetClipMask(display, gc, clippixmap); 5211 5212 /+ 5213 XSetForeground(display, gc, 5214 cast(uint) 0 << 16 | 5215 cast(uint) 0 << 8 | 5216 cast(uint) 0); 5217 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 5218 +/ 5219 5220 if (img is null) { 5221 XSetForeground(display, gc, 5222 cast(uint) 0 << 16 | 5223 cast(uint) 127 << 8 | 5224 cast(uint) 0); 5225 XFillArc(display, nativeHandle, 5226 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 5227 } else { 5228 int dx = 0; 5229 int dy = 0; 5230 if(width > img.width) 5231 dx = (width - img.width) / 2; 5232 if(height > img.height) 5233 dy = (height - img.height) / 2; 5234 // writeln(img.width, " ", img.height, " vs ", width, " ", height); 5235 XSetClipOrigin(display, gc, dx, dy); 5236 5237 int max(int a, int b) { 5238 if(a > b) return a; else return b; 5239 } 5240 5241 if (img.usingXshm) 5242 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false); 5243 else 5244 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height)); 5245 } 5246 XSetClipMask(display, gc, None); 5247 flushGui(); 5248 } 5249 5250 static Window getTrayOwner() { 5251 auto display = XDisplayConnection.get; 5252 auto i = cast(int) DefaultScreen(display); 5253 if(i < 10 && i >= 0) { 5254 static Atom atom; 5255 if(atom == None) 5256 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 5257 return XGetSelectionOwner(display, atom); 5258 } 5259 return None; 5260 } 5261 5262 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 5263 auto to = getTrayOwner(); 5264 auto display = XDisplayConnection.get; 5265 XEvent ev; 5266 ev.xclient.type = EventType.ClientMessage; 5267 ev.xclient.window = to; 5268 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 5269 ev.xclient.format = 32; 5270 ev.xclient.data.l[0] = CurrentTime; 5271 ev.xclient.data.l[1] = message; 5272 ev.xclient.data.l[2] = d1; 5273 ev.xclient.data.l[3] = d2; 5274 ev.xclient.data.l[4] = d3; 5275 5276 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 5277 } 5278 5279 private static NotificationAreaIcon[] activeIcons; 5280 5281 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 5282 private void newManager() { 5283 close(); 5284 createXWin(); 5285 5286 if(this.clippixmap) 5287 XFreePixmap(XDisplayConnection.get, clippixmap); 5288 if(this.originalMemoryImage) 5289 this.icon = this.originalMemoryImage; 5290 else if(this.img) 5291 this.icon = this.img; 5292 } 5293 5294 private bool useAlpha = false; 5295 5296 private void createXWin () { 5297 // create window 5298 auto display = XDisplayConnection.get; 5299 5300 // to check for MANAGER on root window to catch new/changed tray owners 5301 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 5302 // so if a thing does appear, we can handle it 5303 foreach(ai; activeIcons) 5304 if(ai is this) 5305 goto alreadythere; 5306 activeIcons ~= this; 5307 alreadythere: 5308 5309 // and check for an existing tray 5310 auto trayOwner = getTrayOwner(); 5311 if(trayOwner == None) 5312 return; 5313 //throw new Exception("No notification area found"); 5314 5315 Visual* v = cast(Visual*) CopyFromParent; 5316 5317 // GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales 5318 // from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send 5319 // a resize event later. 5320 width = 22; 5321 height = 22; 5322 5323 // if they system gave us a 32 bit visual we need to switch to it too 5324 int depth = 24; 5325 5326 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 5327 if(visualProp !is null) { 5328 c_ulong[] info = cast(c_ulong[]) visualProp; 5329 if(info.length == 1) { 5330 auto vid = info[0]; 5331 int returned; 5332 XVisualInfo t; 5333 t.visualid = vid; 5334 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 5335 if(got !is null) { 5336 if(returned == 1) { 5337 v = got.visual; 5338 depth = got.depth; 5339 // writeln("using special visual ", got.depth); 5340 // writeln(depth); 5341 } 5342 XFree(got); 5343 } 5344 } 5345 } 5346 5347 int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect; 5348 XSetWindowAttributes attr; 5349 attr.background_pixel = 0; 5350 attr.border_pixel = 0; 5351 attr.override_redirect = 0; 5352 if(v !is cast(Visual*) CopyFromParent) { 5353 attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone); 5354 CWFlags |= CWColormap; 5355 if(depth == 32) 5356 useAlpha = true; 5357 else 5358 goto plain; 5359 } else { 5360 plain: 5361 attr.background_pixmap = 1 /* ParentRelative */; 5362 CWFlags |= CWBackPixmap; 5363 } 5364 5365 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr); 5366 5367 assert(nativeWindow); 5368 5369 if(!useAlpha) 5370 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 5371 5372 nativeHandle = nativeWindow; 5373 5374 ///+ 5375 arch_ulong[2] info; 5376 info[0] = 0; 5377 info[1] = 1; 5378 5379 string title = this.name is null ? "simpledisplay.d program" : this.name; 5380 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 5381 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 5382 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 5383 5384 XChangeProperty( 5385 display, 5386 nativeWindow, 5387 GetAtom!("_XEMBED_INFO", true)(display), 5388 GetAtom!("_XEMBED_INFO", true)(display), 5389 32 /* bits */, 5390 0 /*PropModeReplace*/, 5391 info.ptr, 5392 2); 5393 5394 import core.sys.posix.unistd; 5395 arch_ulong pid = getpid(); 5396 5397 // XSetCommand(display, nativeWindow, ["sdpy".ptr].ptr, 1); 5398 5399 XChangeProperty( 5400 display, 5401 nativeWindow, 5402 GetAtom!("_NET_WM_PID", true)(display), 5403 XA_CARDINAL, 5404 32 /* bits */, 5405 0 /*PropModeReplace*/, 5406 &pid, 5407 1); 5408 5409 updateNetWmIcon(); 5410 5411 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 5412 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 5413 XClassHint klass; 5414 XWMHints wh; 5415 XSizeHints size; 5416 klass.res_name = sdpyWindowClassStr; 5417 klass.res_class = sdpyWindowClassStr; 5418 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 5419 } 5420 5421 // believe it or not, THIS is what xfce needed for the 9999 issue 5422 XSizeHints sh; 5423 c_long spr; 5424 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 5425 sh.flags |= PMaxSize | PMinSize; 5426 // FIXME maybe nicer resizing 5427 sh.min_width = 16; 5428 sh.min_height = 16; 5429 sh.max_width = 22; 5430 sh.max_height = 22; 5431 XSetWMNormalHints(display, nativeWindow, &sh); 5432 5433 5434 //+/ 5435 5436 5437 XSelectInput(display, nativeWindow, 5438 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 5439 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 5440 5441 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 5442 // XMapWindow(display, nativeWindow); // to demo it w/o a tray 5443 5444 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 5445 active = true; 5446 } 5447 5448 void updateNetWmIcon() { 5449 if(img is null) return; 5450 auto display = XDisplayConnection.get; 5451 // FIXME: ensure this is correct 5452 arch_ulong[] buffer; 5453 auto imgMi = img.toTrueColorImage; 5454 buffer ~= imgMi.width; 5455 buffer ~= imgMi.height; 5456 foreach(c; imgMi.imageData.colors) { 5457 arch_ulong b; 5458 b |= c.a << 24; 5459 b |= c.r << 16; 5460 b |= c.g << 8; 5461 b |= c.b; 5462 buffer ~= b; 5463 } 5464 5465 XChangeProperty( 5466 display, 5467 nativeHandle, 5468 GetAtom!"_NET_WM_ICON"(display), 5469 GetAtom!"CARDINAL"(display), 5470 32 /* bits */, 5471 0 /*PropModeReplace*/, 5472 buffer.ptr, 5473 cast(int) buffer.length); 5474 } 5475 5476 5477 5478 private SimpleWindow balloon; 5479 version(with_timer) 5480 private Timer timer; 5481 5482 private Window nativeHandle; 5483 private Pixmap clippixmap = None; 5484 private int width = 16; 5485 private int height = 16; 5486 private bool active = false; 5487 5488 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 5489 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 5490 void delegate () onLeave; /// X11 only. 5491 5492 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 5493 5494 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 5495 void getWindowRect (out int x, out int y, out int width, out int height) { 5496 if (!active) { width = 1; height = 1; return; } // 1: just in case 5497 Window dummyw; 5498 auto dpy = XDisplayConnection.get; 5499 //XWindowAttributes xwa; 5500 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 5501 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 5502 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 5503 width = this.width; 5504 height = this.height; 5505 } 5506 } 5507 5508 /+ 5509 What I actually want from this: 5510 5511 * set / change: icon, tooltip 5512 * handle: mouse click, right click 5513 * show: notification bubble. 5514 +/ 5515 5516 version(Windows) { 5517 WindowsIcon win32Icon; 5518 HWND hwnd; 5519 5520 NOTIFYICONDATAW data; 5521 5522 NativeEventHandler getNativeEventHandler() { 5523 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 5524 if(msg == WM_USER) { 5525 auto event = LOWORD(lParam); 5526 auto iconId = HIWORD(lParam); 5527 //auto x = GET_X_LPARAM(wParam); 5528 //auto y = GET_Y_LPARAM(wParam); 5529 switch(event) { 5530 case WM_LBUTTONDOWN: 5531 onClick()(MouseButton.left); 5532 break; 5533 case WM_RBUTTONDOWN: 5534 onClick()(MouseButton.right); 5535 break; 5536 case WM_MBUTTONDOWN: 5537 onClick()(MouseButton.middle); 5538 break; 5539 case WM_MOUSEMOVE: 5540 // sent, we could use it. 5541 break; 5542 case WM_MOUSEWHEEL: 5543 // NOT SENT 5544 break; 5545 //case NIN_KEYSELECT: 5546 //case NIN_SELECT: 5547 //break; 5548 default: {} 5549 } 5550 } 5551 return 0; 5552 }; 5553 } 5554 5555 enum NIF_SHOWTIP = 0x00000080; 5556 5557 private static struct NOTIFYICONDATAW { 5558 DWORD cbSize; 5559 HWND hWnd; 5560 UINT uID; 5561 UINT uFlags; 5562 UINT uCallbackMessage; 5563 HICON hIcon; 5564 WCHAR[128] szTip; 5565 DWORD dwState; 5566 DWORD dwStateMask; 5567 WCHAR[256] szInfo; 5568 union { 5569 UINT uTimeout; 5570 UINT uVersion; 5571 } 5572 WCHAR[64] szInfoTitle; 5573 DWORD dwInfoFlags; 5574 GUID guidItem; 5575 HICON hBalloonIcon; 5576 } 5577 5578 } 5579 5580 /++ 5581 Note that on Windows, only left, right, and middle buttons are sent. 5582 Mouse wheel buttons are NOT set, so don't rely on those events if your 5583 program is meant to be used on Windows too. 5584 +/ 5585 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 5586 // The canonical constructor for Windows needs the MemoryImage, so it is here, 5587 // but on X, we need an Image, so its canonical ctor is there. They should 5588 // forward to each other though. 5589 version(X11) { 5590 this.name = name; 5591 this.onClick = onClick; 5592 createXWin(); 5593 this.icon = icon; 5594 } else version(Windows) { 5595 this.onClick = onClick; 5596 this.win32Icon = new WindowsIcon(icon); 5597 5598 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 5599 5600 static bool registered = false; 5601 if(!registered) { 5602 WNDCLASSEX wc; 5603 wc.cbSize = wc.sizeof; 5604 wc.hInstance = hInstance; 5605 wc.lpfnWndProc = &WndProc; 5606 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 5607 if(!RegisterClassExW(&wc)) 5608 throw new WindowsApiException("RegisterClass", GetLastError()); 5609 registered = true; 5610 } 5611 5612 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 5613 if(hwnd is null) 5614 throw new WindowsApiException("CreateWindow", GetLastError()); 5615 5616 data.cbSize = data.sizeof; 5617 data.hWnd = hwnd; 5618 data.uID = cast(uint) cast(void*) this; 5619 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 5620 // NIF_INFO means show balloon 5621 data.uCallbackMessage = WM_USER; 5622 data.hIcon = this.win32Icon.hIcon; 5623 data.szTip = ""; // FIXME 5624 data.dwState = 0; // NIS_HIDDEN; // windows vista 5625 data.dwStateMask = NIS_HIDDEN; // windows vista 5626 5627 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 5628 5629 5630 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 5631 5632 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 5633 } else version(OSXCocoa) { 5634 throw new NotYetImplementedException(); 5635 } else static assert(0); 5636 } 5637 5638 /// ditto 5639 this(string name, Image icon, void delegate(MouseButton button) onClick) { 5640 version(X11) { 5641 this.onClick = onClick; 5642 this.name = name; 5643 createXWin(); 5644 this.icon = icon; 5645 } else version(Windows) { 5646 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 5647 } else version(OSXCocoa) { 5648 throw new NotYetImplementedException(); 5649 } else static assert(0); 5650 } 5651 5652 version(X11) { 5653 /++ 5654 X-specific extension (for now at least) 5655 +/ 5656 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5657 this.onClickEx = onClickEx; 5658 createXWin(); 5659 if (icon !is null) this.icon = icon; 5660 } 5661 5662 /// ditto 5663 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5664 this.onClickEx = onClickEx; 5665 createXWin(); 5666 this.icon = icon; 5667 } 5668 } 5669 5670 private void delegate (MouseButton button) onClick_; 5671 5672 /// 5673 @property final void delegate(MouseButton) onClick() { 5674 if(onClick_ is null) 5675 onClick_ = delegate void(MouseButton) {}; 5676 return onClick_; 5677 } 5678 5679 /// ditto 5680 @property final void onClick(void delegate(MouseButton) handler) { 5681 // I made this a property setter so we can wrap smaller arg 5682 // delegates and just forward all to onClickEx or something. 5683 onClick_ = handler; 5684 } 5685 5686 5687 string name_; 5688 @property void name(string n) { 5689 name_ = n; 5690 } 5691 5692 @property string name() { 5693 return name_; 5694 } 5695 5696 private MemoryImage originalMemoryImage; 5697 5698 /// 5699 @property void icon(MemoryImage i) { 5700 version(X11) { 5701 this.originalMemoryImage = i; 5702 if (!active) return; 5703 if (i !is null) { 5704 this.img = Image.fromMemoryImage(i, useAlpha, false); 5705 if(!useAlpha) 5706 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 5707 // writeln("using pixmap ", clippixmap); 5708 updateNetWmIcon(); 5709 redraw(); 5710 } else { 5711 if (this.img !is null) { 5712 this.img = null; 5713 redraw(); 5714 } 5715 } 5716 } else version(Windows) { 5717 this.win32Icon = new WindowsIcon(i); 5718 5719 data.uFlags = NIF_ICON; 5720 data.hIcon = this.win32Icon.hIcon; 5721 5722 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5723 } else version(OSXCocoa) { 5724 throw new NotYetImplementedException(); 5725 } else static assert(0); 5726 } 5727 5728 /// ditto 5729 @property void icon (Image i) { 5730 version(X11) { 5731 if (!active) return; 5732 if (i !is img) { 5733 originalMemoryImage = null; 5734 img = i; 5735 redraw(); 5736 } 5737 } else version(Windows) { 5738 this.icon(i is null ? null : i.toTrueColorImage()); 5739 } else version(OSXCocoa) { 5740 throw new NotYetImplementedException(); 5741 } else static assert(0); 5742 } 5743 5744 /++ 5745 Shows a balloon notification. You can only show one balloon at a time, if you call 5746 it twice while one is already up, the first balloon will be replaced. 5747 5748 5749 The user is free to block notifications and they will automatically disappear after 5750 a timeout period. 5751 5752 Params: 5753 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 5754 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 5755 icon = the icon to display with the notification. If null, it uses your existing icon. 5756 onclick = delegate called if the user clicks the balloon. (not yet implemented) 5757 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 5758 +/ 5759 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 5760 bool useCustom = true; 5761 version(libnotify) { 5762 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 5763 try { 5764 if(!active) return; 5765 5766 if(libnotify is null) { 5767 libnotify = new C_DynamicLibrary("libnotify.so"); 5768 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 5769 } 5770 5771 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 5772 5773 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 5774 5775 if(onclick) { 5776 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 5777 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); 5778 libnotify_action_delegates_count++; 5779 } 5780 5781 // FIXME icon 5782 5783 // set hint image-data 5784 // set default action for onclick 5785 5786 void* error; 5787 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 5788 5789 useCustom = false; 5790 } catch(Exception e) { 5791 5792 } 5793 } 5794 5795 version(X11) { 5796 if(useCustom) { 5797 if(!active) return; 5798 if(balloon) { 5799 hideBalloon(); 5800 } 5801 // I know there are two specs for this, but one is never 5802 // implemented by any window manager I have ever seen, and 5803 // the other is a bloated mess and too complicated for simpledisplay... 5804 // so doing my own little window instead. 5805 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 5806 5807 int x, y, width, height; 5808 getWindowRect(x, y, width, height); 5809 5810 int bx = x - balloon.width; 5811 int by = y - balloon.height; 5812 if(bx < 0) 5813 bx = x + width + balloon.width; 5814 if(by < 0) 5815 by = y + height; 5816 5817 // just in case, make sure it is actually on scren 5818 if(bx < 0) 5819 bx = 0; 5820 if(by < 0) 5821 by = 0; 5822 5823 balloon.move(bx, by); 5824 auto painter = balloon.draw(); 5825 painter.fillColor = Color(220, 220, 220); 5826 painter.outlineColor = Color.black; 5827 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 5828 auto iconWidth = icon is null ? 0 : icon.width; 5829 if(icon) 5830 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 5831 iconWidth += 6; // margin around the icon 5832 5833 // draw a close button 5834 painter.outlineColor = Color(44, 44, 44); 5835 painter.fillColor = Color(255, 255, 255); 5836 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 5837 painter.pen = Pen(Color.black, 3); 5838 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 5839 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 5840 painter.pen = Pen(Color.black, 1); 5841 painter.fillColor = Color(220, 220, 220); 5842 5843 // Draw the title and message 5844 painter.drawText(Point(4 + iconWidth, 4), title); 5845 painter.drawLine( 5846 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 5847 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 5848 ); 5849 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 5850 5851 balloon.setEventHandlers( 5852 (MouseEvent ev) { 5853 if(ev.type == MouseEventType.buttonPressed) { 5854 if(ev.x > balloon.width - 16 && ev.y < 16) 5855 hideBalloon(); 5856 else if(onclick) 5857 onclick(); 5858 } 5859 } 5860 ); 5861 balloon.show(); 5862 5863 version(with_timer) 5864 timer = new Timer(timeout, &hideBalloon); 5865 else {} // FIXME 5866 } 5867 } else version(Windows) { 5868 enum NIF_INFO = 0x00000010; 5869 5870 data.uFlags = NIF_INFO; 5871 5872 // FIXME: go back to the last valid unicode code point 5873 if(title.length > 40) 5874 title = title[0 .. 40]; 5875 if(message.length > 220) 5876 message = message[0 .. 220]; 5877 5878 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 5879 enum NIIF_LARGE_ICON = 0x00000020; 5880 enum NIIF_NOSOUND = 0x00000010; 5881 enum NIIF_USER = 0x00000004; 5882 enum NIIF_ERROR = 0x00000003; 5883 enum NIIF_WARNING = 0x00000002; 5884 enum NIIF_INFO = 0x00000001; 5885 enum NIIF_NONE = 0; 5886 5887 WCharzBuffer t = WCharzBuffer(title); 5888 WCharzBuffer m = WCharzBuffer(message); 5889 5890 t.copyInto(data.szInfoTitle); 5891 m.copyInto(data.szInfo); 5892 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 5893 5894 if(icon !is null) { 5895 auto i = new WindowsIcon(icon); 5896 data.hBalloonIcon = i.hIcon; 5897 data.dwInfoFlags |= NIIF_USER; 5898 } 5899 5900 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5901 } else version(OSXCocoa) { 5902 throw new NotYetImplementedException(); 5903 } else static assert(0); 5904 } 5905 5906 /// 5907 //version(Windows) 5908 void show() { 5909 version(X11) { 5910 if(!hidden) 5911 return; 5912 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 5913 hidden = false; 5914 } else version(Windows) { 5915 data.uFlags = NIF_STATE; 5916 data.dwState = 0; // NIS_HIDDEN; // windows vista 5917 data.dwStateMask = NIS_HIDDEN; // windows vista 5918 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5919 } else version(OSXCocoa) { 5920 throw new NotYetImplementedException(); 5921 } else static assert(0); 5922 } 5923 5924 version(X11) 5925 bool hidden = false; 5926 5927 /// 5928 //version(Windows) 5929 void hide() { 5930 version(X11) { 5931 if(hidden) 5932 return; 5933 hidden = true; 5934 XUnmapWindow(XDisplayConnection.get, nativeHandle); 5935 } else version(Windows) { 5936 data.uFlags = NIF_STATE; 5937 data.dwState = NIS_HIDDEN; // windows vista 5938 data.dwStateMask = NIS_HIDDEN; // windows vista 5939 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5940 } else version(OSXCocoa) { 5941 throw new NotYetImplementedException(); 5942 } else static assert(0); 5943 } 5944 5945 /// 5946 void close () { 5947 version(X11) { 5948 if (active) { 5949 active = false; // event handler will set this too, but meh 5950 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 5951 XDestroyWindow(XDisplayConnection.get, nativeHandle); 5952 flushGui(); 5953 } 5954 } else version(Windows) { 5955 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 5956 } else version(OSXCocoa) { 5957 throw new NotYetImplementedException(); 5958 } else static assert(0); 5959 } 5960 5961 ~this() { 5962 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 5963 version(X11) 5964 if(clippixmap != None) 5965 XFreePixmap(XDisplayConnection.get, clippixmap); 5966 close(); 5967 } 5968 } 5969 5970 version(X11) 5971 /// Call `XFreePixmap` on the return value. 5972 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 5973 char[] data = new char[](i.width * i.height / 8 + 2); 5974 data[] = 0; 5975 5976 int bitOffset = 0; 5977 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 5978 ubyte v = c.a > 128 ? 1 : 0; 5979 data[bitOffset / 8] |= v << (bitOffset%8); 5980 bitOffset++; 5981 } 5982 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 5983 return handle; 5984 } 5985 5986 5987 // basic functions to make timers 5988 /** 5989 A timer that will trigger your function on a given interval. 5990 5991 5992 You create a timer with an interval and a callback. It will continue 5993 to fire on the interval until it is destroyed. 5994 5995 There are currently no one-off timers (instead, just create one and 5996 destroy it when it is triggered) nor are there pause/resume functions - 5997 the timer must again be destroyed and recreated if you want to pause it. 5998 5999 --- 6000 auto timer = new Timer(50, { it happened!; }); 6001 timer.destroy(); 6002 --- 6003 6004 Timers can only be expected to fire when the event loop is running and only 6005 once per iteration through the event loop. 6006 6007 History: 6008 Prior to December 9, 2020, a timer pulse set too high with a handler too 6009 slow could lock up the event loop. It now guarantees other things will 6010 get a chance to run between timer calls, even if that means not keeping up 6011 with the requested interval. 6012 */ 6013 version(with_timer) { 6014 static if(use_arsd_core) { 6015 alias Timer = arsd.core.Timer; // FIXME should probably wrap it for a stable api 6016 } else 6017 class Timer { 6018 // FIXME: needs pause and unpause 6019 // FIXME: I might add overloads for ones that take a count of 6020 // how many elapsed since last time (on Windows, it will divide 6021 // the ticks thing given, on Linux it is just available) and 6022 // maybe one that takes an instance of the Timer itself too 6023 /// Create a timer with a callback when it triggers. 6024 this(int intervalInMilliseconds, void delegate() onPulse) @trusted { 6025 assert(onPulse !is null); 6026 6027 this.intervalInMilliseconds = intervalInMilliseconds; 6028 this.onPulse = onPulse; 6029 6030 version(Windows) { 6031 /* 6032 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 6033 if(handle == 0) 6034 throw new WindowsApiException("SetTimer", GetLastError()); 6035 */ 6036 6037 // thanks to Archival 998 for the WaitableTimer blocks 6038 handle = CreateWaitableTimer(null, false, null); 6039 long initialTime = -intervalInMilliseconds; 6040 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 6041 throw new WindowsApiException("SetWaitableTimer", GetLastError()); 6042 6043 mapping[handle] = this; 6044 6045 } else version(Emscripten) { 6046 } else version(linux) { 6047 static import ep = core.sys.linux.epoll; 6048 6049 import core.sys.linux.timerfd; 6050 6051 fd = timerfd_create(CLOCK_MONOTONIC, 0); 6052 if(fd == -1) 6053 throw new Exception("timer create failed"); 6054 6055 mapping[fd] = this; 6056 6057 itimerspec value = makeItimerspec(intervalInMilliseconds); 6058 6059 if(timerfd_settime(fd, 0, &value, null) == -1) 6060 throw new Exception("couldn't make pulse timer"); 6061 6062 version(with_eventloop) { 6063 import arsd.eventloop; 6064 addFileEventListeners(fd, &trigger, null, null); 6065 } else { 6066 prepareEventLoop(); 6067 6068 ep.epoll_event ev = void; 6069 ev.events = ep.EPOLLIN; 6070 ev.data.fd = fd; 6071 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 6072 } 6073 } else featureNotImplemented(); 6074 } 6075 6076 private int intervalInMilliseconds; 6077 6078 // just cuz I sometimes call it this. 6079 alias dispose = destroy; 6080 6081 /// Stop and destroy the timer object. 6082 void destroy() { 6083 version(Windows) { 6084 staticDestroy(handle); 6085 handle = null; 6086 } else version(linux) { 6087 staticDestroy(fd); 6088 fd = -1; 6089 } else featureNotImplemented(); 6090 } 6091 6092 version(Windows) 6093 static void staticDestroy(HANDLE handle) { 6094 if(handle) { 6095 // KillTimer(null, handle); 6096 CancelWaitableTimer(cast(void*)handle); 6097 mapping.remove(handle); 6098 CloseHandle(handle); 6099 } 6100 } 6101 else version(Emscripten) 6102 static void staticDestroy(int fd) @system { 6103 assert(0); 6104 } 6105 else version(linux) 6106 static void staticDestroy(int fd) @system { 6107 if(fd != -1) { 6108 import unix = core.sys.posix.unistd; 6109 static import ep = core.sys.linux.epoll; 6110 6111 version(with_eventloop) { 6112 import arsd.eventloop; 6113 removeFileEventListeners(fd); 6114 } else { 6115 ep.epoll_event ev = void; 6116 ev.events = ep.EPOLLIN; 6117 ev.data.fd = fd; 6118 6119 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 6120 } 6121 unix.close(fd); 6122 mapping.remove(fd); 6123 } 6124 } 6125 6126 ~this() { 6127 version(Windows) { if(handle) 6128 cleanupQueue.queue!staticDestroy(handle); 6129 } else version(linux) { if(fd != -1) 6130 cleanupQueue.queue!staticDestroy(fd); 6131 } 6132 } 6133 6134 void changeTime(int intervalInMilliseconds) 6135 { 6136 this.intervalInMilliseconds = intervalInMilliseconds; 6137 version(Windows) 6138 { 6139 if(handle) 6140 { 6141 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 6142 long initialTime = -intervalInMilliseconds; 6143 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 6144 throw new WindowsApiException("couldn't change pulse timer", GetLastError()); 6145 } 6146 } else version(linux) { 6147 import core.sys.linux.timerfd; 6148 6149 itimerspec value = makeItimerspec(intervalInMilliseconds); 6150 if(timerfd_settime(fd, 0, &value, null) == -1) { 6151 throw new Exception("couldn't change pulse timer"); 6152 } 6153 } else { 6154 assert(false, "Timer.changeTime(int) is not implemented for this platform"); 6155 } 6156 } 6157 6158 6159 private: 6160 6161 void delegate() onPulse; 6162 6163 int lastEventLoopRoundTriggered; 6164 6165 version(linux) { 6166 static auto makeItimerspec(int intervalInMilliseconds) { 6167 import core.sys.linux.timerfd; 6168 6169 itimerspec value; 6170 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 6171 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 6172 6173 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 6174 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 6175 6176 return value; 6177 } 6178 } 6179 6180 void trigger() { 6181 version(linux) { 6182 import unix = core.sys.posix.unistd; 6183 long val; 6184 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 6185 } else version(Windows) { 6186 if(this.lastEventLoopRoundTriggered == eventLoopRound) 6187 return; // never try to actually run faster than the event loop 6188 lastEventLoopRoundTriggered = eventLoopRound; 6189 } else featureNotImplemented(); 6190 6191 onPulse(); 6192 } 6193 6194 version(Windows) 6195 void rearm() { 6196 6197 } 6198 6199 version(Windows) 6200 extern(Windows) 6201 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 6202 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 6203 if(Timer* t = timer in mapping) { 6204 try 6205 (*t).trigger(); 6206 catch(Exception e) { sdpy_abort(e); assert(0); } 6207 } 6208 } 6209 6210 version(Windows) { 6211 //UINT_PTR handle; 6212 //static Timer[UINT_PTR] mapping; 6213 HANDLE handle; 6214 __gshared Timer[HANDLE] mapping; 6215 } else version(linux) { 6216 int fd = -1; 6217 __gshared Timer[int] mapping; 6218 } else version(OSXCocoa) { 6219 } else static assert(0, "timer not supported"); 6220 } 6221 } 6222 6223 version(Windows) 6224 private int eventLoopRound; 6225 6226 version(Windows) 6227 /// 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 6228 class WindowsHandleReader { 6229 /// 6230 this(void delegate() onReady, HANDLE handle) { 6231 this.onReady = onReady; 6232 this.handle = handle; 6233 6234 mapping[handle] = this; 6235 6236 enable(); 6237 } 6238 6239 static if(use_arsd_core) 6240 ICoreEventLoop.UnregisterToken unregisterToken; 6241 6242 /// 6243 void enable() { 6244 static if(use_arsd_core) { 6245 unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnHandleReady(handle, new CallbackHelper(&ready)); 6246 } else { 6247 auto el = EventLoop.get().impl; 6248 el.handles ~= handle; 6249 } 6250 } 6251 6252 /// 6253 void disable() { 6254 static if(use_arsd_core) { 6255 unregisterToken.unregister(); 6256 } else { 6257 auto el = EventLoop.get().impl; 6258 for(int i = 0; i < el.handles.length; i++) { 6259 if(el.handles[i] is handle) { 6260 el.handles[i] = el.handles[$-1]; 6261 el.handles = el.handles[0 .. $-1]; 6262 return; 6263 } 6264 } 6265 } 6266 } 6267 6268 void dispose() { 6269 disable(); 6270 if(handle) 6271 mapping.remove(handle); 6272 handle = null; 6273 } 6274 6275 void ready() { 6276 if(onReady) 6277 onReady(); 6278 } 6279 6280 HANDLE handle; 6281 void delegate() onReady; 6282 6283 __gshared WindowsHandleReader[HANDLE] mapping; 6284 } 6285 6286 version(Posix) 6287 /// Lets you add files to the event loop for reading. Use at your own risk. 6288 class PosixFdReader { 6289 /// 6290 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 6291 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 6292 } 6293 6294 /// 6295 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 6296 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 6297 } 6298 6299 /// 6300 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 6301 this.onReady = onReady; 6302 this.fd = fd; 6303 this.captureWrites = captureWrites; 6304 this.captureReads = captureReads; 6305 6306 mapping[fd] = this; 6307 6308 version(with_eventloop) { 6309 import arsd.eventloop; 6310 addFileEventListeners(fd, &readyel); 6311 } else { 6312 enable(); 6313 } 6314 } 6315 6316 bool captureReads; 6317 bool captureWrites; 6318 6319 static if(use_arsd_core) { 6320 import arsd.core; 6321 ICoreEventLoop.UnregisterToken unregisterToken; 6322 } 6323 6324 version(with_eventloop) {} else 6325 /// 6326 void enable() @system { 6327 enabled = true; 6328 6329 static if(use_arsd_core) { 6330 unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnFdReadable(fd, new CallbackHelper( 6331 () { onReady(fd, true, false); } 6332 )); 6333 // FIXME: what if it is writeable? 6334 6335 } else version(linux) { 6336 prepareEventLoop(); 6337 static import ep = core.sys.linux.epoll; 6338 ep.epoll_event ev = void; 6339 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 6340 // writeln("enable ", fd, " ", captureReads, " ", captureWrites); 6341 ev.data.fd = fd; 6342 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 6343 } else { 6344 6345 } 6346 } 6347 6348 version(with_eventloop) {} else 6349 /// 6350 void disable() @system { 6351 enabled = false; 6352 6353 static if(use_arsd_core) { 6354 unregisterToken.unregister(); 6355 } else 6356 version(linux) { 6357 prepareEventLoop(); 6358 static import ep = core.sys.linux.epoll; 6359 ep.epoll_event ev = void; 6360 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 6361 // writeln("disable ", fd, " ", captureReads, " ", captureWrites); 6362 ev.data.fd = fd; 6363 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 6364 } 6365 } 6366 6367 version(with_eventloop) {} else 6368 /// 6369 void dispose() { 6370 if(enabled) 6371 disable(); 6372 if(fd != -1) 6373 mapping.remove(fd); 6374 fd = -1; 6375 } 6376 6377 void delegate(int, bool, bool) onReady; 6378 6379 version(with_eventloop) 6380 void readyel() { 6381 onReady(fd, true, true); 6382 } 6383 6384 void ready(uint flags) { 6385 version(Emscripten) { 6386 assert(0); 6387 } else version(linux) { 6388 static import ep = core.sys.linux.epoll; 6389 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 6390 } else { 6391 import core.sys.posix.poll; 6392 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 6393 } 6394 } 6395 6396 void hup(uint flags) { 6397 if(onHup) 6398 onHup(); 6399 } 6400 6401 void delegate() onHup; 6402 6403 int fd = -1; 6404 private bool enabled; 6405 __gshared PosixFdReader[int] mapping; 6406 } 6407 6408 // basic functions to access the clipboard 6409 /+ 6410 6411 6412 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 6413 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 6414 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 6415 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 6416 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx 6417 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 6418 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx 6419 6420 +/ 6421 6422 /++ 6423 this does a delegate because it is actually an async call on X... 6424 the receiver may never be called if the clipboard is empty or unavailable 6425 gets plain text from the clipboard. 6426 +/ 6427 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) @system { 6428 version(Windows) { 6429 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 6430 if(OpenClipboard(hwndOwner) == 0) 6431 throw new WindowsApiException("OpenClipboard", GetLastError()); 6432 scope(exit) 6433 CloseClipboard(); 6434 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat 6435 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 6436 6437 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 6438 scope(exit) 6439 GlobalUnlock(dataHandle); 6440 6441 // FIXME: CR/LF conversions 6442 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 6443 int len = 0; 6444 auto d = data; 6445 while(*d) { 6446 d++; 6447 len++; 6448 } 6449 string s; 6450 s.reserve(len); 6451 foreach(dchar ch; data[0 .. len]) { 6452 s ~= ch; 6453 } 6454 receiver(s); 6455 } 6456 } 6457 } else version(X11) { 6458 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 6459 } else version(OSXCocoa) { 6460 throw new NotYetImplementedException(); 6461 } else version(Emscripten) { 6462 throw new NotYetImplementedException(); 6463 } else static assert(0); 6464 } 6465 6466 // FIXME: a clipboard listener might be cool btw 6467 6468 /++ 6469 this does a delegate because it is actually an async call on X... 6470 the receiver may never be called if the clipboard is empty or unavailable 6471 gets image from the clipboard. 6472 6473 templated because it introduces an optional dependency on arsd.bmp 6474 +/ 6475 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) { 6476 version(Windows) { 6477 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 6478 if(OpenClipboard(hwndOwner) == 0) 6479 throw new WindowsApiException("OpenClipboard", GetLastError()); 6480 scope(exit) 6481 CloseClipboard(); 6482 if(auto dataHandle = GetClipboardData(CF_DIBV5)) { 6483 if(auto data = cast(ubyte*) GlobalLock(dataHandle)) { 6484 scope(exit) 6485 GlobalUnlock(dataHandle); 6486 6487 auto len = GlobalSize(dataHandle); 6488 6489 import arsd.bmp; 6490 auto img = readBmp(data[0 .. len], false); 6491 receiver(img); 6492 } 6493 } 6494 } else version(X11) { 6495 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 6496 } else version(OSXCocoa) { 6497 throw new NotYetImplementedException(); 6498 } else version(Emscripten) { 6499 throw new NotYetImplementedException(); 6500 } else static assert(0); 6501 } 6502 6503 /// Copies some text to the clipboard. 6504 void setClipboardText(SimpleWindow clipboardOwner, string text) { 6505 assert(clipboardOwner !is null); 6506 version(Windows) { 6507 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6508 throw new WindowsApiException("OpenClipboard", GetLastError()); 6509 scope(exit) 6510 CloseClipboard(); 6511 EmptyClipboard(); 6512 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6513 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 6514 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6515 if(auto data = cast(wchar*) GlobalLock(handle)) { 6516 auto slice = data[0 .. sz]; 6517 scope(failure) 6518 GlobalUnlock(handle); 6519 6520 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6521 6522 GlobalUnlock(handle); 6523 SetClipboardData(CF_UNICODETEXT, handle); 6524 } 6525 } else version(X11) { 6526 // we set BOTH clipboard and primary on an explicit action 6527 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 6528 setX11Selection!"PRIMARY"(clipboardOwner, text); 6529 } else version(OSXCocoa) { 6530 throw new NotYetImplementedException(); 6531 } else version(Emscripten) { 6532 throw new NotYetImplementedException(); 6533 } else static assert(0); 6534 } 6535 6536 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) { 6537 assert(clipboardOwner !is null); 6538 version(Windows) { 6539 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6540 throw new WindowsApiException("OpenClipboard", GetLastError()); 6541 scope(exit) 6542 CloseClipboard(); 6543 EmptyClipboard(); 6544 6545 6546 import arsd.bmp; 6547 ubyte[] mdata; 6548 mdata.reserve(img.width * img.height); 6549 void sink(ubyte b) { 6550 mdata ~= b; 6551 } 6552 writeBmpIndirect(img, &sink, false); 6553 6554 auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length); 6555 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6556 if(auto data = cast(ubyte*) GlobalLock(handle)) { 6557 auto slice = data[0 .. mdata.length]; 6558 scope(failure) 6559 GlobalUnlock(handle); 6560 6561 slice[] = mdata[]; 6562 6563 GlobalUnlock(handle); 6564 SetClipboardData(CF_DIB, handle); 6565 } 6566 } else version(X11) { 6567 static class X11SetSelectionHandler_Image : X11SetSelectionHandler { 6568 mixin X11SetSelectionHandler_Basics; 6569 private const(ubyte)[] mdata; 6570 private const(ubyte)[] mdata_original; 6571 this(MemoryImage img) { 6572 import arsd.bmp; 6573 6574 mdata.reserve(img.width * img.height); 6575 void sink(ubyte b) { 6576 mdata ~= b; 6577 } 6578 writeBmpIndirect(img, &sink, true); 6579 6580 mdata_original = mdata; 6581 } 6582 6583 Atom[] availableFormats() { 6584 auto display = XDisplayConnection.get; 6585 return [ 6586 GetAtom!"image/bmp"(display), 6587 GetAtom!"TARGETS"(display) 6588 ]; 6589 } 6590 6591 ubyte[] getData(Atom format, return scope ubyte[] data) { 6592 if(mdata.length < data.length) { 6593 data[0 .. mdata.length] = mdata[]; 6594 auto ret = data[0 .. mdata.length]; 6595 mdata = mdata[$..$]; 6596 return ret; 6597 } else { 6598 data[] = mdata[0 .. data.length]; 6599 mdata = mdata[data.length .. $]; 6600 return data[]; 6601 } 6602 } 6603 6604 void done() { 6605 mdata = mdata_original; 6606 } 6607 } 6608 6609 auto handler = new X11SetSelectionHandler_Image(img); 6610 setX11Selection!"PRIMARY"(clipboardOwner, handler); 6611 setX11Selection!"CLIPBOARD"(clipboardOwner, handler); 6612 } else version(OSXCocoa) { 6613 throw new NotYetImplementedException(); 6614 } else version(Emscripten) { 6615 throw new NotYetImplementedException(); 6616 } else static assert(0); 6617 } 6618 6619 6620 version(X11) { 6621 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 6622 6623 private __gshared Atom*[] interredAtoms; // for discardAndRecreate 6624 6625 // FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all. 6626 /// Platform-specific for X11. 6627 /// History: On February 21, 2021, I changed the default value of `create` to be true. 6628 @property Atom GetAtom(string name, bool create = true)(Display* display) { 6629 __gshared static Atom a; 6630 if(!a) { 6631 a = XInternAtom(display, name, !create); 6632 // FIXME: might need to synchronize this and attach it to the actual object 6633 interredAtoms ~= &a; 6634 } 6635 if(a == None) 6636 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 6637 return a; 6638 } 6639 6640 /// Platform-specific for X11 - gets atom names as a string. 6641 string getAtomName(Atom atom, Display* display) { 6642 auto got = XGetAtomName(display, atom); 6643 scope(exit) XFree(got); 6644 import core.stdc.string; 6645 string s = got[0 .. strlen(got)].idup; 6646 return s; 6647 } 6648 6649 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later. 6650 void setPrimarySelection(SimpleWindow window, string text) { 6651 setX11Selection!"PRIMARY"(window, text); 6652 } 6653 6654 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later. 6655 void setSecondarySelection(SimpleWindow window, string text) { 6656 setX11Selection!"SECONDARY"(window, text); 6657 } 6658 6659 interface X11SetSelectionHandler { 6660 // should include TARGETS right now 6661 Atom[] availableFormats(); 6662 // Return the slice of data you filled, empty slice if done. 6663 // this is to support the incremental thing 6664 ubyte[] getData(Atom format, return scope ubyte[] data); 6665 6666 void done(); 6667 6668 void handleRequest(XEvent); 6669 6670 bool matchesIncr(Window, Atom); 6671 void sendMoreIncr(XPropertyEvent*); 6672 } 6673 6674 mixin template X11SetSelectionHandler_Basics() { 6675 Window incrWindow; 6676 Atom incrAtom; 6677 Atom selectionAtom; 6678 Atom formatAtom; 6679 ubyte[] toSend; 6680 bool matchesIncr(Window w, Atom a) { 6681 return incrAtom && incrAtom == a && w == incrWindow; 6682 } 6683 void sendMoreIncr(XPropertyEvent* event) { 6684 auto display = XDisplayConnection.get; 6685 6686 XChangeProperty (display, 6687 incrWindow, 6688 incrAtom, 6689 formatAtom, 6690 8 /* bits */, PropModeReplace, 6691 toSend.ptr, cast(int) toSend.length); 6692 6693 if(toSend.length != 0) { 6694 toSend = this.getData(formatAtom, toSend[]); 6695 } else { 6696 this.done(); 6697 incrWindow = None; 6698 incrAtom = None; 6699 selectionAtom = None; 6700 formatAtom = None; 6701 toSend = null; 6702 } 6703 } 6704 void handleRequest(XEvent ev) { 6705 6706 auto display = XDisplayConnection.get; 6707 6708 XSelectionRequestEvent* event = &ev.xselectionrequest; 6709 XSelectionEvent selectionEvent; 6710 selectionEvent.type = EventType.SelectionNotify; 6711 selectionEvent.display = event.display; 6712 selectionEvent.requestor = event.requestor; 6713 selectionEvent.selection = event.selection; 6714 selectionEvent.time = event.time; 6715 selectionEvent.target = event.target; 6716 6717 bool supportedType() { 6718 foreach(t; this.availableFormats()) 6719 if(t == event.target) 6720 return true; 6721 return false; 6722 } 6723 6724 if(event.property == None) { 6725 selectionEvent.property = event.target; 6726 6727 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6728 XFlush(display); 6729 } if(event.target == GetAtom!"TARGETS"(display)) { 6730 /* respond with the supported types */ 6731 auto tlist = this.availableFormats(); 6732 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length); 6733 selectionEvent.property = event.property; 6734 6735 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6736 XFlush(display); 6737 } else if(supportedType()) { 6738 auto buffer = new ubyte[](1024 * 64); 6739 auto toSend = this.getData(event.target, buffer[]); 6740 6741 if(toSend.length < 32 * 1024) { 6742 // small enough to send directly... 6743 selectionEvent.property = event.property; 6744 XChangeProperty (display, 6745 selectionEvent.requestor, 6746 selectionEvent.property, 6747 event.target, 6748 8 /* bits */, 0 /* PropModeReplace */, 6749 toSend.ptr, cast(int) toSend.length); 6750 6751 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6752 XFlush(display); 6753 } else { 6754 // large, let's send incrementally 6755 arch_ulong l = toSend.length; 6756 6757 // if I wanted other events from this window don't want to clear that out.... 6758 XWindowAttributes xwa; 6759 XGetWindowAttributes(display, selectionEvent.requestor, &xwa); 6760 6761 XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask)); 6762 6763 incrWindow = event.requestor; 6764 incrAtom = event.property; 6765 formatAtom = event.target; 6766 selectionAtom = event.selection; 6767 this.toSend = toSend; 6768 6769 selectionEvent.property = event.property; 6770 XChangeProperty (display, 6771 selectionEvent.requestor, 6772 selectionEvent.property, 6773 GetAtom!"INCR"(display), 6774 32 /* bits */, PropModeReplace, 6775 &l, 1); 6776 6777 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6778 XFlush(display); 6779 } 6780 //if(after) 6781 //after(); 6782 } else { 6783 debug(sdpy_clip) { 6784 writeln("Unsupported data ", getAtomName(event.target, display)); 6785 } 6786 selectionEvent.property = None; // I don't know how to handle this type... 6787 XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent); 6788 XFlush(display); 6789 } 6790 } 6791 } 6792 6793 class X11SetSelectionHandler_Text : X11SetSelectionHandler { 6794 mixin X11SetSelectionHandler_Basics; 6795 private const(ubyte)[] text; 6796 private const(ubyte)[] text_original; 6797 this(string text) { 6798 this.text = cast(const ubyte[]) text; 6799 this.text_original = this.text; 6800 } 6801 Atom[] availableFormats() { 6802 auto display = XDisplayConnection.get; 6803 return [ 6804 GetAtom!"UTF8_STRING"(display), 6805 GetAtom!"text/plain"(display), 6806 XA_STRING, 6807 GetAtom!"TARGETS"(display) 6808 ]; 6809 } 6810 6811 ubyte[] getData(Atom format, return scope ubyte[] data) { 6812 if(text.length < data.length) { 6813 data[0 .. text.length] = text[]; 6814 return data[0 .. text.length]; 6815 } else { 6816 data[] = text[0 .. data.length]; 6817 text = text[data.length .. $]; 6818 return data[]; 6819 } 6820 } 6821 6822 void done() { 6823 text = text_original; 6824 } 6825 } 6826 6827 /// 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?!) 6828 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 6829 setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after); 6830 } 6831 6832 private __gshared bool mightShortCircuitClipboard; 6833 6834 void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) { 6835 assert(window !is null); 6836 6837 auto display = XDisplayConnection.get(); 6838 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6839 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6840 else Atom a = GetAtom!atomName(display); 6841 6842 if(mightShortCircuitClipboard) 6843 if(auto ptr = a in window.impl.setSelectionHandlers) { 6844 // we already have it, don't even need to inform the X server 6845 // sdpyPrintDebugString("short circuit in set"); 6846 *ptr = data; 6847 return; 6848 } 6849 6850 // we don't have it, tell X we want it 6851 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 6852 window.impl.setSelectionHandlers[a] = data; 6853 mightShortCircuitClipboard = true; 6854 } 6855 6856 /+ 6857 /++ 6858 History: 6859 Added September 28, 2024 6860 +/ 6861 bool hasX11Selection(string atomName)(SimpleWindow window) { 6862 auto display = XDisplayConnection.get(); 6863 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6864 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6865 else Atom a = GetAtom!atomName(display); 6866 6867 if(a in window.impl.setSelectionHandlers) 6868 return true; 6869 else 6870 return false; 6871 } 6872 +/ 6873 6874 /// 6875 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 6876 getX11Selection!"PRIMARY"(window, handler); 6877 } 6878 6879 // added July 28, 2020 6880 // undocumented as experimental tho 6881 interface X11GetSelectionHandler { 6882 void handleData(Atom target, in ubyte[] data); 6883 Atom findBestFormat(Atom[] answer); 6884 6885 void prepareIncremental(Window, Atom); 6886 bool matchesIncr(Window, Atom); 6887 void handleIncrData(Atom, in ubyte[] data); 6888 } 6889 6890 mixin template X11GetSelectionHandler_Basics() { 6891 Window incrWindow; 6892 Atom incrAtom; 6893 6894 void prepareIncremental(Window w, Atom a) { 6895 incrWindow = w; 6896 incrAtom = a; 6897 } 6898 bool matchesIncr(Window w, Atom a) { 6899 return incrWindow == w && incrAtom == a; 6900 } 6901 6902 Atom incrFormatAtom; 6903 ubyte[] incrData; 6904 void handleIncrData(Atom format, in ubyte[] data) { 6905 incrFormatAtom = format; 6906 6907 if(data.length) 6908 incrData ~= data; 6909 else 6910 handleData(incrFormatAtom, incrData); 6911 6912 } 6913 } 6914 6915 static class X11GetSelectionHandler_Text : X11GetSelectionHandler { 6916 this(void delegate(in char[]) handler) { 6917 this.handler = handler; 6918 } 6919 6920 mixin X11GetSelectionHandler_Basics; 6921 6922 void delegate(in char[]) handler; 6923 6924 void handleData(Atom target, in ubyte[] data) { 6925 // import std.stdio; writeln(target, " ", data); 6926 if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 6927 handler(cast(const char[]) data); 6928 else if(target == None && data is null) 6929 handler(null); // no suitable selection exists 6930 } 6931 6932 Atom findBestFormat(Atom[] answer) { 6933 Atom best = None; 6934 foreach(option; answer) { 6935 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 6936 best = option; 6937 break; 6938 } else if(option == XA_STRING) { 6939 best = option; 6940 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 6941 best = option; 6942 } 6943 } 6944 return best; 6945 } 6946 } 6947 6948 /// 6949 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) { 6950 assert(window !is null); 6951 6952 auto display = XDisplayConnection.get(); 6953 6954 static if (atomName == "PRIMARY") Atom atom = XA_PRIMARY; 6955 else static if (atomName == "SECONDARY") Atom atom = XA_SECONDARY; 6956 else Atom atom = GetAtom!atomName(display); 6957 6958 if(mightShortCircuitClipboard) 6959 if(auto ptr = atom in window.impl.setSelectionHandlers) { 6960 if(auto txt = (cast(X11SetSelectionHandler_Text) *ptr)) { 6961 // we already have it! short circuit everything 6962 6963 // sdpyPrintDebugString("short circuit in get"); 6964 handler(cast(char[]) txt.text_original); 6965 return; 6966 } 6967 } 6968 6969 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler); 6970 6971 auto target = GetAtom!"TARGETS"(display); 6972 6973 // SDD_DATA is "simpledisplay.d data" 6974 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp); 6975 } 6976 6977 /// Gets the image on the clipboard, if there is one. Added July 2020. 6978 /// only supports bmps. using this function will import arsd.bmp. 6979 void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) { 6980 assert(window !is null); 6981 6982 auto display = XDisplayConnection.get(); 6983 auto atom = GetAtom!atomName(display); 6984 6985 static class X11GetSelectionHandler_Image : X11GetSelectionHandler { 6986 this(void delegate(MemoryImage) handler) { 6987 this.handler = handler; 6988 } 6989 6990 mixin X11GetSelectionHandler_Basics; 6991 6992 void delegate(MemoryImage) handler; 6993 6994 void handleData(Atom target, in ubyte[] data) { 6995 if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6996 import arsd.bmp; 6997 handler(readBmp(data)); 6998 } 6999 } 7000 7001 Atom findBestFormat(Atom[] answer) { 7002 Atom best = None; 7003 foreach(option; answer) { 7004 if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) { 7005 best = option; 7006 } 7007 } 7008 return best; 7009 } 7010 7011 } 7012 7013 7014 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler); 7015 7016 auto target = GetAtom!"TARGETS"(display); 7017 7018 // SDD_DATA is "simpledisplay.d data" 7019 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 7020 } 7021 7022 7023 /// 7024 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 7025 Atom actualType; 7026 int actualFormat; 7027 arch_ulong actualItems; 7028 arch_ulong bytesRemaining; 7029 void* data; 7030 7031 auto display = XDisplayConnection.get(); 7032 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 7033 if(actualFormat == 0) 7034 return null; 7035 else { 7036 int byteLength; 7037 if(actualFormat == 32) { 7038 // 32 means it is a C long... which is variable length 7039 actualFormat = cast(int) arch_long.sizeof * 8; 7040 } 7041 7042 // then it is just a bit count 7043 byteLength = cast(int) (actualItems * actualFormat / 8); 7044 7045 auto d = new ubyte[](byteLength); 7046 d[] = cast(ubyte[]) data[0 .. byteLength]; 7047 XFree(data); 7048 return d; 7049 } 7050 } 7051 return null; 7052 } 7053 7054 /* defined in the systray spec */ 7055 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 7056 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 7057 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 7058 7059 7060 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 7061 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 7062 public class GlobalHotkey { 7063 KeyEvent key; 7064 void delegate () handler; 7065 7066 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 7067 7068 /// Create from initialzed KeyEvent object 7069 this (KeyEvent akey, void delegate () ahandler=null) { 7070 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 7071 key = akey; 7072 handler = ahandler; 7073 } 7074 7075 /// Create from emacs-like key name ("C-M-Y", etc.) 7076 this (const(char)[] akey, void delegate () ahandler=null) { 7077 key = KeyEvent.parse(akey); 7078 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 7079 handler = ahandler; 7080 } 7081 7082 } 7083 7084 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 7085 //conwriteln("failed to grab key"); 7086 GlobalHotkeyManager.ghfailed = true; 7087 return 0; 7088 } 7089 7090 private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 7091 Image.impl.xshmfailed = true; 7092 return 0; 7093 } 7094 7095 private __gshared int errorHappened; 7096 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 7097 import core.stdc.stdio; 7098 char[265] buffer; 7099 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 7100 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); 7101 errorHappened = true; 7102 return 0; 7103 } 7104 7105 /++ 7106 Global hotkey manager. It contains static methods to manage global hotkeys. 7107 7108 --- 7109 try { 7110 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 7111 } catch (Exception e) { 7112 conwriteln("ERROR registering hotkey!"); 7113 } 7114 EventLoop.get.run(); 7115 --- 7116 7117 The key strings are based on Emacs. In practical terms, 7118 `M` means `alt` and `H` means the Windows logo key. `C` 7119 is `ctrl`. 7120 7121 $(WARNING 7122 This is X-specific right now. If you are on 7123 Windows, try [registerHotKey] instead. 7124 7125 We will probably merge these into a single 7126 interface later. 7127 ) 7128 +/ 7129 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 7130 version(X11) { 7131 void recreateAfterDisconnect() { 7132 throw new Exception("NOT IMPLEMENTED"); 7133 } 7134 void discardConnectionState() { 7135 throw new Exception("NOT IMPLEMENTED"); 7136 } 7137 } 7138 7139 private static immutable uint[8] masklist = [ 0, 7140 KeyOrButtonMask.LockMask, 7141 KeyOrButtonMask.Mod2Mask, 7142 KeyOrButtonMask.Mod3Mask, 7143 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 7144 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 7145 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 7146 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 7147 ]; 7148 private __gshared GlobalHotkeyManager ghmanager; 7149 private __gshared bool ghfailed = false; 7150 7151 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 7152 if (modmask == 0) return false; 7153 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 7154 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 7155 return true; 7156 } 7157 7158 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 7159 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 7160 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 7161 return modmask; 7162 } 7163 7164 private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) { 7165 uint keycode = cast(uint)ke.key; 7166 auto dpy = XDisplayConnection.get; 7167 return XKeysymToKeycode(dpy, keycode); 7168 } 7169 7170 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 7171 7172 private __gshared GlobalHotkey[ulong] globalHotkeyList; 7173 7174 NativeEventHandler getNativeEventHandler () { 7175 return delegate int (XEvent e) { 7176 if (e.type != EventType.KeyPress) return 1; 7177 auto kev = cast(const(XKeyEvent)*)&e; 7178 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 7179 if (auto ghkp = hash in globalHotkeyList) { 7180 try { 7181 ghkp.doHandle(); 7182 } catch (Exception e) { 7183 import core.stdc.stdio : stderr, fprintf; 7184 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 7185 } 7186 } 7187 return 1; 7188 }; 7189 } 7190 7191 private this () { 7192 auto dpy = XDisplayConnection.get; 7193 auto root = RootWindow(dpy, DefaultScreen(dpy)); 7194 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 7195 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 7196 } 7197 7198 /// Register new global hotkey with initialized `GlobalHotkey` object. 7199 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 7200 static void register (GlobalHotkey gh) { 7201 if (gh is null) return; 7202 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 7203 7204 auto dpy = XDisplayConnection.get; 7205 immutable keycode = keyEvent2KeyCode(gh.key); 7206 7207 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 7208 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 7209 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 7210 XSync(dpy, 0/*False*/); 7211 7212 Window root = RootWindow(dpy, DefaultScreen(dpy)); 7213 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 7214 ghfailed = false; 7215 foreach (immutable uint ormask; masklist[]) { 7216 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 7217 } 7218 XSync(dpy, 0/*False*/); 7219 XSetErrorHandler(savedErrorHandler); 7220 7221 if (ghfailed) { 7222 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 7223 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 7224 XSync(dpy, 0/*False*/); 7225 XSetErrorHandler(savedErrorHandler); 7226 throw new Exception("cannot register global hotkey"); 7227 } 7228 7229 globalHotkeyList[hash] = gh; 7230 } 7231 7232 /// Ditto 7233 static void register (const(char)[] akey, void delegate () ahandler) { 7234 register(new GlobalHotkey(akey, ahandler)); 7235 } 7236 7237 private static void removeByHash (ulong hash) { 7238 if (auto ghp = hash in globalHotkeyList) { 7239 auto dpy = XDisplayConnection.get; 7240 immutable keycode = keyEvent2KeyCode(ghp.key); 7241 Window root = RootWindow(dpy, DefaultScreen(dpy)); 7242 XSync(dpy, 0/*False*/); 7243 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 7244 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 7245 XSync(dpy, 0/*False*/); 7246 XSetErrorHandler(savedErrorHandler); 7247 globalHotkeyList.remove(hash); 7248 } 7249 } 7250 7251 /// Register new global hotkey with previously used `GlobalHotkey` object. 7252 /// It is safe to unregister unknown or invalid hotkey. 7253 static void unregister (GlobalHotkey gh) { 7254 //TODO: add second AA for faster search? prolly doesn't worth it. 7255 if (gh is null) return; 7256 foreach (const ref kv; globalHotkeyList.byKeyValue) { 7257 if (kv.value is gh) { 7258 removeByHash(kv.key); 7259 return; 7260 } 7261 } 7262 } 7263 7264 /// Ditto. 7265 static void unregister (const(char)[] key) { 7266 auto kev = KeyEvent.parse(key); 7267 immutable keycode = keyEvent2KeyCode(kev); 7268 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 7269 } 7270 } 7271 } 7272 7273 version(Windows) { 7274 /++ 7275 See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications. 7276 7277 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). 7278 +/ 7279 void sendSyntheticInput(wstring s) { 7280 INPUT[] inputs; 7281 inputs.reserve(s.length * 2); 7282 7283 foreach(wchar c; s) { 7284 INPUT input; 7285 input.type = INPUT_KEYBOARD; 7286 input.ki.wScan = c; 7287 input.ki.dwFlags = KEYEVENTF_UNICODE; 7288 inputs ~= input; 7289 7290 input.ki.dwFlags |= KEYEVENTF_KEYUP; 7291 inputs ~= input; 7292 } 7293 7294 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 7295 throw new WindowsApiException("SendInput", GetLastError()); 7296 } 7297 7298 } 7299 7300 7301 // global hotkey helper function 7302 7303 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these. 7304 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) @system { 7305 __gshared int hotkeyId = 0; 7306 int id = ++hotkeyId; 7307 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 7308 throw new Exception("RegisterHotKey"); 7309 7310 __gshared void delegate()[WPARAM][HWND] handlers; 7311 7312 handlers[window.impl.hwnd][id] = handler; 7313 7314 int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler; 7315 7316 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 7317 switch(msg) { 7318 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 7319 case WM_HOTKEY: 7320 if(auto list = hwnd in handlers) { 7321 if(auto h = wParam in *list) { 7322 (*h)(); 7323 return 0; 7324 } 7325 } 7326 goto default; 7327 default: 7328 } 7329 if(oldHandler) 7330 return oldHandler(hwnd, msg, wParam, lParam, mustReturn); 7331 return 1; // pass it on 7332 }; 7333 7334 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 7335 oldHandler = window.handleNativeEvent; 7336 window.handleNativeEvent = nativeEventHandler; 7337 } 7338 7339 return id; 7340 } 7341 7342 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey]. 7343 void unregisterHotKey(SimpleWindow window, int id) { 7344 if(!UnregisterHotKey(window.impl.hwnd, id)) 7345 throw new WindowsApiException("UnregisterHotKey", GetLastError()); 7346 } 7347 } 7348 7349 version (X11) { 7350 pragma(lib, "dl"); 7351 import core.sys.posix.dlfcn; 7352 } 7353 7354 /++ 7355 Allows for sending synthetic input to the X server via the Xtst 7356 extension or on Windows using SendInput. 7357 7358 Please remember user input is meant to be user - don't use this 7359 if you have some other alternative! 7360 7361 History: 7362 Added May 17, 2020 with the X implementation. 7363 7364 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.) 7365 Bugs: 7366 All methods on OSX Cocoa will throw not yet implemented exceptions. 7367 +/ 7368 struct SyntheticInput { 7369 @disable this(); 7370 7371 private int* refcount; 7372 7373 version(X11) { 7374 private void* lib; 7375 7376 private extern(C) { 7377 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 7378 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 7379 } 7380 } 7381 7382 /// The dummy param must be 0. 7383 this(int dummy) { 7384 version(X11) { 7385 lib = dlopen("libXtst.so", RTLD_NOW); 7386 if(lib is null) 7387 throw new Exception("cannot load xtest lib extension"); 7388 scope(failure) 7389 dlclose(lib); 7390 7391 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 7392 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 7393 7394 if(XTestFakeKeyEvent is null) 7395 throw new Exception("No XTestFakeKeyEvent"); 7396 if(XTestFakeButtonEvent is null) 7397 throw new Exception("No XTestFakeButtonEvent"); 7398 } 7399 7400 refcount = new int; 7401 *refcount = 1; 7402 } 7403 7404 this(this) { 7405 if(refcount) 7406 *refcount += 1; 7407 } 7408 7409 ~this() { 7410 if(refcount) { 7411 *refcount -= 1; 7412 if(*refcount == 0) 7413 // I commented this because if I close the lib before 7414 // XCloseDisplay, it is liable to segfault... so just 7415 // gonna keep it loaded if it is loaded, no big deal 7416 // anyway. 7417 {} // dlclose(lib); 7418 } 7419 } 7420 7421 /++ 7422 Simulates typing a string into the keyboard. 7423 7424 Bugs: 7425 On X11, this ONLY works with basic ascii! On Windows, it can handle more. 7426 7427 Not implemented except on Windows and X11. 7428 +/ 7429 void sendSyntheticInput(string s) { 7430 version(Windows) { 7431 INPUT[] inputs; 7432 inputs.reserve(s.length * 2); 7433 7434 auto ei = GetMessageExtraInfo(); 7435 7436 foreach(wchar c; s) { 7437 INPUT input; 7438 input.type = INPUT_KEYBOARD; 7439 input.ki.wScan = c; 7440 input.ki.dwFlags = KEYEVENTF_UNICODE; 7441 input.ki.dwExtraInfo = ei; 7442 inputs ~= input; 7443 7444 input.ki.dwFlags |= KEYEVENTF_KEYUP; 7445 inputs ~= input; 7446 } 7447 7448 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 7449 throw new WindowsApiException("SendInput", GetLastError()); 7450 } 7451 } else version(X11) { 7452 int delay = 0; 7453 foreach(ch; s) { 7454 pressKey(cast(Key) ch, true, delay); 7455 pressKey(cast(Key) ch, false, delay); 7456 delay += 5; 7457 } 7458 } else throw new NotYetImplementedException(); 7459 } 7460 7461 /++ 7462 Sends a fake press or release key event. 7463 7464 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 7465 7466 Bugs: 7467 The `delay` parameter is not implemented yet on Windows. 7468 7469 Not implemented except on Windows and X11. 7470 +/ 7471 void pressKey(Key key, bool pressed, int delay = 0) { 7472 version(Windows) { 7473 INPUT input; 7474 input.type = INPUT_KEYBOARD; 7475 input.ki.wVk = cast(ushort) key; 7476 7477 input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP; 7478 input.ki.dwExtraInfo = GetMessageExtraInfo(); 7479 7480 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7481 throw new WindowsApiException("SendInput", GetLastError()); 7482 } 7483 } else version(X11) { 7484 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 7485 } else throw new NotYetImplementedException(); 7486 } 7487 7488 /++ 7489 Sends a fake mouse button press or release event. 7490 7491 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 7492 7493 `pressed` param must be `true` if button is `wheelUp` or `wheelDown`. 7494 7495 Bugs: 7496 The `delay` parameter is not implemented yet on Windows. 7497 7498 The backButton and forwardButton will throw NotYetImplementedException on Windows. 7499 7500 All arguments will throw NotYetImplementedException on OSX Cocoa. 7501 +/ 7502 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 7503 version(Windows) { 7504 INPUT input; 7505 input.type = INPUT_MOUSE; 7506 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7507 7508 // input.mi.mouseData for a wheel event 7509 7510 switch(button) { 7511 case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break; 7512 case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break; 7513 case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break; 7514 case MouseButton.wheelUp: 7515 case MouseButton.wheelDown: 7516 input.mi.dwFlags = MOUSEEVENTF_WHEEL; 7517 input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120; 7518 break; 7519 case MouseButton.backButton: throw new NotYetImplementedException(); 7520 case MouseButton.forwardButton: throw new NotYetImplementedException(); 7521 default: 7522 } 7523 7524 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7525 throw new WindowsApiException("SendInput", GetLastError()); 7526 } 7527 } else version(X11) { 7528 int btn; 7529 7530 switch(button) { 7531 case MouseButton.left: btn = 1; break; 7532 case MouseButton.middle: btn = 2; break; 7533 case MouseButton.right: btn = 3; break; 7534 case MouseButton.wheelUp: btn = 4; break; 7535 case MouseButton.wheelDown: btn = 5; break; 7536 case MouseButton.backButton: btn = 8; break; 7537 case MouseButton.forwardButton: btn = 9; break; 7538 default: 7539 } 7540 7541 assert(btn); 7542 7543 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 7544 } else throw new NotYetImplementedException(); 7545 } 7546 7547 /// 7548 static void moveMouseArrowBy(int dx, int dy) { 7549 version(Windows) { 7550 INPUT input; 7551 input.type = INPUT_MOUSE; 7552 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7553 input.mi.dx = dx; 7554 input.mi.dy = dy; 7555 input.mi.dwFlags = MOUSEEVENTF_MOVE; 7556 7557 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7558 throw new WindowsApiException("SendInput", GetLastError()); 7559 } 7560 } else version(X11) { 7561 auto disp = XDisplayConnection.get(); 7562 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 7563 XFlush(disp); 7564 } else throw new NotYetImplementedException(); 7565 } 7566 7567 /// 7568 static void moveMouseArrowTo(int x, int y) { 7569 version(Windows) { 7570 INPUT input; 7571 input.type = INPUT_MOUSE; 7572 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7573 input.mi.dx = x; 7574 input.mi.dy = y; 7575 input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; 7576 7577 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7578 throw new WindowsApiException("SendInput", GetLastError()); 7579 } 7580 } else version(X11) { 7581 auto disp = XDisplayConnection.get(); 7582 auto root = RootWindow(disp, DefaultScreen(disp)); 7583 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 7584 XFlush(disp); 7585 } else throw new NotYetImplementedException(); 7586 } 7587 } 7588 7589 7590 7591 /++ 7592 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 7593 7594 See_Also: 7595 $(LIST 7596 *[ScreenPainter] 7597 *[ScreenPainter.rasterOp] 7598 ) 7599 +/ 7600 enum RasterOp { 7601 normal, /// Replaces the pixel. 7602 xor, /// Uses bitwise xor to draw. 7603 } 7604 7605 // being phobos-free keeps the size WAY down 7606 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 7607 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 7608 package(arsd) const(wchar)* toWStringz(string s) { 7609 wstring r; 7610 foreach(dchar c; s) 7611 r ~= c; 7612 r ~= '\0'; 7613 return r.ptr; 7614 } 7615 private string[] split(in void[] a, char c) { 7616 string[] ret; 7617 size_t previous = 0; 7618 foreach(i, char ch; cast(ubyte[]) a) { 7619 if(ch == c) { 7620 ret ~= cast(string) a[previous .. i]; 7621 previous = i + 1; 7622 } 7623 } 7624 if(previous != a.length) 7625 ret ~= cast(string) a[previous .. $]; 7626 return ret; 7627 } 7628 7629 version(without_opengl) { 7630 enum OpenGlOptions { 7631 no, 7632 } 7633 } else { 7634 /++ 7635 Determines if you want an OpenGL context created on the new window. 7636 7637 7638 See more: [#topics-3d|in the 3d topic]. 7639 7640 --- 7641 import arsd.simpledisplay; 7642 void main() { 7643 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 7644 7645 // Set up the matrix 7646 window.setAsCurrentOpenGlContext(); // make this window active 7647 7648 // This is called on each frame, we will draw our scene 7649 window.redrawOpenGlScene = delegate() { 7650 7651 }; 7652 7653 window.eventLoop(0); 7654 } 7655 --- 7656 +/ 7657 enum OpenGlOptions { 7658 no, /// No OpenGL context is created 7659 yes, /// Yes, create an OpenGL context 7660 } 7661 7662 version(X11) { 7663 static if (!SdpyIsUsingIVGLBinds) { 7664 7665 7666 struct __GLXFBConfigRec {} 7667 alias GLXFBConfig = __GLXFBConfigRec*; 7668 7669 //pragma(lib, "GL"); 7670 //pragma(lib, "GLU"); 7671 interface GLX { 7672 extern(C) nothrow @nogc { 7673 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 7674 const int *attrib_list); 7675 7676 void glXCopyContext(Display *dpy, GLXContext src, 7677 GLXContext dst, arch_ulong mask); 7678 7679 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 7680 GLXContext share_list, Bool direct); 7681 7682 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 7683 Pixmap pixmap); 7684 7685 void glXDestroyContext(Display *dpy, GLXContext ctx); 7686 7687 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 7688 7689 int glXGetConfig(Display *dpy, XVisualInfo *vis, 7690 int attrib, int *value); 7691 7692 GLXContext glXGetCurrentContext(); 7693 7694 GLXDrawable glXGetCurrentDrawable(); 7695 7696 Bool glXIsDirect(Display *dpy, GLXContext ctx); 7697 7698 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 7699 GLXContext ctx); 7700 7701 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 7702 7703 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 7704 7705 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 7706 7707 void glXUseXFont(Font font, int first, int count, int list_base); 7708 7709 void glXWaitGL(); 7710 7711 void glXWaitX(); 7712 7713 7714 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 7715 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 7716 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 7717 7718 char* glXQueryExtensionsString (Display*, int); 7719 void* glXGetProcAddress (const(char)*); 7720 7721 } 7722 } 7723 7724 version(OSX) 7725 mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx; 7726 else 7727 mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx; 7728 shared static this() { 7729 glx.loadDynamicLibrary(); 7730 } 7731 7732 alias glbindGetProcAddress = glXGetProcAddress; 7733 } 7734 } else version(Windows) { 7735 /* it is done below by interface GL */ 7736 } else 7737 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."); 7738 } 7739 7740 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 7741 alias Resizablity = Resizability; 7742 7743 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 7744 enum Resizability { 7745 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. 7746 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. 7747 /++ 7748 $(PITFALL 7749 Planned for the future but not implemented. 7750 ) 7751 7752 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. 7753 7754 History: 7755 Added November 11, 2022, but not yet implemented and may not be for some time. 7756 +/ 7757 /*@__future*/ allowResizingMaintainingAspectRatio, 7758 /++ 7759 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. 7760 7761 History: 7762 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. 7763 7764 Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all. 7765 +/ 7766 automaticallyScaleIfPossible, 7767 } 7768 /// ditto 7769 alias Resizeability = Resizability; 7770 7771 7772 /++ 7773 Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 7774 +/ 7775 enum TextAlignment : uint { 7776 Left = 0, /// 7777 Center = 1, /// 7778 Right = 2, /// 7779 7780 VerticalTop = 0, /// 7781 VerticalCenter = 4, /// 7782 VerticalBottom = 8, /// 7783 } 7784 7785 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 7786 alias Rectangle = arsd.color.Rectangle; 7787 7788 7789 /++ 7790 Keyboard press and release events. 7791 +/ 7792 struct KeyEvent { 7793 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 7794 Key key; 7795 ubyte hardwareCode; /// A platform and hardware specific code for the key 7796 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 7797 7798 deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character; 7799 7800 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 7801 7802 SimpleWindow window; /// associated Window 7803 7804 /++ 7805 A view into the upcoming buffer holding coming character events that are sent if and only if neither 7806 the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))` 7807 to predict if char events are actually coming.. 7808 7809 Only available on X systems since this information is not given ahead of time elsewhere. 7810 (Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.) 7811 7812 I'm adding this because it is useful to the terminal emulator, but given its platform specificness 7813 and potential quirks I'd recommend avoiding it. 7814 7815 History: 7816 Added April 26, 2021 (dub v9.5) 7817 +/ 7818 version(X11) 7819 dchar[] charsPossible; 7820 7821 // convert key event to simplified string representation a-la emacs 7822 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 7823 uint dpos = 0; 7824 void put (const(char)[] s...) nothrow @trusted { 7825 static if (growdest) { 7826 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 7827 } else { 7828 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 7829 } 7830 } 7831 7832 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 7833 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 7834 } 7835 7836 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 7837 7838 // put modifiers 7839 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 7840 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 7841 putMod(ModifierState.alt, Key.Alt, "Alt+"); 7842 putMod(ModifierState.windows, Key.Shift, "Windows+"); 7843 putMod(ModifierState.shift, Key.Shift, "Shift+"); 7844 7845 if (this.key) { 7846 foreach (string kn; __traits(allMembers, Key)) { 7847 if (this.key == __traits(getMember, Key, kn)) { 7848 // HACK! 7849 static if (kn == "N0") put("0"); 7850 else static if (kn == "N1") put("1"); 7851 else static if (kn == "N2") put("2"); 7852 else static if (kn == "N3") put("3"); 7853 else static if (kn == "N4") put("4"); 7854 else static if (kn == "N5") put("5"); 7855 else static if (kn == "N6") put("6"); 7856 else static if (kn == "N7") put("7"); 7857 else static if (kn == "N8") put("8"); 7858 else static if (kn == "N9") put("9"); 7859 else put(kn); 7860 return dest[0..dpos]; 7861 } 7862 } 7863 put("Unknown"); 7864 } else { 7865 if (dpos && dest[dpos-1] == '+') --dpos; 7866 } 7867 return dest[0..dpos]; 7868 } 7869 7870 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 7871 7872 /** Parse string into key name with modifiers. It accepts things like: 7873 * 7874 * C-H-1 -- emacs style (ctrl, and windows, and 1) 7875 * 7876 * Ctrl+Win+1 -- windows style 7877 * 7878 * Ctrl-Win-1 -- '-' is a valid delimiter too 7879 * 7880 * Ctrl Win 1 -- and space 7881 * 7882 * and even "Win + 1 + Ctrl". 7883 */ 7884 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 7885 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 7886 7887 // remove trailing spaces 7888 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 7889 7890 // tokens delimited by blank, '+', or '-' 7891 // null on eol 7892 const(char)[] getToken () nothrow @trusted @nogc { 7893 // remove leading spaces and delimiters 7894 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 7895 if (name.length == 0) return null; // oops, no more tokens 7896 // get token 7897 size_t epos = 0; 7898 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 7899 assert(epos > 0 && epos <= name.length); 7900 auto res = name[0..epos]; 7901 name = name[epos..$]; 7902 return res; 7903 } 7904 7905 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 7906 if (s0.length != s1.length) return false; 7907 foreach (immutable ci, char c0; s0) { 7908 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 7909 char c1 = s1[ci]; 7910 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 7911 if (c0 != c1) return false; 7912 } 7913 return true; 7914 } 7915 7916 if (ignoreModsOut !is null) *ignoreModsOut = false; 7917 if (updown !is null) *updown = -1; 7918 KeyEvent res; 7919 res.key = cast(Key)0; // just in case 7920 const(char)[] tk, tkn; // last token 7921 bool allowEmascStyle = true; 7922 bool ignoreModifiers = false; 7923 tokenloop: for (;;) { 7924 tk = tkn; 7925 tkn = getToken(); 7926 //k8: yay, i took "Bloody Mess" trait from Fallout! 7927 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 7928 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 7929 if (allowEmascStyle && tkn.length != 0) { 7930 if (tk.length == 1) { 7931 char mdc = tk[0]; 7932 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 7933 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7934 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7935 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7936 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7937 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 7938 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 7939 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 7940 } 7941 } 7942 allowEmascStyle = false; 7943 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7944 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7945 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7946 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7947 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 7948 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 7949 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 7950 if (tk.length == 0) continue; 7951 // try key name 7952 if (res.key == 0) { 7953 // little hack 7954 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 7955 final switch (tk[0]) { 7956 case '0': tk = "N0"; break; 7957 case '1': tk = "N1"; break; 7958 case '2': tk = "N2"; break; 7959 case '3': tk = "N3"; break; 7960 case '4': tk = "N4"; break; 7961 case '5': tk = "N5"; break; 7962 case '6': tk = "N6"; break; 7963 case '7': tk = "N7"; break; 7964 case '8': tk = "N8"; break; 7965 case '9': tk = "N9"; break; 7966 } 7967 } 7968 foreach (string kn; __traits(allMembers, Key)) { 7969 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 7970 } 7971 } 7972 // unknown or duplicate key name, get out of here 7973 break; 7974 } 7975 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 7976 return res; // something 7977 } 7978 7979 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 7980 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 7981 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 7982 if (kk == k) { mask |= mst; kk = cast(Key)0; } 7983 } 7984 bool ignoreMods; 7985 int updown; 7986 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 7987 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 7988 if (this.key != ke.key) { 7989 // things like "ctrl+alt" are complicated 7990 uint tkm = this.modifierState&modmask; 7991 uint kkm = ke.modifierState&modmask; 7992 Key tk = this.key; 7993 // ke 7994 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 7995 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 7996 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 7997 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 7998 // this 7999 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 8000 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 8001 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 8002 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 8003 return (tk == ke.key && tkm == kkm); 8004 } 8005 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 8006 } 8007 } 8008 8009 /// Sets the application name. 8010 @property string ApplicationName(string name) { 8011 return _applicationName = name; 8012 } 8013 8014 string _applicationName; 8015 8016 /// ditto 8017 @property string ApplicationName() { 8018 if(_applicationName is null) { 8019 import core.runtime; 8020 return Runtime.args[0]; 8021 } 8022 return _applicationName; 8023 } 8024 8025 8026 /// Type of a [MouseEvent]. 8027 enum MouseEventType : int { 8028 motion = 0, /// The mouse moved inside the window 8029 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 8030 buttonReleased = 2, /// A mouse button was released 8031 } 8032 8033 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 8034 /++ 8035 Listen for this on your event listeners if you are interested in mouse action. 8036 8037 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. 8038 8039 Examples: 8040 8041 This will draw boxes on the window with the mouse as you hold the left button. 8042 --- 8043 import arsd.simpledisplay; 8044 8045 void main() { 8046 auto window = new SimpleWindow(); 8047 8048 window.eventLoop(0, 8049 (MouseEvent ev) { 8050 if(ev.modifierState & ModifierState.leftButtonDown) { 8051 auto painter = window.draw(); 8052 painter.fillColor = Color.red; 8053 painter.outlineColor = Color.black; 8054 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 8055 } 8056 } 8057 ); 8058 } 8059 --- 8060 +/ 8061 struct MouseEvent { 8062 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 8063 8064 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. 8065 int y; /// Current Y position of the cursor when the event fired. 8066 8067 int dx; /// Change in X position since last report 8068 int dy; /// Change in Y position since last report 8069 8070 MouseButton button; /// See [MouseButton] 8071 int modifierState; /// See [ModifierState] 8072 8073 version(X11) 8074 private Time timestamp; 8075 8076 /// Returns a linear representation of mouse button, 8077 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 8078 /// 8079 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 8080 @property ubyte buttonLinear() const { 8081 import core.bitop; 8082 if(button == 0) 8083 return 0; 8084 return (bsf(button) + 1) & 0b1111; 8085 } 8086 8087 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 8088 8089 SimpleWindow window; /// The window in which the event happened. 8090 8091 Point globalCoordinates() { 8092 Point p; 8093 if(window is null) 8094 throw new Exception("wtf"); 8095 static if(UsingSimpledisplayX11) { 8096 Window child; 8097 XTranslateCoordinates( 8098 XDisplayConnection.get, 8099 window.impl.window, 8100 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 8101 x, y, &p.x, &p.y, &child); 8102 return p; 8103 } else version(Windows) { 8104 POINT[1] points; 8105 points[0].x = x; 8106 points[0].y = y; 8107 MapWindowPoints( 8108 window.impl.hwnd, 8109 null, 8110 points.ptr, 8111 points.length 8112 ); 8113 p.x = points[0].x; 8114 p.y = points[0].y; 8115 8116 return p; 8117 } else version(OSXCocoa) { 8118 auto rect = window.window.frame; 8119 // FIXME: mapped right? 8120 return Point(cast(int) rect.origin.x + x, cast(int) rect.origin.y + y); 8121 } else version(Emscripten) { 8122 throw new NotYetImplementedException(); 8123 } else static assert(0); 8124 } 8125 8126 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 8127 8128 /** 8129 can contain emacs-like modifier prefix 8130 case-insensitive names: 8131 lmbX/leftX 8132 rmbX/rightX 8133 mmbX/middleX 8134 wheelX 8135 motion (no prefix allowed) 8136 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 8137 */ 8138 static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 8139 if (str.length == 0) return false; // just in case 8140 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 8141 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 8142 auto anchor = str; 8143 uint mods = 0; // uint.max == any 8144 // interesting bits in kmod 8145 uint kmodmask = 8146 ModifierState.shift| 8147 ModifierState.ctrl| 8148 ModifierState.alt| 8149 ModifierState.windows| 8150 ModifierState.leftButtonDown| 8151 ModifierState.middleButtonDown| 8152 ModifierState.rightButtonDown| 8153 0; 8154 uint lastButt = uint.max; // otherwise, bit 31 means "down" 8155 bool wasButtons = false; 8156 while (str.length) { 8157 if (str.ptr[0] <= ' ') { 8158 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 8159 continue; 8160 } 8161 // one-letter modifier? 8162 if (str.length >= 2 && str.ptr[1] == '-') { 8163 switch (str.ptr[0]) { 8164 case '*': // "any" modifier (cannot be undone) 8165 mods = mods.max; 8166 break; 8167 case 'C': case 'c': // emacs "ctrl" 8168 if (mods != mods.max) mods |= ModifierState.ctrl; 8169 break; 8170 case 'M': case 'm': // emacs "meta" 8171 if (mods != mods.max) mods |= ModifierState.alt; 8172 break; 8173 case 'S': case 's': // emacs "shift" 8174 if (mods != mods.max) mods |= ModifierState.shift; 8175 break; 8176 case 'H': case 'h': // emacs "hyper" (aka winkey) 8177 if (mods != mods.max) mods |= ModifierState.windows; 8178 break; 8179 default: 8180 return false; // unknown modifier 8181 } 8182 str = str[2..$]; 8183 continue; 8184 } 8185 // word 8186 char[16] buf = void; // locased 8187 auto wep = 0; 8188 while (str.length) { 8189 immutable char ch = str.ptr[0]; 8190 if (ch <= ' ' || ch == '-') break; 8191 str = str[1..$]; 8192 if (wep > buf.length) return false; // too long 8193 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 8194 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 8195 else return false; // invalid char 8196 } 8197 if (wep == 0) return false; // just in case 8198 uint bnum; 8199 enum UpDown { None = -1, Up, Down, Any } 8200 auto updown = UpDown.None; // 0: up; 1: down 8201 switch (buf[0..wep]) { 8202 // left button 8203 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 8204 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 8205 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 8206 case "lmb": case "left": bnum = 0; break; 8207 // middle button 8208 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 8209 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 8210 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 8211 case "mmb": case "middle": bnum = 1; break; 8212 // right button 8213 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 8214 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 8215 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 8216 case "rmb": case "right": bnum = 2; break; 8217 // wheel 8218 case "wheelup": updown = UpDown.Up; goto case "wheel"; 8219 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 8220 case "wheelany": updown = UpDown.Any; goto case "wheel"; 8221 case "wheel": bnum = 3; break; 8222 // motion 8223 case "motion": bnum = 7; break; 8224 // unknown 8225 default: return false; 8226 } 8227 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 8228 // parse possible "-up" or "-down" 8229 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 8230 wep = 0; 8231 foreach (immutable idx, immutable char ch; str[1..$]) { 8232 if (ch <= ' ' || ch == '-') break; 8233 assert(idx == wep); // for now; trick 8234 if (wep > buf.length) { wep = 0; break; } // too long 8235 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 8236 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 8237 else { wep = 0; break; } // invalid char 8238 } 8239 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 8240 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 8241 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 8242 // remove parsed part 8243 if (updown != UpDown.None) str = str[wep+1..$]; 8244 } 8245 if (updown == UpDown.None) { 8246 updown = UpDown.Down; 8247 } 8248 wasButtons = wasButtons || (bnum <= 2); 8249 //assert(updown != UpDown.None); 8250 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 8251 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 8252 if (lastButt != lastButt.max) { 8253 if ((lastButt&0xff) >= 3) return false; // wheel or motion 8254 if (mods != mods.max) { 8255 uint butbit = 0; 8256 final switch (lastButt&0x03) { 8257 case 0: butbit = ModifierState.leftButtonDown; break; 8258 case 1: butbit = ModifierState.middleButtonDown; break; 8259 case 2: butbit = ModifierState.rightButtonDown; break; 8260 } 8261 if (lastButt&Flag.Down) mods |= butbit; 8262 else if (lastButt&Flag.Up) mods &= ~butbit; 8263 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 8264 } 8265 } 8266 // remember last button 8267 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 8268 } 8269 // no button -- nothing to do 8270 if (lastButt == lastButt.max) return false; 8271 // done parsing, check if something's left 8272 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 8273 // remove action button from mask 8274 if ((lastButt&0xff) < 3) { 8275 final switch (lastButt&0x03) { 8276 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 8277 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 8278 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 8279 } 8280 } 8281 // special case: "Motion" means "ignore buttons" 8282 if ((lastButt&0xff) == 7 && !wasButtons) { 8283 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 8284 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 8285 } 8286 uint kmod = event.modifierState&kmodmask; 8287 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 8288 // check modifier state 8289 if (mods != mods.max) { 8290 if (kmod != mods) return false; 8291 } 8292 // now check type 8293 if ((lastButt&0xff) == 7) { 8294 // motion 8295 if (event.type != MouseEventType.motion) return false; 8296 } else if ((lastButt&0xff) == 3) { 8297 // wheel 8298 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 8299 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 8300 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 8301 return false; 8302 } else { 8303 // buttons 8304 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 8305 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 8306 { 8307 return false; 8308 } 8309 // button number 8310 switch (lastButt&0x03) { 8311 case 0: if (event.button != MouseButton.left) return false; break; 8312 case 1: if (event.button != MouseButton.middle) return false; break; 8313 case 2: if (event.button != MouseButton.right) return false; break; 8314 default: return false; 8315 } 8316 } 8317 return true; 8318 } 8319 } 8320 8321 version(arsd_mevent_strcmp_test) unittest { 8322 MouseEvent event; 8323 event.type = MouseEventType.buttonPressed; 8324 event.button = MouseButton.left; 8325 event.modifierState = ModifierState.ctrl; 8326 assert(event == "C-LMB"); 8327 assert(event != "C-LMBUP"); 8328 assert(event != "C-LMB-UP"); 8329 assert(event != "C-S-LMB"); 8330 assert(event == "*-LMB"); 8331 assert(event != "*-LMB-UP"); 8332 8333 event.type = MouseEventType.buttonReleased; 8334 assert(event != "C-LMB"); 8335 assert(event == "C-LMBUP"); 8336 assert(event == "C-LMB-UP"); 8337 assert(event != "C-S-LMB"); 8338 assert(event != "*-LMB"); 8339 assert(event == "*-LMB-UP"); 8340 8341 event.button = MouseButton.right; 8342 event.modifierState |= ModifierState.shift; 8343 event.type = MouseEventType.buttonPressed; 8344 assert(event != "C-LMB"); 8345 assert(event != "C-LMBUP"); 8346 assert(event != "C-LMB-UP"); 8347 assert(event != "C-S-LMB"); 8348 assert(event != "*-LMB"); 8349 assert(event != "*-LMB-UP"); 8350 8351 assert(event != "C-RMB"); 8352 assert(event != "C-RMBUP"); 8353 assert(event != "C-RMB-UP"); 8354 assert(event == "C-S-RMB"); 8355 assert(event == "*-RMB"); 8356 assert(event != "*-RMB-UP"); 8357 } 8358 8359 /// This gives a few more options to drawing lines and such 8360 struct Pen { 8361 Color color; /// the foreground color 8362 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. 8363 Style style; /// See [Style] 8364 /+ 8365 // From X.h 8366 8367 #define LineSolid 0 8368 #define LineOnOffDash 1 8369 #define LineDoubleDash 2 8370 LineDou- The full path of the line is drawn, but the 8371 bleDash even dashes are filled differently from the 8372 odd dashes (see fill-style) with CapButt 8373 style used where even and odd dashes meet. 8374 8375 8376 8377 /* capStyle */ 8378 8379 #define CapNotLast 0 8380 #define CapButt 1 8381 #define CapRound 2 8382 #define CapProjecting 3 8383 8384 /* joinStyle */ 8385 8386 #define JoinMiter 0 8387 #define JoinRound 1 8388 #define JoinBevel 2 8389 8390 /* fillStyle */ 8391 8392 #define FillSolid 0 8393 #define FillTiled 1 8394 #define FillStippled 2 8395 #define FillOpaqueStippled 3 8396 8397 8398 +/ 8399 /// Style of lines drawn 8400 enum Style { 8401 Solid, /// a solid line 8402 Dashed, /// a dashed line 8403 Dotted, /// a dotted line 8404 } 8405 } 8406 8407 8408 /++ 8409 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 8410 8411 8412 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 8413 8414 $(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.) 8415 8416 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. 8417 8418 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 8419 8420 $(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. 8421 8422 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! 8423 8424 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!) 8425 8426 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 8427 8428 --- 8429 auto image = new Image(256, 256); 8430 scope(exit) destroy(image); 8431 --- 8432 8433 As long as you don't hold on to it outside the scope. 8434 8435 I might change it to be an owned pointer at some point in the future. 8436 8437 ) 8438 8439 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 8440 you can also often get a fair amount of speedup by getting the raw data format and 8441 writing some custom code. 8442 8443 FIXME INSERT EXAMPLES HERE 8444 8445 8446 +/ 8447 final class Image { 8448 /// 8449 this(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 8450 this.width = width; 8451 this.height = height; 8452 this.enableAlpha = enableAlpha; 8453 8454 impl.createImage(width, height, forcexshm, enableAlpha); 8455 } 8456 8457 /// 8458 this(Size size, bool forcexshm=false, bool enableAlpha = false) { 8459 this(size.width, size.height, forcexshm, enableAlpha); 8460 } 8461 8462 private bool suppressDestruction; 8463 8464 version(X11) 8465 this(XImage* handle) { 8466 this.handle = handle; 8467 this.rawData = cast(ubyte*) handle.data; 8468 this.width = handle.width; 8469 this.height = handle.height; 8470 this.enableAlpha = handle.depth == 32; 8471 suppressDestruction = true; 8472 } 8473 8474 ~this() { 8475 if(suppressDestruction) return; 8476 impl.dispose(); 8477 } 8478 8479 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 8480 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 8481 pure const @system nothrow { 8482 /* 8483 To use these to draw a blue rectangle with size WxH at position X,Y... 8484 8485 // make certain that it will fit before we proceed 8486 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! 8487 8488 // gather all the values you'll need up front. These can be kept until the image changes size if you want 8489 // (though calculating them isn't really that expensive). 8490 auto nextLineAdjustment = img.adjustmentForNextLine(); 8491 auto offR = img.redByteOffset(); 8492 auto offB = img.blueByteOffset(); 8493 auto offG = img.greenByteOffset(); 8494 auto bpp = img.bytesPerPixel(); 8495 8496 auto data = img.getDataPointer(); 8497 8498 // figure out the starting byte offset 8499 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 8500 8501 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 8502 8503 // and now our drawing loop for the rectangle 8504 foreach(y; 0 .. H) { 8505 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 8506 foreach(x; 0 .. W) { 8507 // write our color 8508 data[offR] = 0; 8509 data[offG] = 0; 8510 data[offB] = 255; 8511 8512 data += bpp; // moving to the next pixel is just an addition... 8513 } 8514 startOfLine += nextLineAdjustment; 8515 } 8516 8517 8518 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 8519 8520 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 8521 can be made into a bitmask or something so we can write them as *uint... 8522 */ 8523 8524 /// 8525 int offsetForTopLeftPixel() { 8526 version(X11) { 8527 return 0; 8528 } else version(Windows) { 8529 if(enableAlpha) { 8530 return (width * 4) * (height - 1); 8531 } else { 8532 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 8533 } 8534 } else version(OSXCocoa) { 8535 return 0 ; //throw new NotYetImplementedException(); 8536 } else version(Emscripten) { 8537 return 0; 8538 } else static assert(0, "fill in this info for other OSes"); 8539 } 8540 8541 /// 8542 int offsetForPixel(int x, int y) { 8543 version(X11) { 8544 auto offset = (y * width + x) * 4; 8545 return offset; 8546 } else version(Windows) { 8547 if(enableAlpha) { 8548 auto itemsPerLine = width * 4; 8549 // remember, bmps are upside down 8550 auto offset = itemsPerLine * (height - y - 1) + x * 4; 8551 return offset; 8552 } else { 8553 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 8554 // remember, bmps are upside down 8555 auto offset = itemsPerLine * (height - y - 1) + x * 3; 8556 return offset; 8557 } 8558 } else version(OSXCocoa) { 8559 return (y * width + x) * 4 ; //throw new NotYetImplementedException(); 8560 } else version(Emscripten) { 8561 return (y * width + x) * 4 ; //throw new NotYetImplementedException(); 8562 } else static assert(0, "fill in this info for other OSes"); 8563 } 8564 8565 /// 8566 int adjustmentForNextLine() { 8567 version(X11) { 8568 return width * 4; 8569 } else version(Windows) { 8570 // windows bmps are upside down, so the adjustment is actually negative 8571 if(enableAlpha) 8572 return - (cast(int) width * 4); 8573 else 8574 return -((cast(int) width * 3 + 3) / 4) * 4; 8575 } else version(OSXCocoa) { 8576 return width * 4 ; //throw new NotYetImplementedException(); 8577 } else version(Emscripten) { 8578 return width * 4 ; //throw new NotYetImplementedException(); 8579 } else static assert(0, "fill in this info for other OSes"); 8580 } 8581 8582 /// once you have the position of a pixel, use these to get to the proper color 8583 int redByteOffset() { 8584 version(X11) { 8585 return 2; 8586 } else version(Windows) { 8587 return 2; 8588 } else version(OSXCocoa) { 8589 return 2 ; //throw new NotYetImplementedException(); 8590 } else version(Emscripten) { 8591 return 2 ; //throw new NotYetImplementedException(); 8592 } else static assert(0, "fill in this info for other OSes"); 8593 } 8594 8595 /// 8596 int greenByteOffset() { 8597 version(X11) { 8598 return 1; 8599 } else version(Windows) { 8600 return 1; 8601 } else version(OSXCocoa) { 8602 return 1 ; //throw new NotYetImplementedException(); 8603 } else version(Emscripten) { 8604 return 1 ; //throw new NotYetImplementedException(); 8605 } else static assert(0, "fill in this info for other OSes"); 8606 } 8607 8608 /// 8609 int blueByteOffset() { 8610 version(X11) { 8611 return 0; 8612 } else version(Windows) { 8613 return 0; 8614 } else version(OSXCocoa) { 8615 return 0 ; //throw new NotYetImplementedException(); 8616 } else version(Emscripten) { 8617 return 0 ; //throw new NotYetImplementedException(); 8618 } else static assert(0, "fill in this info for other OSes"); 8619 } 8620 8621 /// Only valid if [enableAlpha] is true 8622 int alphaByteOffset() { 8623 version(X11) { 8624 return 3; 8625 } else version(Windows) { 8626 return 3; 8627 } else version(OSXCocoa) { 8628 return 3; //throw new NotYetImplementedException(); 8629 } else version(Emscripten) { 8630 return 3 ; //throw new NotYetImplementedException(); 8631 } else static assert(0, "fill in this info for other OSes"); 8632 } 8633 } 8634 8635 /// 8636 final void putPixel(int x, int y, Color c) { 8637 if(x < 0 || x >= width) 8638 return; 8639 if(y < 0 || y >= height) 8640 return; 8641 8642 impl.setPixel(x, y, c); 8643 } 8644 8645 /// 8646 final Color getPixel(int x, int y) { 8647 if(x < 0 || x >= width) 8648 return Color.transparent; 8649 if(y < 0 || y >= height) 8650 return Color.transparent; 8651 8652 version(OSXCocoa) throw new NotYetImplementedException(); else 8653 return impl.getPixel(x, y); 8654 } 8655 8656 /// 8657 final void opIndexAssign(Color c, int x, int y) { 8658 putPixel(x, y, c); 8659 } 8660 8661 /// 8662 TrueColorImage toTrueColorImage() { 8663 auto tci = new TrueColorImage(width, height); 8664 convertToRgbaBytes(tci.imageData.bytes); 8665 return tci; 8666 } 8667 8668 /// 8669 static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) { 8670 auto tci = i.getAsTrueColorImage(); 8671 auto img = new Image(tci.width, tci.height, false, enableAlpha); 8672 static if(UsingSimpledisplayX11) 8673 img.premultiply = premultiply; 8674 img.setRgbaBytes(tci.imageData.bytes); 8675 return img; 8676 } 8677 8678 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 8679 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 8680 /// if you pass null, it will allocate a new one. 8681 ubyte[] getRgbaBytes(ubyte[] where = null) { 8682 if(where is null) 8683 where = new ubyte[this.width*this.height*4]; 8684 convertToRgbaBytes(where); 8685 return where; 8686 } 8687 8688 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 8689 void setRgbaBytes(in ubyte[] from ) { 8690 assert(from.length == this.width * this.height * 4); 8691 setFromRgbaBytes(from); 8692 } 8693 8694 // FIXME: make properly cross platform by getting rgba right 8695 8696 /// warning: this is not portable across platforms because the data format can change 8697 ubyte* getDataPointer() { 8698 return impl.rawData; 8699 } 8700 8701 /// for use with getDataPointer 8702 final int bytesPerLine() const pure @safe nothrow { 8703 version(Windows) 8704 return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 8705 else version(X11) 8706 return 4 * width; 8707 else version(OSXCocoa) 8708 return 4 * width; 8709 else static assert(0); 8710 } 8711 8712 /// for use with getDataPointer 8713 final int bytesPerPixel() const pure @safe nothrow { 8714 version(Windows) 8715 return enableAlpha ? 4 : 3; 8716 else version(X11) 8717 return 4; 8718 else version(OSXCocoa) 8719 return 4; 8720 else static assert(0); 8721 } 8722 8723 /// 8724 immutable int width; 8725 8726 /// 8727 immutable int height; 8728 8729 /// 8730 immutable bool enableAlpha; 8731 //private: 8732 mixin NativeImageImplementation!() impl; 8733 } 8734 8735 /++ 8736 A convenience function to pop up a window displaying the image. 8737 If you pass a win, it will draw the image in it. Otherwise, it will 8738 create a window with the size of the image and run its event loop, closing 8739 when a key is pressed. 8740 8741 History: 8742 `BlockingMode` parameter added on December 8, 2021. Previously, it would 8743 always block until the application quit which could cause bizarre behavior 8744 inside a more complex application. Now, the default is to block until 8745 this window closes if it is the only event loop running, and otherwise, 8746 not to block at all and just pop up the display window asynchronously. 8747 +/ 8748 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) { 8749 if(win is null) { 8750 win = new SimpleWindow(image); 8751 { 8752 auto p = win.draw; 8753 p.drawImage(Point(0, 0), image); 8754 } 8755 win.eventLoopWithBlockingMode( 8756 bm, 0, 8757 (KeyEvent ev) { 8758 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 8759 } ); 8760 } else { 8761 win.image = image; 8762 } 8763 } 8764 8765 enum FontWeight : int { 8766 dontcare = 0, 8767 thin = 100, 8768 extralight = 200, 8769 light = 300, 8770 regular = 400, 8771 medium = 500, 8772 semibold = 600, 8773 bold = 700, 8774 extrabold = 800, 8775 heavy = 900 8776 } 8777 8778 /++ 8779 Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont]. 8780 8781 History: 8782 Added October 24, 2022. The methods were already on [OperatingSystemFont] before that. 8783 +/ 8784 interface MeasurableFont { 8785 /++ 8786 History: 8787 Added April 12, 2025 8788 +/ 8789 //version(OSXCocoa) 8790 alias fnum = float; 8791 //else 8792 //alias fnum = int; 8793 8794 8795 /++ 8796 Returns true if it is a monospace font, meaning each of the 8797 glyphs (at least the ascii characters) have matching width 8798 and no kerning, so you can determine the display width of some 8799 strings by simply multiplying the string width by [averageWidth]. 8800 8801 (Please note that multiply doesn't $(I actually) work in general, 8802 consider characters like tab and newline, but it does sometimes.) 8803 +/ 8804 bool isMonospace(); 8805 8806 /++ 8807 The average width of glyphs in the font, traditionally equal to the 8808 width of the lowercase x. Can be used to estimate bounding boxes, 8809 especially if the font [isMonospace]. 8810 8811 Given in pixels. 8812 +/ 8813 fnum averageWidth(); 8814 /++ 8815 The height of the bounding box of a line. 8816 +/ 8817 fnum height(); 8818 /++ 8819 The maximum ascent of a glyph above the baseline. 8820 8821 Given in pixels. 8822 +/ 8823 fnum ascent(); 8824 /++ 8825 The maximum descent of a glyph below the baseline. For example, how low the g might go. 8826 8827 Given in pixels. 8828 +/ 8829 fnum descent(); 8830 /++ 8831 The display width of the given string, and if you provide a window, it will use it to 8832 make the pixel count on screen more accurate too, but this shouldn't generally be necessary. 8833 8834 Given in pixels. 8835 +/ 8836 fnum stringWidth(scope const(char)[] s, SimpleWindow window = null); 8837 8838 } 8839 8840 int castFnumToCnum(MeasurableFont.fnum i) { 8841 static if(is(MeasurableFont.fnum : long)) 8842 return cast(int) i; 8843 else 8844 return cast(int) (i + 0.9); 8845 } 8846 8847 // FIXME: i need a font cache and it needs to handle disconnects. 8848 8849 /++ 8850 Represents a font loaded off the operating system or the X server. 8851 8852 8853 While the api here is unified cross platform, the fonts are not necessarily 8854 available, even across machines of the same platform, so be sure to always check 8855 for null (using [isNull]) and have a fallback plan. 8856 8857 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 8858 8859 Worst case, a null font will automatically fall back to the default font loaded 8860 for your system. 8861 +/ 8862 class OperatingSystemFont : MeasurableFont { 8863 // FIXME: when the X Connection is lost, these need to be invalidated! 8864 // that means I need to store the original stuff again to reconstruct it too. 8865 8866 version(Emscripten) { 8867 void* font; 8868 } else version(X11) { 8869 XFontStruct* font; 8870 XFontSet fontset; 8871 8872 version(with_xft) { 8873 XftFont* xftFont; 8874 bool isXft; 8875 } 8876 } else version(Windows) { 8877 HFONT font; 8878 int width_; 8879 int height_; 8880 } else version(OSXCocoa) { 8881 NSFont font; 8882 } else static assert(0); 8883 8884 /++ 8885 Constructs the class and immediately calls [load]. 8886 +/ 8887 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8888 load(name, size, weight, italic); 8889 } 8890 8891 /++ 8892 Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object. 8893 8894 You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you. 8895 8896 History: 8897 Added January 24, 2021. 8898 +/ 8899 this() { 8900 // this space intentionally left blank 8901 } 8902 8903 /++ 8904 Constructs a copy of the given font object. 8905 8906 History: 8907 Added January 7, 2023. 8908 +/ 8909 this(OperatingSystemFont font) { 8910 if(font is null || font.loadedInfo is LoadedInfo.init) 8911 loadDefault(); 8912 else 8913 load(font.loadedInfo.tupleof); 8914 } 8915 8916 /++ 8917 Loads specifically with the Xft library - a freetype font from a fontconfig string. 8918 8919 History: 8920 Added November 13, 2020. 8921 +/ 8922 version(with_xft) 8923 bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8924 unload(); 8925 8926 if(!XftLibrary.attempted) { 8927 XftLibrary.loadDynamicLibrary(); 8928 } 8929 8930 if(!XftLibrary.loadSuccessful) 8931 return false; 8932 8933 auto display = XDisplayConnection.get; 8934 8935 char[256] nameBuffer = void; 8936 int nbp = 0; 8937 8938 void add(in char[] a) { 8939 nameBuffer[nbp .. nbp + a.length] = a[]; 8940 nbp += a.length; 8941 } 8942 add(name); 8943 8944 if(size) { 8945 add(":size="); 8946 add(toInternal!string(size)); 8947 } 8948 if(weight != FontWeight.dontcare && weight != 400) { 8949 if(weight < 400) 8950 add(":style=Light"); 8951 else 8952 add(":style=Bold"); 8953 add(":weight="); 8954 add(weightToString(weight)); 8955 } 8956 if(italic) { 8957 if(weight == FontWeight.dontcare) 8958 add(":style=Italic"); 8959 add(":slant=100"); 8960 } 8961 8962 nameBuffer[nbp] = 0; 8963 8964 this.xftFont = XftFontOpenName( 8965 display, 8966 DefaultScreen(display), 8967 nameBuffer.ptr 8968 ); 8969 8970 this.isXft = true; 8971 8972 if(xftFont !is null) { 8973 isMonospace_ = stringWidth("x") == stringWidth("M"); 8974 ascent_ = xftFont.ascent; 8975 descent_ = xftFont.descent; 8976 } 8977 8978 return !isNull(); 8979 } 8980 8981 /++ 8982 Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor. 8983 8984 8985 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. 8986 8987 If `pattern` is null, it returns all available font families. 8988 8989 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. 8990 8991 The format of the pattern is platform-specific. 8992 8993 History: 8994 Added May 1, 2021 (dub v9.5) 8995 +/ 8996 static void listFonts(string pattern, bool delegate(in char[] name) handler) { 8997 version(Windows) { 8998 auto hdc = GetDC(null); 8999 scope(exit) ReleaseDC(null, hdc); 9000 LOGFONT logfont; 9001 static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) { 9002 auto localHandler = *(cast(typeof(handler)*) p); 9003 return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0; 9004 } 9005 EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0); 9006 } else version(X11) { 9007 //import core.stdc.stdio; 9008 bool done = false; 9009 version(with_xft) { 9010 if(!XftLibrary.attempted) { 9011 XftLibrary.loadDynamicLibrary(); 9012 } 9013 9014 if(!XftLibrary.loadSuccessful) 9015 goto skipXft; 9016 9017 if(!FontConfigLibrary.attempted) 9018 FontConfigLibrary.loadDynamicLibrary(); 9019 if(!FontConfigLibrary.loadSuccessful) 9020 goto skipXft; 9021 9022 { 9023 auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null); 9024 if(got is null) 9025 goto skipXft; 9026 scope(exit) FcFontSetDestroy(got); 9027 9028 auto fontPatterns = got.fonts[0 .. got.nfont]; 9029 foreach(candidate; fontPatterns) { 9030 char* where, whereStyle; 9031 9032 char* pmg = FcNameUnparse(candidate); 9033 9034 //FcPatternGetString(candidate, "family", 0, &where); 9035 //FcPatternGetString(candidate, "style", 0, &whereStyle); 9036 //if(where && whereStyle) { 9037 if(pmg) { 9038 if(!handler(pmg.sliceCString)) 9039 return; 9040 //printf("%s || %s %s\n", pmg, where, whereStyle); 9041 } 9042 } 9043 } 9044 } 9045 9046 skipXft: 9047 9048 if(pattern is null) 9049 pattern = "*"; 9050 9051 int count; 9052 auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count); 9053 scope(exit) XFreeFontNames(coreFontsRaw); 9054 9055 auto coreFonts = coreFontsRaw[0 .. count]; 9056 9057 foreach(font; coreFonts) { 9058 char[128] tmp; 9059 tmp[0 ..5] = "core:"; 9060 auto cf = font.sliceCString; 9061 if(5 + cf.length > tmp.length) 9062 assert(0, "a font name was too long, sorry i didn't bother implementing a fallback"); 9063 tmp[5 .. 5 + cf.length] = cf; 9064 if(!handler(tmp[0 .. 5 + cf.length])) 9065 return; 9066 } 9067 } 9068 } 9069 9070 /++ 9071 Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont 9072 to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega]. 9073 9074 Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the 9075 underlying system doesn't support returning the raw bytes. 9076 9077 History: 9078 Added September 10, 2021 (dub v10.3) 9079 +/ 9080 ubyte[] getTtfBytes() { 9081 if(isNull) 9082 return null; 9083 9084 version(Windows) { 9085 auto dc = GetDC(null); 9086 auto orig = SelectObject(dc, font); 9087 9088 scope(exit) { 9089 SelectObject(dc, orig); 9090 ReleaseDC(null, dc); 9091 } 9092 9093 auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0); 9094 if(res == GDI_ERROR) 9095 return null; 9096 9097 ubyte[] buffer = new ubyte[](res); 9098 res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length); 9099 if(res == GDI_ERROR) 9100 return null; // wtf really tbh 9101 9102 return buffer; 9103 } else version(with_xft) { 9104 if(isXft && xftFont) { 9105 if(!FontConfigLibrary.attempted) 9106 FontConfigLibrary.loadDynamicLibrary(); 9107 if(!FontConfigLibrary.loadSuccessful) 9108 return null; 9109 9110 char* file; 9111 if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) { 9112 if (file !is null && file[0]) { 9113 import core.stdc.stdio; 9114 auto fp = fopen(file, "rb"); 9115 if(fp is null) 9116 return null; 9117 scope(exit) 9118 fclose(fp); 9119 fseek(fp, 0, SEEK_END); 9120 ubyte[] buffer = new ubyte[](ftell(fp)); 9121 fseek(fp, 0, SEEK_SET); 9122 9123 auto got = fread(buffer.ptr, 1, buffer.length, fp); 9124 if(got != buffer.length) 9125 return null; 9126 9127 return buffer; 9128 } 9129 } 9130 } 9131 return null; 9132 } else throw new NotYetImplementedException(); 9133 } 9134 9135 // see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352 9136 9137 private string weightToString(FontWeight weight) { 9138 with(FontWeight) 9139 final switch(weight) { 9140 case dontcare: return "*"; 9141 case thin: return "extralight"; 9142 case extralight: return "extralight"; 9143 case light: return "light"; 9144 case regular: return "regular"; 9145 case medium: return "medium"; 9146 case semibold: return "demibold"; 9147 case bold: return "bold"; 9148 case extrabold: return "demibold"; 9149 case heavy: return "black"; 9150 } 9151 } 9152 9153 /++ 9154 Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance. 9155 9156 History: 9157 Added November 13, 2020. Before then, this code was integrated in the [load] function. 9158 +/ 9159 version(X11) 9160 bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 9161 unload(); 9162 9163 string xfontstr; 9164 9165 if(name.length > 3 && name[0 .. 3] == "-*-") { 9166 // this is kinda a disgusting hack but if the user sends an exact 9167 // string I'd like to honor it... 9168 xfontstr = name; 9169 } else { 9170 string weightstr = weightToString(weight); 9171 string sizestr; 9172 if(size == 0) 9173 sizestr = "*"; 9174 else 9175 sizestr = toInternal!string(size); 9176 xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 9177 } 9178 9179 // writeln(xfontstr); 9180 9181 auto display = XDisplayConnection.get; 9182 9183 font = XLoadQueryFont(display, xfontstr.ptr); 9184 if(font is null) 9185 return false; 9186 9187 char** lol; 9188 int lol2; 9189 char* lol3; 9190 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 9191 9192 prepareFontInfo(); 9193 9194 return !isNull(); 9195 } 9196 9197 version(X11) 9198 private void prepareFontInfo() { 9199 if(font !is null) { 9200 isMonospace_ = stringWidth("l") == stringWidth("M"); 9201 ascent_ = font.max_bounds.ascent; 9202 descent_ = font.max_bounds.descent; 9203 } 9204 } 9205 9206 version(OSXCocoa) 9207 private void prepareFontInfo() { 9208 if(font !is null) { 9209 isMonospace_ = font.isFixedPitch; 9210 ascent_ = cast(int) font.ascender; 9211 descent_ = cast(int) - font.descender; 9212 } 9213 } 9214 9215 9216 /++ 9217 Loads a Windows font. You probably want to use [load] instead to be more generic. 9218 9219 History: 9220 Added November 13, 2020. Before then, this code was integrated in the [load] function. 9221 +/ 9222 version(Windows) 9223 bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) { 9224 unload(); 9225 9226 WCharzBuffer buffer = WCharzBuffer(name); 9227 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 9228 9229 prepareFontInfo(hdc); 9230 9231 return !isNull(); 9232 } 9233 9234 version(Windows) 9235 void prepareFontInfo(HDC hdc = null) { 9236 if(font is null) 9237 return; 9238 9239 TEXTMETRIC tm; 9240 auto dc = hdc ? hdc : GetDC(null); 9241 auto orig = SelectObject(dc, font); 9242 GetTextMetrics(dc, &tm); 9243 SelectObject(dc, orig); 9244 if(hdc is null) 9245 ReleaseDC(null, dc); 9246 9247 width_ = tm.tmAveCharWidth; 9248 height_ = tm.tmHeight; 9249 ascent_ = tm.tmAscent; 9250 descent_ = tm.tmDescent; 9251 // 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. 9252 isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0; 9253 } 9254 9255 9256 /++ 9257 `name` is a font name, but it can also be a more complicated string parsed in an OS-specific way. 9258 9259 On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise, 9260 it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX]. 9261 9262 On Windows, it forwards directly to [loadWin32]. 9263 9264 Params: 9265 name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences. 9266 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. 9267 weight = approximate boldness, results may vary. 9268 italic = try to get a slanted version of the given font. 9269 9270 History: 9271 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. 9272 +/ 9273 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 9274 this.loadedInfo = LoadedInfo(name, size, weight, italic); 9275 version(X11) { 9276 version(with_xft) { 9277 if(name.length > 5 && name[0 .. 5] == "core:") { 9278 goto core; 9279 } 9280 9281 if(loadXft(name, size, weight, italic)) 9282 return true; 9283 // if xft fails, fallback to core to avoid breaking 9284 // code that already depended on this. 9285 } 9286 9287 core: 9288 9289 if(name.length > 5 && name[0 .. 5] == "core:") { 9290 name = name[5 .. $]; 9291 } 9292 9293 return loadCoreX(name, size, weight, italic); 9294 } else version(Windows) { 9295 return loadWin32(name, size, weight, italic); 9296 } else version(OSXCocoa) { 9297 return loadCocoa(name, size, weight, italic); 9298 } else static assert(0); 9299 } 9300 9301 version(OSXCocoa) 9302 bool loadCocoa(string name, int size, FontWeight weight, bool italic) { 9303 unload(); 9304 9305 font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic? 9306 font.retain(); 9307 prepareFontInfo(); 9308 9309 return !isNull(); 9310 } 9311 9312 private struct LoadedInfo { 9313 string name; 9314 int size; 9315 FontWeight weight; 9316 bool italic; 9317 } 9318 private LoadedInfo loadedInfo; 9319 9320 // int size() { return loadedInfo.size; } 9321 9322 /// 9323 void unload() { 9324 if(isNull()) 9325 return; 9326 9327 version(X11) { 9328 auto display = XDisplayConnection.display; 9329 9330 if(display is null) 9331 return; 9332 9333 version(with_xft) { 9334 if(isXft) { 9335 if(xftFont) 9336 XftFontClose(display, xftFont); 9337 isXft = false; 9338 xftFont = null; 9339 return; 9340 } 9341 } 9342 9343 if(font && font !is ScreenPainterImplementation.defaultfont) 9344 XFreeFont(display, font); 9345 if(fontset && fontset !is ScreenPainterImplementation.defaultfontset) 9346 XFreeFontSet(display, fontset); 9347 9348 font = null; 9349 fontset = null; 9350 } else version(Windows) { 9351 DeleteObject(font); 9352 font = null; 9353 } else version(OSXCocoa) { 9354 font.release(); 9355 font = null; 9356 } else static assert(0); 9357 } 9358 9359 private bool isMonospace_; 9360 9361 /++ 9362 History: 9363 Added January 16, 2021 9364 +/ 9365 bool isMonospace() { 9366 return isMonospace_; 9367 } 9368 9369 /++ 9370 Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character. 9371 9372 History: 9373 Added March 26, 2020 9374 Documented January 16, 2021 9375 +/ 9376 fnum averageWidth() { 9377 version(X11) { 9378 return stringWidth("x"); 9379 } version(OSXCocoa) { 9380 return stringWidth("x"); 9381 } else version(Windows) 9382 return width_; 9383 else assert(0); 9384 } 9385 9386 /++ 9387 Returns the width of the string as drawn on the specified window, or the default screen if the window is null. 9388 9389 History: 9390 Added January 16, 2021 9391 +/ 9392 fnum stringWidth(scope const(char)[] s, SimpleWindow window = null) { 9393 // FIXME: what about tab? 9394 if(isNull) 9395 return 0; 9396 9397 version(X11) { 9398 version(with_xft) 9399 if(isXft && xftFont !is null) { 9400 //return xftFont.max_advance_width; 9401 XGlyphInfo extents; 9402 XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents); 9403 // writeln(extents); 9404 return extents.xOff; 9405 } 9406 if(font is null) 9407 return 0; 9408 else if(fontset) { 9409 XRectangle rect; 9410 Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect); 9411 9412 return rect.width; 9413 } else { 9414 return XTextWidth(font, s.ptr, cast(int) s.length); 9415 } 9416 } else version(Windows) { 9417 WCharzBuffer buffer = WCharzBuffer(s); 9418 9419 return stringWidth(buffer.slice, window); 9420 } else version(OSXCocoa) { 9421 /+ 9422 int charCount = [string length]; 9423 CGGlyph glyphs[charCount]; 9424 CGRect rects[charCount]; 9425 9426 CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount); 9427 CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount); 9428 9429 int totalwidth = 0, maxheight = 0; 9430 for (int i=0; i < charCount; i++) 9431 { 9432 totalwidth += rects[i].size.width; 9433 maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight; 9434 } 9435 9436 dim = CGSizeMake(totalwidth, maxheight); 9437 +/ 9438 MacString str = MacString(s); 9439 NSDictionary dict = NSDictionary.dictionaryWithObject( 9440 font, 9441 /*forKey:*/cast(void*) NSFontAttributeName 9442 ); 9443 // scope(exit) dict.release(); 9444 NSSize size = str.borrow.sizeWithAttributes(dict); 9445 9446 // import std.stdio; writeln(s, " ", size); 9447 9448 return size.width; // cast(int) (size.width + 0.9 /* to round up */); // FIXME 9449 } 9450 else assert(0); 9451 } 9452 9453 version(Windows) 9454 /// ditto 9455 int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) { 9456 if(isNull) 9457 return 0; 9458 version(Windows) { 9459 SIZE size; 9460 9461 prepareContext(window); 9462 scope(exit) releaseContext(); 9463 9464 GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size); 9465 9466 return size.cx; 9467 } else { 9468 // std.conv can do this easily but it is slow to import and i don't think it is worth it 9469 static assert(0, "not implemented yet"); 9470 //return stringWidth(s, window); 9471 } 9472 } 9473 9474 private { 9475 int prepRefcount; 9476 9477 version(Windows) { 9478 HDC dc; 9479 HANDLE orig; 9480 HWND hwnd; 9481 } 9482 } 9483 /++ 9484 [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. 9485 9486 History: 9487 Added January 23, 2021 9488 +/ 9489 void prepareContext(SimpleWindow window = null) { 9490 prepRefcount++; 9491 if(prepRefcount == 1) { 9492 version(Windows) { 9493 hwnd = window is null ? null : window.impl.hwnd; 9494 dc = GetDC(hwnd); 9495 orig = SelectObject(dc, font); 9496 } 9497 } 9498 } 9499 /// ditto 9500 void releaseContext() { 9501 prepRefcount--; 9502 if(prepRefcount == 0) { 9503 version(Windows) { 9504 SelectObject(dc, orig); 9505 ReleaseDC(hwnd, dc); 9506 hwnd = null; 9507 dc = null; 9508 orig = null; 9509 } 9510 } 9511 } 9512 9513 /+ 9514 FIXME: I think I need advance and kerning pair 9515 9516 int advance(dchar from, dchar to) { } // use dchar.init for first item in string 9517 +/ 9518 9519 /++ 9520 Returns the height of the font. 9521 9522 History: 9523 Added March 26, 2020 9524 Documented January 16, 2021 9525 +/ 9526 fnum height() { 9527 version(X11) { 9528 version(with_xft) 9529 if(isXft && xftFont !is null) { 9530 return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel 9531 } 9532 if(font is null) 9533 return 0; 9534 return font.max_bounds.ascent + font.max_bounds.descent; 9535 } else version(Windows) { 9536 return height_; 9537 } else version(OSXCocoa) { 9538 if(font is null) 9539 return 0; 9540 // the descender likely negative so minus means we actually add 9541 return cast(int) (font.ascender - font.descender + 0.9 /* to round up */); 9542 // return cast(int) font.capHeight; 9543 } 9544 else assert(0); 9545 } 9546 9547 private fnum ascent_; 9548 private fnum descent_; 9549 9550 /++ 9551 Max ascent above the baseline. 9552 9553 History: 9554 Added January 22, 2021 9555 +/ 9556 fnum ascent() { 9557 return ascent_; 9558 } 9559 9560 /++ 9561 Max descent below the baseline. 9562 9563 History: 9564 Added January 22, 2021 9565 +/ 9566 fnum descent() { 9567 return descent_; 9568 } 9569 9570 /++ 9571 Loads the default font used by [ScreenPainter] if none others are loaded. 9572 9573 Returns: 9574 This method mutates the `this` object, but then returns `this` for 9575 easy chaining like: 9576 9577 --- 9578 auto font = foo.isNull ? foo : foo.loadDefault 9579 --- 9580 9581 History: 9582 Added previously, but left unimplemented until January 24, 2021. 9583 +/ 9584 OperatingSystemFont loadDefault() { 9585 unload(); 9586 9587 loadedInfo = LoadedInfo.init; 9588 9589 version(X11) { 9590 // another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html 9591 // but meh since sdpy does its own thing, this should be ok too 9592 9593 ScreenPainterImplementation.ensureDefaultFontLoaded(); 9594 this.font = ScreenPainterImplementation.defaultfont; 9595 this.fontset = ScreenPainterImplementation.defaultfontset; 9596 9597 prepareFontInfo(); 9598 return this; 9599 } else version(Windows) { 9600 ScreenPainterImplementation.ensureDefaultFontLoaded(); 9601 this.font = ScreenPainterImplementation.defaultGuiFont; 9602 9603 prepareFontInfo(); 9604 return this; 9605 } else version(OSXCocoa) { 9606 this.font = NSFont.systemFontOfSize(15); 9607 font.retain(); 9608 9609 prepareFontInfo(); 9610 9611 // import std.stdio; writeln("Load default: ", this.height()); 9612 return this; 9613 } else throw new NotYetImplementedException(); 9614 } 9615 9616 /// 9617 bool isNull() { 9618 version(with_xft) 9619 if(isXft) 9620 return xftFont is null; 9621 return font is null; 9622 } 9623 9624 /* Metrics */ 9625 /+ 9626 GetABCWidth 9627 GetKerningPairs 9628 9629 if I do it right, I can size it all here, and match 9630 what happens when I draw the full string with the OS functions. 9631 9632 subclasses might do the same thing while getting the glyphs on images 9633 struct GlyphInfo { 9634 int glyph; 9635 9636 size_t stringIdxStart; 9637 size_t stringIdxEnd; 9638 9639 Rectangle boundingBox; 9640 } 9641 GlyphInfo[] getCharBoxes() { 9642 // XftTextExtentsUtf8 9643 return null; 9644 9645 } 9646 +/ 9647 9648 ~this() { 9649 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 9650 unload(); 9651 } 9652 } 9653 9654 version(Windows) 9655 private string sliceCString(const(wchar)[] w) { 9656 return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr); 9657 } 9658 9659 private inout(char)[] sliceCString(inout(char)* s) { 9660 import core.stdc.string; 9661 auto len = strlen(s); 9662 return s[0 .. len]; 9663 } 9664 9665 version(OSXCocoa) 9666 alias PaintingHandle = NSObject; 9667 else 9668 alias PaintingHandle = NativeWindowHandle; 9669 9670 /** 9671 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 9672 than constructing it directly. Then, it is reference counted so you can pass it 9673 at around and when the last ref goes out of scope, the buffered drawing activities 9674 are all carried out. 9675 9676 9677 Most functions use the outlineColor instead of taking a color themselves. 9678 ScreenPainter is reference counted and draws its buffer to the screen when its 9679 final reference goes out of scope. 9680 */ 9681 struct ScreenPainter { 9682 CapableOfBeingDrawnUpon window; 9683 this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) { 9684 this.window = window; 9685 if(window.closed) 9686 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 9687 //currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 9688 currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max); 9689 if(window.activeScreenPainter !is null) { 9690 impl = window.activeScreenPainter; 9691 if(impl.referenceCount == 0) { 9692 impl.window = window; 9693 impl.create(handle); 9694 } 9695 impl.manualInvalidations = manualInvalidations; 9696 impl.referenceCount++; 9697 // writeln("refcount ++ ", impl.referenceCount); 9698 } else { 9699 impl = new ScreenPainterImplementation; 9700 impl.window = window; 9701 impl.create(handle); 9702 impl.referenceCount = 1; 9703 impl.manualInvalidations = manualInvalidations; 9704 window.activeScreenPainter = impl; 9705 // writeln("constructed"); 9706 } 9707 9708 copyActiveOriginals(); 9709 } 9710 9711 /++ 9712 EXPERIMENTAL. subject to change. 9713 9714 When you draw a cursor, you can draw this to notify your window of where it is, 9715 for IME systems to use. 9716 +/ 9717 void notifyCursorPosition(int x, int y, int width, int height) { 9718 if(auto w = cast(SimpleWindow) window) { 9719 w.setIMEPopupLocation(x + _originX + width, y + _originY + height); 9720 } 9721 } 9722 9723 /++ 9724 If you are using manual invalidations, this informs the 9725 window system that a section needs to be redrawn. 9726 9727 If you didn't opt into manual invalidation, you don't 9728 have to call this. 9729 9730 History: 9731 Added December 30, 2021 (dub v10.5) 9732 +/ 9733 void invalidateRect(Rectangle rect) { 9734 if(impl is null) return; 9735 9736 // transform(rect) 9737 rect.left += _originX; 9738 rect.right += _originX; 9739 rect.top += _originY; 9740 rect.bottom += _originY; 9741 9742 impl.invalidateRect(rect); 9743 } 9744 9745 private Pen originalPen; 9746 private Color originalFillColor; 9747 private arsd.color.Rectangle originalClipRectangle; 9748 private OperatingSystemFont originalFont; 9749 void copyActiveOriginals() { 9750 if(impl is null) return; 9751 originalPen = impl._activePen; 9752 originalFillColor = impl._fillColor; 9753 originalClipRectangle = impl._clipRectangle; 9754 version(OSXCocoa) {} else 9755 originalFont = impl._activeFont; 9756 } 9757 9758 ~this() { 9759 if(impl is null) return; 9760 impl.referenceCount--; 9761 //writeln("refcount -- ", impl.referenceCount); 9762 if(impl.referenceCount == 0) { 9763 // writeln("destructed"); 9764 impl.dispose(); 9765 *window.activeScreenPainter = ScreenPainterImplementation.init; 9766 // writeln("paint finished"); 9767 } else { 9768 // there is still an active reference, reset stuff so the 9769 // next user doesn't get weirdness via the reference 9770 this.rasterOp = RasterOp.normal; 9771 pen = originalPen; 9772 fillColor = originalFillColor; 9773 if(originalFont) 9774 setFont(originalFont); 9775 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 9776 } 9777 } 9778 9779 this(this) { 9780 if(impl is null) return; 9781 impl.referenceCount++; 9782 //writeln("refcount ++ ", impl.referenceCount); 9783 9784 copyActiveOriginals(); 9785 } 9786 9787 private int _originX; 9788 private int _originY; 9789 @property int originX() { return _originX; } 9790 @property int originY() { return _originY; } 9791 @property int originX(int a) { 9792 _originX = a; 9793 return _originX; 9794 } 9795 @property int originY(int a) { 9796 _originY = a; 9797 return _originY; 9798 } 9799 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 9800 private void transform(ref Point p) { 9801 if(impl is null) return; 9802 p.x += _originX; 9803 p.y += _originY; 9804 } 9805 9806 // this needs to be checked BEFORE the originX/Y transformation 9807 private bool isClipped(Point p) { 9808 return !currentClipRectangle.contains(p); 9809 } 9810 private bool isClipped(Point p, int width, int height) { 9811 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 9812 } 9813 private bool isClipped(Point p, Size s) { 9814 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 9815 } 9816 private bool isClipped(Point p, Point p2) { 9817 // need to ensure the end points are actually included inside, so the +1 does that 9818 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 9819 } 9820 9821 9822 /++ 9823 Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 9824 9825 Returns: 9826 The old clip rectangle. 9827 9828 History: 9829 Return value was `void` prior to May 10, 2021. 9830 9831 +/ 9832 arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) { 9833 if(impl is null) return currentClipRectangle; 9834 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 9835 return currentClipRectangle; // no need to do anything 9836 auto old = currentClipRectangle; 9837 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 9838 transform(pt); 9839 9840 impl.setClipRectangle(pt.x, pt.y, width, height); 9841 9842 return old; 9843 } 9844 9845 /// ditto 9846 arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) { 9847 if(impl is null) return currentClipRectangle; 9848 return setClipRectangle(rect.upperLeft, rect.width, rect.height); 9849 } 9850 9851 /// 9852 void setFont(OperatingSystemFont font) { 9853 if(impl is null) return; 9854 impl.setFont(font); 9855 } 9856 9857 /// 9858 int fontHeight() { 9859 if(impl is null) return 0; 9860 return impl.fontHeight(); 9861 } 9862 9863 private Pen activePen; 9864 9865 /// 9866 @property void pen(Pen p) { 9867 if(impl is null) return; 9868 activePen = p; 9869 impl.pen(p); 9870 } 9871 9872 /// 9873 @scriptable 9874 @property void outlineColor(Color c) { 9875 if(impl is null) return; 9876 if(activePen.color == c) 9877 return; 9878 activePen.color = c; 9879 impl.pen(activePen); 9880 } 9881 9882 /// 9883 @scriptable 9884 @property void fillColor(Color c) { 9885 if(impl is null) return; 9886 impl.fillColor(c); 9887 } 9888 9889 /// 9890 @property void rasterOp(RasterOp op) { 9891 if(impl is null) return; 9892 impl.rasterOp(op); 9893 } 9894 9895 9896 void updateDisplay() { 9897 // FIXME this should do what the dtor does 9898 } 9899 9900 /// 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) 9901 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 9902 if(impl is null) return; 9903 if(isClipped(upperLeft, width, height)) return; 9904 transform(upperLeft); 9905 version(Windows) { 9906 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 9907 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 9908 RECT clip = scroll; 9909 RECT uncovered; 9910 HRGN hrgn; 9911 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 9912 throw new WindowsApiException("ScrollDC", GetLastError()); 9913 9914 } else version(X11) { 9915 // FIXME: clip stuff outside this rectangle 9916 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 9917 } else version(OSXCocoa) { 9918 throw new NotYetImplementedException(); 9919 } else static assert(0); 9920 } 9921 9922 /// 9923 void clear(Color color = Color.white()) { 9924 if(impl is null) return; 9925 fillColor = color; 9926 outlineColor = color; 9927 drawRectangle(Point(0, 0), window.width, window.height); 9928 } 9929 9930 /++ 9931 Draws a pixmap (represented by the [Sprite] class) on the drawable. 9932 9933 Params: 9934 upperLeft = point on the window where the upper left corner of the image will be drawn 9935 imageUpperLeft = point on the image to start the slice to draw 9936 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. 9937 History: 9938 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9939 +/ 9940 void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9941 if(impl is null) return; 9942 if(isClipped(upperLeft, s.width, s.height)) return; 9943 transform(upperLeft); 9944 impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height); 9945 } 9946 9947 /// 9948 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 9949 if(impl is null) return; 9950 //if(isClipped(upperLeft, w, h)) return; // FIXME 9951 transform(upperLeft); 9952 if(w == 0 || w > i.width) 9953 w = i.width; 9954 if(h == 0 || h > i.height) 9955 h = i.height; 9956 if(upperLeftOfImage.x < 0) 9957 upperLeftOfImage.x = 0; 9958 if(upperLeftOfImage.y < 0) 9959 upperLeftOfImage.y = 0; 9960 9961 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 9962 } 9963 9964 /// 9965 Size textSize(in char[] text) { 9966 if(impl is null) return Size(0, 0); 9967 return impl.textSize(text); 9968 } 9969 9970 /++ 9971 Draws a string in the window with the set font (see [setFont] to change it). 9972 9973 Params: 9974 upperLeft = the upper left point of the bounding box of the text 9975 text = the string to draw 9976 lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound. 9977 alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags 9978 +/ 9979 @scriptable 9980 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 9981 if(impl is null) return; 9982 if(lowerRight.x != 0 || lowerRight.y != 0) { 9983 if(isClipped(upperLeft, lowerRight)) return; 9984 transform(lowerRight); 9985 } else { 9986 if(isClipped(upperLeft, textSize(text))) return; 9987 } 9988 transform(upperLeft); 9989 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 9990 } 9991 9992 /++ 9993 Draws text using a custom font. 9994 9995 This is still MAJOR work in progress. 9996 9997 Creating a [DrawableFont] can be tricky and require additional dependencies. 9998 +/ 9999 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 10000 if(impl is null) return; 10001 if(isClipped(upperLeft, Point(int.max, int.max))) return; 10002 transform(upperLeft); 10003 font.drawString(this, upperLeft, text); 10004 } 10005 10006 version(Windows) 10007 void drawText(Point upperLeft, scope const(wchar)[] text) { 10008 if(impl is null) return; 10009 if(isClipped(upperLeft, Point(int.max, int.max))) return; 10010 transform(upperLeft); 10011 10012 if(text.length && text[$-1] == '\n') 10013 text = text[0 .. $-1]; // tailing newlines are weird on windows... 10014 10015 TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length); 10016 } 10017 10018 static struct TextDrawingContext { 10019 Point boundingBoxUpperLeft; 10020 Point boundingBoxLowerRight; 10021 10022 Point currentLocation; 10023 10024 Point lastDrewUpperLeft; 10025 Point lastDrewLowerRight; 10026 10027 // how do i do right aligned rich text? 10028 // i kinda want to do a pre-made drawing then right align 10029 // draw the whole block. 10030 // 10031 // That's exactly the diff: inline vs block stuff. 10032 10033 // I need to get coordinates of an inline section out too, 10034 // not just a bounding box, but a series of bounding boxes 10035 // should be ok. Consider what's needed to detect a click 10036 // on a link in the middle of a paragraph breaking a line. 10037 // 10038 // Generally, we should be able to get the rectangles of 10039 // any portion we draw. 10040 // 10041 // It also needs to tell what text is left if it overflows 10042 // out of the box, so we can do stuff like float images around 10043 // it. It should not attempt to draw a letter that would be 10044 // clipped. 10045 // 10046 // I might also turn off word wrap stuff. 10047 } 10048 10049 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 10050 if(impl is null) return; 10051 // FIXME 10052 } 10053 10054 /// Drawing an individual pixel is slow. Avoid it if possible. 10055 void drawPixel(Point where) { 10056 if(impl is null) return; 10057 if(isClipped(where)) return; 10058 transform(where); 10059 impl.drawPixel(where.x, where.y); 10060 } 10061 10062 10063 /// Draws a pen using the current pen / outlineColor 10064 @scriptable 10065 void drawLine(Point starting, Point ending) { 10066 if(impl is null) return; 10067 if(isClipped(starting, ending)) return; 10068 transform(starting); 10069 transform(ending); 10070 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 10071 } 10072 10073 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 10074 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 10075 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 10076 @scriptable 10077 void drawRectangle(Point upperLeft, int width, int height) { 10078 if(impl is null) return; 10079 if(isClipped(upperLeft, width, height)) return; 10080 transform(upperLeft); 10081 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 10082 } 10083 10084 /// ditto 10085 void drawRectangle(Point upperLeft, Size size) { 10086 if(impl is null) return; 10087 if(isClipped(upperLeft, size.width, size.height)) return; 10088 transform(upperLeft); 10089 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 10090 } 10091 10092 /// ditto 10093 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 10094 if(impl is null) return; 10095 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 10096 transform(upperLeft); 10097 transform(lowerRightInclusive); 10098 impl.drawRectangle(upperLeft.x, upperLeft.y, 10099 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 10100 } 10101 10102 // overload added on May 12, 2021 10103 /// ditto 10104 void drawRectangle(Rectangle rect) { 10105 drawRectangle(rect.upperLeft, rect.size); 10106 } 10107 10108 /// Arguments are the points of the bounding rectangle 10109 void drawEllipse(Point upperLeft, Point lowerRight) { 10110 if(impl is null) return; 10111 if(isClipped(upperLeft, lowerRight)) return; 10112 transform(upperLeft); 10113 transform(lowerRight); 10114 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 10115 } 10116 10117 /++ 10118 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. 10119 10120 10121 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. 10122 10123 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. 10124 10125 Bugs: 10126 They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now). 10127 10128 The arc outline on Linux sometimes goes over the target. 10129 10130 The fill on Windows sometimes stops short. 10131 10132 History: 10133 This function was broken af, totally inconsistent on platforms until September 24, 2021. 10134 10135 The interpretation of the final argument was incorrectly documented and implemented until August 2, 2024. 10136 +/ 10137 void drawArc(Point upperLeft, int width, int height, int start, int length) { 10138 if(impl is null) return; 10139 // FIXME: not actually implemented 10140 if(isClipped(upperLeft, width, height)) return; 10141 transform(upperLeft); 10142 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, length); 10143 } 10144 10145 /// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 10146 void drawCircle(Point upperLeft, int diameter) { 10147 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 10148 } 10149 10150 /++ 10151 Draws a rectangle with rounded corners. It is outlined with the current foreground pen and filled with the current background brush. 10152 10153 10154 Bugs: 10155 Not implemented on Mac; it will instead draw a non-rounded rectangle for now. 10156 10157 History: 10158 Added August 3, 2024 10159 +/ 10160 void drawRectangleRounded(Rectangle rect, int borderRadius) { 10161 drawRectangleRounded(rect.upperLeft, rect.lowerRight, borderRadius); 10162 } 10163 10164 /// ditto 10165 void drawRectangleRounded(Point upperLeft, Size size, int borderRadius) { 10166 drawRectangleRounded(upperLeft, upperLeft + Point(size.width, size.height), borderRadius); 10167 } 10168 10169 /// ditto 10170 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 10171 if(borderRadius <= 0) { 10172 drawRectangle(upperLeft, lowerRight); 10173 return; 10174 } 10175 10176 transform(upperLeft); 10177 transform(lowerRight); 10178 10179 impl.drawRectangleRounded(upperLeft, lowerRight, borderRadius); 10180 } 10181 10182 /// . 10183 void drawPolygon(Point[] vertexes) { 10184 if(impl is null) return; 10185 assert(vertexes.length); 10186 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 10187 foreach(ref vertex; vertexes) { 10188 if(vertex.x < minX) 10189 minX = vertex.x; 10190 if(vertex.y < minY) 10191 minY = vertex.y; 10192 if(vertex.x > maxX) 10193 maxX = vertex.x; 10194 if(vertex.y > maxY) 10195 maxY = vertex.y; 10196 transform(vertex); 10197 } 10198 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 10199 impl.drawPolygon(vertexes); 10200 } 10201 10202 /// ditto 10203 void drawPolygon(Point[] vertexes...) { 10204 if(impl is null) return; 10205 drawPolygon(vertexes); 10206 } 10207 10208 10209 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 10210 10211 //mixin NativeScreenPainterImplementation!() impl; 10212 10213 10214 // HACK: if I mixin the impl directly, it won't let me override the copy 10215 // constructor! The linker complains about there being multiple definitions. 10216 // I'll make the best of it and reference count it though. 10217 ScreenPainterImplementation* impl; 10218 } 10219 10220 // HACK: I need a pointer to the implementation so it's separate 10221 struct ScreenPainterImplementation { 10222 CapableOfBeingDrawnUpon window; 10223 int referenceCount; 10224 mixin NativeScreenPainterImplementation!(); 10225 } 10226 10227 // FIXME: i haven't actually tested the sprite class on MS Windows 10228 10229 /** 10230 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 10231 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 10232 10233 10234 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 10235 though I'm not sure that's ideal and the implementation might change. 10236 10237 You create one by giving a window and an image. It optimizes for that window, 10238 and copies the image into it to use as the initial picture. Creating a sprite 10239 can be quite slow (especially over a network connection) so you should do it 10240 as little as possible and just hold on to your sprite handles after making them. 10241 simpledisplay does try to do its best though, using the XSHM extension if available, 10242 but you should still write your code as if it will always be slow. 10243 10244 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 10245 a fast operation - much faster than drawing the Image itself every time. 10246 10247 `Sprite` represents a scarce resource which should be freed when you 10248 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 10249 after it has been disposed. If you are unsure about this, don't take chances, 10250 just let the garbage collector do it for you. But ideally, you can manage its 10251 lifetime more efficiently. 10252 10253 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 10254 support alpha blending in its drawing at this time. That might change in the 10255 future, but if you need alpha blending right now, use OpenGL instead. See 10256 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 10257 10258 Update: on April 23, 2021, I finally added alpha blending support. You must opt 10259 in by setting the enableAlpha = true in the constructor. 10260 */ 10261 class Sprite : CapableOfBeingDrawnUpon { 10262 10263 /// 10264 ScreenPainter draw() { 10265 return ScreenPainter(this, handle, false); 10266 } 10267 10268 /++ 10269 Copies the sprite's current state into a [TrueColorImage]. 10270 10271 Be warned: this can be a very slow operation 10272 10273 History: 10274 Actually implemented on March 14, 2021 10275 +/ 10276 TrueColorImage takeScreenshot() { 10277 return trueColorImageFromNativeHandle(handle, width, height); 10278 } 10279 10280 void delegate() paintingFinishedDg() { return null; } 10281 bool closed() { return false; } 10282 ScreenPainterImplementation* activeScreenPainter_; 10283 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 10284 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 10285 10286 version(Windows) 10287 private ubyte* rawData; 10288 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 10289 // ditto on the XPicture stuff 10290 10291 version(X11) { 10292 private static XRenderPictFormat* RGB24; 10293 private static XRenderPictFormat* ARGB32; 10294 10295 private Picture xrenderPicture; 10296 } 10297 10298 version(X11) 10299 private static void requireXRender() { 10300 if(!XRenderLibrary.loadAttempted) { 10301 XRenderLibrary.loadDynamicLibrary(); 10302 } 10303 10304 if(!XRenderLibrary.loadSuccessful) 10305 throw new Exception("XRender library load failure"); 10306 10307 auto display = XDisplayConnection.get; 10308 10309 // FIXME: if we migrate X displays, these need to be changed 10310 if(RGB24 is null) 10311 RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); 10312 if(ARGB32 is null) 10313 ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); 10314 } 10315 10316 protected this() {} 10317 10318 this(SimpleWindow win, int width, int height, bool enableAlpha = false) { 10319 this._width = width; 10320 this._height = height; 10321 this.enableAlpha = enableAlpha; 10322 10323 version(X11) { 10324 auto display = XDisplayConnection.get(); 10325 10326 if(enableAlpha) { 10327 requireXRender(); 10328 } 10329 10330 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display)); 10331 10332 if(enableAlpha) { 10333 XRenderPictureAttributes attrs; 10334 xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs); 10335 } 10336 } else version(Windows) { 10337 version(CRuntime_DigitalMars) { 10338 //if(enableAlpha) 10339 //throw new Exception("Alpha support not available, try recompiling with -m32mscoff"); 10340 } 10341 10342 BITMAPINFO infoheader; 10343 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 10344 infoheader.bmiHeader.biWidth = width; 10345 infoheader.bmiHeader.biHeight = height; 10346 infoheader.bmiHeader.biPlanes = 1; 10347 infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24; 10348 infoheader.bmiHeader.biCompression = BI_RGB; 10349 10350 // FIXME: this should prolly be a device dependent bitmap... 10351 handle = CreateDIBSection( 10352 null, 10353 &infoheader, 10354 DIB_RGB_COLORS, 10355 cast(void**) &rawData, 10356 null, 10357 0); 10358 10359 if(handle is null) 10360 throw new WindowsApiException("couldn't create pixmap", GetLastError()); 10361 } 10362 } 10363 10364 /// Makes a sprite based on the image with the initial contents from the Image 10365 this(SimpleWindow win, Image i) { 10366 this(win, i.width, i.height, i.enableAlpha); 10367 10368 version(X11) { 10369 auto display = XDisplayConnection.get(); 10370 auto gc = XCreateGC(display, this.handle, 0, null); 10371 scope(exit) XFreeGC(display, gc); 10372 if(i.usingXshm) 10373 XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 10374 else 10375 XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 10376 } else version(Windows) { 10377 auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4); 10378 auto arrLength = itemsPerLine * height; 10379 rawData[0..arrLength] = i.rawData[0..arrLength]; 10380 } else version(OSXCocoa) { 10381 // FIXME: I have no idea if this is even any good 10382 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 10383 handle = CGBitmapContextCreate(null, width, height, 8, 4*width, 10384 colorSpace, 10385 kCGImageAlphaPremultipliedLast 10386 |kCGBitmapByteOrder32Big); 10387 CGColorSpaceRelease(colorSpace); 10388 auto rawData = CGBitmapContextGetData(handle); 10389 10390 auto rdl = (width * height * 4); 10391 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 10392 } else static assert(0); 10393 } 10394 10395 /++ 10396 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 10397 10398 Params: 10399 where = point on the window where the upper left corner of the image will be drawn 10400 imageUpperLeft = point on the image to start the slice to draw 10401 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. 10402 History: 10403 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 10404 +/ 10405 void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 10406 painter.drawPixmap(this, where, imageUpperLeft, sliceSize); 10407 } 10408 10409 /// Call this when you're ready to get rid of it 10410 void dispose() { 10411 version(X11) { 10412 staticDispose(xrenderPicture, handle); 10413 xrenderPicture = None; 10414 handle = None; 10415 } else version(Windows) { 10416 staticDispose(handle); 10417 handle = null; 10418 } else version(OSXCocoa) { 10419 staticDispose(handle); 10420 handle = null; 10421 } else static assert(0); 10422 10423 } 10424 10425 version(X11) 10426 static void staticDispose(Picture xrenderPicture, Pixmap handle) { 10427 if(xrenderPicture) 10428 XRenderFreePicture(XDisplayConnection.get, xrenderPicture); 10429 if(handle) 10430 XFreePixmap(XDisplayConnection.get(), handle); 10431 } 10432 else version(Windows) 10433 static void staticDispose(HBITMAP handle) { 10434 if(handle) 10435 DeleteObject(handle); 10436 } 10437 else version(OSXCocoa) 10438 static void staticDispose(CGContextRef context) { 10439 if(context) 10440 CGContextRelease(context); 10441 } 10442 10443 ~this() { 10444 version(X11) { if(xrenderPicture || handle) 10445 cleanupQueue.queue!staticDispose(xrenderPicture, handle); 10446 } else version(Windows) { if(handle) 10447 cleanupQueue.queue!staticDispose(handle); 10448 } else version(OSXCocoa) { if(handle) 10449 cleanupQueue.queue!staticDispose(handle); 10450 } else static assert(0); 10451 } 10452 10453 /// 10454 final @property int width() { return _width; } 10455 10456 /// 10457 final @property int height() { return _height; } 10458 10459 /// 10460 static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) { 10461 return new Sprite(win, Image.fromMemoryImage(img, enableAlpha)); 10462 } 10463 10464 auto nativeHandle() { 10465 return handle; 10466 } 10467 10468 private: 10469 10470 int _width; 10471 int _height; 10472 bool enableAlpha; 10473 version(X11) 10474 Pixmap handle; 10475 else version(Windows) 10476 HBITMAP handle; 10477 else version(OSXCocoa) 10478 CGContextRef handle; 10479 else version(Emscripten) 10480 void* handle; 10481 else static assert(0); 10482 } 10483 10484 /++ 10485 Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient]. 10486 10487 History: 10488 Added November 20, 2021 (dub v10.4) 10489 +/ 10490 version(OSXCocoa) {} else // NotYetImplementedException 10491 abstract class Gradient : Sprite { 10492 protected this(int w, int h) { 10493 version(X11) { 10494 Sprite.requireXRender(); 10495 10496 super(); 10497 enableAlpha = true; 10498 _width = w; 10499 _height = h; 10500 } else version(Windows) { 10501 super(null, w, h, true); // on Windows i'm just making a bitmap myself 10502 } 10503 } 10504 10505 version(Windows) 10506 final void forEachPixel(scope Color delegate(int x, int y) dg) @system { 10507 auto ptr = rawData; 10508 foreach(j; 0 .. _height) 10509 foreach(i; 0 .. _width) { 10510 auto color = dg(i, _height - j - 1); // cuz of upside down bitmap 10511 *rawData = (color.a * color.b) / 255; rawData++; 10512 *rawData = (color.a * color.g) / 255; rawData++; 10513 *rawData = (color.a * color.r) / 255; rawData++; 10514 *rawData = color.a; rawData++; 10515 } 10516 } 10517 10518 version(X11) 10519 protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) { 10520 assert(stops.length > 0); 10521 assert(stops.length <= 16, "I got lazy with buffers"); 10522 10523 XFixed[16] stopsPositions = void; 10524 XRenderColor[16] colors = void; 10525 10526 foreach(idx, stop; stops) { 10527 stopsPositions[idx] = cast(int)(stop.percentage * ushort.max); 10528 auto c = stop.c; 10529 colors[idx] = XRenderColor( 10530 cast(ushort)(c.r * ushort.max / 255), 10531 cast(ushort)(c.g * ushort.max / 255), 10532 cast(ushort)(c.b * ushort.max / 255), 10533 cast(ushort)(c.a * ubyte.max) // max value here is fractional 10534 ); 10535 } 10536 10537 xrenderPicture = dg(stopsPositions, colors); 10538 } 10539 10540 /// 10541 static struct Stop { 10542 float percentage; /// between 0 and 1.0 10543 Color c; 10544 } 10545 } 10546 10547 /++ 10548 Creates a linear gradient between p1 and p2. 10549 10550 X ONLY RIGHT NOW 10551 10552 History: 10553 Added November 20, 2021 (dub v10.4) 10554 10555 Bugs: 10556 Not yet implemented on Windows. 10557 +/ 10558 version(OSXCocoa) {} else // NotYetImplementedException 10559 class LinearGradient : Gradient { 10560 /++ 10561 10562 +/ 10563 this(Point p1, Point p2, Stop[] stops...) { 10564 super(p2.x, p2.y); 10565 10566 version(X11) { 10567 XLinearGradient gradient; 10568 gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max); 10569 gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max); 10570 10571 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10572 return XRenderCreateLinearGradient( 10573 XDisplayConnection.get, 10574 &gradient, 10575 stopsPositions.ptr, 10576 colors.ptr, 10577 cast(int) stops.length); 10578 }); 10579 } else version(Windows) { 10580 // FIXME 10581 forEachPixel((int x, int y) { 10582 import core.stdc.math; 10583 10584 //sqrtf( 10585 10586 return Color.transparent; 10587 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10588 }); 10589 } 10590 } 10591 } 10592 10593 /++ 10594 A conical gradient goes from color to color around a circumference from a center point. 10595 10596 X ONLY RIGHT NOW 10597 10598 History: 10599 Added November 20, 2021 (dub v10.4) 10600 10601 Bugs: 10602 Not yet implemented on Windows. 10603 +/ 10604 version(OSXCocoa) {} else // NotYetImplementedException 10605 class ConicalGradient : Gradient { 10606 /++ 10607 10608 +/ 10609 this(Point center, float angleInDegrees, Stop[] stops...) { 10610 super(center.x * 2, center.y * 2); 10611 10612 version(X11) { 10613 XConicalGradient gradient; 10614 gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max); 10615 gradient.angle = cast(int)(angleInDegrees * ushort.max); 10616 10617 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10618 return XRenderCreateConicalGradient( 10619 XDisplayConnection.get, 10620 &gradient, 10621 stopsPositions.ptr, 10622 colors.ptr, 10623 cast(int) stops.length); 10624 }); 10625 } else version(Windows) { 10626 // FIXME 10627 forEachPixel((int x, int y) { 10628 import core.stdc.math; 10629 10630 //sqrtf( 10631 10632 return Color.transparent; 10633 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10634 }); 10635 10636 } 10637 } 10638 } 10639 10640 /++ 10641 A radial gradient goes from color to color based on distance from the center. 10642 It is like rings of color. 10643 10644 X ONLY RIGHT NOW 10645 10646 10647 More specifically, you create two circles: an inner circle and an outer circle. 10648 The gradient is only drawn in the area outside the inner circle but inside the outer 10649 circle. The closest line between those two circles forms the line for the gradient 10650 and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around. 10651 10652 History: 10653 Added November 20, 2021 (dub v10.4) 10654 10655 Bugs: 10656 Not yet implemented on Windows. 10657 +/ 10658 version(OSXCocoa) {} else // NotYetImplementedException 10659 class RadialGradient : Gradient { 10660 /++ 10661 10662 +/ 10663 this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) { 10664 super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5)); 10665 10666 version(X11) { 10667 XRadialGradient gradient; 10668 gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max)); 10669 gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max)); 10670 10671 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10672 return XRenderCreateRadialGradient( 10673 XDisplayConnection.get, 10674 &gradient, 10675 stopsPositions.ptr, 10676 colors.ptr, 10677 cast(int) stops.length); 10678 }); 10679 } else version(Windows) { 10680 // FIXME 10681 forEachPixel((int x, int y) { 10682 import core.stdc.math; 10683 10684 //sqrtf( 10685 10686 return Color.transparent; 10687 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10688 }); 10689 } 10690 } 10691 } 10692 10693 10694 10695 /+ 10696 NOT IMPLEMENTED 10697 10698 A display-stored image optimized for relatively quick drawing, like 10699 [Sprite], but this one supports alpha channel blending and does NOT 10700 support direct drawing upon it with a [ScreenPainter]. 10701 10702 You can think of it as an [arsd.game.OpenGlTexture] for usage with a 10703 plain [ScreenPainter]... sort of. 10704 10705 On X11, it requires the Xrender extension and library. This is available 10706 almost everywhere though. 10707 10708 History: 10709 Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED 10710 +/ 10711 version(none) 10712 class AlphaSprite { 10713 /++ 10714 Copies the given image into it. 10715 +/ 10716 this(MemoryImage img) { 10717 10718 if(!XRenderLibrary.loadAttempted) { 10719 XRenderLibrary.loadDynamicLibrary(); 10720 10721 // FIXME: this needs to be reconstructed when the X server changes 10722 repopulateX(); 10723 } 10724 if(!XRenderLibrary.loadSuccessful) 10725 throw new Exception("XRender library load failure"); 10726 10727 // I probably need to put the alpha mask in a separate Picture 10728 // ugh 10729 // maybe the Sprite itself can have an alpha bitmask anyway 10730 10731 10732 auto display = XDisplayConnection.get(); 10733 pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 10734 10735 10736 XRenderPictureAttributes attrs; 10737 10738 handle = XRenderCreatePicture( 10739 XDisplayConnection.get, 10740 pixmap, 10741 RGBA, 10742 0, 10743 &attrs 10744 ); 10745 10746 } 10747 10748 // maybe i'll use the create gradient functions too with static factories.. 10749 10750 void drawAt(ScreenPainter painter, Point where) { 10751 //painter.drawPixmap(this, where); 10752 10753 XRenderPictureAttributes attrs; 10754 10755 auto pic = XRenderCreatePicture( 10756 XDisplayConnection.get, 10757 painter.impl.d, 10758 RGB, 10759 0, 10760 &attrs 10761 ); 10762 10763 XRenderComposite( 10764 XDisplayConnection.get, 10765 3, // PictOpOver 10766 handle, 10767 None, 10768 pic, 10769 0, // src 10770 0, 10771 0, // mask 10772 0, 10773 10, // dest 10774 10, 10775 100, // width 10776 100 10777 ); 10778 10779 /+ 10780 XRenderFreePicture( 10781 XDisplayConnection.get, 10782 pic 10783 ); 10784 10785 XRenderFreePicture( 10786 XDisplayConnection.get, 10787 fill 10788 ); 10789 +/ 10790 // on Windows you can stretch but Xrender still can't :( 10791 } 10792 10793 static XRenderPictFormat* RGB; 10794 static XRenderPictFormat* RGBA; 10795 static void repopulateX() { 10796 auto display = XDisplayConnection.get; 10797 RGB = XRenderFindStandardFormat(display, PictStandardRGB24); 10798 RGBA = XRenderFindStandardFormat(display, PictStandardARGB32); 10799 } 10800 10801 XPixmap pixmap; 10802 Picture handle; 10803 } 10804 10805 /// 10806 interface CapableOfBeingDrawnUpon { 10807 /// 10808 ScreenPainter draw(); 10809 /// 10810 int width(); 10811 /// 10812 int height(); 10813 protected ScreenPainterImplementation* activeScreenPainter(); 10814 protected void activeScreenPainter(ScreenPainterImplementation*); 10815 bool closed(); 10816 10817 void delegate() paintingFinishedDg(); 10818 10819 /// Be warned: this can be a very slow operation 10820 TrueColorImage takeScreenshot(); 10821 } 10822 10823 /// 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]. 10824 void flushGui() { 10825 version(X11) { 10826 auto dpy = XDisplayConnection.get(); 10827 XLockDisplay(dpy); 10828 scope(exit) XUnlockDisplay(dpy); 10829 XFlush(dpy); 10830 } 10831 } 10832 10833 /++ 10834 Runs the given code in the GUI thread when its event loop 10835 is available, blocking until it completes. This allows you 10836 to create and manipulate windows from another thread without 10837 invoking undefined behavior. 10838 10839 If this is the gui thread, it runs the code immediately. 10840 10841 If no gui thread exists yet, the current thread is assumed 10842 to be it. Attempting to create windows or run the event loop 10843 in any other thread will cause an assertion failure. 10844 10845 10846 $(TIP 10847 Did you know you can use UFCS on delegate literals? 10848 10849 () { 10850 // code here 10851 }.runInGuiThread; 10852 ) 10853 10854 Returns: 10855 `true` if the function was called, `false` if it was not. 10856 The function may not be called because the gui thread had 10857 already terminated by the time you called this. 10858 10859 History: 10860 Added April 10, 2020 (v7.2.0) 10861 10862 Return value added and implementation tweaked to avoid locking 10863 at program termination on February 24, 2021 (v9.2.1). 10864 +/ 10865 bool runInGuiThread(scope void delegate() dg) @trusted { 10866 claimGuiThread(); 10867 10868 if(thisIsGuiThread) { 10869 dg(); 10870 return true; 10871 } 10872 10873 if(guiThreadTerminating) 10874 return false; 10875 10876 import core.sync.semaphore; 10877 static Semaphore sc; 10878 if(sc is null) 10879 sc = new Semaphore(); 10880 10881 static RunQueueMember* rqm; 10882 if(rqm is null) 10883 rqm = new RunQueueMember; 10884 rqm.dg = cast(typeof(rqm.dg)) dg; 10885 rqm.signal = sc; 10886 rqm.thrown = null; 10887 10888 synchronized(runInGuiThreadLock) { 10889 runInGuiThreadQueue ~= rqm; 10890 } 10891 10892 if(!SimpleWindow.eventWakeUp()) 10893 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10894 10895 rqm.signal.wait(); 10896 auto t = rqm.thrown; 10897 10898 if(t) 10899 throw t; 10900 10901 return true; 10902 } 10903 10904 // note it runs sync if this is the gui thread.... 10905 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow { 10906 claimGuiThread(); 10907 10908 try { 10909 10910 if(thisIsGuiThread) { 10911 dg(); 10912 return; 10913 } 10914 10915 if(guiThreadTerminating) 10916 return; 10917 10918 RunQueueMember* rqm = new RunQueueMember; 10919 rqm.dg = cast(typeof(rqm.dg)) dg; 10920 rqm.signal = null; 10921 rqm.thrown = null; 10922 10923 synchronized(runInGuiThreadLock) { 10924 runInGuiThreadQueue ~= rqm; 10925 } 10926 10927 if(!SimpleWindow.eventWakeUp()) 10928 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10929 } catch(Exception e) { 10930 if(handleError) 10931 handleError(e); 10932 } 10933 } 10934 10935 private void runPendingRunInGuiThreadDelegates() { 10936 more: 10937 RunQueueMember* next; 10938 synchronized(runInGuiThreadLock) { 10939 if(runInGuiThreadQueue.length) { 10940 next = runInGuiThreadQueue[0]; 10941 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 10942 } else { 10943 next = null; 10944 } 10945 } 10946 10947 if(next) { 10948 try { 10949 next.dg(); 10950 next.thrown = null; 10951 } catch(Throwable t) { 10952 next.thrown = t; 10953 } 10954 10955 if(next.signal) 10956 next.signal.notify(); 10957 10958 goto more; 10959 } 10960 } 10961 10962 private void claimGuiThread() nothrow { 10963 import core.atomic; 10964 if(cas(&guiThreadExists_, false, true)) 10965 thisIsGuiThread = true; 10966 } 10967 10968 private struct RunQueueMember { 10969 void delegate() dg; 10970 import core.sync.semaphore; 10971 Semaphore signal; 10972 Throwable thrown; 10973 } 10974 10975 private __gshared RunQueueMember*[] runInGuiThreadQueue; 10976 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE 10977 private bool thisIsGuiThread = false; 10978 private shared bool guiThreadExists_ = false; 10979 private shared bool guiThreadTerminating = false; 10980 10981 /++ 10982 Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d 10983 event loop. All windows must be exclusively created and managed by a single thread. 10984 10985 If no gui thread exists, simpledisplay.d will automatically adopt the current thread 10986 when you call one of its constructors. 10987 10988 If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this 10989 one. If so, you can run gui functions on it. If not, don't. The helper functions 10990 [runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically. 10991 10992 The reason this function is available is in case you want to message pass between a gui 10993 thread and your current thread. If no gui thread exists or if this is the gui thread, 10994 you're liable to deadlock when trying to communicate since you'd end up talking to yourself. 10995 10996 History: 10997 Added December 3, 2021 (dub v10.5) 10998 +/ 10999 public bool guiThreadExists() { 11000 return guiThreadExists_; 11001 } 11002 11003 /++ 11004 Returns `true` if this thread is either running or set to be running the 11005 simpledisplay.d gui core event loop because it owns windows. 11006 11007 It is important to keep gui-related functionality in the right thread, so you will 11008 want to `runInGuiThread` when you call them (with some specific exceptions called 11009 out in those specific functions' documentation). Notably, all windows must be 11010 created and managed only from the gui thread. 11011 11012 Will return false if simpledisplay's other functions haven't been called 11013 yet; check [guiThreadExists] in addition to this. 11014 11015 History: 11016 Added December 3, 2021 (dub v10.5) 11017 +/ 11018 public bool thisThreadRunningGui() { 11019 return thisIsGuiThread; 11020 } 11021 11022 /++ 11023 Function to help temporarily print debugging info. It will bypass any stdout/err redirection 11024 and go to the controlling tty or console (attaching to the parent and/or allocating one as 11025 needed on Windows. Please note it may overwrite output from other programs in the parent and the 11026 allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log 11027 file instead if you are in one of those situations). 11028 11029 It does not support outputting very many types; just strings and ints are likely to actually work. 11030 11031 It will perform very slowly and swallows any errors that may occur. Moreover, the specific output 11032 is unspecified meaning I can change it at any time. The only point of this function is to help 11033 in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword 11034 and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use 11035 in those contexts. 11036 11037 $(WARNING 11038 I reserve the right to change this function at any time. You can use it if it helps you 11039 but do not rely on it for anything permanent. 11040 ) 11041 11042 History: 11043 Added December 3, 2021. Not formally supported under any stable tag. 11044 +/ 11045 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted { 11046 try { 11047 version(Windows) { 11048 import core.sys.windows.wincon; 11049 if(!AttachConsole(ATTACH_PARENT_PROCESS)) 11050 AllocConsole(); 11051 const(char)* fn = "CONOUT$"; 11052 } else version(Posix) { 11053 const(char)* fn = "/dev/tty"; 11054 } else static assert(0, "Function not implemented for your system"); 11055 11056 if(fileOverride.length) 11057 fn = fileOverride.ptr; 11058 11059 import core.stdc.stdio; 11060 auto fp = fopen(fn, "wt"); 11061 if(fp is null) return; 11062 scope(exit) fclose(fp); 11063 11064 string str; 11065 foreach(item; t) { 11066 static if(is(typeof(item) : const(char)[])) 11067 str ~= item; 11068 else 11069 str ~= toInternal!string(item); 11070 str ~= " "; 11071 } 11072 str ~= "\n"; 11073 11074 fwrite(str.ptr, 1, str.length, fp); 11075 fflush(fp); 11076 } catch(Exception e) { 11077 // sorry no hope 11078 } 11079 } 11080 11081 private void guiThreadFinalize() { 11082 assert(thisIsGuiThread); 11083 11084 guiThreadTerminating = true; // don't add any more from this point on 11085 runPendingRunInGuiThreadDelegates(); 11086 } 11087 11088 /+ 11089 interface IPromise { 11090 void reportProgress(int current, int max, string message); 11091 11092 /+ // not formally in cuz of templates but still 11093 IPromise Then(); 11094 IPromise Catch(); 11095 IPromise Finally(); 11096 +/ 11097 } 11098 11099 /+ 11100 auto promise = async({ ... }); 11101 promise.Then(whatever). 11102 Then(whateverelse). 11103 Catch((exception) { }); 11104 11105 11106 A promise is run inside a fiber and it looks something like: 11107 11108 try { 11109 auto res = whatever(); 11110 auto res2 = whateverelse(res); 11111 } catch(Exception e) { 11112 { }(e); 11113 } 11114 11115 When a thing succeeds, it is passed as an arg to the next 11116 +/ 11117 class Promise(T) : IPromise { 11118 auto Then() { return null; } 11119 auto Catch() { return null; } 11120 auto Finally() { return null; } 11121 11122 // wait for it to resolve and return the value, or rethrow the error if that occurred. 11123 // cannot be called from the gui thread, but this is caught at runtime instead of compile time. 11124 T await(); 11125 } 11126 11127 interface Task { 11128 } 11129 11130 interface Resolvable(T) : Task { 11131 void run(); 11132 11133 void resolve(T); 11134 11135 Resolvable!T then(void delegate(T)); // returns a new promise 11136 Resolvable!T error(Throwable); // js catch 11137 Resolvable!T completed(); // js finally 11138 11139 } 11140 11141 /++ 11142 Runs `work` in a helper thread and sends its return value back to the main gui 11143 thread as the argument to `uponCompletion`. If `work` throws, the exception is 11144 sent to the `uponThrown` if given, or if null, rethrown from the event loop to 11145 kill the program. 11146 11147 You can call reportProgress(position, max, message) to update your parent window 11148 on your progress. 11149 11150 I should also use `shared` methods. FIXME 11151 11152 History: 11153 Added March 6, 2021 (dub version 9.3). 11154 +/ 11155 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) { 11156 uponCompletion(work(null)); 11157 } 11158 11159 +/ 11160 11161 /// Used internal to dispatch events to various classes. 11162 interface CapableOfHandlingNativeEvent { 11163 NativeEventHandler getNativeEventHandler(); 11164 11165 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 11166 11167 version(X11) { 11168 // if this is impossible, you are allowed to just throw from it 11169 // Note: if you call it from another object, set a flag cuz the manger will call you again 11170 void recreateAfterDisconnect(); 11171 // discard any *connection specific* state, but keep enough that you 11172 // can be recreated if possible. discardConnectionState() is always called immediately 11173 // before recreateAfterDisconnect(), so you can set a flag there to decide if 11174 // you need initialization order 11175 void discardConnectionState(); 11176 } 11177 } 11178 11179 version(X11) 11180 /++ 11181 State of keys on mouse events, especially motion. 11182 11183 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 11184 +/ 11185 enum ModifierState : uint { 11186 shift = 1, /// 11187 capsLock = 2, /// 11188 ctrl = 4, /// 11189 alt = 8, /// Not always available on Windows 11190 windows = 64, /// ditto 11191 numLock = 16, /// 11192 11193 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 11194 middleButtonDown = 512, /// ditto 11195 rightButtonDown = 1024, /// ditto 11196 } 11197 else version(Emscripten) 11198 enum ModifierState : uint { 11199 shift = 1, /// 11200 capsLock = 2, /// 11201 ctrl = 4, /// 11202 alt = 8, /// Not always available on Windows 11203 windows = 64, /// ditto 11204 numLock = 16, /// 11205 11206 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 11207 middleButtonDown = 512, /// ditto 11208 rightButtonDown = 1024, /// ditto 11209 } 11210 else version(Windows) 11211 /// ditto 11212 enum ModifierState : uint { 11213 shift = 4, /// 11214 ctrl = 8, /// 11215 11216 // i'm not sure if the next two are available 11217 alt = 256, /// not always available on Windows 11218 windows = 512, /// ditto 11219 11220 capsLock = 1024, /// 11221 numLock = 2048, /// 11222 11223 leftButtonDown = 1, /// not available on key events 11224 middleButtonDown = 16, /// ditto 11225 rightButtonDown = 2, /// ditto 11226 11227 backButtonDown = 0x20, /// not available on X 11228 forwardButtonDown = 0x40, /// ditto 11229 } 11230 else version(OSXCocoa) 11231 // FIXME FIXME NotYetImplementedException 11232 enum ModifierState : uint { 11233 shift = 1, /// 11234 capsLock = 2, /// 11235 ctrl = 4, /// 11236 alt = 8, /// Not always available on Windows 11237 windows = 64, /// ditto 11238 numLock = 16, /// 11239 11240 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 11241 middleButtonDown = 512, /// ditto 11242 rightButtonDown = 1024, /// ditto 11243 } 11244 11245 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them. 11246 enum MouseButton : int { 11247 none = 0, 11248 left = 1, /// 11249 right = 2, /// 11250 middle = 4, /// 11251 wheelUp = 8, /// 11252 wheelDown = 16, /// 11253 backButton = 32, /// often found on the thumb and used for back in browsers 11254 forwardButton = 64, /// often found on the thumb and used for forward in browsers 11255 } 11256 11257 /// Corresponds to the values found in MouseEvent.buttonLinear, being equal to `core.bitop.bsf(button) + 1` 11258 enum MouseButtonLinear : ubyte { 11259 left = 1, /// 11260 right, /// 11261 middle, /// 11262 wheelUp, /// 11263 wheelDown, /// 11264 backButton, /// often found on the thumb and used for back in browsers 11265 forwardButton, /// often found on the thumb and used for forward in browsers 11266 } 11267 11268 version(WebAssembly) { 11269 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 11270 enum Key { 11271 Escape = 0xff1b, /// 11272 F1 = 0xffbe, /// 11273 F2 = 0xffbf, /// 11274 F3 = 0xffc0, /// 11275 F4 = 0xffc1, /// 11276 F5 = 0xffc2, /// 11277 F6 = 0xffc3, /// 11278 F7 = 0xffc4, /// 11279 F8 = 0xffc5, /// 11280 F9 = 0xffc6, /// 11281 F10 = 0xffc7, /// 11282 F11 = 0xffc8, /// 11283 F12 = 0xffc9, /// 11284 PrintScreen = 0xff61, /// 11285 ScrollLock = 0xff14, /// 11286 Pause = 0xff13, /// 11287 Grave = 0x60, /// The $(BACKTICK) ~ key 11288 // number keys across the top of the keyboard 11289 N1 = 0x31, /// Number key atop the keyboard 11290 N2 = 0x32, /// 11291 N3 = 0x33, /// 11292 N4 = 0x34, /// 11293 N5 = 0x35, /// 11294 N6 = 0x36, /// 11295 N7 = 0x37, /// 11296 N8 = 0x38, /// 11297 N9 = 0x39, /// 11298 N0 = 0x30, /// 11299 Dash = 0x2d, /// 11300 Equals = 0x3d, /// 11301 Backslash = 0x5c, /// The \ | key 11302 Backspace = 0xff08, /// 11303 Insert = 0xff63, /// 11304 Home = 0xff50, /// 11305 PageUp = 0xff55, /// 11306 Delete = 0xffff, /// 11307 End = 0xff57, /// 11308 PageDown = 0xff56, /// 11309 Up = 0xff52, /// 11310 Down = 0xff54, /// 11311 Left = 0xff51, /// 11312 Right = 0xff53, /// 11313 11314 Tab = 0xff09, /// 11315 Q = 0x71, /// 11316 W = 0x77, /// 11317 E = 0x65, /// 11318 R = 0x72, /// 11319 T = 0x74, /// 11320 Y = 0x79, /// 11321 U = 0x75, /// 11322 I = 0x69, /// 11323 O = 0x6f, /// 11324 P = 0x70, /// 11325 LeftBracket = 0x5b, /// the [ { key 11326 RightBracket = 0x5d, /// the ] } key 11327 CapsLock = 0xffe5, /// 11328 A = 0x61, /// 11329 S = 0x73, /// 11330 D = 0x64, /// 11331 F = 0x66, /// 11332 G = 0x67, /// 11333 H = 0x68, /// 11334 J = 0x6a, /// 11335 K = 0x6b, /// 11336 L = 0x6c, /// 11337 Semicolon = 0x3b, /// 11338 Apostrophe = 0x27, /// 11339 Enter = 0xff0d, /// 11340 Shift = 0xffe1, /// 11341 Z = 0x7a, /// 11342 X = 0x78, /// 11343 C = 0x63, /// 11344 V = 0x76, /// 11345 B = 0x62, /// 11346 N = 0x6e, /// 11347 M = 0x6d, /// 11348 Comma = 0x2c, /// 11349 Period = 0x2e, /// 11350 Slash = 0x2f, /// the / ? key 11351 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 11352 Ctrl = 0xffe3, /// 11353 Windows = 0xffeb, /// 11354 Alt = 0xffe9, /// 11355 Space = 0x20, /// 11356 Alt_r = 0xffea, /// ditto of shift_r 11357 Windows_r = 0xffec, /// 11358 Menu = 0xff67, /// 11359 Ctrl_r = 0xffe4, /// 11360 11361 NumLock = 0xff7f, /// 11362 Divide = 0xffaf, /// The / key on the number pad 11363 Multiply = 0xffaa, /// The * key on the number pad 11364 Minus = 0xffad, /// The - key on the number pad 11365 Plus = 0xffab, /// The + key on the number pad 11366 PadEnter = 0xff8d, /// Numberpad enter key 11367 Pad1 = 0xff9c, /// Numberpad keys 11368 Pad2 = 0xff99, /// 11369 Pad3 = 0xff9b, /// 11370 Pad4 = 0xff96, /// 11371 Pad5 = 0xff9d, /// 11372 Pad6 = 0xff98, /// 11373 Pad7 = 0xff95, /// 11374 Pad8 = 0xff97, /// 11375 Pad9 = 0xff9a, /// 11376 Pad0 = 0xff9e, /// 11377 PadDot = 0xff9f, /// 11378 } 11379 } version(X11) { 11380 // FIXME: match ASCII whenever we can. Most of it is already there, 11381 // but there's a few exceptions and mismatches with Windows 11382 11383 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 11384 enum Key { 11385 Escape = 0xff1b, /// 11386 F1 = 0xffbe, /// 11387 F2 = 0xffbf, /// 11388 F3 = 0xffc0, /// 11389 F4 = 0xffc1, /// 11390 F5 = 0xffc2, /// 11391 F6 = 0xffc3, /// 11392 F7 = 0xffc4, /// 11393 F8 = 0xffc5, /// 11394 F9 = 0xffc6, /// 11395 F10 = 0xffc7, /// 11396 F11 = 0xffc8, /// 11397 F12 = 0xffc9, /// 11398 PrintScreen = 0xff61, /// 11399 ScrollLock = 0xff14, /// 11400 Pause = 0xff13, /// 11401 Grave = 0x60, /// The $(BACKTICK) ~ key 11402 // number keys across the top of the keyboard 11403 N1 = 0x31, /// Number key atop the keyboard 11404 N2 = 0x32, /// 11405 N3 = 0x33, /// 11406 N4 = 0x34, /// 11407 N5 = 0x35, /// 11408 N6 = 0x36, /// 11409 N7 = 0x37, /// 11410 N8 = 0x38, /// 11411 N9 = 0x39, /// 11412 N0 = 0x30, /// 11413 Dash = 0x2d, /// 11414 Equals = 0x3d, /// 11415 Backslash = 0x5c, /// The \ | key 11416 Backspace = 0xff08, /// 11417 Insert = 0xff63, /// 11418 Home = 0xff50, /// 11419 PageUp = 0xff55, /// 11420 Delete = 0xffff, /// 11421 End = 0xff57, /// 11422 PageDown = 0xff56, /// 11423 Up = 0xff52, /// 11424 Down = 0xff54, /// 11425 Left = 0xff51, /// 11426 Right = 0xff53, /// 11427 11428 Tab = 0xff09, /// 11429 Q = 0x71, /// 11430 W = 0x77, /// 11431 E = 0x65, /// 11432 R = 0x72, /// 11433 T = 0x74, /// 11434 Y = 0x79, /// 11435 U = 0x75, /// 11436 I = 0x69, /// 11437 O = 0x6f, /// 11438 P = 0x70, /// 11439 LeftBracket = 0x5b, /// the [ { key 11440 RightBracket = 0x5d, /// the ] } key 11441 CapsLock = 0xffe5, /// 11442 A = 0x61, /// 11443 S = 0x73, /// 11444 D = 0x64, /// 11445 F = 0x66, /// 11446 G = 0x67, /// 11447 H = 0x68, /// 11448 J = 0x6a, /// 11449 K = 0x6b, /// 11450 L = 0x6c, /// 11451 Semicolon = 0x3b, /// 11452 Apostrophe = 0x27, /// 11453 Enter = 0xff0d, /// 11454 Shift = 0xffe1, /// 11455 Z = 0x7a, /// 11456 X = 0x78, /// 11457 C = 0x63, /// 11458 V = 0x76, /// 11459 B = 0x62, /// 11460 N = 0x6e, /// 11461 M = 0x6d, /// 11462 Comma = 0x2c, /// 11463 Period = 0x2e, /// 11464 Slash = 0x2f, /// the / ? key 11465 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 11466 Ctrl = 0xffe3, /// 11467 Windows = 0xffeb, /// 11468 Alt = 0xffe9, /// 11469 Space = 0x20, /// 11470 Alt_r = 0xffea, /// ditto of shift_r 11471 Windows_r = 0xffec, /// 11472 Menu = 0xff67, /// 11473 Ctrl_r = 0xffe4, /// 11474 11475 NumLock = 0xff7f, /// 11476 Divide = 0xffaf, /// The / key on the number pad 11477 Multiply = 0xffaa, /// The * key on the number pad 11478 Minus = 0xffad, /// The - key on the number pad 11479 Plus = 0xffab, /// The + key on the number pad 11480 PadEnter = 0xff8d, /// Numberpad enter key 11481 Pad1 = 0xff9c, /// Numberpad keys 11482 Pad2 = 0xff99, /// 11483 Pad3 = 0xff9b, /// 11484 Pad4 = 0xff96, /// 11485 Pad5 = 0xff9d, /// 11486 Pad6 = 0xff98, /// 11487 Pad7 = 0xff95, /// 11488 Pad8 = 0xff97, /// 11489 Pad9 = 0xff9a, /// 11490 Pad0 = 0xff9e, /// 11491 PadDot = 0xff9f, /// 11492 } 11493 } else version(Windows) { 11494 // the character here is for en-us layouts and for illustration only 11495 // if you actually want to get characters, wait for character events 11496 // (the argument to your event handler is simply a dchar) 11497 // those will be converted by the OS for the right locale. 11498 11499 enum Key { 11500 Escape = 0x1b, 11501 F1 = 0x70, 11502 F2 = 0x71, 11503 F3 = 0x72, 11504 F4 = 0x73, 11505 F5 = 0x74, 11506 F6 = 0x75, 11507 F7 = 0x76, 11508 F8 = 0x77, 11509 F9 = 0x78, 11510 F10 = 0x79, 11511 F11 = 0x7a, 11512 F12 = 0x7b, 11513 PrintScreen = 0x2c, 11514 ScrollLock = 0x91, 11515 Pause = 0x13, 11516 Grave = 0xc0, 11517 // number keys across the top of the keyboard 11518 N1 = 0x31, 11519 N2 = 0x32, 11520 N3 = 0x33, 11521 N4 = 0x34, 11522 N5 = 0x35, 11523 N6 = 0x36, 11524 N7 = 0x37, 11525 N8 = 0x38, 11526 N9 = 0x39, 11527 N0 = 0x30, 11528 Dash = 0xbd, 11529 Equals = 0xbb, 11530 Backslash = 0xdc, 11531 Backspace = 0x08, 11532 Insert = 0x2d, 11533 Home = 0x24, 11534 PageUp = 0x21, 11535 Delete = 0x2e, 11536 End = 0x23, 11537 PageDown = 0x22, 11538 Up = 0x26, 11539 Down = 0x28, 11540 Left = 0x25, 11541 Right = 0x27, 11542 11543 Tab = 0x09, 11544 Q = 0x51, 11545 W = 0x57, 11546 E = 0x45, 11547 R = 0x52, 11548 T = 0x54, 11549 Y = 0x59, 11550 U = 0x55, 11551 I = 0x49, 11552 O = 0x4f, 11553 P = 0x50, 11554 LeftBracket = 0xdb, 11555 RightBracket = 0xdd, 11556 CapsLock = 0x14, 11557 A = 0x41, 11558 S = 0x53, 11559 D = 0x44, 11560 F = 0x46, 11561 G = 0x47, 11562 H = 0x48, 11563 J = 0x4a, 11564 K = 0x4b, 11565 L = 0x4c, 11566 Semicolon = 0xba, 11567 Apostrophe = 0xde, 11568 Enter = 0x0d, 11569 Shift = 0x10, 11570 Z = 0x5a, 11571 X = 0x58, 11572 C = 0x43, 11573 V = 0x56, 11574 B = 0x42, 11575 N = 0x4e, 11576 M = 0x4d, 11577 Comma = 0xbc, 11578 Period = 0xbe, 11579 Slash = 0xbf, 11580 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 11581 Ctrl = 0x11, 11582 Windows = 0x5b, 11583 Alt = -5, // FIXME 11584 Space = 0x20, 11585 Alt_r = 0xffea, // ditto of shift_r 11586 Windows_r = 0x5c, // ditto of shift_r 11587 Menu = 0x5d, 11588 Ctrl_r = 0xa3, // ditto of shift_r 11589 11590 NumLock = 0x90, 11591 Divide = 0x6f, 11592 Multiply = 0x6a, 11593 Minus = 0x6d, 11594 Plus = 0x6b, 11595 PadEnter = -8, // FIXME 11596 Pad1 = 0x61, 11597 Pad2 = 0x62, 11598 Pad3 = 0x63, 11599 Pad4 = 0x64, 11600 Pad5 = 0x65, 11601 Pad6 = 0x66, 11602 Pad7 = 0x67, 11603 Pad8 = 0x68, 11604 Pad9 = 0x69, 11605 Pad0 = 0x60, 11606 PadDot = 0x6e, 11607 } 11608 11609 // I'm keeping this around for reference purposes 11610 // ideally all these buttons will be listed for all platforms, 11611 // but now now I'm just focusing on my US keyboard 11612 version(none) 11613 enum Key { 11614 LBUTTON = 0x01, 11615 RBUTTON = 0x02, 11616 CANCEL = 0x03, 11617 MBUTTON = 0x04, 11618 //static if (_WIN32_WINNT > = 0x500) { 11619 XBUTTON1 = 0x05, 11620 XBUTTON2 = 0x06, 11621 //} 11622 BACK = 0x08, 11623 TAB = 0x09, 11624 CLEAR = 0x0C, 11625 RETURN = 0x0D, 11626 SHIFT = 0x10, 11627 CONTROL = 0x11, 11628 MENU = 0x12, 11629 PAUSE = 0x13, 11630 CAPITAL = 0x14, 11631 KANA = 0x15, 11632 HANGEUL = 0x15, 11633 HANGUL = 0x15, 11634 JUNJA = 0x17, 11635 FINAL = 0x18, 11636 HANJA = 0x19, 11637 KANJI = 0x19, 11638 ESCAPE = 0x1B, 11639 CONVERT = 0x1C, 11640 NONCONVERT = 0x1D, 11641 ACCEPT = 0x1E, 11642 MODECHANGE = 0x1F, 11643 SPACE = 0x20, 11644 PRIOR = 0x21, 11645 NEXT = 0x22, 11646 END = 0x23, 11647 HOME = 0x24, 11648 LEFT = 0x25, 11649 UP = 0x26, 11650 RIGHT = 0x27, 11651 DOWN = 0x28, 11652 SELECT = 0x29, 11653 PRINT = 0x2A, 11654 EXECUTE = 0x2B, 11655 SNAPSHOT = 0x2C, 11656 INSERT = 0x2D, 11657 DELETE = 0x2E, 11658 HELP = 0x2F, 11659 LWIN = 0x5B, 11660 RWIN = 0x5C, 11661 APPS = 0x5D, 11662 SLEEP = 0x5F, 11663 NUMPAD0 = 0x60, 11664 NUMPAD1 = 0x61, 11665 NUMPAD2 = 0x62, 11666 NUMPAD3 = 0x63, 11667 NUMPAD4 = 0x64, 11668 NUMPAD5 = 0x65, 11669 NUMPAD6 = 0x66, 11670 NUMPAD7 = 0x67, 11671 NUMPAD8 = 0x68, 11672 NUMPAD9 = 0x69, 11673 MULTIPLY = 0x6A, 11674 ADD = 0x6B, 11675 SEPARATOR = 0x6C, 11676 SUBTRACT = 0x6D, 11677 DECIMAL = 0x6E, 11678 DIVIDE = 0x6F, 11679 F1 = 0x70, 11680 F2 = 0x71, 11681 F3 = 0x72, 11682 F4 = 0x73, 11683 F5 = 0x74, 11684 F6 = 0x75, 11685 F7 = 0x76, 11686 F8 = 0x77, 11687 F9 = 0x78, 11688 F10 = 0x79, 11689 F11 = 0x7A, 11690 F12 = 0x7B, 11691 F13 = 0x7C, 11692 F14 = 0x7D, 11693 F15 = 0x7E, 11694 F16 = 0x7F, 11695 F17 = 0x80, 11696 F18 = 0x81, 11697 F19 = 0x82, 11698 F20 = 0x83, 11699 F21 = 0x84, 11700 F22 = 0x85, 11701 F23 = 0x86, 11702 F24 = 0x87, 11703 NUMLOCK = 0x90, 11704 SCROLL = 0x91, 11705 LSHIFT = 0xA0, 11706 RSHIFT = 0xA1, 11707 LCONTROL = 0xA2, 11708 RCONTROL = 0xA3, 11709 LMENU = 0xA4, 11710 RMENU = 0xA5, 11711 //static if (_WIN32_WINNT > = 0x500) { 11712 BROWSER_BACK = 0xA6, 11713 BROWSER_FORWARD = 0xA7, 11714 BROWSER_REFRESH = 0xA8, 11715 BROWSER_STOP = 0xA9, 11716 BROWSER_SEARCH = 0xAA, 11717 BROWSER_FAVORITES = 0xAB, 11718 BROWSER_HOME = 0xAC, 11719 VOLUME_MUTE = 0xAD, 11720 VOLUME_DOWN = 0xAE, 11721 VOLUME_UP = 0xAF, 11722 MEDIA_NEXT_TRACK = 0xB0, 11723 MEDIA_PREV_TRACK = 0xB1, 11724 MEDIA_STOP = 0xB2, 11725 MEDIA_PLAY_PAUSE = 0xB3, 11726 LAUNCH_MAIL = 0xB4, 11727 LAUNCH_MEDIA_SELECT = 0xB5, 11728 LAUNCH_APP1 = 0xB6, 11729 LAUNCH_APP2 = 0xB7, 11730 //} 11731 OEM_1 = 0xBA, 11732 //static if (_WIN32_WINNT > = 0x500) { 11733 OEM_PLUS = 0xBB, 11734 OEM_COMMA = 0xBC, 11735 OEM_MINUS = 0xBD, 11736 OEM_PERIOD = 0xBE, 11737 //} 11738 OEM_2 = 0xBF, 11739 OEM_3 = 0xC0, 11740 OEM_4 = 0xDB, 11741 OEM_5 = 0xDC, 11742 OEM_6 = 0xDD, 11743 OEM_7 = 0xDE, 11744 OEM_8 = 0xDF, 11745 //static if (_WIN32_WINNT > = 0x500) { 11746 OEM_102 = 0xE2, 11747 //} 11748 PROCESSKEY = 0xE5, 11749 //static if (_WIN32_WINNT > = 0x500) { 11750 PACKET = 0xE7, 11751 //} 11752 ATTN = 0xF6, 11753 CRSEL = 0xF7, 11754 EXSEL = 0xF8, 11755 EREOF = 0xF9, 11756 PLAY = 0xFA, 11757 ZOOM = 0xFB, 11758 NONAME = 0xFC, 11759 PA1 = 0xFD, 11760 OEM_CLEAR = 0xFE, 11761 } 11762 11763 } else version(OSXCocoa) { 11764 enum Key { 11765 Escape = 53, 11766 F1 = 122, 11767 F2 = 120, 11768 F3 = 99, 11769 F4 = 118, 11770 F5 = 96, 11771 F6 = 97, 11772 F7 = 98, 11773 F8 = 100, 11774 F9 = 101, 11775 F10 = 109, 11776 F11 = 103, 11777 F12 = 111, 11778 PrintScreen = 105, 11779 ScrollLock = 107, 11780 Pause = 113, 11781 Grave = 50, 11782 // number keys across the top of the keyboard 11783 N1 = 18, 11784 N2 = 19, 11785 N3 = 20, 11786 N4 = 21, 11787 N5 = 23, 11788 N6 = 22, 11789 N7 = 26, 11790 N8 = 28, 11791 N9 = 25, 11792 N0 = 29, 11793 Dash = 27, 11794 Equals = 24, 11795 Backslash = 42, 11796 Backspace = 51, 11797 Insert = 114, 11798 Home = 115, 11799 PageUp = 116, 11800 Delete = 117, 11801 End = 119, 11802 PageDown = 121, 11803 Up = 126, 11804 Down = 125, 11805 Left = 123, 11806 Right = 124, 11807 11808 Tab = 48, 11809 Q = 12, 11810 W = 13, 11811 E = 14, 11812 R = 15, 11813 T = 17, 11814 Y = 16, 11815 U = 32, 11816 I = 34, 11817 O = 31, 11818 P = 35, 11819 LeftBracket = 33, 11820 RightBracket = 30, 11821 CapsLock = 57, 11822 A = 0, 11823 S = 1, 11824 D = 2, 11825 F = 3, 11826 G = 5, 11827 H = 4, 11828 J = 38, 11829 K = 40, 11830 L = 37, 11831 Semicolon = 41, 11832 Apostrophe = 39, 11833 Enter = 36, 11834 Shift = 56, 11835 Z = 6, 11836 X = 7, 11837 C = 8, 11838 V = 9, 11839 B = 11, 11840 N = 45, 11841 M = 46, 11842 Comma = 43, 11843 Period = 47, 11844 Slash = 44, 11845 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 11846 Ctrl = 59, 11847 Windows = 55, 11848 Alt = 58, 11849 Space = 49, 11850 Alt_r = -3, // ditto of shift_r 11851 Windows_r = -2, 11852 Menu = 110, 11853 Ctrl_r = -1, 11854 11855 NumLock = 1, 11856 Divide = 75, 11857 Multiply = 67, 11858 Minus = 78, 11859 Plus = 69, 11860 PadEnter = 76, 11861 Pad1 = 83, 11862 Pad2 = 84, 11863 Pad3 = 85, 11864 Pad4 = 86, 11865 Pad5 = 87, 11866 Pad6 = 88, 11867 Pad7 = 89, 11868 Pad8 = 91, 11869 Pad9 = 92, 11870 Pad0 = 82, 11871 PadDot = 65, 11872 } 11873 11874 } 11875 11876 char keyToLetterCharAssumingLotsOfThingsThatYouMightBetterNotAssume(Key key) { 11877 version(OSXCocoa) { 11878 return char.init; // FIXME 11879 } else { 11880 return cast(char)(key - Key.A + 'a'); 11881 } 11882 } 11883 11884 /* Additional utilities */ 11885 11886 11887 Color fromHsl(real h, real s, real l) { 11888 return arsd.color.fromHsl([h,s,l]); 11889 } 11890 11891 11892 11893 /* ********** What follows is the system-specific implementations *********/ 11894 version(Windows) { 11895 11896 11897 // helpers for making HICONs from MemoryImages 11898 class WindowsIcon { 11899 struct Win32Icon { 11900 align(1): 11901 uint biSize; 11902 int biWidth; 11903 int biHeight; 11904 ushort biPlanes; 11905 ushort biBitCount; 11906 uint biCompression; 11907 uint biSizeImage; 11908 int biXPelsPerMeter; 11909 int biYPelsPerMeter; 11910 uint biClrUsed; 11911 uint biClrImportant; 11912 // RGBQUAD[colorCount] biColors; 11913 /* Pixels: 11914 Uint8 pixels[] 11915 */ 11916 /* Mask: 11917 Uint8 mask[] 11918 */ 11919 } 11920 11921 ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 11922 11923 assert(mi.width <= 256, "image too wide"); 11924 assert(mi.height <= 256, "image too tall"); 11925 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 11926 assert(mi.height % 4 == 0, "image not multiple of 4 height"); 11927 11928 int icon_plen = mi.width * mi.height * 4; 11929 int icon_mlen = mi.width * mi.height / 8; 11930 11931 int colorCount = 0; 11932 icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 11933 11934 ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen); 11935 Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr; 11936 11937 auto data = memory[Win32Icon.sizeof .. $]; 11938 11939 width = mi.width; 11940 height = mi.height; 11941 11942 auto trueColorImage = mi.getAsTrueColorImage(); 11943 11944 icon_win32.biSize = 40; 11945 icon_win32.biWidth = mi.width; 11946 icon_win32.biHeight = mi.height*2; 11947 icon_win32.biPlanes = 1; 11948 icon_win32.biBitCount = 32; 11949 icon_win32.biSizeImage = icon_plen + icon_mlen; 11950 11951 int offset = 0; 11952 int andOff = icon_plen * 8; // the and offset is in bits 11953 11954 // leaving the and mask as the default 0 so the rgba alpha blend 11955 // does its thing instead 11956 for(int y = height - 1; y >= 0; y--) { 11957 int off2 = y * width * 4; 11958 foreach(x; 0 .. width) { 11959 data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0]; 11960 data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1]; 11961 data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2]; 11962 data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3]; 11963 11964 offset += 4; 11965 off2 += 4; 11966 } 11967 } 11968 11969 return memory; 11970 } 11971 11972 this(MemoryImage mi) { 11973 int icon_len, width, height; 11974 11975 auto icon_win32 = fromMemoryImage(mi, icon_len, width, height); 11976 11977 /* 11978 PNG* png = readPnpngData); 11979 PNGHeader pngh = getHeader(png); 11980 void* icon_win32; 11981 if(pngh.depth == 4) { 11982 auto i = new Win32Icon!(16); 11983 i.fromPNG(png, pngh, icon_len, width, height); 11984 icon_win32 = i; 11985 } 11986 else if(pngh.depth == 8) { 11987 auto i = new Win32Icon!(256); 11988 i.fromPNG(png, pngh, icon_len, width, height); 11989 icon_win32 = i; 11990 } else assert(0); 11991 */ 11992 11993 hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0); 11994 11995 if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError()); 11996 } 11997 11998 ~this() { 11999 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 12000 DestroyIcon(hIcon); 12001 } 12002 12003 HICON hIcon; 12004 } 12005 12006 12007 12008 12009 12010 12011 alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler; 12012 alias HWND NativeWindowHandle; 12013 12014 extern(Windows) 12015 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 12016 try { 12017 if(SimpleWindow.handleNativeGlobalEvent !is null) { 12018 // it returns zero if the message is handled, so we won't do anything more there 12019 // do I like that though? 12020 int mustReturn; 12021 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn); 12022 if(mustReturn) 12023 return ret; 12024 } 12025 12026 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 12027 if(window.getNativeEventHandler !is null) { 12028 int mustReturn; 12029 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn); 12030 if(mustReturn) 12031 return ret; 12032 } 12033 if(auto w = cast(SimpleWindow) (*window)) 12034 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 12035 else 12036 return DefWindowProc(hWnd, iMessage, wParam, lParam); 12037 } else { 12038 return DefWindowProc(hWnd, iMessage, wParam, lParam); 12039 } 12040 } catch (Exception e) { 12041 try { 12042 sdpy_abort(e); 12043 return 0; 12044 } catch(Exception e) { assert(0); } 12045 } 12046 } 12047 12048 void sdpy_abort(Throwable e) nothrow { 12049 try 12050 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 12051 catch(Exception e) 12052 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 12053 ExitProcess(1); 12054 } 12055 12056 mixin template NativeScreenPainterImplementation() { 12057 HDC hdc; 12058 HWND hwnd; 12059 //HDC windowHdc; 12060 HBITMAP oldBmp; 12061 12062 void create(PaintingHandle window) { 12063 hwnd = window; 12064 12065 if(auto sw = cast(SimpleWindow) this.window) { 12066 // drawing on a window, double buffer 12067 auto windowHdc = GetDC(hwnd); 12068 12069 auto buffer = sw.impl.buffer; 12070 if(buffer is null) { 12071 hdc = windowHdc; 12072 windowDc = true; 12073 } else { 12074 hdc = CreateCompatibleDC(windowHdc); 12075 12076 ReleaseDC(hwnd, windowHdc); 12077 12078 oldBmp = SelectObject(hdc, buffer); 12079 } 12080 } else { 12081 // drawing on something else, draw directly 12082 hdc = CreateCompatibleDC(null); 12083 SelectObject(hdc, window); 12084 } 12085 12086 // X doesn't draw a text background, so neither should we 12087 SetBkMode(hdc, TRANSPARENT); 12088 12089 ensureDefaultFontLoaded(); 12090 12091 if(defaultGuiFont) { 12092 SelectObject(hdc, defaultGuiFont); 12093 // DeleteObject(defaultGuiFont); 12094 } 12095 } 12096 12097 static HFONT defaultGuiFont; 12098 static void ensureDefaultFontLoaded() { 12099 static bool triedDefaultGuiFont = false; 12100 if(!triedDefaultGuiFont) { 12101 NONCLIENTMETRICS params; 12102 params.cbSize = params.sizeof; 12103 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 12104 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 12105 } 12106 triedDefaultGuiFont = true; 12107 } 12108 } 12109 12110 private OperatingSystemFont _activeFont; 12111 12112 void setFont(OperatingSystemFont font) { 12113 _activeFont = font; 12114 if(font && font.font) { 12115 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 12116 // error... how to handle tho? 12117 } else { 12118 12119 } 12120 } 12121 else if(defaultGuiFont) 12122 SelectObject(hdc, defaultGuiFont); 12123 } 12124 12125 arsd.color.Rectangle _clipRectangle; 12126 12127 void setClipRectangle(int x, int y, int width, int height) { 12128 auto old = _clipRectangle; 12129 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 12130 if(old == _clipRectangle) 12131 return; 12132 12133 if(width == 0 || height == 0) { 12134 SelectClipRgn(hdc, null); 12135 } else { 12136 auto region = CreateRectRgn(x, y, x + width, y + height); 12137 SelectClipRgn(hdc, region); 12138 DeleteObject(region); 12139 } 12140 } 12141 12142 12143 // just because we can on Windows... 12144 //void create(Image image); 12145 12146 void invalidateRect(Rectangle invalidRect) { 12147 RECT rect; 12148 rect.left = invalidRect.left; 12149 rect.right = invalidRect.right; 12150 rect.top = invalidRect.top; 12151 rect.bottom = invalidRect.bottom; 12152 InvalidateRect(hwnd, &rect, false); 12153 } 12154 bool manualInvalidations; 12155 12156 void dispose() { 12157 // FIXME: this.window.width/height is probably wrong 12158 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 12159 // ReleaseDC(hwnd, windowHdc); 12160 12161 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 12162 if(cast(SimpleWindow) this.window) { 12163 if(!manualInvalidations) 12164 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 12165 } 12166 12167 if(originalPen !is null) 12168 SelectObject(hdc, originalPen); 12169 if(currentPen !is null) 12170 DeleteObject(currentPen); 12171 if(originalBrush !is null) 12172 SelectObject(hdc, originalBrush); 12173 if(currentBrush !is null) 12174 DeleteObject(currentBrush); 12175 12176 SelectObject(hdc, oldBmp); 12177 12178 if(windowDc) 12179 ReleaseDC(hwnd, hdc); 12180 else 12181 DeleteDC(hdc); 12182 12183 if(window.paintingFinishedDg !is null) 12184 window.paintingFinishedDg()(); 12185 } 12186 12187 bool windowDc; 12188 HPEN originalPen; 12189 HPEN currentPen; 12190 12191 Pen _activePen; 12192 12193 Color _outlineColor; 12194 12195 @property void pen(Pen p) { 12196 _activePen = p; 12197 _outlineColor = p.color; 12198 12199 HPEN pen; 12200 if(p.color.a == 0) { 12201 pen = GetStockObject(NULL_PEN); 12202 } else { 12203 int style = PS_SOLID; 12204 final switch(p.style) { 12205 case Pen.Style.Solid: 12206 style = PS_SOLID; 12207 break; 12208 case Pen.Style.Dashed: 12209 style = PS_DASH; 12210 break; 12211 case Pen.Style.Dotted: 12212 style = PS_DOT; 12213 break; 12214 } 12215 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 12216 } 12217 auto orig = SelectObject(hdc, pen); 12218 if(originalPen is null) 12219 originalPen = orig; 12220 12221 if(currentPen !is null) 12222 DeleteObject(currentPen); 12223 12224 currentPen = pen; 12225 12226 // the outline is like a foreground since it's done that way on X 12227 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 12228 12229 } 12230 12231 @property void rasterOp(RasterOp op) { 12232 int mode; 12233 final switch(op) { 12234 case RasterOp.normal: 12235 mode = R2_COPYPEN; 12236 break; 12237 case RasterOp.xor: 12238 mode = R2_XORPEN; 12239 break; 12240 } 12241 SetROP2(hdc, mode); 12242 } 12243 12244 HBRUSH originalBrush; 12245 HBRUSH currentBrush; 12246 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 12247 @property void fillColor(Color c) { 12248 if(c == _fillColor) 12249 return; 12250 _fillColor = c; 12251 HBRUSH brush; 12252 if(c.a == 0) { 12253 brush = GetStockObject(HOLLOW_BRUSH); 12254 } else { 12255 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 12256 } 12257 auto orig = SelectObject(hdc, brush); 12258 if(originalBrush is null) 12259 originalBrush = orig; 12260 12261 if(currentBrush !is null) 12262 DeleteObject(currentBrush); 12263 12264 currentBrush = brush; 12265 12266 // background color is NOT set because X doesn't draw text backgrounds 12267 // SetBkColor(hdc, RGB(255, 255, 255)); 12268 } 12269 12270 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 12271 BITMAP bm; 12272 12273 HDC hdcMem = CreateCompatibleDC(hdc); 12274 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 12275 12276 GetObject(i.handle, bm.sizeof, &bm); 12277 12278 // or should I AlphaBlend!??!?! 12279 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 12280 12281 SelectObject(hdcMem, hbmOld); 12282 DeleteDC(hdcMem); 12283 } 12284 12285 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 12286 BITMAP bm; 12287 12288 HDC hdcMem = CreateCompatibleDC(hdc); 12289 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 12290 12291 GetObject(s.handle, bm.sizeof, &bm); 12292 12293 version(CRuntime_DigitalMars) goto noalpha; 12294 12295 // or should I AlphaBlend!??!?! note it is supposed to be premultiplied http://www.fengyuan.com/article/alphablend.html 12296 if(s.enableAlpha) { 12297 auto dw = w ? w : bm.bmWidth; 12298 auto dh = h ? h : bm.bmHeight; 12299 BLENDFUNCTION bf; 12300 bf.BlendOp = AC_SRC_OVER; 12301 bf.SourceConstantAlpha = 255; 12302 bf.AlphaFormat = AC_SRC_ALPHA; 12303 AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf); 12304 } else { 12305 noalpha: 12306 BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY); 12307 } 12308 12309 SelectObject(hdcMem, hbmOld); 12310 DeleteDC(hdcMem); 12311 } 12312 12313 Size textSize(scope const(char)[] text) { 12314 bool dummyX; 12315 if(text.length == 0) { 12316 text = " "; 12317 dummyX = true; 12318 } 12319 RECT rect; 12320 WCharzBuffer buffer = WCharzBuffer(text); 12321 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX); 12322 return Size(dummyX ? 0 : rect.right, rect.bottom); 12323 } 12324 12325 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 12326 if(text.length && text[$-1] == '\n') 12327 text = text[0 .. $-1]; // tailing newlines are weird on windows... 12328 if(text.length && text[$-1] == '\r') 12329 text = text[0 .. $-1]; 12330 12331 WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines); 12332 if(x2 == 0 && y2 == 0) { 12333 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 12334 } else { 12335 RECT rect; 12336 rect.left = x; 12337 rect.top = y; 12338 rect.right = x2; 12339 rect.bottom = y2; 12340 12341 uint mode = DT_LEFT; 12342 if(alignment & TextAlignment.Right) 12343 mode = DT_RIGHT; 12344 else if(alignment & TextAlignment.Center) 12345 mode = DT_CENTER; 12346 12347 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 12348 if(alignment & TextAlignment.VerticalCenter) 12349 mode |= DT_VCENTER | DT_SINGLELINE; 12350 12351 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX); 12352 } 12353 12354 /* 12355 uint mode; 12356 12357 if(alignment & TextAlignment.Center) 12358 mode = TA_CENTER; 12359 12360 SetTextAlign(hdc, mode); 12361 */ 12362 } 12363 12364 int fontHeight() { 12365 TEXTMETRIC metric; 12366 if(GetTextMetricsW(hdc, &metric)) { 12367 return metric.tmHeight; 12368 } 12369 12370 return 16; // idk just guessing here, maybe we should throw 12371 } 12372 12373 void drawPixel(int x, int y) { 12374 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 12375 } 12376 12377 // The basic shapes, outlined 12378 12379 void drawLine(int x1, int y1, int x2, int y2) { 12380 MoveToEx(hdc, x1, y1, null); 12381 LineTo(hdc, x2, y2); 12382 } 12383 12384 void drawRectangle(int x, int y, int width, int height) { 12385 // FIXME: with a wider pen this might not draw quite right. im not sure. 12386 gdi.Rectangle(hdc, x, y, x + width, y + height); 12387 } 12388 12389 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 12390 RoundRect( 12391 hdc, 12392 upperLeft.x, upperLeft.y, 12393 lowerRight.x, lowerRight.y, 12394 borderRadius, borderRadius 12395 ); 12396 } 12397 12398 /// Arguments are the points of the bounding rectangle 12399 void drawEllipse(int x1, int y1, int x2, int y2) { 12400 Ellipse(hdc, x1, y1, x2, y2); 12401 } 12402 12403 void drawArc(int x1, int y1, int width, int height, int start, int length) { 12404 //if(length > 360*64) 12405 //length = 360*64; 12406 12407 if((start == 0 && length == 360*64)) { 12408 drawEllipse(x1, y1, x1 + width, y1 + height); 12409 } else { 12410 import core.stdc.math; 12411 12412 bool clockwise = false; 12413 if(length < 0) { 12414 clockwise = true; 12415 length = -length; 12416 } 12417 12418 double startAngle = cast(double) start / 64.0 / 180.0 * 3.14159265358979323; 12419 double endAngle = cast(double) (start + length) / 64.0 / 180.0 * 3.14159265358979323; 12420 12421 auto c1 = cast(int) (cos(startAngle) * width / 2.0 + double(x1) + double(width) / 2.0); 12422 auto c2 = cast(int) (-sin(startAngle) * height / 2.0 + double(y1) + double(height) / 2.0); 12423 auto c3 = cast(int) (cos(endAngle) * width / 2.0 + double(x1) + double(width) / 2.0); 12424 auto c4 = cast(int) (-sin(endAngle) * height / 2.0 + double(y1) + double(height) / 2.0); 12425 12426 if(clockwise) { 12427 auto t1 = c1; 12428 auto t2 = c2; 12429 c1 = c3; 12430 c2 = c4; 12431 c3 = t1; 12432 c4 = t2; 12433 } 12434 12435 //if(_activePen.color.a) 12436 //Arc(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4); 12437 //if(_fillColor.a) 12438 12439 Pie(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4); 12440 } 12441 } 12442 12443 void drawPolygon(Point[] vertexes) { 12444 POINT[] points; 12445 points.length = vertexes.length; 12446 12447 foreach(i, p; vertexes) { 12448 points[i].x = p.x; 12449 points[i].y = p.y; 12450 } 12451 12452 Polygon(hdc, points.ptr, cast(int) points.length); 12453 } 12454 } 12455 12456 12457 // Mix this into the SimpleWindow class 12458 mixin template NativeSimpleWindowImplementation() { 12459 int curHidden = 0; // counter 12460 __gshared static bool[string] knownWinClasses; 12461 static bool altPressed = false; 12462 12463 HANDLE oldCursor; 12464 12465 void hideCursor () { 12466 if(curHidden == 0) 12467 oldCursor = SetCursor(null); 12468 ++curHidden; 12469 } 12470 12471 void showCursor () { 12472 --curHidden; 12473 if(curHidden == 0) { 12474 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 12475 } 12476 } 12477 12478 12479 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 12480 12481 void setMinSize (int minwidth, int minheight) { 12482 minWidth = minwidth; 12483 minHeight = minheight; 12484 } 12485 void setMaxSize (int maxwidth, int maxheight) { 12486 maxWidth = maxwidth; 12487 maxHeight = maxheight; 12488 } 12489 12490 // FIXME i'm not sure that Windows has this functionality 12491 // though it is nonessential anyway. 12492 void setResizeGranularity (int granx, int grany) {} 12493 12494 ScreenPainter getPainter(bool manualInvalidations) { 12495 return ScreenPainter(this, hwnd, manualInvalidations); 12496 } 12497 12498 HBITMAP buffer; 12499 12500 void setTitle(string title) { 12501 WCharzBuffer bfr = WCharzBuffer(title); 12502 SetWindowTextW(hwnd, bfr.ptr); 12503 } 12504 12505 string getTitle() { 12506 auto len = GetWindowTextLengthW(hwnd); 12507 if (!len) 12508 return null; 12509 wchar[256] tmpBuffer; 12510 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 12511 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 12512 auto str = buffer[0 .. len2]; 12513 return makeUtf8StringFromWindowsString(str); 12514 } 12515 12516 void move(int x, int y) { 12517 RECT rect; 12518 GetWindowRect(hwnd, &rect); 12519 // move it while maintaining the same size... 12520 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 12521 } 12522 12523 void resize(int w, int h) { 12524 RECT rect; 12525 GetWindowRect(hwnd, &rect); 12526 12527 RECT client; 12528 GetClientRect(hwnd, &client); 12529 12530 rect.right = rect.right - client.right + w; 12531 rect.bottom = rect.bottom - client.bottom + h; 12532 12533 // same position, new size for the client rectangle 12534 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 12535 12536 updateOpenglViewportIfNeeded(w, h); 12537 } 12538 12539 void moveResize (int x, int y, int w, int h) { 12540 // what's given is the client rectangle, we need to adjust 12541 12542 RECT rect; 12543 rect.left = x; 12544 rect.top = y; 12545 rect.right = w + x; 12546 rect.bottom = h + y; 12547 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 12548 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12549 12550 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 12551 updateOpenglViewportIfNeeded(w, h); 12552 if (windowResized !is null) windowResized(w, h); 12553 } 12554 12555 version(without_opengl) {} else { 12556 HGLRC ghRC; 12557 HDC ghDC; 12558 } 12559 12560 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 12561 string cnamec; 12562 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 12563 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 12564 cnamec = "DSimpleWindow"; 12565 } else { 12566 cnamec = sdpyWindowClass; 12567 } 12568 12569 WCharzBuffer cn = WCharzBuffer(cnamec); 12570 12571 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 12572 12573 if(cnamec !in knownWinClasses) { 12574 WNDCLASSEX wc; 12575 12576 // FIXME: I might be able to use cbWndExtra to hold the pointer back 12577 // to the object. Maybe. 12578 wc.cbSize = wc.sizeof; 12579 wc.cbClsExtra = 0; 12580 wc.cbWndExtra = 0; 12581 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 12582 wc.hCursor = LoadCursorW(null, IDC_ARROW); 12583 wc.hIcon = LoadIcon(hInstance, null); 12584 wc.hInstance = hInstance; 12585 wc.lpfnWndProc = &WndProc; 12586 wc.lpszClassName = cn.ptr; 12587 wc.hIconSm = null; 12588 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 12589 if(!RegisterClassExW(&wc)) 12590 throw new WindowsApiException("RegisterClassExW", GetLastError()); 12591 knownWinClasses[cnamec] = true; 12592 } 12593 12594 int style; 12595 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 12596 12597 // FIXME: windowType and customizationFlags 12598 final switch(windowType) { 12599 case WindowTypes.normal: 12600 if(resizability == Resizability.fixedSize) { 12601 style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION; 12602 } else { 12603 style = WS_OVERLAPPEDWINDOW; 12604 } 12605 break; 12606 case WindowTypes.undecorated: 12607 style = WS_POPUP | WS_SYSMENU; 12608 break; 12609 case WindowTypes.eventOnly: 12610 _hidden = true; 12611 break; 12612 case WindowTypes.tooltip: 12613 case WindowTypes.dnd: 12614 case WindowTypes.comboBoxDropdown: 12615 case WindowTypes.dropdownMenu: 12616 case WindowTypes.popupMenu: 12617 case WindowTypes.notification: 12618 style = WS_POPUP; 12619 flags |= WS_EX_NOACTIVATE; 12620 break; 12621 case WindowTypes.dialog: 12622 style = WS_OVERLAPPEDWINDOW; 12623 break; 12624 case WindowTypes.nestedChild: 12625 style = WS_CHILD; 12626 break; 12627 case WindowTypes.minimallyWrapped: 12628 assert(0, "construct minimally wrapped through the other ctor overlad"); 12629 } 12630 12631 if ((customizationFlags & WindowFlags.extraComposite) != 0) 12632 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 12633 12634 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 12635 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 12636 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 12637 12638 if(!hwnd) 12639 throw new WindowsApiException("CreateWindowEx", GetLastError()); 12640 12641 if ((customizationFlags & WindowFlags.extraComposite) != 0) 12642 setOpacity(255); 12643 12644 SimpleWindow.nativeMapping[hwnd] = this; 12645 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 12646 12647 if(windowType == WindowTypes.eventOnly) 12648 return; 12649 12650 HDC hdc = GetDC(hwnd); 12651 12652 if(!hdc) 12653 throw new WindowsApiException("GetDC", GetLastError()); 12654 12655 version(without_opengl) {} 12656 else { 12657 if(opengl == OpenGlOptions.yes) { 12658 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 12659 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 12660 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 12661 ghDC = hdc; 12662 PIXELFORMATDESCRIPTOR pfd; 12663 12664 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 12665 pfd.nVersion = 1; 12666 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 12667 pfd.dwLayerMask = PFD_MAIN_PLANE; 12668 pfd.iPixelType = PFD_TYPE_RGBA; 12669 pfd.cColorBits = 24; 12670 pfd.cDepthBits = 24; 12671 pfd.cAccumBits = 0; 12672 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 12673 12674 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 12675 12676 if (pixelformat == 0) 12677 throw new WindowsApiException("ChoosePixelFormat", GetLastError()); 12678 12679 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 12680 throw new WindowsApiException("SetPixelFormat", GetLastError()); 12681 12682 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 12683 // windoze is idiotic: we have to have OpenGL context to get function addresses 12684 // so we will create fake context to get that stupid address 12685 auto tmpcc = wglCreateContext(ghDC); 12686 if (tmpcc !is null) { 12687 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 12688 wglMakeCurrent(ghDC, tmpcc); 12689 wglInitOtherFunctions(); 12690 } 12691 } 12692 12693 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 12694 int[9] contextAttribs = [ 12695 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 12696 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 12697 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 12698 // for modern context, set "forward compatibility" flag too 12699 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 12700 0/*None*/, 12701 ]; 12702 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 12703 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 12704 // activate fallback mode 12705 // 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; 12706 ghRC = wglCreateContext(ghDC); 12707 } 12708 if (ghRC is null) 12709 throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError()); 12710 } else { 12711 // try to do at least something 12712 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 12713 sdpyOpenGLContextVersion = 0; 12714 ghRC = wglCreateContext(ghDC); 12715 } 12716 if (ghRC is null) 12717 throw new WindowsApiException("wglCreateContext", GetLastError()); 12718 } 12719 } 12720 } 12721 12722 if(opengl == OpenGlOptions.no) { 12723 buffer = CreateCompatibleBitmap(hdc, width, height); 12724 12725 auto hdcBmp = CreateCompatibleDC(hdc); 12726 // make sure it's filled with a blank slate 12727 auto oldBmp = SelectObject(hdcBmp, buffer); 12728 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 12729 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 12730 gdi.Rectangle(hdcBmp, 0, 0, width, height); 12731 SelectObject(hdcBmp, oldBmp); 12732 SelectObject(hdcBmp, oldBrush); 12733 SelectObject(hdcBmp, oldPen); 12734 DeleteDC(hdcBmp); 12735 12736 bmpWidth = width; 12737 bmpHeight = height; 12738 12739 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 12740 } 12741 12742 // We want the window's client area to match the image size 12743 RECT rcClient, rcWindow; 12744 POINT ptDiff; 12745 GetClientRect(hwnd, &rcClient); 12746 GetWindowRect(hwnd, &rcWindow); 12747 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 12748 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 12749 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 12750 12751 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 12752 ShowWindow(hwnd, SW_SHOWNORMAL); 12753 } else { 12754 _hidden = true; 12755 } 12756 this._visibleForTheFirstTimeCalled = false; // hack! 12757 } 12758 12759 12760 void dispose() { 12761 if(buffer) 12762 DeleteObject(buffer); 12763 } 12764 12765 void closeWindow() { 12766 if(ghRC) { 12767 wglDeleteContext(ghRC); 12768 ghRC = null; 12769 } 12770 DestroyWindow(hwnd); 12771 } 12772 12773 bool setOpacity(ubyte alpha) { 12774 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 12775 } 12776 12777 HANDLE currentCursor; 12778 12779 // returns zero if it recognized the event 12780 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 12781 MouseEvent mouse; 12782 12783 void mouseEvent(bool isScreen, ulong mods) { 12784 auto x = LOWORD(lParam); 12785 auto y = HIWORD(lParam); 12786 if(isScreen) { 12787 POINT p; 12788 p.x = x; 12789 p.y = y; 12790 ScreenToClient(hwnd, &p); 12791 x = cast(ushort) p.x; 12792 y = cast(ushort) p.y; 12793 } 12794 12795 if(wind.resizability == Resizability.automaticallyScaleIfPossible) { 12796 x = cast(ushort)( x * wind._virtualWidth / wind._width ); 12797 y = cast(ushort)( y * wind._virtualHeight / wind._height ); 12798 } 12799 12800 mouse.x = x + offsetX; 12801 mouse.y = y + offsetY; 12802 12803 wind.mdx(mouse); 12804 mouse.modifierState = cast(int) mods; 12805 mouse.window = wind; 12806 12807 if(wind.handleMouseEvent) 12808 wind.handleMouseEvent(mouse); 12809 } 12810 12811 switch(msg) { 12812 case WM_GETMINMAXINFO: 12813 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 12814 12815 if(wind.minWidth > 0) { 12816 RECT rect; 12817 rect.left = 100; 12818 rect.top = 100; 12819 rect.right = wind.minWidth + 100; 12820 rect.bottom = wind.minHeight + 100; 12821 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 12822 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12823 12824 mmi.ptMinTrackSize.x = rect.right - rect.left; 12825 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 12826 } 12827 12828 if(wind.maxWidth < int.max) { 12829 RECT rect; 12830 rect.left = 100; 12831 rect.top = 100; 12832 rect.right = wind.maxWidth + 100; 12833 rect.bottom = wind.maxHeight + 100; 12834 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 12835 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12836 12837 mmi.ptMaxTrackSize.x = rect.right - rect.left; 12838 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 12839 } 12840 break; 12841 case WM_CHAR: 12842 wchar c = cast(wchar) wParam; 12843 if(wind.handleCharEvent) 12844 wind.handleCharEvent(cast(dchar) c); 12845 break; 12846 case WM_SETFOCUS: 12847 case WM_KILLFOCUS: 12848 wind._focused = (msg == WM_SETFOCUS); 12849 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 12850 if(wind.onFocusChange) 12851 wind.onFocusChange(msg == WM_SETFOCUS); 12852 break; 12853 12854 case WM_SYSKEYDOWN: 12855 goto case; 12856 case WM_SYSKEYUP: 12857 if(lParam & (1 << 29)) { 12858 goto case; 12859 } else { 12860 // no window has keyboard focus 12861 goto default; 12862 } 12863 case WM_KEYDOWN: 12864 case WM_KEYUP: 12865 KeyEvent ev; 12866 ev.key = cast(Key) wParam; 12867 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 12868 if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way 12869 12870 ev.hardwareCode = (lParam & 0xff0000) >> 16; 12871 12872 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 12873 ev.modifierState |= ModifierState.shift; 12874 //k8: this doesn't work; thanks for nothing, windows 12875 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 12876 ev.modifierState |= ModifierState.alt;*/ 12877 // this never seems to actually be set 12878 // if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12879 12880 if (wParam == 0x12) { 12881 altPressed = (msg == WM_SYSKEYDOWN); 12882 } 12883 12884 if(msg == WM_KEYDOWN || msg == WM_KEYUP) { 12885 altPressed = false; 12886 } 12887 // sdpyPrintDebugString(altPressed ? "alt down" : " up "); 12888 12889 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12890 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 12891 ev.modifierState |= ModifierState.ctrl; 12892 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 12893 ev.modifierState |= ModifierState.windows; 12894 if(GetKeyState(Key.NumLock)) 12895 ev.modifierState |= ModifierState.numLock; 12896 if(GetKeyState(Key.CapsLock)) 12897 ev.modifierState |= ModifierState.capsLock; 12898 12899 /+ 12900 // we always want to send the character too, so let's convert it 12901 ubyte[256] state; 12902 wchar[16] buffer; 12903 GetKeyboardState(state.ptr); 12904 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 12905 12906 foreach(dchar d; buffer) { 12907 ev.character = d; 12908 break; 12909 } 12910 +/ 12911 12912 ev.window = wind; 12913 if(wind.handleKeyEvent) 12914 wind.handleKeyEvent(ev); 12915 break; 12916 case 0x020a /*WM_MOUSEWHEEL*/: 12917 // send click 12918 mouse.type = cast(MouseEventType) 1; 12919 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12920 mouseEvent(true, LOWORD(wParam)); 12921 12922 // also send release 12923 mouse.type = cast(MouseEventType) 2; 12924 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12925 mouseEvent(true, LOWORD(wParam)); 12926 break; 12927 case WM_MOUSEMOVE: 12928 mouse.type = cast(MouseEventType) 0; 12929 mouseEvent(false, wParam); 12930 break; 12931 case WM_LBUTTONDOWN: 12932 case WM_LBUTTONDBLCLK: 12933 mouse.type = cast(MouseEventType) 1; 12934 mouse.button = MouseButton.left; 12935 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 12936 mouseEvent(false, wParam); 12937 break; 12938 case WM_LBUTTONUP: 12939 mouse.type = cast(MouseEventType) 2; 12940 mouse.button = MouseButton.left; 12941 mouseEvent(false, wParam); 12942 break; 12943 case WM_RBUTTONDOWN: 12944 case WM_RBUTTONDBLCLK: 12945 mouse.type = cast(MouseEventType) 1; 12946 mouse.button = MouseButton.right; 12947 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 12948 mouseEvent(false, wParam); 12949 break; 12950 case WM_RBUTTONUP: 12951 mouse.type = cast(MouseEventType) 2; 12952 mouse.button = MouseButton.right; 12953 mouseEvent(false, wParam); 12954 break; 12955 case WM_MBUTTONDOWN: 12956 case WM_MBUTTONDBLCLK: 12957 mouse.type = cast(MouseEventType) 1; 12958 mouse.button = MouseButton.middle; 12959 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 12960 mouseEvent(false, wParam); 12961 break; 12962 case WM_MBUTTONUP: 12963 mouse.type = cast(MouseEventType) 2; 12964 mouse.button = MouseButton.middle; 12965 mouseEvent(false, wParam); 12966 break; 12967 case WM_XBUTTONDOWN: 12968 case WM_XBUTTONDBLCLK: 12969 mouse.type = cast(MouseEventType) 1; 12970 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12971 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 12972 mouseEvent(false, wParam); 12973 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 12974 case WM_XBUTTONUP: 12975 mouse.type = cast(MouseEventType) 2; 12976 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12977 mouseEvent(false, wParam); 12978 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 12979 12980 default: return 1; 12981 } 12982 return 0; 12983 } 12984 12985 HWND hwnd; 12986 private int oldWidth; 12987 private int oldHeight; 12988 private bool inSizeMove; 12989 12990 /++ 12991 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. 12992 12993 History: 12994 Added November 23, 2021 12995 12996 Not fully stable, may be moved out of the impl struct. 12997 12998 Default value changed to `true` on February 15, 2021 12999 +/ 13000 bool doLiveResizing = true; 13001 13002 package int bmpWidth; 13003 package int bmpHeight; 13004 13005 // the extern(Windows) wndproc should just forward to this 13006 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 13007 try { 13008 assert(hwnd is this.hwnd); 13009 13010 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 13011 switch(msg) { 13012 case WM_MENUCHAR: // menu active but key not associated with a thing. 13013 // you would ideally use this for like a search function but sdpy not that ideally designed. alas. 13014 // The main things we can do are select, execute, close, or ignore 13015 // the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to 13016 // the user. This can be a bit annoying for sdpy things so instead im overriding and setting it 13017 // to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy 13018 // that's the lesser bad choice rn. Can always override by returning true in triggerEvents.... 13019 13020 // returns the value in the *high order word* of the return value 13021 // hence the << 16 13022 return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user 13023 case WM_SETCURSOR: 13024 if(cast(HWND) wParam !is hwnd) 13025 return 0; // further processing elsewhere 13026 13027 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 13028 SetCursor(this.curHidden > 0 ? null : currentCursor); 13029 return 1; 13030 } else { 13031 return DefWindowProc(hwnd, msg, wParam, lParam); 13032 } 13033 //break; 13034 13035 case WM_CLOSE: 13036 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 13037 break; 13038 case WM_DESTROY: 13039 if (this.visibilityChanged !is null && this._visible) this.visibilityChanged(false); 13040 13041 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 13042 SimpleWindow.nativeMapping.remove(hwnd); 13043 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 13044 13045 bool anyImportant = false; 13046 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 13047 if(w.beingOpenKeepsAppOpen) { 13048 anyImportant = true; 13049 break; 13050 } 13051 if(!anyImportant) { 13052 PostQuitMessage(0); 13053 } 13054 break; 13055 case 0x02E0 /*WM_DPICHANGED*/: 13056 this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs 13057 13058 RECT* prcNewWindow = cast(RECT*)lParam; 13059 // docs say this is the recommended position and we should honor it 13060 SetWindowPos(hwnd, 13061 null, 13062 prcNewWindow.left, 13063 prcNewWindow.top, 13064 prcNewWindow.right - prcNewWindow.left, 13065 prcNewWindow.bottom - prcNewWindow.top, 13066 SWP_NOZORDER | SWP_NOACTIVATE); 13067 13068 // doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp 13069 // im not sure it is completely correct 13070 // but without it the tabs and such do look weird as things change. 13071 if(SystemParametersInfoForDpi) { 13072 LOGFONT lfText; 13073 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_); 13074 HFONT hFontNew = CreateFontIndirect(&lfText); 13075 if (hFontNew) 13076 { 13077 //DeleteObject(hFontOld); 13078 static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) { 13079 SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0)); 13080 return TRUE; 13081 } 13082 EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew); 13083 } 13084 } 13085 13086 if(this.onDpiChanged) 13087 this.onDpiChanged(); 13088 break; 13089 case WM_ENTERIDLE: 13090 // when a menu is up, it stops normal event processing (modal message loop) 13091 // but this at least gives us a chance to SOMETIMES catch up 13092 // FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it. 13093 SimpleWindow.processAllCustomEvents; 13094 SimpleWindow.processAllCustomEvents; 13095 SleepEx(0, true); 13096 break; 13097 case WM_SIZE: 13098 if(wParam == 1 /* SIZE_MINIMIZED */) 13099 break; 13100 _width = LOWORD(lParam); 13101 _height = HIWORD(lParam); 13102 13103 // I want to avoid tearing in the windows (my code is inefficient 13104 // so this is a hack around that) so while sizing, we don't trigger, 13105 // but we do want to trigger on events like mazimize. 13106 if(!inSizeMove || doLiveResizing) 13107 goto size_changed; 13108 break; 13109 /+ 13110 case WM_SIZING: 13111 writeln("size"); 13112 break; 13113 +/ 13114 // I don't like the tearing I get when redrawing on WM_SIZE 13115 // (I know there's other ways to fix that but I don't like that behavior anyway) 13116 // so instead it is going to redraw only at the end of a size. 13117 case 0x0231: /* WM_ENTERSIZEMOVE */ 13118 inSizeMove = true; 13119 break; 13120 case 0x0232: /* WM_EXITSIZEMOVE */ 13121 inSizeMove = false; 13122 13123 size_changed: 13124 13125 // nothing relevant changed, don't bother redrawing 13126 if(oldWidth == _width && oldHeight == _height) { 13127 if(msg == 0x0232) 13128 goto finalize_resize; 13129 break; 13130 } 13131 13132 // note: OpenGL windows don't use a backing bmp, so no need to change them 13133 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 13134 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 13135 // gotta get the double buffer bmp to match the window 13136 // FIXME: could this be more efficient? it never relinquishes a large bitmap 13137 13138 // if it is auto-scaled, we keep the backing bitmap the same size all the time 13139 if(resizability != Resizability.automaticallyScaleIfPossible) 13140 if(_width > bmpWidth || _height > bmpHeight) { 13141 auto hdc = GetDC(hwnd); 13142 auto oldBuffer = buffer; 13143 buffer = CreateCompatibleBitmap(hdc, _width, _height); 13144 13145 auto hdcBmp = CreateCompatibleDC(hdc); 13146 auto oldBmp = SelectObject(hdcBmp, buffer); 13147 13148 auto hdcOldBmp = CreateCompatibleDC(hdc); 13149 auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer); 13150 13151 /+ 13152 RECT r; 13153 r.left = 0; 13154 r.top = 0; 13155 r.right = width; 13156 r.bottom = height; 13157 auto c = Color.green; 13158 auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 13159 FillRect(hdcBmp, &r, brush); 13160 DeleteObject(brush); 13161 +/ 13162 13163 BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY); 13164 13165 bmpWidth = _width; 13166 bmpHeight = _height; 13167 13168 SelectObject(hdcOldBmp, oldOldBmp); 13169 DeleteDC(hdcOldBmp); 13170 13171 SelectObject(hdcBmp, oldBmp); 13172 DeleteDC(hdcBmp); 13173 13174 ReleaseDC(hwnd, hdc); 13175 13176 DeleteObject(oldBuffer); 13177 } 13178 } 13179 13180 updateOpenglViewportIfNeeded(_width, _height); 13181 13182 if(resizability != Resizability.automaticallyScaleIfPossible) 13183 if(windowResized !is null) 13184 windowResized(_width, _height); 13185 13186 /+ 13187 if(inSizeMove) { 13188 // SimpleWindow.processAllCustomEvents(); 13189 // SimpleWindow.processAllCustomEvents(); 13190 13191 //RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 13192 //sdpyPrintDebugString("redraw b"); 13193 } else { 13194 +/ { 13195 finalize_resize: 13196 // when it is all done, make sure everything is freshly drawn or there might be 13197 // weird bugs left. 13198 SimpleWindow.processAllCustomEvents(); 13199 SimpleWindow.processAllCustomEvents(); 13200 13201 RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 13202 // sdpyPrintDebugString("redraw"); 13203 } 13204 13205 oldWidth = this._width; 13206 oldHeight = this._height; 13207 break; 13208 case WM_ERASEBKGND: 13209 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 13210 if (!this._visibleForTheFirstTimeCalled) { 13211 this._visibleForTheFirstTimeCalled = true; 13212 if (this.visibleForTheFirstTime !is null) { 13213 this.visibleForTheFirstTime(); 13214 } 13215 } 13216 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 13217 version(without_opengl) {} else { 13218 if (openglMode == OpenGlOptions.yes) return 1; 13219 } 13220 // call windows default handler, so it can paint standard controls 13221 goto default; 13222 case WM_CTLCOLORBTN: 13223 case WM_CTLCOLORSTATIC: 13224 SetBkMode(cast(HDC) wParam, TRANSPARENT); 13225 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 13226 GetSysColorBrush(COLOR_3DFACE); 13227 //break; 13228 case WM_SHOWWINDOW: 13229 auto before = this._visible; 13230 this._visible = (wParam != 0); 13231 if (!this._visibleForTheFirstTimeCalled && this._visible) { 13232 this._visibleForTheFirstTimeCalled = true; 13233 if (this.visibleForTheFirstTime !is null) { 13234 this.visibleForTheFirstTime(); 13235 } 13236 } 13237 if (this.visibilityChanged !is null && this._visible != before) this.visibilityChanged(this._visible); 13238 break; 13239 case WM_PAINT: { 13240 if (!this._visibleForTheFirstTimeCalled) { 13241 this._visibleForTheFirstTimeCalled = true; 13242 if (this.visibleForTheFirstTime !is null) { 13243 this.visibleForTheFirstTime(); 13244 } 13245 } 13246 13247 BITMAP bm; 13248 PAINTSTRUCT ps; 13249 13250 HDC hdc = BeginPaint(hwnd, &ps); 13251 13252 if(openglMode == OpenGlOptions.no) { 13253 13254 HDC hdcMem = CreateCompatibleDC(hdc); 13255 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 13256 13257 GetObject(buffer, bm.sizeof, &bm); 13258 13259 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 13260 if(resizability == Resizability.automaticallyScaleIfPossible) 13261 StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 13262 else 13263 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 13264 //BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY); 13265 13266 SelectObject(hdcMem, hbmOld); 13267 DeleteDC(hdcMem); 13268 EndPaint(hwnd, &ps); 13269 } else { 13270 EndPaint(hwnd, &ps); 13271 version(without_opengl) {} else 13272 redrawOpenGlSceneSoon(); 13273 } 13274 } break; 13275 default: 13276 return DefWindowProc(hwnd, msg, wParam, lParam); 13277 } 13278 return 0; 13279 13280 } 13281 catch(Throwable t) { 13282 sdpyPrintDebugString(t.toString); 13283 return 0; 13284 } 13285 } 13286 } 13287 13288 mixin template NativeImageImplementation() { 13289 HBITMAP handle; 13290 ubyte* rawData; 13291 13292 final: 13293 13294 Color getPixel(int x, int y) @system { 13295 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 13296 // remember, bmps are upside down 13297 auto offset = itemsPerLine * (height - y - 1) + x * 3; 13298 13299 Color c; 13300 if(enableAlpha) 13301 c.a = rawData[offset + 3]; 13302 else 13303 c.a = 255; 13304 c.b = rawData[offset + 0]; 13305 c.g = rawData[offset + 1]; 13306 c.r = rawData[offset + 2]; 13307 c.unPremultiply(); 13308 return c; 13309 } 13310 13311 void setPixel(int x, int y, Color c) @system { 13312 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 13313 // remember, bmps are upside down 13314 auto offset = itemsPerLine * (height - y - 1) + x * 3; 13315 13316 if(enableAlpha) 13317 c.premultiply(); 13318 13319 rawData[offset + 0] = c.b; 13320 rawData[offset + 1] = c.g; 13321 rawData[offset + 2] = c.r; 13322 if(enableAlpha) 13323 rawData[offset + 3] = c.a; 13324 } 13325 13326 void convertToRgbaBytes(ubyte[] where) @system { 13327 assert(where.length == this.width * this.height * 4); 13328 13329 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 13330 int idx = 0; 13331 int offset = itemsPerLine * (height - 1); 13332 // remember, bmps are upside down 13333 for(int y = height - 1; y >= 0; y--) { 13334 auto offsetStart = offset; 13335 for(int x = 0; x < width; x++) { 13336 where[idx + 0] = rawData[offset + 2]; // r 13337 where[idx + 1] = rawData[offset + 1]; // g 13338 where[idx + 2] = rawData[offset + 0]; // b 13339 if(enableAlpha) { 13340 where[idx + 3] = rawData[offset + 3]; // a 13341 unPremultiplyRgba(where[idx .. idx + 4]); 13342 offset++; 13343 } else 13344 where[idx + 3] = 255; // a 13345 idx += 4; 13346 offset += 3; 13347 } 13348 13349 offset = offsetStart - itemsPerLine; 13350 } 13351 } 13352 13353 void setFromRgbaBytes(in ubyte[] what) @system { 13354 assert(what.length == this.width * this.height * 4); 13355 13356 auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 13357 int idx = 0; 13358 int offset = itemsPerLine * (height - 1); 13359 // remember, bmps are upside down 13360 for(int y = height - 1; y >= 0; y--) { 13361 auto offsetStart = offset; 13362 for(int x = 0; x < width; x++) { 13363 if(enableAlpha) { 13364 auto a = what[idx + 3]; 13365 13366 rawData[offset + 2] = (a * what[idx + 0]) / 255; // r 13367 rawData[offset + 1] = (a * what[idx + 1]) / 255; // g 13368 rawData[offset + 0] = (a * what[idx + 2]) / 255; // b 13369 rawData[offset + 3] = a; // a 13370 //premultiplyBgra(rawData[offset .. offset + 4]); 13371 offset++; 13372 } else { 13373 rawData[offset + 2] = what[idx + 0]; // r 13374 rawData[offset + 1] = what[idx + 1]; // g 13375 rawData[offset + 0] = what[idx + 2]; // b 13376 } 13377 idx += 4; 13378 offset += 3; 13379 } 13380 13381 offset = offsetStart - itemsPerLine; 13382 } 13383 } 13384 13385 13386 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 13387 BITMAPINFO infoheader; 13388 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 13389 infoheader.bmiHeader.biWidth = width; 13390 infoheader.bmiHeader.biHeight = height; 13391 infoheader.bmiHeader.biPlanes = 1; 13392 infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24; 13393 infoheader.bmiHeader.biCompression = BI_RGB; 13394 13395 handle = CreateDIBSection( 13396 null, 13397 &infoheader, 13398 DIB_RGB_COLORS, 13399 cast(void**) &rawData, 13400 null, 13401 0); 13402 if(handle is null) 13403 throw new WindowsApiException("create image failed", GetLastError()); 13404 13405 } 13406 13407 void dispose() { 13408 DeleteObject(handle); 13409 } 13410 } 13411 13412 enum KEY_ESCAPE = 27; 13413 } 13414 13415 version(Emscripten) { 13416 alias int delegate(void*) NativeEventHandler; 13417 alias void* NativeWindowHandle; 13418 13419 mixin template NativeSimpleWindowImplementation() { } 13420 mixin template NativeScreenPainterImplementation() { } 13421 mixin template NativeImageImplementation() { } 13422 } 13423 13424 version(X11) { 13425 /// This is the default font used. You might change this before doing anything else with 13426 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 13427 /// for cross-platform compatibility. 13428 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 13429 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 13430 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 13431 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 13432 13433 alias int delegate(XEvent) NativeEventHandler; 13434 alias Window NativeWindowHandle; 13435 13436 enum KEY_ESCAPE = 9; 13437 13438 mixin template NativeScreenPainterImplementation() { 13439 Display* display; 13440 Drawable d; 13441 Drawable destiny; 13442 13443 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 13444 GC gc; 13445 13446 __gshared bool fontAttempted; 13447 13448 __gshared XFontStruct* defaultfont; 13449 __gshared XFontSet defaultfontset; 13450 13451 XFontStruct* font; 13452 XFontSet fontset; 13453 13454 void create(PaintingHandle window) { 13455 this.display = XDisplayConnection.get(); 13456 13457 Drawable buffer = None; 13458 if(auto sw = cast(SimpleWindow) this.window) { 13459 buffer = sw.impl.buffer; 13460 this.destiny = cast(Drawable) window; 13461 } else { 13462 buffer = cast(Drawable) window; 13463 this.destiny = None; 13464 } 13465 13466 this.d = cast(Drawable) buffer; 13467 13468 auto dgc = DefaultGC(display, DefaultScreen(display)); 13469 13470 this.gc = XCreateGC(display, d, 0, null); 13471 13472 XCopyGC(display, dgc, 0xffffffff, this.gc); 13473 13474 ensureDefaultFontLoaded(); 13475 13476 font = defaultfont; 13477 fontset = defaultfontset; 13478 13479 if(font) { 13480 XSetFont(display, gc, font.fid); 13481 } 13482 } 13483 13484 static void ensureDefaultFontLoaded() { 13485 if(!fontAttempted) { 13486 auto display = XDisplayConnection.get; 13487 auto font = XLoadQueryFont(display, xfontstr.ptr); 13488 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 13489 if(font is null) { 13490 xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"; 13491 font = XLoadQueryFont(display, xfontstr.ptr); 13492 } 13493 13494 char** lol; 13495 int lol2; 13496 char* lol3; 13497 auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 13498 13499 fontAttempted = true; 13500 13501 defaultfont = font; 13502 defaultfontset = fontset; 13503 } 13504 } 13505 13506 arsd.color.Rectangle _clipRectangle; 13507 void setClipRectangle(int x, int y, int width, int height) { 13508 auto old = _clipRectangle; 13509 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 13510 if(old == _clipRectangle) 13511 return; 13512 13513 if(width == 0 || height == 0) { 13514 XSetClipMask(display, gc, None); 13515 13516 if(xrenderPicturePainter) { 13517 13518 XRectangle[1] rects; 13519 rects[0] = XRectangle(short.min, short.min, short.max, short.max); 13520 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 13521 } 13522 13523 version(with_xft) { 13524 if(xftDraw is null) 13525 return; 13526 XftDrawSetClip(xftDraw, null); 13527 } 13528 } else { 13529 XRectangle[1] rects; 13530 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 13531 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 13532 13533 if(xrenderPicturePainter) 13534 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 13535 13536 version(with_xft) { 13537 if(xftDraw is null) 13538 return; 13539 XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1); 13540 } 13541 } 13542 } 13543 13544 version(with_xft) { 13545 XftFont* xftFont; 13546 XftDraw* xftDraw; 13547 13548 XftColor xftColor; 13549 13550 void updateXftColor() { 13551 if(xftFont is null) 13552 return; 13553 13554 // not bothering with XftColorFree since p sure i don't need it on 24 bit displays.... 13555 XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255); 13556 13557 XftColorAllocValue( 13558 display, 13559 DefaultVisual(display, DefaultScreen(display)), 13560 DefaultColormap(display, 0), 13561 &colorIn, 13562 &xftColor 13563 ); 13564 } 13565 } 13566 13567 void enableXftDraw() { 13568 if(xftDraw is null) { 13569 xftDraw = XftDrawCreate( 13570 display, 13571 d, 13572 DefaultVisual(display, DefaultScreen(display)), 13573 DefaultColormap(display, 0) 13574 ); 13575 13576 updateXftColor(); 13577 } 13578 } 13579 13580 private OperatingSystemFont _activeFont; 13581 void setFont(OperatingSystemFont font) { 13582 _activeFont = font; 13583 version(with_xft) { 13584 if(font && font.isXft && font.xftFont) 13585 this.xftFont = font.xftFont; 13586 else 13587 this.xftFont = null; 13588 13589 if(this.xftFont) { 13590 enableXftDraw(); 13591 return; 13592 } 13593 } 13594 13595 if(font && font.font) { 13596 this.font = font.font; 13597 this.fontset = font.fontset; 13598 XSetFont(display, gc, font.font.fid); 13599 } else { 13600 this.font = defaultfont; 13601 this.fontset = defaultfontset; 13602 } 13603 13604 } 13605 13606 private Picture xrenderPicturePainter; 13607 13608 bool manualInvalidations; 13609 void invalidateRect(Rectangle invalidRect) { 13610 // FIXME if manualInvalidations 13611 } 13612 13613 void dispose() { 13614 this.rasterOp = RasterOp.normal; 13615 13616 if(xrenderPicturePainter) { 13617 XRenderFreePicture(display, xrenderPicturePainter); 13618 xrenderPicturePainter = None; 13619 } 13620 13621 // FIXME: this.window.width/height is probably wrong 13622 13623 // src x,y then dest x, y 13624 if(destiny != None) { 13625 // FIXME: if manual invalidations we can actually only copy some of the area. 13626 // if(manualInvalidations) 13627 XSetClipMask(display, gc, None); 13628 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 13629 } 13630 13631 XFreeGC(display, gc); 13632 13633 version(with_xft) 13634 if(xftDraw) { 13635 XftDrawDestroy(xftDraw); 13636 xftDraw = null; 13637 } 13638 13639 /+ 13640 // this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource. 13641 if(font && font !is defaultfont) { 13642 XFreeFont(display, font); 13643 font = null; 13644 } 13645 if(fontset && fontset !is defaultfontset) { 13646 XFreeFontSet(display, fontset); 13647 fontset = null; 13648 } 13649 +/ 13650 XFlush(display); 13651 13652 if(window.paintingFinishedDg !is null) 13653 window.paintingFinishedDg()(); 13654 } 13655 13656 bool backgroundIsNotTransparent = true; 13657 bool foregroundIsNotTransparent = true; 13658 13659 bool _penInitialized = false; 13660 Pen _activePen; 13661 13662 Color _outlineColor; 13663 Color _fillColor; 13664 13665 @property void pen(Pen p) { 13666 if(_penInitialized && p == _activePen) { 13667 return; 13668 } 13669 _penInitialized = true; 13670 _activePen = p; 13671 _outlineColor = p.color; 13672 13673 int style; 13674 13675 byte dashLength; 13676 13677 final switch(p.style) { 13678 case Pen.Style.Solid: 13679 style = 0 /*LineSolid*/; 13680 break; 13681 case Pen.Style.Dashed: 13682 style = 1 /*LineOnOffDash*/; 13683 dashLength = 4; 13684 break; 13685 case Pen.Style.Dotted: 13686 style = 1 /*LineOnOffDash*/; 13687 dashLength = 1; 13688 break; 13689 } 13690 13691 XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0); 13692 if(dashLength) 13693 XSetDashes(display, gc, 0, &dashLength, 1); 13694 13695 if(p.color.a == 0) { 13696 foregroundIsNotTransparent = false; 13697 return; 13698 } 13699 13700 foregroundIsNotTransparent = true; 13701 13702 XSetForeground(display, gc, colorToX(p.color, display)); 13703 13704 version(with_xft) 13705 updateXftColor(); 13706 } 13707 13708 RasterOp _currentRasterOp; 13709 bool _currentRasterOpInitialized = false; 13710 @property void rasterOp(RasterOp op) { 13711 if(_currentRasterOpInitialized && _currentRasterOp == op) 13712 return; 13713 _currentRasterOp = op; 13714 _currentRasterOpInitialized = true; 13715 int mode; 13716 final switch(op) { 13717 case RasterOp.normal: 13718 mode = GXcopy; 13719 break; 13720 case RasterOp.xor: 13721 mode = GXxor; 13722 break; 13723 } 13724 XSetFunction(display, gc, mode); 13725 } 13726 13727 13728 bool _fillColorInitialized = false; 13729 13730 @property void fillColor(Color c) { 13731 if(_fillColorInitialized && _fillColor == c) 13732 return; // already good, no need to waste time calling it 13733 _fillColor = c; 13734 _fillColorInitialized = true; 13735 if(c.a == 0) { 13736 backgroundIsNotTransparent = false; 13737 return; 13738 } 13739 13740 backgroundIsNotTransparent = true; 13741 13742 XSetBackground(display, gc, colorToX(c, display)); 13743 13744 } 13745 13746 void swapColors() { 13747 auto tmp = _fillColor; 13748 fillColor = _outlineColor; 13749 auto newPen = _activePen; 13750 newPen.color = tmp; 13751 pen(newPen); 13752 } 13753 13754 uint colorToX(Color c, Display* display) { 13755 auto visual = DefaultVisual(display, DefaultScreen(display)); 13756 import core.bitop; 13757 uint color = 0; 13758 { 13759 auto startBit = bsf(visual.red_mask); 13760 auto lastBit = bsr(visual.red_mask); 13761 auto r = cast(uint) c.r; 13762 r >>= 7 - (lastBit - startBit); 13763 r <<= startBit; 13764 color |= r; 13765 } 13766 { 13767 auto startBit = bsf(visual.green_mask); 13768 auto lastBit = bsr(visual.green_mask); 13769 auto g = cast(uint) c.g; 13770 g >>= 7 - (lastBit - startBit); 13771 g <<= startBit; 13772 color |= g; 13773 } 13774 { 13775 auto startBit = bsf(visual.blue_mask); 13776 auto lastBit = bsr(visual.blue_mask); 13777 auto b = cast(uint) c.b; 13778 b >>= 7 - (lastBit - startBit); 13779 b <<= startBit; 13780 color |= b; 13781 } 13782 13783 13784 13785 return color; 13786 } 13787 13788 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 13789 // source x, source y 13790 if(ix >= i.width) return; 13791 if(iy >= i.height) return; 13792 if(ix + w > i.width) w = i.width - ix; 13793 if(iy + h > i.height) h = i.height - iy; 13794 if(i.usingXshm) 13795 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 13796 else 13797 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 13798 } 13799 13800 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 13801 if(s.enableAlpha) { 13802 // the Sprite must be created first, meaning if we're here, XRender is already loaded 13803 if(this.xrenderPicturePainter == None) { 13804 XRenderPictureAttributes attrs; 13805 // FIXME: I can prolly reuse this as long as the pixmap itself is valid. 13806 xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs); 13807 13808 // need to initialize the clip 13809 XRectangle[1] rects; 13810 rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height); 13811 13812 if(_clipRectangle != Rectangle.init) 13813 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 13814 } 13815 13816 XRenderComposite( 13817 display, 13818 3, // PicOpOver 13819 s.xrenderPicture, 13820 None, 13821 this.xrenderPicturePainter, 13822 ix, 13823 iy, 13824 0, 13825 0, 13826 x, 13827 y, 13828 w ? w : s.width, 13829 h ? h : s.height 13830 ); 13831 } else { 13832 XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y); 13833 } 13834 } 13835 13836 int fontHeight() { 13837 version(with_xft) 13838 if(xftFont !is null) 13839 return xftFont.height; 13840 if(font) 13841 return font.max_bounds.ascent + font.max_bounds.descent; 13842 return 12; // pretty common default... 13843 } 13844 13845 int textWidth(in char[] line) { 13846 version(with_xft) 13847 if(xftFont) { 13848 if(line.length == 0) 13849 return 0; 13850 XGlyphInfo extents; 13851 XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents); 13852 return extents.width; 13853 } 13854 13855 if(fontset) { 13856 if(line.length == 0) 13857 return 0; 13858 XRectangle rect; 13859 Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect); 13860 13861 return rect.width; 13862 } 13863 13864 if(font) 13865 // FIXME: unicode 13866 return XTextWidth( font, line.ptr, cast(int) line.length); 13867 else 13868 return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 13869 } 13870 13871 Size textSize(in char[] text) { 13872 auto maxWidth = 0; 13873 auto lineHeight = fontHeight; 13874 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 13875 foreach(line; text.split('\n')) { 13876 int textWidth = this.textWidth(line); 13877 if(textWidth > maxWidth) 13878 maxWidth = textWidth; 13879 h += lineHeight + 4; 13880 } 13881 return Size(maxWidth, h); 13882 } 13883 13884 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 13885 const(char)[] text; 13886 version(with_xft) 13887 if(xftFont) { 13888 text = originalText; 13889 goto loaded; 13890 } 13891 13892 if(fontset) 13893 text = originalText; 13894 else { 13895 text.reserve(originalText.length); 13896 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 13897 // then strip the rest so there isn't garbage 13898 foreach(dchar ch; originalText) 13899 if(ch < 256) 13900 text ~= cast(ubyte) ch; 13901 else 13902 text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space 13903 } 13904 loaded: 13905 if(text.length == 0) 13906 return; 13907 13908 // FIXME: should we clip it to the bounding box? 13909 int textHeight = fontHeight; 13910 13911 auto lines = text.split('\n'); 13912 13913 const lineHeight = textHeight; 13914 textHeight *= lines.length; 13915 13916 int cy = y; 13917 13918 if(alignment & TextAlignment.VerticalBottom) { 13919 if(y2 <= 0) 13920 return; 13921 auto h = y2 - y; 13922 if(h > textHeight) { 13923 cy += h - textHeight; 13924 cy -= lineHeight / 2; 13925 } 13926 } else if(alignment & TextAlignment.VerticalCenter) { 13927 if(y2 <= 0) 13928 return; 13929 auto h = y2 - y; 13930 if(textHeight < h) { 13931 cy += (h - textHeight) / 2; 13932 //cy -= lineHeight / 4; 13933 } 13934 } 13935 13936 foreach(line; text.split('\n')) { 13937 int textWidth = this.textWidth(line); 13938 13939 int px = x, py = cy; 13940 13941 if(alignment & TextAlignment.Center) { 13942 if(x2 <= 0) 13943 return; 13944 auto w = x2 - x; 13945 if(w > textWidth) 13946 px += (w - textWidth) / 2; 13947 } else if(alignment & TextAlignment.Right) { 13948 if(x2 <= 0) 13949 return; 13950 auto pos = x2 - textWidth; 13951 if(pos > x) 13952 px = pos; 13953 } 13954 13955 version(with_xft) 13956 if(xftFont) { 13957 XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length); 13958 13959 goto carry_on; 13960 } 13961 13962 if(fontset) 13963 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13964 else 13965 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13966 carry_on: 13967 cy += lineHeight + 4; 13968 } 13969 } 13970 13971 void drawPixel(int x, int y) { 13972 XDrawPoint(display, d, gc, x, y); 13973 } 13974 13975 // The basic shapes, outlined 13976 13977 void drawLine(int x1, int y1, int x2, int y2) { 13978 if(foregroundIsNotTransparent) 13979 XDrawLine(display, d, gc, x1, y1, x2, y2); 13980 } 13981 13982 void drawRectangle(int x, int y, int width, int height) { 13983 if(backgroundIsNotTransparent) { 13984 swapColors(); 13985 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 13986 swapColors(); 13987 } 13988 // 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 13989 if(foregroundIsNotTransparent) 13990 XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2); 13991 } 13992 13993 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 13994 int[4] radii = borderRadius; 13995 auto r = Rectangle(upperLeft, lowerRight); 13996 13997 if(backgroundIsNotTransparent) { 13998 swapColors(); 13999 // FIXME these overlap and thus draw the pixels multiple times 14000 XFillRectangle(display, d, gc, r.left, r.top + borderRadius/2, r.width, r.height - borderRadius); 14001 XFillRectangle(display, d, gc, r.left + borderRadius/2, r.top, r.width - borderRadius, r.height); 14002 swapColors(); 14003 } 14004 14005 drawLine(r.left + borderRadius / 2, r.top, r.right - borderRadius / 2, r.top); 14006 drawLine(r.left + borderRadius / 2, r.bottom-1, r.right - borderRadius / 2, r.bottom-1); 14007 drawLine(r.left, r.top + borderRadius / 2, r.left, r.bottom - borderRadius / 2); 14008 drawLine(r.right - 1, r.top + borderRadius / 2, r.right - 1, r.bottom - borderRadius / 2); 14009 14010 //drawRectangle(r.left + borderRadius/2, r.top, r.width - borderRadius, r.height); 14011 14012 drawArc(r.upperLeft.x, r.upperLeft.y, radii[0], radii[0], 90*64, 90*64); 14013 drawArc(r.upperRight.x - radii[1], r.upperRight.y, radii[1] - 1, radii[1], 0*64, 90*64); 14014 drawArc(r.lowerLeft.x, r.lowerLeft.y - radii[2], radii[2], radii[2] - 1, 180*64, 90*64); 14015 drawArc(r.lowerRight.x - radii[3], r.lowerRight.y - radii[3], radii[3] - 1, radii[3] - 1, 270*64, 90*64); 14016 } 14017 14018 14019 /// Arguments are the points of the bounding rectangle 14020 void drawEllipse(int x1, int y1, int x2, int y2) { 14021 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 14022 } 14023 14024 // NOTE: start and finish are in units of degrees * 64 14025 void drawArc(int x1, int y1, int width, int height, int start, int length) { 14026 if(backgroundIsNotTransparent) { 14027 swapColors(); 14028 XFillArc(display, d, gc, x1, y1, width, height, start, length); 14029 swapColors(); 14030 } 14031 if(foregroundIsNotTransparent) { 14032 XDrawArc(display, d, gc, x1, y1, width, height, start, length); 14033 14034 // Windows draws the straight lines on the edges too so FIXME sort of 14035 } 14036 } 14037 14038 void drawPolygon(Point[] vertexes) { 14039 XPoint[16] pointsBuffer; 14040 XPoint[] points; 14041 if(vertexes.length <= pointsBuffer.length) 14042 points = pointsBuffer[0 .. vertexes.length]; 14043 else 14044 points.length = vertexes.length; 14045 14046 foreach(i, p; vertexes) { 14047 points[i].x = cast(short) p.x; 14048 points[i].y = cast(short) p.y; 14049 } 14050 14051 if(backgroundIsNotTransparent) { 14052 swapColors(); 14053 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 14054 swapColors(); 14055 } 14056 if(foregroundIsNotTransparent) { 14057 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 14058 } 14059 } 14060 } 14061 14062 /* XRender { */ 14063 14064 struct XRenderColor { 14065 ushort red; 14066 ushort green; 14067 ushort blue; 14068 ushort alpha; 14069 } 14070 14071 alias Picture = XID; 14072 alias PictFormat = XID; 14073 14074 struct XGlyphInfo { 14075 ushort width; 14076 ushort height; 14077 short x; 14078 short y; 14079 short xOff; 14080 short yOff; 14081 } 14082 14083 struct XRenderDirectFormat { 14084 short red; 14085 short redMask; 14086 short green; 14087 short greenMask; 14088 short blue; 14089 short blueMask; 14090 short alpha; 14091 short alphaMask; 14092 } 14093 14094 struct XRenderPictFormat { 14095 PictFormat id; 14096 int type; 14097 int depth; 14098 XRenderDirectFormat direct; 14099 Colormap colormap; 14100 } 14101 14102 enum PictFormatID = (1 << 0); 14103 enum PictFormatType = (1 << 1); 14104 enum PictFormatDepth = (1 << 2); 14105 enum PictFormatRed = (1 << 3); 14106 enum PictFormatRedMask =(1 << 4); 14107 enum PictFormatGreen = (1 << 5); 14108 enum PictFormatGreenMask=(1 << 6); 14109 enum PictFormatBlue = (1 << 7); 14110 enum PictFormatBlueMask =(1 << 8); 14111 enum PictFormatAlpha = (1 << 9); 14112 enum PictFormatAlphaMask=(1 << 10); 14113 enum PictFormatColormap =(1 << 11); 14114 14115 struct XRenderPictureAttributes { 14116 int repeat; 14117 Picture alpha_map; 14118 int alpha_x_origin; 14119 int alpha_y_origin; 14120 int clip_x_origin; 14121 int clip_y_origin; 14122 Pixmap clip_mask; 14123 Bool graphics_exposures; 14124 int subwindow_mode; 14125 int poly_edge; 14126 int poly_mode; 14127 Atom dither; 14128 Bool component_alpha; 14129 } 14130 14131 alias int XFixed; 14132 14133 struct XPointFixed { 14134 XFixed x, y; 14135 } 14136 14137 struct XCircle { 14138 XFixed x; 14139 XFixed y; 14140 XFixed radius; 14141 } 14142 14143 struct XTransform { 14144 XFixed[3][3] matrix; 14145 } 14146 14147 struct XFilters { 14148 int nfilter; 14149 char **filter; 14150 int nalias; 14151 short *alias_; 14152 } 14153 14154 struct XIndexValue { 14155 c_ulong pixel; 14156 ushort red, green, blue, alpha; 14157 } 14158 14159 struct XAnimCursor { 14160 Cursor cursor; 14161 c_ulong delay; 14162 } 14163 14164 struct XLinearGradient { 14165 XPointFixed p1; 14166 XPointFixed p2; 14167 } 14168 14169 struct XRadialGradient { 14170 XCircle inner; 14171 XCircle outer; 14172 } 14173 14174 struct XConicalGradient { 14175 XPointFixed center; 14176 XFixed angle; /* in degrees */ 14177 } 14178 14179 enum PictStandardARGB32 = 0; 14180 enum PictStandardRGB24 = 1; 14181 enum PictStandardA8 = 2; 14182 enum PictStandardA4 = 3; 14183 enum PictStandardA1 = 4; 14184 enum PictStandardNUM = 5; 14185 14186 interface XRender { 14187 extern(C) @nogc: 14188 14189 Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep); 14190 14191 Status XRenderQueryVersion (Display *dpy, 14192 int *major_versionp, 14193 int *minor_versionp); 14194 14195 Status XRenderQueryFormats (Display *dpy); 14196 14197 int XRenderQuerySubpixelOrder (Display *dpy, int screen); 14198 14199 Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel); 14200 14201 XRenderPictFormat * 14202 XRenderFindVisualFormat (Display *dpy, const Visual *visual); 14203 14204 XRenderPictFormat * 14205 XRenderFindFormat (Display *dpy, 14206 c_ulong mask, 14207 const XRenderPictFormat *templ, 14208 int count); 14209 XRenderPictFormat * 14210 XRenderFindStandardFormat (Display *dpy, 14211 int format); 14212 14213 XIndexValue * 14214 XRenderQueryPictIndexValues(Display *dpy, 14215 const XRenderPictFormat *format, 14216 int *num); 14217 14218 Picture XRenderCreatePicture( 14219 Display *dpy, 14220 Drawable drawable, 14221 const XRenderPictFormat *format, 14222 c_ulong valuemask, 14223 const XRenderPictureAttributes *attributes); 14224 14225 void XRenderChangePicture (Display *dpy, 14226 Picture picture, 14227 c_ulong valuemask, 14228 const XRenderPictureAttributes *attributes); 14229 14230 void 14231 XRenderSetPictureClipRectangles (Display *dpy, 14232 Picture picture, 14233 int xOrigin, 14234 int yOrigin, 14235 const XRectangle *rects, 14236 int n); 14237 14238 void 14239 XRenderSetPictureClipRegion (Display *dpy, 14240 Picture picture, 14241 Region r); 14242 14243 void 14244 XRenderSetPictureTransform (Display *dpy, 14245 Picture picture, 14246 XTransform *transform); 14247 14248 void 14249 XRenderFreePicture (Display *dpy, 14250 Picture picture); 14251 14252 void 14253 XRenderComposite (Display *dpy, 14254 int op, 14255 Picture src, 14256 Picture mask, 14257 Picture dst, 14258 int src_x, 14259 int src_y, 14260 int mask_x, 14261 int mask_y, 14262 int dst_x, 14263 int dst_y, 14264 uint width, 14265 uint height); 14266 14267 14268 Picture XRenderCreateSolidFill (Display *dpy, 14269 const XRenderColor *color); 14270 14271 Picture XRenderCreateLinearGradient (Display *dpy, 14272 const XLinearGradient *gradient, 14273 const XFixed *stops, 14274 const XRenderColor *colors, 14275 int nstops); 14276 14277 Picture XRenderCreateRadialGradient (Display *dpy, 14278 const XRadialGradient *gradient, 14279 const XFixed *stops, 14280 const XRenderColor *colors, 14281 int nstops); 14282 14283 Picture XRenderCreateConicalGradient (Display *dpy, 14284 const XConicalGradient *gradient, 14285 const XFixed *stops, 14286 const XRenderColor *colors, 14287 int nstops); 14288 14289 14290 14291 Cursor 14292 XRenderCreateCursor (Display *dpy, 14293 Picture source, 14294 uint x, 14295 uint y); 14296 14297 XFilters * 14298 XRenderQueryFilters (Display *dpy, Drawable drawable); 14299 14300 void 14301 XRenderSetPictureFilter (Display *dpy, 14302 Picture picture, 14303 const char *filter, 14304 XFixed *params, 14305 int nparams); 14306 14307 Cursor 14308 XRenderCreateAnimCursor (Display *dpy, 14309 int ncursor, 14310 XAnimCursor *cursors); 14311 } 14312 14313 __gshared bool XRenderLibrarySuccessfullyLoaded = true; 14314 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary; 14315 14316 /* XRender } */ 14317 14318 /* Xrandr { */ 14319 14320 struct XRRMonitorInfo { 14321 Atom name; 14322 Bool primary; 14323 Bool automatic; 14324 int noutput; 14325 int x; 14326 int y; 14327 int width; 14328 int height; 14329 int mwidth; 14330 int mheight; 14331 /*RROutput*/ void *outputs; 14332 } 14333 14334 struct XRRScreenChangeNotifyEvent { 14335 int type; /* event base */ 14336 c_ulong serial; /* # of last request processed by server */ 14337 Bool send_event; /* true if this came from a SendEvent request */ 14338 Display *display; /* Display the event was read from */ 14339 Window window; /* window which selected for this event */ 14340 Window root; /* Root window for changed screen */ 14341 Time timestamp; /* when the screen change occurred */ 14342 Time config_timestamp; /* when the last configuration change */ 14343 ushort/*SizeID*/ size_index; 14344 ushort/*SubpixelOrder*/ subpixel_order; 14345 ushort/*Rotation*/ rotation; 14346 int width; 14347 int height; 14348 int mwidth; 14349 int mheight; 14350 } 14351 14352 enum RRScreenChangeNotify = 0; 14353 14354 enum RRScreenChangeNotifyMask = 1; 14355 14356 __gshared int xrrEventBase = -1; 14357 14358 14359 interface XRandr { 14360 extern(C) @nogc: 14361 Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return); 14362 Status XRRQueryVersion (Display *dpy, int *major_version_return, int *minor_version_return); 14363 14364 XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors); 14365 void XRRFreeMonitors(XRRMonitorInfo *monitors); 14366 14367 void XRRSelectInput(Display *dpy, Window window, int mask); 14368 } 14369 14370 __gshared bool XRandrLibrarySuccessfullyLoaded = true; 14371 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary; 14372 /* Xrandr } */ 14373 14374 /* Xft { */ 14375 14376 // actually freetype 14377 alias void FT_Face; 14378 14379 // actually fontconfig 14380 private alias FcBool = int; 14381 alias void FcCharSet; 14382 alias void FcPattern; 14383 alias void FcResult; 14384 enum FcEndian { FcEndianBig, FcEndianLittle } 14385 struct FcFontSet { 14386 int nfont; 14387 int sfont; 14388 FcPattern** fonts; 14389 } 14390 14391 // actually XRegion 14392 struct BOX { 14393 short x1, x2, y1, y2; 14394 } 14395 struct _XRegion { 14396 c_long size; 14397 c_long numRects; 14398 BOX* rects; 14399 BOX extents; 14400 } 14401 14402 alias Region = _XRegion*; 14403 14404 // ok actually Xft 14405 14406 struct XftFontInfo; 14407 14408 struct XftFont { 14409 int ascent; 14410 int descent; 14411 int height; 14412 int max_advance_width; 14413 FcCharSet* charset; 14414 FcPattern* pattern; 14415 } 14416 14417 struct XftDraw; 14418 14419 struct XftColor { 14420 c_ulong pixel; 14421 XRenderColor color; 14422 } 14423 14424 struct XftCharSpec { 14425 dchar ucs4; 14426 short x; 14427 short y; 14428 } 14429 14430 struct XftCharFontSpec { 14431 XftFont *font; 14432 dchar ucs4; 14433 short x; 14434 short y; 14435 } 14436 14437 struct XftGlyphSpec { 14438 uint glyph; 14439 short x; 14440 short y; 14441 } 14442 14443 struct XftGlyphFontSpec { 14444 XftFont *font; 14445 uint glyph; 14446 short x; 14447 short y; 14448 } 14449 14450 interface Xft { 14451 extern(C) @nogc pure: 14452 14453 Bool XftColorAllocName (Display *dpy, 14454 const Visual *visual, 14455 Colormap cmap, 14456 const char *name, 14457 XftColor *result); 14458 14459 Bool XftColorAllocValue (Display *dpy, 14460 Visual *visual, 14461 Colormap cmap, 14462 const XRenderColor *color, 14463 XftColor *result); 14464 14465 void XftColorFree (Display *dpy, 14466 Visual *visual, 14467 Colormap cmap, 14468 XftColor *color); 14469 14470 Bool XftDefaultHasRender (Display *dpy); 14471 14472 Bool XftDefaultSet (Display *dpy, FcPattern *defaults); 14473 14474 void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern); 14475 14476 XftDraw * XftDrawCreate (Display *dpy, 14477 Drawable drawable, 14478 Visual *visual, 14479 Colormap colormap); 14480 14481 XftDraw * XftDrawCreateBitmap (Display *dpy, 14482 Pixmap bitmap); 14483 14484 XftDraw * XftDrawCreateAlpha (Display *dpy, 14485 Pixmap pixmap, 14486 int depth); 14487 14488 void XftDrawChange (XftDraw *draw, 14489 Drawable drawable); 14490 14491 Display * XftDrawDisplay (XftDraw *draw); 14492 14493 Drawable XftDrawDrawable (XftDraw *draw); 14494 14495 Colormap XftDrawColormap (XftDraw *draw); 14496 14497 Visual * XftDrawVisual (XftDraw *draw); 14498 14499 void XftDrawDestroy (XftDraw *draw); 14500 14501 Picture XftDrawPicture (XftDraw *draw); 14502 14503 Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color); 14504 14505 void XftDrawGlyphs (XftDraw *draw, 14506 const XftColor *color, 14507 XftFont *pub, 14508 int x, 14509 int y, 14510 const uint *glyphs, 14511 int nglyphs); 14512 14513 void XftDrawString8 (XftDraw *draw, 14514 const XftColor *color, 14515 XftFont *pub, 14516 int x, 14517 int y, 14518 const char *string, 14519 int len); 14520 14521 void XftDrawString16 (XftDraw *draw, 14522 const XftColor *color, 14523 XftFont *pub, 14524 int x, 14525 int y, 14526 const wchar *string, 14527 int len); 14528 14529 void XftDrawString32 (XftDraw *draw, 14530 const XftColor *color, 14531 XftFont *pub, 14532 int x, 14533 int y, 14534 const dchar *string, 14535 int len); 14536 14537 void XftDrawStringUtf8 (XftDraw *draw, 14538 const XftColor *color, 14539 XftFont *pub, 14540 int x, 14541 int y, 14542 const char *string, 14543 int len); 14544 void XftDrawStringUtf16 (XftDraw *draw, 14545 const XftColor *color, 14546 XftFont *pub, 14547 int x, 14548 int y, 14549 const char *string, 14550 FcEndian endian, 14551 int len); 14552 14553 void XftDrawCharSpec (XftDraw *draw, 14554 const XftColor *color, 14555 XftFont *pub, 14556 const XftCharSpec *chars, 14557 int len); 14558 14559 void XftDrawCharFontSpec (XftDraw *draw, 14560 const XftColor *color, 14561 const XftCharFontSpec *chars, 14562 int len); 14563 14564 void XftDrawGlyphSpec (XftDraw *draw, 14565 const XftColor *color, 14566 XftFont *pub, 14567 const XftGlyphSpec *glyphs, 14568 int len); 14569 14570 void XftDrawGlyphFontSpec (XftDraw *draw, 14571 const XftColor *color, 14572 const XftGlyphFontSpec *glyphs, 14573 int len); 14574 14575 void XftDrawRect (XftDraw *draw, 14576 const XftColor *color, 14577 int x, 14578 int y, 14579 uint width, 14580 uint height); 14581 14582 Bool XftDrawSetClip (XftDraw *draw, 14583 Region r); 14584 14585 14586 Bool XftDrawSetClipRectangles (XftDraw *draw, 14587 int xOrigin, 14588 int yOrigin, 14589 const XRectangle *rects, 14590 int n); 14591 14592 void XftDrawSetSubwindowMode (XftDraw *draw, 14593 int mode); 14594 14595 void XftGlyphExtents (Display *dpy, 14596 XftFont *pub, 14597 const uint *glyphs, 14598 int nglyphs, 14599 XGlyphInfo *extents); 14600 14601 void XftTextExtents8 (Display *dpy, 14602 XftFont *pub, 14603 const char *string, 14604 int len, 14605 XGlyphInfo *extents); 14606 14607 void XftTextExtents16 (Display *dpy, 14608 XftFont *pub, 14609 const wchar *string, 14610 int len, 14611 XGlyphInfo *extents); 14612 14613 void XftTextExtents32 (Display *dpy, 14614 XftFont *pub, 14615 const dchar *string, 14616 int len, 14617 XGlyphInfo *extents); 14618 14619 void XftTextExtentsUtf8 (Display *dpy, 14620 XftFont *pub, 14621 const char *string, 14622 int len, 14623 XGlyphInfo *extents); 14624 14625 void XftTextExtentsUtf16 (Display *dpy, 14626 XftFont *pub, 14627 const char *string, 14628 FcEndian endian, 14629 int len, 14630 XGlyphInfo *extents); 14631 14632 FcPattern * XftFontMatch (Display *dpy, 14633 int screen, 14634 const FcPattern *pattern, 14635 FcResult *result); 14636 14637 XftFont * XftFontOpen (Display *dpy, int screen, ...); 14638 14639 XftFont * XftFontOpenName (Display *dpy, int screen, const char *name); 14640 14641 XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd); 14642 14643 FT_Face XftLockFace (XftFont *pub); 14644 14645 void XftUnlockFace (XftFont *pub); 14646 14647 XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern); 14648 14649 void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi); 14650 14651 dchar XftFontInfoHash (const XftFontInfo *fi); 14652 14653 FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b); 14654 14655 XftFont * XftFontOpenInfo (Display *dpy, 14656 FcPattern *pattern, 14657 XftFontInfo *fi); 14658 14659 XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern); 14660 14661 XftFont * XftFontCopy (Display *dpy, XftFont *pub); 14662 14663 void XftFontClose (Display *dpy, XftFont *pub); 14664 14665 FcBool XftInitFtLibrary(); 14666 void XftFontLoadGlyphs (Display *dpy, 14667 XftFont *pub, 14668 FcBool need_bitmaps, 14669 const uint *glyphs, 14670 int nglyph); 14671 14672 void XftFontUnloadGlyphs (Display *dpy, 14673 XftFont *pub, 14674 const uint *glyphs, 14675 int nglyph); 14676 14677 FcBool XftFontCheckGlyph (Display *dpy, 14678 XftFont *pub, 14679 FcBool need_bitmaps, 14680 uint glyph, 14681 uint *missing, 14682 int *nmissing); 14683 14684 FcBool XftCharExists (Display *dpy, 14685 XftFont *pub, 14686 dchar ucs4); 14687 14688 uint XftCharIndex (Display *dpy, 14689 XftFont *pub, 14690 dchar ucs4); 14691 FcBool XftInit (const char *config); 14692 14693 int XftGetVersion (); 14694 14695 FcFontSet * XftListFonts (Display *dpy, 14696 int screen, 14697 ...); 14698 14699 FcPattern *XftNameParse (const char *name); 14700 14701 void XftGlyphRender (Display *dpy, 14702 int op, 14703 Picture src, 14704 XftFont *pub, 14705 Picture dst, 14706 int srcx, 14707 int srcy, 14708 int x, 14709 int y, 14710 const uint *glyphs, 14711 int nglyphs); 14712 14713 void XftGlyphSpecRender (Display *dpy, 14714 int op, 14715 Picture src, 14716 XftFont *pub, 14717 Picture dst, 14718 int srcx, 14719 int srcy, 14720 const XftGlyphSpec *glyphs, 14721 int nglyphs); 14722 14723 void XftCharSpecRender (Display *dpy, 14724 int op, 14725 Picture src, 14726 XftFont *pub, 14727 Picture dst, 14728 int srcx, 14729 int srcy, 14730 const XftCharSpec *chars, 14731 int len); 14732 void XftGlyphFontSpecRender (Display *dpy, 14733 int op, 14734 Picture src, 14735 Picture dst, 14736 int srcx, 14737 int srcy, 14738 const XftGlyphFontSpec *glyphs, 14739 int nglyphs); 14740 14741 void XftCharFontSpecRender (Display *dpy, 14742 int op, 14743 Picture src, 14744 Picture dst, 14745 int srcx, 14746 int srcy, 14747 const XftCharFontSpec *chars, 14748 int len); 14749 14750 void XftTextRender8 (Display *dpy, 14751 int op, 14752 Picture src, 14753 XftFont *pub, 14754 Picture dst, 14755 int srcx, 14756 int srcy, 14757 int x, 14758 int y, 14759 const char *string, 14760 int len); 14761 void XftTextRender16 (Display *dpy, 14762 int op, 14763 Picture src, 14764 XftFont *pub, 14765 Picture dst, 14766 int srcx, 14767 int srcy, 14768 int x, 14769 int y, 14770 const wchar *string, 14771 int len); 14772 14773 void XftTextRender16BE (Display *dpy, 14774 int op, 14775 Picture src, 14776 XftFont *pub, 14777 Picture dst, 14778 int srcx, 14779 int srcy, 14780 int x, 14781 int y, 14782 const char *string, 14783 int len); 14784 14785 void XftTextRender16LE (Display *dpy, 14786 int op, 14787 Picture src, 14788 XftFont *pub, 14789 Picture dst, 14790 int srcx, 14791 int srcy, 14792 int x, 14793 int y, 14794 const char *string, 14795 int len); 14796 14797 void XftTextRender32 (Display *dpy, 14798 int op, 14799 Picture src, 14800 XftFont *pub, 14801 Picture dst, 14802 int srcx, 14803 int srcy, 14804 int x, 14805 int y, 14806 const dchar *string, 14807 int len); 14808 14809 void XftTextRender32BE (Display *dpy, 14810 int op, 14811 Picture src, 14812 XftFont *pub, 14813 Picture dst, 14814 int srcx, 14815 int srcy, 14816 int x, 14817 int y, 14818 const char *string, 14819 int len); 14820 14821 void XftTextRender32LE (Display *dpy, 14822 int op, 14823 Picture src, 14824 XftFont *pub, 14825 Picture dst, 14826 int srcx, 14827 int srcy, 14828 int x, 14829 int y, 14830 const char *string, 14831 int len); 14832 14833 void XftTextRenderUtf8 (Display *dpy, 14834 int op, 14835 Picture src, 14836 XftFont *pub, 14837 Picture dst, 14838 int srcx, 14839 int srcy, 14840 int x, 14841 int y, 14842 const char *string, 14843 int len); 14844 14845 void XftTextRenderUtf16 (Display *dpy, 14846 int op, 14847 Picture src, 14848 XftFont *pub, 14849 Picture dst, 14850 int srcx, 14851 int srcy, 14852 int x, 14853 int y, 14854 const char *string, 14855 FcEndian endian, 14856 int len); 14857 FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete); 14858 14859 } 14860 14861 interface FontConfig { 14862 extern(C) @nogc pure: 14863 int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s); 14864 void FcFontSetDestroy(FcFontSet*); 14865 char* FcNameUnparse(const FcPattern *); 14866 } 14867 14868 mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary; 14869 mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary; 14870 14871 14872 /* Xft } */ 14873 14874 class XDisconnectException : Exception { 14875 bool userRequested; 14876 this(bool userRequested = true) { 14877 this.userRequested = userRequested; 14878 super("X disconnected"); 14879 } 14880 } 14881 14882 /++ 14883 Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this. 14884 14885 Please note that it returns 14886 +/ 14887 XErrorEvent[] trapXErrors(scope void delegate() dg) { 14888 14889 static XErrorEvent[] errorBuffer; 14890 14891 static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow { 14892 errorBuffer ~= *evt; 14893 return 0; 14894 } 14895 14896 auto savedErrorHandler = XSetErrorHandler(&handler); 14897 14898 try { 14899 dg(); 14900 } finally { 14901 XSync(XDisplayConnection.get, 0/*False*/); 14902 XSetErrorHandler(savedErrorHandler); 14903 } 14904 14905 auto bfr = errorBuffer; 14906 errorBuffer = null; 14907 14908 return bfr; 14909 } 14910 14911 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`. 14912 class XDisplayConnection { 14913 private __gshared Display* display; 14914 private __gshared XIM xim; 14915 private __gshared char* displayName; 14916 14917 private __gshared int connectionSequence_; 14918 private __gshared bool isLocal_; 14919 14920 /// use this for lazy caching when reconnection 14921 static int connectionSequenceNumber() { return connectionSequence_; } 14922 14923 /++ 14924 Guesses if the connection appears to be local. 14925 14926 History: 14927 Added June 3, 2021 14928 +/ 14929 static @property bool isLocal() nothrow @trusted @nogc { 14930 return isLocal_; 14931 } 14932 14933 /// Attempts recreation of state, may require application assistance 14934 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 14935 /// then call this, and if successful, reenter the loop. 14936 static void discardAndRecreate(string newDisplayString = null) { 14937 if(insideXEventLoop) 14938 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 14939 14940 // 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 14941 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 14942 14943 foreach(handle; chnenhm) { 14944 handle.discardConnectionState(); 14945 } 14946 14947 discardState(); 14948 14949 if(newDisplayString !is null) 14950 setDisplayName(newDisplayString); 14951 14952 auto display = get(); 14953 14954 foreach(handle; chnenhm) { 14955 handle.recreateAfterDisconnect(); 14956 } 14957 } 14958 14959 private __gshared EventMask rootEventMask; 14960 14961 /++ 14962 Requests the specified input from the root window on the connection, in addition to any other request. 14963 14964 14965 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. 14966 14967 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 14968 +/ 14969 static void addRootInput(EventMask mask) { 14970 auto old = rootEventMask; 14971 rootEventMask |= mask; 14972 get(); // to ensure display connected 14973 if(display !is null && rootEventMask != old) 14974 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 14975 } 14976 14977 static void discardState() { 14978 freeImages(); 14979 14980 foreach(atomPtr; interredAtoms) 14981 *atomPtr = 0; 14982 interredAtoms = null; 14983 interredAtoms.assumeSafeAppend(); 14984 14985 ScreenPainterImplementation.fontAttempted = false; 14986 ScreenPainterImplementation.defaultfont = null; 14987 ScreenPainterImplementation.defaultfontset = null; 14988 14989 Image.impl.xshmQueryCompleted = false; 14990 Image.impl._xshmAvailable = false; 14991 14992 SimpleWindow.nativeMapping = null; 14993 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 14994 // GlobalHotkeyManager 14995 14996 display = null; 14997 xim = null; 14998 } 14999 15000 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 15001 private static void createXIM () { 15002 import core.stdc.locale : setlocale, LC_ALL; 15003 import core.stdc.stdio : stderr, fprintf; 15004 import core.stdc.stdlib : free; 15005 import core.stdc.string : strdup; 15006 15007 static immutable string[3] mtry = [ "", "@im=local", "@im=" ]; 15008 15009 auto olocale = strdup(setlocale(LC_ALL, null)); 15010 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 15011 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 15012 15013 //fprintf(stderr, "opening IM...\n"); 15014 foreach (string s; mtry) { 15015 XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 15016 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 15017 } 15018 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 15019 } 15020 15021 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 15022 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 15023 static struct ImgList { 15024 size_t img; // class; hide it from GC 15025 ImgList* next; 15026 } 15027 15028 static __gshared ImgList* imglist = null; 15029 static __gshared bool imglistLocked = false; // true: don't register and unregister images 15030 15031 static void registerImage (Image img) { 15032 if (!imglistLocked && img !is null) { 15033 import core.stdc.stdlib : malloc; 15034 auto it = cast(ImgList*)malloc(ImgList.sizeof); 15035 assert(it !is null); // do proper checks 15036 it.img = cast(size_t)cast(void*)img; 15037 it.next = imglist; 15038 imglist = it; 15039 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 15040 } 15041 } 15042 15043 static void unregisterImage (Image img) { 15044 if (!imglistLocked && img !is null) { 15045 import core.stdc.stdlib : free; 15046 ImgList* prev = null; 15047 ImgList* cur = imglist; 15048 while (cur !is null) { 15049 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 15050 prev = cur; 15051 cur = cur.next; 15052 } 15053 if (cur !is null) { 15054 if (prev is null) imglist = cur.next; else prev.next = cur.next; 15055 free(cur); 15056 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 15057 } else { 15058 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 15059 } 15060 } 15061 } 15062 15063 static void freeImages () { // needed for discardAndRecreate 15064 imglistLocked = true; 15065 scope(exit) imglistLocked = false; 15066 ImgList* cur = imglist; 15067 ImgList* next = null; 15068 while (cur !is null) { 15069 import core.stdc.stdlib : free; 15070 next = cur.next; 15071 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 15072 (cast(Image)cast(void*)cur.img).dispose(); 15073 free(cur); 15074 cur = next; 15075 } 15076 imglist = null; 15077 } 15078 15079 /// can be used to override normal handling of display name 15080 /// from environment and/or command line 15081 static setDisplayName(string newDisplayName) { 15082 displayName = cast(char*) (newDisplayName ~ '\0'); 15083 } 15084 15085 /// resets to the default display string 15086 static resetDisplayName() { 15087 displayName = null; 15088 } 15089 15090 /// 15091 static Display* get() { 15092 if(display is null) { 15093 if(!librariesSuccessfullyLoaded) 15094 throw new Exception("Unable to load X11 client libraries"); 15095 display = XOpenDisplay(displayName); 15096 15097 isLocal_ = false; 15098 15099 connectionSequence_++; 15100 if(display is null) 15101 throw new Exception("Unable to open X display"); 15102 15103 auto str = display.display_name; 15104 // this is a bit of a hack but like if it looks like a unix socket we assume it is local 15105 // and otherwise it probably isn't 15106 if(str is null || (str[0] != ':' && str[0] != '/')) 15107 isLocal_ = false; 15108 else 15109 isLocal_ = true; 15110 15111 XSetErrorHandler(&adrlogger); 15112 15113 debug(sdpy_x_errors) { 15114 XSynchronize(display, true); 15115 15116 extern(C) int wtf() { 15117 if(errorHappened) { 15118 asm { int 3; } 15119 errorHappened = false; 15120 } 15121 return 0; 15122 } 15123 XSetAfterFunction(display, &wtf); 15124 } 15125 15126 15127 XSetIOErrorHandler(&x11ioerrCB); 15128 Bool sup; 15129 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 15130 createXIM(); 15131 version(with_eventloop) { 15132 import arsd.eventloop; 15133 addFileEventListeners(display.fd, &eventListener, null, null); 15134 } 15135 } 15136 15137 return display; 15138 } 15139 15140 extern(C) 15141 static int x11ioerrCB(Display* dpy) { 15142 throw new XDisconnectException(false); 15143 } 15144 15145 version(with_eventloop) { 15146 import arsd.eventloop; 15147 static void eventListener(OsFileHandle fd) { 15148 //this.mtLock(); 15149 //scope(exit) this.mtUnlock(); 15150 while(XPending(display)) 15151 doXNextEvent(display); 15152 } 15153 } 15154 15155 // close connection on program exit -- we need this to properly free all images 15156 static ~this () { 15157 // the gui thread must clean up after itself or else Xlib might deadlock 15158 // using this flag on any thread destruction is the easiest way i know of 15159 // (shared static this is run by the LAST thread to exit, which may not be 15160 // the gui thread, and normal static this run by ALL threads, so we gotta check.) 15161 if(thisIsGuiThread) 15162 close(); 15163 } 15164 15165 /// 15166 static void close() { 15167 if(display is null) 15168 return; 15169 15170 version(with_eventloop) { 15171 import arsd.eventloop; 15172 removeFileEventListeners(display.fd); 15173 } 15174 15175 // now remove all registered images to prevent shared memory leaks 15176 freeImages(); 15177 15178 // tbh I don't know why it is doing this but like if this happens to run 15179 // from the other thread there's frequent hanging inside here. 15180 if(thisIsGuiThread) 15181 XCloseDisplay(display); 15182 display = null; 15183 } 15184 } 15185 15186 mixin template NativeImageImplementation() { 15187 XImage* handle; 15188 ubyte* rawData; 15189 15190 XShmSegmentInfo shminfo; 15191 bool premultiply = true; 15192 15193 __gshared bool xshmQueryCompleted; 15194 __gshared bool _xshmAvailable; 15195 public static @property bool xshmAvailable() { 15196 if(!xshmQueryCompleted) { 15197 int i1, i2, i3; 15198 xshmQueryCompleted = true; 15199 15200 if(!XDisplayConnection.isLocal) 15201 _xshmAvailable = false; 15202 else 15203 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 15204 } 15205 return _xshmAvailable; 15206 } 15207 15208 bool usingXshm; 15209 final: 15210 15211 private __gshared bool xshmfailed; 15212 15213 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 15214 auto display = XDisplayConnection.get(); 15215 assert(display !is null); 15216 auto screen = DefaultScreen(display); 15217 15218 // it will only use shared memory for somewhat largish images, 15219 // since otherwise we risk wasting shared memory handles on a lot of little ones 15220 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 15221 15222 15223 // it is possible for the query extension to return true, the DISPLAY check to pass, yet 15224 // the actual use still fails. For example, if the program is in a container and permission denied 15225 // on shared memory, or if it is a local thing forwarded to a remote server, etc. 15226 // 15227 // If it does fail, we need to detect it now, abort the xshm and fall back to core protocol. 15228 15229 15230 // synchronize so preexisting buffers are clear 15231 XSync(display, false); 15232 xshmfailed = false; 15233 15234 auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler); 15235 15236 15237 usingXshm = true; 15238 handle = XShmCreateImage( 15239 display, 15240 DefaultVisual(display, screen), 15241 enableAlpha ? 32: 24, 15242 ImageFormat.ZPixmap, 15243 null, 15244 &shminfo, 15245 width, height); 15246 if(handle is null) 15247 goto abortXshm1; 15248 15249 if(handle.bytes_per_line != 4 * width) 15250 goto abortXshm2; 15251 15252 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 15253 if(shminfo.shmid < 0) 15254 goto abortXshm3; 15255 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 15256 if(rawData == cast(ubyte*) -1) 15257 goto abortXshm4; 15258 shminfo.readOnly = 0; 15259 XShmAttach(display, &shminfo); 15260 15261 // and now to the final error check to ensure it actually worked. 15262 XSync(display, false); 15263 if(xshmfailed) 15264 goto abortXshm5; 15265 15266 XSetErrorHandler(oldErrorHandler); 15267 15268 XDisplayConnection.registerImage(this); 15269 // if I don't flush here there's a chance the dtor will run before the 15270 // ctor and lead to a bad value X error. While this hurts the efficiency 15271 // it is local anyway so prolly better to keep it simple 15272 XFlush(display); 15273 15274 return; 15275 15276 abortXshm5: 15277 shmdt(shminfo.shmaddr); 15278 rawData = null; 15279 15280 abortXshm4: 15281 shmctl(shminfo.shmid, IPC_RMID, null); 15282 15283 abortXshm3: 15284 // nothing needed, the shmget failed so there's nothing to free 15285 15286 abortXshm2: 15287 XDestroyImage(handle); 15288 handle = null; 15289 15290 abortXshm1: 15291 XSetErrorHandler(oldErrorHandler); 15292 usingXshm = false; 15293 handle = null; 15294 15295 shminfo = typeof(shminfo).init; 15296 15297 _xshmAvailable = false; // don't try again in the future 15298 15299 // writeln("fallingback"); 15300 15301 goto fallback; 15302 15303 } else { 15304 fallback: 15305 15306 if (forcexshm) throw new Exception("can't create XShm Image"); 15307 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 15308 import core.stdc.stdlib : malloc; 15309 rawData = cast(ubyte*) malloc(width * height * 4); 15310 15311 handle = XCreateImage( 15312 display, 15313 DefaultVisual(display, screen), 15314 enableAlpha ? 32 : 24, // bpp 15315 ImageFormat.ZPixmap, 15316 0, // offset 15317 rawData, 15318 width, height, 15319 enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line 15320 } 15321 } 15322 15323 void dispose() { 15324 // note: this calls free(rawData) for us 15325 if(handle) { 15326 if (usingXshm) { 15327 XDisplayConnection.unregisterImage(this); 15328 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 15329 } 15330 XDestroyImage(handle); 15331 if(usingXshm) { 15332 shmdt(shminfo.shmaddr); 15333 shmctl(shminfo.shmid, IPC_RMID, null); 15334 } 15335 handle = null; 15336 } 15337 } 15338 15339 Color getPixel(int x, int y) @system { 15340 auto offset = (y * width + x) * 4; 15341 Color c; 15342 c.a = enableAlpha ? rawData[offset + 3] : 255; 15343 c.b = rawData[offset + 0]; 15344 c.g = rawData[offset + 1]; 15345 c.r = rawData[offset + 2]; 15346 if(enableAlpha && premultiply) 15347 c.unPremultiply; 15348 return c; 15349 } 15350 15351 void setPixel(int x, int y, Color c) @system { 15352 if(enableAlpha && premultiply) 15353 c.premultiply(); 15354 auto offset = (y * width + x) * 4; 15355 rawData[offset + 0] = c.b; 15356 rawData[offset + 1] = c.g; 15357 rawData[offset + 2] = c.r; 15358 if(enableAlpha) 15359 rawData[offset + 3] = c.a; 15360 } 15361 15362 void convertToRgbaBytes(ubyte[] where) @system { 15363 assert(where.length == this.width * this.height * 4); 15364 15365 // if rawData had a length.... 15366 //assert(rawData.length == where.length); 15367 for(int idx = 0; idx < where.length; idx += 4) { 15368 where[idx + 0] = rawData[idx + 2]; // r 15369 where[idx + 1] = rawData[idx + 1]; // g 15370 where[idx + 2] = rawData[idx + 0]; // b 15371 where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a 15372 15373 if(enableAlpha && premultiply) 15374 unPremultiplyRgba(where[idx .. idx + 4]); 15375 } 15376 } 15377 15378 void setFromRgbaBytes(in ubyte[] where) @system { 15379 assert(where.length == this.width * this.height * 4); 15380 15381 // if rawData had a length.... 15382 //assert(rawData.length == where.length); 15383 for(int idx = 0; idx < where.length; idx += 4) { 15384 rawData[idx + 2] = where[idx + 0]; // r 15385 rawData[idx + 1] = where[idx + 1]; // g 15386 rawData[idx + 0] = where[idx + 2]; // b 15387 if(enableAlpha) { 15388 rawData[idx + 3] = where[idx + 3]; // a 15389 if(premultiply) 15390 premultiplyBgra(rawData[idx .. idx + 4]); 15391 } 15392 } 15393 } 15394 15395 } 15396 15397 mixin template NativeSimpleWindowImplementation() { 15398 GC gc; 15399 Window window; 15400 Display* display; 15401 15402 Pixmap buffer; 15403 int bufferw, bufferh; // size of the buffer; can be bigger than window 15404 XIC xic; // input context 15405 int curHidden = 0; // counter 15406 Cursor blankCurPtr = 0; 15407 int cursorSequenceNumber = 0; 15408 int warpEventCount = 0; // number of mouse movement events to eat 15409 15410 __gshared X11SetSelectionHandler[Atom] setSelectionHandlers; // FIXME: make sure this is not accessed from other threads. it might be ok to make it TLS 15411 X11GetSelectionHandler[Atom] getSelectionHandlers; 15412 15413 version(without_opengl) {} else 15414 GLXContext glc; 15415 15416 private void fixFixedSize(bool forced=false) (int width, int height) { 15417 if (forced || this.resizability == Resizability.fixedSize) { 15418 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 15419 XSizeHints sh; 15420 static if (!forced) { 15421 c_long spr; 15422 XGetWMNormalHints(display, window, &sh, &spr); 15423 sh.flags |= PMaxSize | PMinSize; 15424 } else { 15425 sh.flags = PMaxSize | PMinSize; 15426 } 15427 sh.min_width = width; 15428 sh.min_height = height; 15429 sh.max_width = width; 15430 sh.max_height = height; 15431 XSetWMNormalHints(display, window, &sh); 15432 //XFlush(display); 15433 } 15434 } 15435 15436 ScreenPainter getPainter(bool manualInvalidations) { 15437 return ScreenPainter(this, window, manualInvalidations); 15438 } 15439 15440 void move(int x, int y) { 15441 XMoveWindow(display, window, x, y); 15442 } 15443 15444 void resize(int w, int h) { 15445 if (w < 1) w = 1; 15446 if (h < 1) h = 1; 15447 XResizeWindow(display, window, w, h); 15448 15449 // calling this now to avoid waiting for the server to 15450 // acknowledge the resize; draws without returning to the 15451 // event loop will thus actually work. the server's event 15452 // btw might overrule this and resize it again 15453 recordX11Resize(display, this, w, h); 15454 15455 updateOpenglViewportIfNeeded(w, h); 15456 } 15457 15458 void moveResize (int x, int y, int w, int h) { 15459 if (w < 1) w = 1; 15460 if (h < 1) h = 1; 15461 XMoveResizeWindow(display, window, x, y, w, h); 15462 updateOpenglViewportIfNeeded(w, h); 15463 } 15464 15465 void hideCursor () { 15466 if (curHidden++ == 0) { 15467 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 15468 static const(char)[1] cmbmp = 0; 15469 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 15470 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 15471 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 15472 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 15473 XFreePixmap(display, pm); 15474 } 15475 XDefineCursor(display, window, blankCurPtr); 15476 } 15477 } 15478 15479 void showCursor () { 15480 if (--curHidden == 0) XUndefineCursor(display, window); 15481 } 15482 15483 void warpMouse (int x, int y) { 15484 // here i will send dummy "ignore next mouse motion" event, 15485 // 'cause `XWarpPointer()` sends synthesised mouse motion, 15486 // and we don't need to report it to the user (as warping is 15487 // used when the user needs movement deltas). 15488 //XClientMessageEvent xclient; 15489 XEvent e; 15490 e.xclient.type = EventType.ClientMessage; 15491 e.xclient.window = window; 15492 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 15493 e.xclient.format = 32; 15494 e.xclient.data.l[0] = 0; 15495 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 15496 //{ 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]); } 15497 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 15498 // now warp pointer... 15499 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 15500 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 15501 // ...and flush 15502 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 15503 XFlush(display); 15504 } 15505 15506 void sendDummyEvent () { 15507 // here i will send dummy event to ping event queue 15508 XEvent e; 15509 e.xclient.type = EventType.ClientMessage; 15510 e.xclient.window = window; 15511 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 15512 e.xclient.format = 32; 15513 e.xclient.data.l[0] = 0; 15514 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 15515 XFlush(display); 15516 } 15517 15518 void setTitle(string title) { 15519 if (title.ptr is null) title = ""; 15520 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 15521 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 15522 XTextProperty windowName; 15523 windowName.value = title.ptr; 15524 windowName.encoding = XA_UTF8; //XA_STRING; 15525 windowName.format = 8; 15526 windowName.nitems = cast(uint)title.length; 15527 XSetWMName(display, window, &windowName); 15528 char[1024] namebuf = 0; 15529 auto maxlen = namebuf.length-1; 15530 if (maxlen > title.length) maxlen = title.length; 15531 namebuf[0..maxlen] = title[0..maxlen]; 15532 XStoreName(display, window, namebuf.ptr); 15533 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 15534 flushGui(); // without this OpenGL windows has a LONG delay before changing title 15535 } 15536 15537 string[] getTitles() { 15538 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 15539 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 15540 XTextProperty textProp; 15541 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 15542 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 15543 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 15544 } else 15545 return []; 15546 } else 15547 return null; 15548 } 15549 15550 string getTitle() { 15551 auto titles = getTitles(); 15552 return titles.length ? titles[0] : null; 15553 } 15554 15555 void setMinSize (int minwidth, int minheight) { 15556 import core.stdc.config : c_long; 15557 if (minwidth < 1) minwidth = 1; 15558 if (minheight < 1) minheight = 1; 15559 XSizeHints sh; 15560 c_long spr; 15561 XGetWMNormalHints(display, window, &sh, &spr); 15562 sh.min_width = minwidth; 15563 sh.min_height = minheight; 15564 sh.flags |= PMinSize; 15565 XSetWMNormalHints(display, window, &sh); 15566 flushGui(); 15567 } 15568 15569 void setMaxSize (int maxwidth, int maxheight) { 15570 import core.stdc.config : c_long; 15571 if (maxwidth < 1) maxwidth = 1; 15572 if (maxheight < 1) maxheight = 1; 15573 XSizeHints sh; 15574 c_long spr; 15575 XGetWMNormalHints(display, window, &sh, &spr); 15576 sh.max_width = maxwidth; 15577 sh.max_height = maxheight; 15578 sh.flags |= PMaxSize; 15579 XSetWMNormalHints(display, window, &sh); 15580 flushGui(); 15581 } 15582 15583 void setResizeGranularity (int granx, int grany) { 15584 import core.stdc.config : c_long; 15585 if (granx < 1) granx = 1; 15586 if (grany < 1) grany = 1; 15587 XSizeHints sh; 15588 c_long spr; 15589 XGetWMNormalHints(display, window, &sh, &spr); 15590 sh.width_inc = granx; 15591 sh.height_inc = grany; 15592 sh.flags |= PResizeInc; 15593 XSetWMNormalHints(display, window, &sh); 15594 flushGui(); 15595 } 15596 15597 void setOpacity (uint opacity) { 15598 arch_ulong o = opacity; 15599 if (opacity == uint.max) 15600 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 15601 else 15602 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 15603 XA_CARDINAL, 32, PropModeReplace, &o, 1); 15604 } 15605 15606 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) @trusted { 15607 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 15608 display = XDisplayConnection.get(); 15609 auto screen = DefaultScreen(display); 15610 15611 bool overrideRedirect = false; 15612 if( 15613 windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || 15614 windowType == WindowTypes.tooltip || 15615 windowType == WindowTypes.notification || 15616 windowType == WindowTypes.dnd || 15617 windowType == WindowTypes.comboBoxDropdown || 15618 (customizationFlags & WindowFlags.overrideRedirect) 15619 )// || windowType == WindowTypes.nestedChild) 15620 overrideRedirect = true; 15621 15622 version(without_opengl) {} 15623 else { 15624 if(opengl == OpenGlOptions.yes) { 15625 GLXFBConfig fbconf = null; 15626 XVisualInfo* vi = null; 15627 bool useLegacy = false; 15628 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 15629 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 15630 int[23] visualAttribs = [ 15631 GLX_X_RENDERABLE , 1/*True*/, 15632 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 15633 GLX_RENDER_TYPE , GLX_RGBA_BIT, 15634 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 15635 GLX_RED_SIZE , 8, 15636 GLX_GREEN_SIZE , 8, 15637 GLX_BLUE_SIZE , 8, 15638 GLX_ALPHA_SIZE , 8, 15639 GLX_DEPTH_SIZE , 24, 15640 GLX_STENCIL_SIZE , 8, 15641 GLX_DOUBLEBUFFER , 1/*True*/, 15642 0/*None*/, 15643 ]; 15644 int fbcount; 15645 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 15646 if (fbcount == 0) { 15647 useLegacy = true; // try to do at least something 15648 } else { 15649 // pick the FB config/visual with the most samples per pixel 15650 int bestidx = -1, bestns = -1; 15651 foreach (int fbi; 0..fbcount) { 15652 int sb, samples; 15653 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 15654 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 15655 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 15656 } 15657 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 15658 fbconf = fbc[bestidx]; 15659 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 15660 XFree(fbc); 15661 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 15662 } 15663 } 15664 if (vi is null || useLegacy) { 15665 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 15666 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 15667 useLegacy = true; 15668 } 15669 if (vi is null) throw new Exception("no open gl visual found"); 15670 15671 XSetWindowAttributes swa; 15672 auto root = RootWindow(display, screen); 15673 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 15674 15675 swa.override_redirect = overrideRedirect; 15676 15677 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 15678 0, 0, width, height, 15679 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa); 15680 15681 // now try to use `glXCreateContextAttribsARB()` if it's here 15682 if (!useLegacy) { 15683 // request fairly advanced context, even with stencil buffer! 15684 int[9] contextAttribs = [ 15685 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 15686 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 15687 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 15688 // for modern context, set "forward compatibility" flag too 15689 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 15690 0/*None*/, 15691 ]; 15692 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 15693 if (glc is null && sdpyOpenGLContextAllowFallback) { 15694 sdpyOpenGLContextVersion = 0; 15695 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 15696 } 15697 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 15698 } else { 15699 // fallback to old GLX call 15700 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 15701 sdpyOpenGLContextVersion = 0; 15702 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 15703 } 15704 } 15705 // sync to ensure any errors generated are processed 15706 XSync(display, 0/*False*/); 15707 //{ import core.stdc.stdio; printf("ogl is here\n"); } 15708 if(glc is null) 15709 throw new Exception("glc"); 15710 } 15711 } 15712 15713 if(opengl == OpenGlOptions.no) { 15714 15715 XSetWindowAttributes swa; 15716 swa.background_pixel = WhitePixel(display, screen); 15717 swa.border_pixel = BlackPixel(display, screen); 15718 swa.override_redirect = overrideRedirect; 15719 auto root = RootWindow(display, screen); 15720 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 15721 15722 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 15723 0, 0, width, height, 15724 // I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit. 15725 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa); 15726 15727 15728 15729 /* 15730 window = XCreateSimpleWindow( 15731 display, 15732 parent is null ? RootWindow(display, screen) : parent.impl.window, 15733 0, 0, // x, y 15734 width, height, 15735 1, // border width 15736 BlackPixel(display, screen), // border 15737 WhitePixel(display, screen)); // background 15738 */ 15739 15740 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 15741 bufferw = width; 15742 bufferh = height; 15743 15744 gc = DefaultGC(display, screen); 15745 15746 // clear out the buffer to get us started... 15747 XSetForeground(display, gc, WhitePixel(display, screen)); 15748 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 15749 XSetForeground(display, gc, BlackPixel(display, screen)); 15750 } 15751 15752 // input context 15753 //TODO: create this only for top-level windows, and reuse that? 15754 populateXic(); 15755 15756 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 15757 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 15758 // window class 15759 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 15760 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 15761 XClassHint klass; 15762 XWMHints wh; 15763 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 15764 wh.input = true; 15765 wh.flags |= InputHint; 15766 } 15767 XSizeHints size; 15768 klass.res_name = sdpyWindowClassStr; 15769 klass.res_class = sdpyWindowClassStr; 15770 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 15771 } 15772 15773 setTitle(title); 15774 SimpleWindow.nativeMapping[window] = this; 15775 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 15776 15777 // This gives our window a close button 15778 if (windowType != WindowTypes.eventOnly) { 15779 Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)]; 15780 int useAtoms; 15781 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 15782 useAtoms = 2; 15783 } else { 15784 useAtoms = 1; 15785 } 15786 assert(useAtoms <= atoms.length); 15787 XSetWMProtocols(display, window, atoms.ptr, useAtoms); 15788 } 15789 15790 // FIXME: windowType and customizationFlags 15791 Atom[8] wsatoms; // here, due to goto 15792 int wmsacount = 0; // here, due to goto 15793 15794 try 15795 final switch(windowType) { 15796 case WindowTypes.normal: 15797 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 15798 break; 15799 case WindowTypes.undecorated: 15800 motifHideDecorations(); 15801 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 15802 break; 15803 case WindowTypes.eventOnly: 15804 _hidden = true; 15805 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 15806 goto hiddenWindow; 15807 //break; 15808 case WindowTypes.nestedChild: 15809 // handled in XCreateWindow calls 15810 break; 15811 15812 case WindowTypes.dropdownMenu: 15813 motifHideDecorations(); 15814 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 15815 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15816 break; 15817 case WindowTypes.popupMenu: 15818 motifHideDecorations(); 15819 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 15820 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15821 break; 15822 case WindowTypes.notification: 15823 motifHideDecorations(); 15824 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 15825 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15826 break; 15827 case WindowTypes.minimallyWrapped: 15828 assert(0, "don't create a minimallyWrapped thing explicitly!"); 15829 15830 case WindowTypes.dialog: 15831 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display)); 15832 break; 15833 case WindowTypes.comboBoxDropdown: 15834 motifHideDecorations(); 15835 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display)); 15836 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15837 break; 15838 case WindowTypes.tooltip: 15839 motifHideDecorations(); 15840 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display)); 15841 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15842 break; 15843 case WindowTypes.dnd: 15844 motifHideDecorations(); 15845 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display)); 15846 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15847 break; 15848 /+ 15849 case WindowTypes.menu: 15850 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 15851 motifHideDecorations(); 15852 break; 15853 case WindowTypes.desktop: 15854 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 15855 break; 15856 case WindowTypes.dock: 15857 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 15858 break; 15859 case WindowTypes.toolbar: 15860 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 15861 break; 15862 case WindowTypes.menu: 15863 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 15864 break; 15865 case WindowTypes.utility: 15866 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 15867 break; 15868 case WindowTypes.splash: 15869 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 15870 break; 15871 case WindowTypes.notification: 15872 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 15873 break; 15874 +/ 15875 } 15876 catch(Exception e) { 15877 // XInternAtom failed, prolly a WM 15878 // that doesn't support these things 15879 } 15880 15881 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 15882 // the two following flags may be ignored by WM 15883 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 15884 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 15885 15886 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 15887 15888 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 15889 15890 // What would be ideal here is if they only were 15891 // selected if there was actually an event handler 15892 // for them... 15893 15894 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 15895 15896 hiddenWindow: 15897 15898 // set the pid property for lookup later by window managers 15899 // a standard convenience 15900 import core.sys.posix.unistd; 15901 arch_ulong pid = getpid(); 15902 15903 XChangeProperty( 15904 display, 15905 impl.window, 15906 GetAtom!("_NET_WM_PID", true)(display), 15907 XA_CARDINAL, 15908 32 /* bits */, 15909 0 /*PropModeReplace*/, 15910 &pid, 15911 1); 15912 15913 if(isTransient && parent) { // customizationFlags & WindowFlags.transient) { 15914 if(parent is null) assert(0); 15915 // sdpyPrintDebugString("transient"); 15916 XChangeProperty( 15917 display, 15918 impl.window, 15919 GetAtom!("WM_TRANSIENT_FOR", true)(display), 15920 XA_WINDOW, 15921 32 /* bits */, 15922 0 /*PropModeReplace*/, 15923 &parent.impl.window, 15924 1); 15925 15926 } 15927 15928 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 15929 XMapWindow(display, window); 15930 } else { 15931 _hidden = true; 15932 } 15933 } 15934 15935 void populateXic() { 15936 if (XDisplayConnection.xim !is null) { 15937 xic = XCreateIC(XDisplayConnection.xim, 15938 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 15939 /*XNClientWindow*/"clientWindow".ptr, window, 15940 /*XNFocusWindow*/"focusWindow".ptr, window, 15941 null); 15942 if (xic is null) { 15943 import core.stdc.stdio : stderr, fprintf; 15944 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 15945 } 15946 } 15947 } 15948 15949 void selectDefaultInput(bool forceIncludeMouseMotion) { 15950 auto mask = EventMask.ExposureMask | 15951 EventMask.KeyPressMask | 15952 EventMask.KeyReleaseMask | 15953 EventMask.PropertyChangeMask | 15954 EventMask.FocusChangeMask | 15955 EventMask.StructureNotifyMask | 15956 EventMask.SubstructureNotifyMask | 15957 EventMask.VisibilityChangeMask 15958 | EventMask.ButtonPressMask 15959 | EventMask.ButtonReleaseMask 15960 ; 15961 15962 // xshm is our shortcut for local connections 15963 if(XDisplayConnection.isLocal || forceIncludeMouseMotion) 15964 mask |= EventMask.PointerMotionMask; 15965 else 15966 mask |= EventMask.ButtonMotionMask; 15967 15968 XSelectInput(display, window, mask); 15969 } 15970 15971 15972 void setNetWMWindowType(Atom type) { 15973 Atom[2] atoms; 15974 15975 atoms[0] = type; 15976 // generic fallback 15977 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 15978 15979 XChangeProperty( 15980 display, 15981 impl.window, 15982 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 15983 XA_ATOM, 15984 32 /* bits */, 15985 0 /*PropModeReplace*/, 15986 atoms.ptr, 15987 cast(int) atoms.length); 15988 } 15989 15990 void motifHideDecorations(bool hide = true) { 15991 MwmHints hints; 15992 hints.flags = MWM_HINTS_DECORATIONS; 15993 hints.decorations = hide ? 0 : 1; 15994 15995 XChangeProperty( 15996 display, 15997 impl.window, 15998 GetAtom!"_MOTIF_WM_HINTS"(display), 15999 GetAtom!"_MOTIF_WM_HINTS"(display), 16000 32 /* bits */, 16001 0 /*PropModeReplace*/, 16002 &hints, 16003 hints.sizeof / 4); 16004 } 16005 16006 /*k8: unused 16007 void createOpenGlContext() { 16008 16009 } 16010 */ 16011 16012 void closeWindow() { 16013 // I can't close this or a child window closing will 16014 // break events for everyone. So I'm just leaking it right 16015 // now and that is probably perfectly fine... 16016 version(none) 16017 if (customEventFDRead != -1) { 16018 import core.sys.posix.unistd : close; 16019 auto same = customEventFDRead == customEventFDWrite; 16020 16021 close(customEventFDRead); 16022 if(!same) 16023 close(customEventFDWrite); 16024 customEventFDRead = -1; 16025 customEventFDWrite = -1; 16026 } 16027 16028 version(without_opengl) {} else 16029 if(glc !is null) { 16030 glXDestroyContext(display, glc); 16031 glc = null; 16032 } 16033 16034 if(buffer) 16035 XFreePixmap(display, buffer); 16036 bufferw = bufferh = 0; 16037 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 16038 XDestroyWindow(display, window); 16039 XFlush(display); 16040 } 16041 16042 void dispose() { 16043 } 16044 16045 bool destroyed = false; 16046 } 16047 16048 bool insideXEventLoop; 16049 } 16050 16051 version(X11) { 16052 16053 int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this. 16054 16055 private class ResizeEvent { 16056 int width, height; 16057 } 16058 16059 void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) { 16060 if(win.windowType == WindowTypes.minimallyWrapped) 16061 return; 16062 16063 if(win.pendingResizeEvent is null) { 16064 win.pendingResizeEvent = new ResizeEvent(); 16065 win.addEventListener((ResizeEvent re) { 16066 recordX11Resize(XDisplayConnection.get, win, re.width, re.height); 16067 }); 16068 } 16069 win.pendingResizeEvent.width = width; 16070 win.pendingResizeEvent.height = height; 16071 if(!win.eventQueued!ResizeEvent) { 16072 win.postEvent(win.pendingResizeEvent); 16073 } 16074 } 16075 16076 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 16077 if(win.windowType == WindowTypes.minimallyWrapped) 16078 return; 16079 if(win.closed) 16080 return; 16081 16082 if(width != win.width || height != win.height) { 16083 16084 // writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window); 16085 win._width = width; 16086 win._height = height; 16087 16088 if(win.openglMode == OpenGlOptions.no) { 16089 // FIXME: could this be more efficient? 16090 16091 if (win.bufferw < width || win.bufferh < height) { 16092 //{ 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); } 16093 // grow the internal buffer to match the window... 16094 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 16095 { 16096 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 16097 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 16098 scope(exit) XFreeGC(win.display, xgc); 16099 XSetClipMask(win.display, xgc, None); 16100 XSetForeground(win.display, xgc, 0); 16101 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 16102 } 16103 XCopyArea(display, 16104 cast(Drawable) win.buffer, 16105 cast(Drawable) newPixmap, 16106 win.gc, 0, 0, 16107 win.bufferw < width ? win.bufferw : win.width, 16108 win.bufferh < height ? win.bufferh : win.height, 16109 0, 0); 16110 16111 XFreePixmap(display, win.buffer); 16112 win.buffer = newPixmap; 16113 win.bufferw = width; 16114 win.bufferh = height; 16115 } 16116 16117 // clear unused parts of the buffer 16118 if (win.bufferw > width || win.bufferh > height) { 16119 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 16120 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 16121 scope(exit) XFreeGC(win.display, xgc); 16122 XSetClipMask(win.display, xgc, None); 16123 XSetForeground(win.display, xgc, 0); 16124 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 16125 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 16126 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 16127 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 16128 } 16129 16130 } 16131 16132 win.updateOpenglViewportIfNeeded(width, height); 16133 16134 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 16135 16136 if(win.resizability != Resizability.automaticallyScaleIfPossible) 16137 if(win.windowResized !is null) { 16138 XUnlockDisplay(display); 16139 scope(exit) XLockDisplay(display); 16140 win.windowResized(width, height); 16141 } 16142 } 16143 } 16144 16145 16146 /// Platform-specific, you might use it when doing a custom event loop. 16147 bool doXNextEvent(Display* display) { 16148 bool done; 16149 XEvent e; 16150 XNextEvent(display, &e); 16151 version(sddddd) { 16152 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 16153 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 16154 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 16155 } 16156 } 16157 16158 // filter out compose events 16159 if (XFilterEvent(&e, None)) { 16160 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 16161 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 16162 return false; 16163 } 16164 // process keyboard mapping changes 16165 if (e.type == EventType.KeymapNotify) { 16166 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 16167 XRefreshKeyboardMapping(&e.xmapping); 16168 return false; 16169 } 16170 16171 version(with_eventloop) 16172 import arsd.eventloop; 16173 16174 if(SimpleWindow.handleNativeGlobalEvent !is null) { 16175 // see windows impl's comments 16176 XUnlockDisplay(display); 16177 scope(exit) XLockDisplay(display); 16178 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 16179 if(ret == 0) 16180 return done; 16181 } 16182 16183 16184 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 16185 if(win.getNativeEventHandler !is null) { 16186 XUnlockDisplay(display); 16187 scope(exit) XLockDisplay(display); 16188 auto ret = win.getNativeEventHandler()(e); 16189 if(ret == 0) 16190 return done; 16191 } 16192 } 16193 16194 if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) { 16195 if(auto win = e.xany.window in SimpleWindow.nativeMapping) { 16196 // we get this because of the RRScreenChangeNotifyMask 16197 16198 // this isn't actually an ideal way to do it since it wastes time 16199 // but meh it is simple and it works. 16200 win.actualDpiLoadAttempted = false; 16201 SimpleWindow.xRandrInfoLoadAttemped = false; 16202 win.updateActualDpi(); // trigger a reload 16203 } 16204 } 16205 16206 switch(e.type) { 16207 case EventType.SelectionClear: 16208 // writeln("SelectionClear"); 16209 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) { 16210 // FIXME so it is supposed to finish any in progress transfers... but idk... 16211 // writeln("SelectionClear"); 16212 } 16213 SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection); 16214 mightShortCircuitClipboard = false; 16215 break; 16216 case EventType.SelectionRequest: 16217 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 16218 if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) { 16219 // printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target)); 16220 XUnlockDisplay(display); 16221 scope(exit) XLockDisplay(display); 16222 (*ssh).handleRequest(e); 16223 } 16224 break; 16225 case EventType.PropertyNotify: 16226 // import core.stdc.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state); 16227 16228 foreach(ssh; SimpleWindow.impl.setSelectionHandlers) { 16229 if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete) 16230 ssh.sendMoreIncr(&e.xproperty); 16231 } 16232 16233 16234 if(auto win = e.xproperty.window in SimpleWindow.nativeMapping) 16235 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 16236 if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) { 16237 Atom target; 16238 int format; 16239 arch_ulong bytesafter, length; 16240 void* value; 16241 16242 ubyte[] s; 16243 Atom targetToKeep; 16244 16245 XGetWindowProperty( 16246 e.xproperty.display, 16247 e.xproperty.window, 16248 e.xproperty.atom, 16249 0, 16250 100000 /* length */, 16251 true, /* erase it to signal we got it and want more */ 16252 0 /*AnyPropertyType*/, 16253 &target, &format, &length, &bytesafter, &value); 16254 16255 if(!targetToKeep) 16256 targetToKeep = target; 16257 16258 auto id = (cast(ubyte*) value)[0 .. length]; 16259 16260 handler.handleIncrData(targetToKeep, id); 16261 if(length == 0) { 16262 win.getSelectionHandlers.remove(e.xproperty.atom); 16263 } 16264 16265 XFree(value); 16266 } 16267 } 16268 break; 16269 case EventType.SelectionNotify: 16270 // import std.stdio; writefln("SelectionNotify %06x %06x", e.xselection.requestor, e.xproperty.atom); 16271 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 16272 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 16273 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 16274 XUnlockDisplay(display); 16275 scope(exit) XLockDisplay(display); 16276 handler.handleData(None, null); 16277 win.getSelectionHandlers.remove(e.xproperty.atom); 16278 } else { 16279 Atom target; 16280 int format; 16281 arch_ulong bytesafter, length; 16282 void* value; 16283 XGetWindowProperty( 16284 e.xselection.display, 16285 e.xselection.requestor, 16286 e.xselection.property, 16287 0, 16288 100000 /* length */, 16289 //false, /* don't erase it */ 16290 true, /* do erase it lol */ 16291 0 /*AnyPropertyType*/, 16292 &target, &format, &length, &bytesafter, &value); 16293 16294 // FIXME: I don't have to copy it now since it is in char[] instead of string 16295 16296 { 16297 XUnlockDisplay(display); 16298 scope(exit) XLockDisplay(display); 16299 16300 if(target == XA_ATOM) { 16301 // initial request, see what they are able to work with and request the best one 16302 // we can handle, if available 16303 16304 Atom[] answer = (cast(Atom*) value)[0 .. length]; 16305 Atom best = handler.findBestFormat(answer); 16306 16307 /+ 16308 writeln("got ", answer); 16309 foreach(a; answer) 16310 writeln(XGetAtomName(display, a).stringz); 16311 writeln("best ", best); 16312 +/ 16313 16314 if(best != None) { 16315 // actually request the best format 16316 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 16317 } 16318 } else if(target == GetAtom!"INCR"(display)) { 16319 // incremental 16320 16321 handler.prepareIncremental(e.xselection.requestor, e.xselection.property); 16322 16323 // signal the sending program that we see 16324 // the incr and are ready to receive more. 16325 XDeleteProperty( 16326 e.xselection.display, 16327 e.xselection.requestor, 16328 e.xselection.property); 16329 } else { 16330 // unsupported type... maybe, forward, then we done with it 16331 if(target != None) { 16332 handler.handleData(target, cast(ubyte[]) value[0 .. length]); 16333 win.getSelectionHandlers.remove(e.xproperty.atom); 16334 } 16335 } 16336 } 16337 XFree(value); 16338 /* 16339 XDeleteProperty( 16340 e.xselection.display, 16341 e.xselection.requestor, 16342 e.xselection.property); 16343 */ 16344 } 16345 } 16346 break; 16347 case EventType.ConfigureNotify: 16348 auto event = e.xconfigure; 16349 if(auto win = event.window in SimpleWindow.nativeMapping) { 16350 if(win.windowType == WindowTypes.minimallyWrapped) 16351 break; 16352 //version(sdddd) { writeln(" w=", event.width, "; h=", event.height); } 16353 16354 /+ 16355 The ICCCM says window managers must send a synthetic event when the window 16356 is moved but NOT when it is resized. In the resize case, an event is sent 16357 with position (0, 0) which can be wrong and break the dpi calculations. 16358 16359 So we only consider the synthetic events from the WM and otherwise 16360 need to wait for some other event to get the position which... sucks. 16361 16362 I'd rather not have windows changing their layout on mouse motion after 16363 switching monitors... might be forced to but for now just ignoring it. 16364 16365 Easiest way to switch monitors without sending a size position is by 16366 maximize or fullscreen in a setup like mine, but on most setups those 16367 work on the monitor it is already living on, so it should be ok most the 16368 time. 16369 +/ 16370 if(event.send_event) { 16371 win.screenPositionKnown = true; 16372 win.screenPositionX = event.x; 16373 win.screenPositionY = event.y; 16374 win.updateActualDpi(); 16375 } 16376 16377 win.updateIMEPopupLocation(); 16378 recordX11ResizeAsync(display, *win, event.width, event.height); 16379 } 16380 break; 16381 case EventType.Expose: 16382 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 16383 if(win.windowType == WindowTypes.minimallyWrapped) 16384 break; 16385 // if it is closing from a popup menu, it can get 16386 // an Expose event right by the end and trigger a 16387 // BadDrawable error ... we'll just check 16388 // closed to handle that. 16389 if((*win).closed) break; 16390 if((*win).openglMode == OpenGlOptions.no) { 16391 bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh 16392 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 16393 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); 16394 } else { 16395 // need to redraw the scene somehow 16396 if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all 16397 XUnlockDisplay(display); 16398 scope(exit) XLockDisplay(display); 16399 version(without_opengl) {} else 16400 win.redrawOpenGlSceneSoon(); 16401 } 16402 } 16403 } 16404 break; 16405 case EventType.FocusIn: 16406 case EventType.FocusOut: 16407 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 16408 16409 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 16410 /+ 16411 16412 void info(string detail) { 16413 string s; 16414 // import std.conv; 16415 // import std.datetime; 16416 s ~= to!string(Clock.currTime); 16417 s ~= " "; 16418 s ~= e.type == EventType.FocusIn ? "in " : "out"; 16419 s ~= " "; 16420 s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main "; 16421 s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed "; 16422 s ~= detail; 16423 s ~= " "; 16424 16425 sdpyPrintDebugString(s); 16426 16427 } 16428 16429 switch(e.xfocus.detail) { 16430 case NotifyDetail.NotifyAncestor: info("Ancestor"); break; 16431 case NotifyDetail.NotifyVirtual: info("Virtual"); break; 16432 case NotifyDetail.NotifyInferior: info("Inferior"); break; 16433 case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break; 16434 case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break; 16435 case NotifyDetail.NotifyPointer: info("pointer"); break; 16436 case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break; 16437 case NotifyDetail.NotifyDetailNone: info("none"); break; 16438 default: 16439 16440 } 16441 +/ 16442 16443 16444 if(e.xfocus.detail == NotifyDetail.NotifyPointer) 16445 break; // just ignore these they seem irrelevant 16446 16447 auto old = win._focused; 16448 win._focused = e.type == EventType.FocusIn; 16449 16450 // yes, we are losing the focus, but to our own child. that's actually kinda keeping it. 16451 if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior) 16452 win._focused = true; 16453 16454 if(win.demandingAttention) 16455 demandAttention(*win, false); 16456 16457 win.updateIMEFocused(); 16458 16459 if(old != win._focused && win.onFocusChange) { 16460 XUnlockDisplay(display); 16461 scope(exit) XLockDisplay(display); 16462 win.onFocusChange(win._focused); 16463 } 16464 } 16465 break; 16466 case EventType.VisibilityNotify: 16467 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 16468 auto before = (*win)._visible; 16469 (*win)._visible = (e.xvisibility.state != VisibilityNotify.VisibilityFullyObscured); 16470 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 16471 if (win.visibilityChanged !is null && before == true) { 16472 XUnlockDisplay(display); 16473 scope(exit) XLockDisplay(display); 16474 win.visibilityChanged(false); 16475 } 16476 } else { 16477 if (win.visibilityChanged !is null && before == false) { 16478 XUnlockDisplay(display); 16479 scope(exit) XLockDisplay(display); 16480 win.visibilityChanged(true); 16481 } 16482 } 16483 } 16484 break; 16485 case EventType.ClientMessage: 16486 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 16487 // "ignore next mouse motion" event, increment ignore counter for teh window 16488 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16489 ++(*win).warpEventCount; 16490 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 16491 } else { 16492 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 16493 } 16494 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 16495 // user clicked the close button on the window manager 16496 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16497 XUnlockDisplay(display); 16498 scope(exit) XLockDisplay(display); 16499 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 16500 } 16501 16502 } else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) { 16503 // writeln("HAPPENED"); 16504 // user clicked the close button on the window manager 16505 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16506 XUnlockDisplay(display); 16507 scope(exit) XLockDisplay(display); 16508 16509 auto setTo = *win; 16510 16511 if(win.setRequestedInputFocus !is null) { 16512 auto s = win.setRequestedInputFocus(); 16513 if(s !is null) { 16514 setTo = s; 16515 } 16516 } 16517 16518 assert(setTo !is null); 16519 16520 // FIXME: so this is actually supposed to focus to a relevant child window if appropriate 16521 16522 // sdpyPrintDebugString("WM_TAKE_FOCUS ", setTo.impl.window); 16523 XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]); 16524 } 16525 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 16526 foreach(nai; NotificationAreaIcon.activeIcons) 16527 nai.newManager(); 16528 } else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16529 16530 bool xDragWindow = true; 16531 if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) { 16532 //XDefineCursor(display, xDragWindow.impl.window, 16533 //writeln("XdndStatus ", e.xclient.data.l); 16534 } 16535 if(auto dh = win.dropHandler) { 16536 16537 static Atom[3] xFormatsBuffer; 16538 static Atom[] xFormats; 16539 16540 void resetXFormats() { 16541 xFormatsBuffer[] = 0; 16542 xFormats = xFormatsBuffer[]; 16543 } 16544 16545 if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) { 16546 // on Windows it is supposed to return the effect you actually do FIXME 16547 16548 auto sourceWindow = e.xclient.data.l[0]; 16549 16550 xFormatsBuffer[0] = e.xclient.data.l[2]; 16551 xFormatsBuffer[1] = e.xclient.data.l[3]; 16552 xFormatsBuffer[2] = e.xclient.data.l[4]; 16553 16554 if(e.xclient.data.l[1] & 1) { 16555 // can just grab it all but like we don't necessarily need them... 16556 xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM); 16557 } else { 16558 int len; 16559 foreach(fmt; xFormatsBuffer) 16560 if(fmt) len++; 16561 xFormats = xFormatsBuffer[0 .. len]; 16562 } 16563 16564 auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats); 16565 16566 dh.dragEnter(&pkg); 16567 } else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) { 16568 16569 auto pack = e.xclient.data.l[2]; 16570 16571 auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords 16572 16573 16574 XClientMessageEvent xclient; 16575 16576 xclient.type = EventType.ClientMessage; 16577 xclient.window = e.xclient.data.l[0]; 16578 xclient.message_type = GetAtom!"XdndStatus"(display); 16579 xclient.format = 32; 16580 xclient.data.l[0] = win.impl.window; 16581 xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept 16582 auto r = result.consistentWithin; 16583 xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top); 16584 xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height); 16585 xclient.data.l[4] = dndActionAtom(e.xany.display, result.action); 16586 16587 XSendEvent( 16588 display, 16589 e.xclient.data.l[0], 16590 false, 16591 EventMask.NoEventMask, 16592 cast(XEvent*) &xclient 16593 ); 16594 16595 16596 } else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) { 16597 //writeln("XdndLeave"); 16598 // drop cancelled. 16599 // data.l[0] is the source window 16600 dh.dragLeave(); 16601 16602 resetXFormats(); 16603 } else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) { 16604 // drop happening, should fetch data, then send finished 16605 // writeln("XdndDrop"); 16606 16607 auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats); 16608 16609 dh.drop(&pkg); 16610 16611 resetXFormats(); 16612 } else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) { 16613 // writeln("XdndFinished"); 16614 16615 dh.finish(); 16616 } 16617 16618 } 16619 } 16620 break; 16621 case EventType.MapNotify: 16622 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 16623 auto before = (*win)._visible; 16624 (*win)._visible = true; 16625 if (!(*win)._visibleForTheFirstTimeCalled) { 16626 (*win)._visibleForTheFirstTimeCalled = true; 16627 if ((*win).visibleForTheFirstTime !is null) { 16628 XUnlockDisplay(display); 16629 scope(exit) XLockDisplay(display); 16630 (*win).visibleForTheFirstTime(); 16631 } 16632 } 16633 if ((*win).visibilityChanged !is null && before == false) { 16634 XUnlockDisplay(display); 16635 scope(exit) XLockDisplay(display); 16636 (*win).visibilityChanged(true); 16637 } 16638 } 16639 break; 16640 case EventType.UnmapNotify: 16641 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 16642 auto before = (*win)._visible; 16643 win._visible = false; 16644 if (win.visibilityChanged !is null && before == true) { 16645 XUnlockDisplay(display); 16646 scope(exit) XLockDisplay(display); 16647 win.visibilityChanged(false); 16648 } 16649 } 16650 break; 16651 case EventType.DestroyNotify: 16652 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 16653 if(win.destroyed) 16654 break; // might get a notification both for itself and from its parent 16655 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 16656 win._closed = true; // just in case 16657 win.destroyed = true; 16658 if (win.xic !is null) { 16659 XDestroyIC(win.xic); 16660 win.xic = null; // just in case 16661 } 16662 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 16663 bool anyImportant = false; 16664 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 16665 if(w.beingOpenKeepsAppOpen) { 16666 anyImportant = true; 16667 break; 16668 } 16669 if(!anyImportant) { 16670 EventLoop.quitApplication(); 16671 done = true; 16672 } 16673 } 16674 auto window = e.xdestroywindow.window; 16675 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 16676 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 16677 16678 version(with_eventloop) { 16679 if(done) exit(); 16680 } 16681 break; 16682 16683 case EventType.MotionNotify: 16684 MouseEvent mouse; 16685 auto event = e.xmotion; 16686 16687 mouse.type = MouseEventType.motion; 16688 mouse.x = event.x; 16689 mouse.y = event.y; 16690 mouse.modifierState = event.state; 16691 16692 mouse.timestamp = event.time; 16693 16694 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 16695 mouse.window = *win; 16696 if (win.warpEventCount > 0) { 16697 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 16698 --(*win).warpEventCount; 16699 (*win).mdx(mouse); // so deltas will be correctly updated 16700 } else { 16701 win.warpEventCount = 0; // just in case 16702 (*win).mdx(mouse); 16703 if((*win).handleMouseEvent) { 16704 XUnlockDisplay(display); 16705 scope(exit) XLockDisplay(display); 16706 (*win).handleMouseEvent(mouse); 16707 } 16708 } 16709 } 16710 16711 version(with_eventloop) 16712 send(mouse); 16713 break; 16714 case EventType.ButtonPress: 16715 case EventType.ButtonRelease: 16716 MouseEvent mouse; 16717 auto event = e.xbutton; 16718 16719 mouse.timestamp = event.time; 16720 16721 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 16722 mouse.x = event.x; 16723 mouse.y = event.y; 16724 16725 static Time lastMouseDownTime = 0; 16726 static int lastMouseDownButton = -1; 16727 16728 mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 16729 if(e.type == EventType.ButtonPress) { 16730 lastMouseDownTime = event.time; 16731 lastMouseDownButton = event.button; 16732 } 16733 16734 switch(event.button) { 16735 case 1: mouse.button = MouseButton.left; break; // left 16736 case 2: mouse.button = MouseButton.middle; break; // middle 16737 case 3: mouse.button = MouseButton.right; break; // right 16738 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 16739 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 16740 case 6: break; // idk 16741 case 7: break; // idk 16742 case 8: mouse.button = MouseButton.backButton; break; 16743 case 9: mouse.button = MouseButton.forwardButton; break; 16744 default: 16745 } 16746 16747 // FIXME: double check this 16748 mouse.modifierState = event.state; 16749 16750 //mouse.modifierState = event.detail; 16751 16752 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 16753 mouse.window = *win; 16754 (*win).mdx(mouse); 16755 if((*win).handleMouseEvent) { 16756 XUnlockDisplay(display); 16757 scope(exit) XLockDisplay(display); 16758 (*win).handleMouseEvent(mouse); 16759 } 16760 } 16761 version(with_eventloop) 16762 send(mouse); 16763 break; 16764 16765 case EventType.KeyPress: 16766 case EventType.KeyRelease: 16767 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 16768 KeyEvent ke; 16769 ke.pressed = e.type == EventType.KeyPress; 16770 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 16771 16772 auto sym = XKeycodeToKeysym( 16773 XDisplayConnection.get(), 16774 e.xkey.keycode, 16775 0); 16776 16777 ke.key = cast(Key) sym;//e.xkey.keycode; 16778 16779 ke.modifierState = e.xkey.state; 16780 16781 // writefln("%x", sym); 16782 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 16783 int charbuflen = 0; // return value of XwcLookupString 16784 if (ke.pressed) { 16785 auto win = e.xkey.window in SimpleWindow.nativeMapping; 16786 if (win !is null && win.xic !is null) { 16787 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 16788 Status status; 16789 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 16790 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 16791 } else { 16792 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 16793 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 16794 char[16] buffer; 16795 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 16796 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 16797 } 16798 } 16799 16800 // if there's no char, subst one 16801 if (charbuflen == 0) { 16802 switch (sym) { 16803 case 0xff09: charbuf[charbuflen++] = '\t'; break; 16804 case 0xff8d: // keypad enter 16805 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 16806 default : // ignore 16807 } 16808 } 16809 16810 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 16811 ke.window = *win; 16812 16813 16814 if(win.inputProxy) 16815 win = &win.inputProxy; 16816 16817 // char events are separate since they are on Windows too 16818 // also, xcompose can generate long char sequences 16819 // don't send char events if Meta and/or Hyper is pressed 16820 // TODO: ctrl+char should only send control chars; not yet 16821 if ((e.xkey.state&ModifierState.ctrl) != 0) { 16822 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 16823 } 16824 16825 dchar[32] charsComingBuffer; 16826 int charsComingPosition; 16827 dchar[] charsComing = charsComingBuffer[]; 16828 16829 if (ke.pressed && charbuflen > 0) { 16830 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 16831 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 16832 if(charsComingPosition >= charsComing.length) 16833 charsComing.length = charsComingPosition + 8; 16834 16835 charsComing[charsComingPosition++] = ch; 16836 } 16837 16838 charsComing = charsComing[0 .. charsComingPosition]; 16839 } else { 16840 charsComing = null; 16841 } 16842 16843 ke.charsPossible = charsComing; 16844 16845 if (win.handleKeyEvent) { 16846 XUnlockDisplay(display); 16847 scope(exit) XLockDisplay(display); 16848 win.handleKeyEvent(ke); 16849 } 16850 16851 // Super and alt modifier keys never actually send the chars, they are assumed to be special. 16852 if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) { 16853 XUnlockDisplay(display); 16854 scope(exit) XLockDisplay(display); 16855 foreach(ch; charsComing) 16856 win.handleCharEvent(ch); 16857 } 16858 } 16859 16860 version(with_eventloop) 16861 send(ke); 16862 break; 16863 default: 16864 } 16865 16866 return done; 16867 } 16868 } 16869 16870 /* *************************************** */ 16871 /* Done with simpledisplay stuff */ 16872 /* *************************************** */ 16873 16874 // Necessary C library bindings follow 16875 version(Windows) {} else 16876 version(Emscripten) {} else 16877 version(X11) { 16878 16879 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 16880 16881 // X11 bindings needed here 16882 /* 16883 A little of this is from the bindings project on 16884 D Source and some of it is copy/paste from the C 16885 header. 16886 16887 The DSource listing consistently used D's long 16888 where C used long. That's wrong - C long is 32 bit, so 16889 it should be int in D. I changed that here. 16890 16891 Note: 16892 This isn't complete, just took what I needed for myself. 16893 */ 16894 16895 import core.stdc.stddef : wchar_t; 16896 16897 interface XLib { 16898 extern(C) nothrow @nogc { 16899 char* XResourceManagerString(Display*); 16900 void XrmInitialize(); 16901 XrmDatabase XrmGetStringDatabase(char* data); 16902 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 16903 16904 Cursor XCreateFontCursor(Display*, uint shape); 16905 int XDefineCursor(Display* display, Window w, Cursor cursor); 16906 int XUndefineCursor(Display* display, Window w); 16907 16908 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 16909 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 16910 int XFreeCursor(Display* display, Cursor cursor); 16911 16912 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 16913 16914 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 16915 16916 XVaNestedList XVaCreateNestedList(int unused, ...); 16917 16918 char *XKeysymToString(KeySym keysym); 16919 KeySym XKeycodeToKeysym( 16920 Display* /* display */, 16921 KeyCode /* keycode */, 16922 int /* index */ 16923 ); 16924 16925 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 16926 16927 int XFree(void*); 16928 int XDeleteProperty(Display *display, Window w, Atom property); 16929 16930 // int XSetCommand(Display*, Window, const char**, int); 16931 16932 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements); 16933 16934 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 16935 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 16936 *actual_type_return, int *actual_format_return, arch_ulong 16937 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 16938 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 16939 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 16940 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 16941 16942 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 16943 16944 Window XGetSelectionOwner(Display *display, Atom selection); 16945 16946 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 16947 16948 char** XListFonts(Display*, const char*, int, int*); 16949 void XFreeFontNames(char**); 16950 16951 Display* XOpenDisplay(const char*); 16952 int XCloseDisplay(Display*); 16953 16954 int function() XSynchronize(Display*, bool); 16955 int function() XSetAfterFunction(Display*, int function() proc); 16956 16957 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 16958 16959 Bool XSupportsLocale(); 16960 char* XSetLocaleModifiers(const(char)* modifier_list); 16961 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16962 Status XCloseOM(XOM om); 16963 16964 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16965 Status XCloseIM(XIM im); 16966 16967 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16968 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16969 Display* XDisplayOfIM(XIM im); 16970 char* XLocaleOfIM(XIM im); 16971 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 16972 void XDestroyIC(XIC ic); 16973 void XSetICFocus(XIC ic); 16974 void XUnsetICFocus(XIC ic); 16975 //wchar_t* XwcResetIC(XIC ic); 16976 char* XmbResetIC(XIC ic); 16977 char* Xutf8ResetIC(XIC ic); 16978 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16979 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16980 XIM XIMOfIC(XIC ic); 16981 16982 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 16983 16984 16985 XFontStruct *XLoadQueryFont(Display *display, scope const char *name); 16986 int XFreeFont(Display *display, XFontStruct *font_struct); 16987 int XSetFont(Display* display, GC gc, Font font); 16988 int XTextWidth(XFontStruct*, scope const char*, int); 16989 16990 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 16991 int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n); 16992 16993 Window XCreateSimpleWindow( 16994 Display* /* display */, 16995 Window /* parent */, 16996 int /* x */, 16997 int /* y */, 16998 uint /* width */, 16999 uint /* height */, 17000 uint /* border_width */, 17001 uint /* border */, 17002 uint /* background */ 17003 ); 17004 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); 17005 17006 int XReparentWindow(Display*, Window, Window, int, int); 17007 int XClearWindow(Display*, Window); 17008 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 17009 int XMoveWindow(Display*, Window, int, int); 17010 int XResizeWindow(Display *display, Window w, uint width, uint height); 17011 17012 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 17013 17014 Status XMatchVisualInfo(Display *display, int screen, int depth, int class_, XVisualInfo *vinfo_return); 17015 17016 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 17017 17018 XImage *XCreateImage( 17019 Display* /* display */, 17020 Visual* /* visual */, 17021 uint /* depth */, 17022 int /* format */, 17023 int /* offset */, 17024 ubyte* /* data */, 17025 uint /* width */, 17026 uint /* height */, 17027 int /* bitmap_pad */, 17028 int /* bytes_per_line */ 17029 ); 17030 17031 Status XInitImage (XImage* image); 17032 17033 Atom XInternAtom( 17034 Display* /* display */, 17035 const char* /* atom_name */, 17036 Bool /* only_if_exists */ 17037 ); 17038 17039 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 17040 char* XGetAtomName(Display*, Atom); 17041 Status XGetAtomNames(Display*, Atom*, int count, char**); 17042 17043 int XPutImage( 17044 Display* /* display */, 17045 Drawable /* d */, 17046 GC /* gc */, 17047 XImage* /* image */, 17048 int /* src_x */, 17049 int /* src_y */, 17050 int /* dest_x */, 17051 int /* dest_y */, 17052 uint /* width */, 17053 uint /* height */ 17054 ); 17055 17056 XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format); 17057 17058 17059 int XDestroyWindow( 17060 Display* /* display */, 17061 Window /* w */ 17062 ); 17063 17064 int XDestroyImage(XImage*); 17065 17066 int XSelectInput( 17067 Display* /* display */, 17068 Window /* w */, 17069 EventMask /* event_mask */ 17070 ); 17071 17072 int XMapWindow( 17073 Display* /* display */, 17074 Window /* w */ 17075 ); 17076 17077 Status XIconifyWindow(Display*, Window, int); 17078 int XMapRaised(Display*, Window); 17079 int XMapSubwindows(Display*, Window); 17080 17081 int XNextEvent( 17082 Display* /* display */, 17083 XEvent* /* event_return */ 17084 ); 17085 17086 int XMaskEvent(Display*, arch_long, XEvent*); 17087 17088 Bool XFilterEvent(XEvent *event, Window window); 17089 int XRefreshKeyboardMapping(XMappingEvent *event_map); 17090 17091 Status XSetWMProtocols( 17092 Display* /* display */, 17093 Window /* w */, 17094 Atom* /* protocols */, 17095 int /* count */ 17096 ); 17097 17098 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 17099 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 17100 17101 17102 Status XInitThreads(); 17103 void XLockDisplay (Display* display); 17104 void XUnlockDisplay (Display* display); 17105 17106 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 17107 17108 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 17109 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 17110 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 17111 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 17112 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 17113 17114 17115 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 17116 int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int); 17117 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 17118 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 17119 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 17120 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 17121 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 17122 int XDrawPoint(Display*, Drawable, GC, int, int); 17123 int XSetForeground(Display*, GC, uint); 17124 int XSetBackground(Display*, GC, uint); 17125 17126 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 17127 void XFreeFontSet(Display*, XFontSet); 17128 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int); 17129 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 17130 17131 int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 17132 17133 17134 //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); 17135 17136 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 17137 int XSetFunction(Display*, GC, int); 17138 17139 GC XCreateGC(Display*, Drawable, uint, void*); 17140 int XCopyGC(Display*, GC, uint, GC); 17141 int XFreeGC(Display*, GC); 17142 17143 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 17144 bool XCheckMaskEvent(Display*, int, XEvent*); 17145 17146 int XPending(Display*); 17147 int XEventsQueued(Display* display, int mode); 17148 17149 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 17150 int XFreePixmap(Display*, Pixmap); 17151 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 17152 int XFlush(Display*); 17153 int XBell(Display*, int); 17154 int XSync(Display*, bool); 17155 17156 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 17157 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 17158 17159 int XGrabKeyboard(Display*, Window, Bool, int, int, Time); 17160 int XUngrabKeyboard(Display*, Time); 17161 17162 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 17163 17164 KeySym XStringToKeysym(const char *string); 17165 17166 Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return); 17167 17168 Window XDefaultRootWindow(Display*); 17169 17170 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); 17171 17172 int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 17173 17174 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 17175 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 17176 17177 Status XAllocColor(Display*, Colormap, XColor*); 17178 17179 int XWithdrawWindow(Display*, Window, int); 17180 int XUnmapWindow(Display*, Window); 17181 int XLowerWindow(Display*, Window); 17182 int XRaiseWindow(Display*, Window); 17183 17184 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); 17185 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); 17186 17187 int XGetInputFocus(Display*, Window*, int*); 17188 int XSetInputFocus(Display*, Window, int, Time); 17189 17190 XErrorHandler XSetErrorHandler(XErrorHandler); 17191 17192 int XGetErrorText(Display*, int, char*, int); 17193 17194 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 17195 17196 17197 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); 17198 int XUngrabPointer(Display *display, Time time); 17199 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 17200 17201 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 17202 17203 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 17204 int XSetClipMask(Display*, GC, Pixmap); 17205 int XSetClipOrigin(Display*, GC, int, int); 17206 17207 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 17208 17209 void XSetWMName(Display*, Window, XTextProperty*); 17210 Status XGetWMName(Display*, Window, XTextProperty*); 17211 int XStoreName(Display* display, Window w, const(char)* window_name); 17212 17213 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 17214 17215 } 17216 } 17217 17218 interface Xext { 17219 extern(C) nothrow @nogc { 17220 Status XShmAttach(Display*, XShmSegmentInfo*); 17221 Status XShmDetach(Display*, XShmSegmentInfo*); 17222 Status XShmPutImage( 17223 Display* /* dpy */, 17224 Drawable /* d */, 17225 GC /* gc */, 17226 XImage* /* image */, 17227 int /* src_x */, 17228 int /* src_y */, 17229 int /* dst_x */, 17230 int /* dst_y */, 17231 uint /* src_width */, 17232 uint /* src_height */, 17233 Bool /* send_event */ 17234 ); 17235 17236 Status XShmQueryExtension(Display*); 17237 17238 XImage *XShmCreateImage( 17239 Display* /* dpy */, 17240 Visual* /* visual */, 17241 uint /* depth */, 17242 int /* format */, 17243 char* /* data */, 17244 XShmSegmentInfo* /* shminfo */, 17245 uint /* width */, 17246 uint /* height */ 17247 ); 17248 17249 Pixmap XShmCreatePixmap( 17250 Display* /* dpy */, 17251 Drawable /* d */, 17252 char* /* data */, 17253 XShmSegmentInfo* /* shminfo */, 17254 uint /* width */, 17255 uint /* height */, 17256 uint /* depth */ 17257 ); 17258 17259 } 17260 } 17261 17262 // this requires -lXpm 17263 //int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 17264 17265 17266 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib; 17267 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext; 17268 shared static this() { 17269 xlib.loadDynamicLibrary(); 17270 xext.loadDynamicLibrary(); 17271 } 17272 17273 17274 extern(C) nothrow @nogc { 17275 17276 alias XrmDatabase = void*; 17277 struct XrmValue { 17278 uint size; 17279 void* addr; 17280 } 17281 17282 struct XVisualInfo { 17283 Visual* visual; 17284 VisualID visualid; 17285 int screen; 17286 uint depth; 17287 int c_class; 17288 c_ulong red_mask; 17289 c_ulong green_mask; 17290 c_ulong blue_mask; 17291 int colormap_size; 17292 int bits_per_rgb; 17293 } 17294 17295 enum VisualNoMask= 0x0; 17296 enum VisualIDMask= 0x1; 17297 enum VisualScreenMask=0x2; 17298 enum VisualDepthMask= 0x4; 17299 enum VisualClassMask= 0x8; 17300 enum VisualRedMaskMask=0x10; 17301 enum VisualGreenMaskMask=0x20; 17302 enum VisualBlueMaskMask=0x40; 17303 enum VisualColormapSizeMask=0x80; 17304 enum VisualBitsPerRGBMask=0x100; 17305 enum VisualAllMask= 0x1FF; 17306 17307 enum AnyKey = 0; 17308 enum AnyModifier = 1 << 15; 17309 17310 // XIM and other crap 17311 struct _XOM {} 17312 struct _XIM {} 17313 struct _XIC {} 17314 alias XOM = _XOM*; 17315 alias XIM = _XIM*; 17316 alias XIC = _XIC*; 17317 17318 alias XVaNestedList = void*; 17319 17320 alias XIMStyle = arch_ulong; 17321 enum : arch_ulong { 17322 XIMPreeditArea = 0x0001, 17323 XIMPreeditCallbacks = 0x0002, 17324 XIMPreeditPosition = 0x0004, 17325 XIMPreeditNothing = 0x0008, 17326 XIMPreeditNone = 0x0010, 17327 XIMStatusArea = 0x0100, 17328 XIMStatusCallbacks = 0x0200, 17329 XIMStatusNothing = 0x0400, 17330 XIMStatusNone = 0x0800, 17331 } 17332 17333 17334 /* X Shared Memory Extension functions */ 17335 //pragma(lib, "Xshm"); 17336 alias arch_ulong ShmSeg; 17337 struct XShmSegmentInfo { 17338 ShmSeg shmseg; 17339 int shmid; 17340 ubyte* shmaddr; 17341 Bool readOnly; 17342 } 17343 17344 // and the necessary OS functions 17345 int shmget(int, size_t, int); 17346 void* shmat(int, scope const void*, int); 17347 int shmdt(scope const void*); 17348 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 17349 17350 enum IPC_PRIVATE = 0; 17351 enum IPC_CREAT = 512; 17352 enum IPC_RMID = 0; 17353 17354 /* MIT-SHM end */ 17355 17356 17357 enum MappingType:int { 17358 MappingModifier =0, 17359 MappingKeyboard =1, 17360 MappingPointer =2 17361 } 17362 17363 /* ImageFormat -- PutImage, GetImage */ 17364 enum ImageFormat:int { 17365 XYBitmap =0, /* depth 1, XYFormat */ 17366 XYPixmap =1, /* depth == drawable depth */ 17367 ZPixmap =2 /* depth == drawable depth */ 17368 } 17369 17370 enum ModifierName:int { 17371 ShiftMapIndex =0, 17372 LockMapIndex =1, 17373 ControlMapIndex =2, 17374 Mod1MapIndex =3, 17375 Mod2MapIndex =4, 17376 Mod3MapIndex =5, 17377 Mod4MapIndex =6, 17378 Mod5MapIndex =7 17379 } 17380 17381 enum ButtonMask:int { 17382 Button1Mask =1<<8, 17383 Button2Mask =1<<9, 17384 Button3Mask =1<<10, 17385 Button4Mask =1<<11, 17386 Button5Mask =1<<12, 17387 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 17388 } 17389 17390 enum KeyOrButtonMask:uint { 17391 ShiftMask =1<<0, 17392 LockMask =1<<1, 17393 ControlMask =1<<2, 17394 Mod1Mask =1<<3, 17395 Mod2Mask =1<<4, 17396 Mod3Mask =1<<5, 17397 Mod4Mask =1<<6, 17398 Mod5Mask =1<<7, 17399 Button1Mask =1<<8, 17400 Button2Mask =1<<9, 17401 Button3Mask =1<<10, 17402 Button4Mask =1<<11, 17403 Button5Mask =1<<12, 17404 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 17405 } 17406 17407 enum ButtonName:int { 17408 Button1 =1, 17409 Button2 =2, 17410 Button3 =3, 17411 Button4 =4, 17412 Button5 =5 17413 } 17414 17415 /* Notify modes */ 17416 enum NotifyModes:int 17417 { 17418 NotifyNormal =0, 17419 NotifyGrab =1, 17420 NotifyUngrab =2, 17421 NotifyWhileGrabbed =3 17422 } 17423 enum NotifyHint = 1; /* for MotionNotify events */ 17424 17425 /* Notify detail */ 17426 enum NotifyDetail:int 17427 { 17428 NotifyAncestor =0, 17429 NotifyVirtual =1, 17430 NotifyInferior =2, 17431 NotifyNonlinear =3, 17432 NotifyNonlinearVirtual =4, 17433 NotifyPointer =5, 17434 NotifyPointerRoot =6, 17435 NotifyDetailNone =7 17436 } 17437 17438 /* Visibility notify */ 17439 17440 enum VisibilityNotify:int 17441 { 17442 VisibilityUnobscured =0, 17443 VisibilityPartiallyObscured =1, 17444 VisibilityFullyObscured =2 17445 } 17446 17447 17448 enum WindowStackingMethod:int 17449 { 17450 Above =0, 17451 Below =1, 17452 TopIf =2, 17453 BottomIf =3, 17454 Opposite =4 17455 } 17456 17457 /* Circulation request */ 17458 enum CirculationRequest:int 17459 { 17460 PlaceOnTop =0, 17461 PlaceOnBottom =1 17462 } 17463 17464 enum PropertyNotification:int 17465 { 17466 PropertyNewValue =0, 17467 PropertyDelete =1 17468 } 17469 17470 enum ColorMapNotification:int 17471 { 17472 ColormapUninstalled =0, 17473 ColormapInstalled =1 17474 } 17475 17476 17477 struct _XPrivate {} 17478 struct _XrmHashBucketRec {} 17479 17480 alias void* XPointer; 17481 alias void* XExtData; 17482 17483 version( X86_64 ) { 17484 alias ulong XID; 17485 alias ulong arch_ulong; 17486 alias long arch_long; 17487 } else version (AArch64) { 17488 alias ulong XID; 17489 alias ulong arch_ulong; 17490 alias long arch_long; 17491 } else { 17492 alias uint XID; 17493 alias uint arch_ulong; 17494 alias int arch_long; 17495 } 17496 17497 alias XID Window; 17498 alias XID Drawable; 17499 alias XID Pixmap; 17500 17501 alias arch_ulong Atom; 17502 alias int Bool; 17503 alias Display XDisplay; 17504 17505 alias int ByteOrder; 17506 alias arch_ulong Time; 17507 alias void ScreenFormat; 17508 17509 struct XImage { 17510 int width, height; /* size of image */ 17511 int xoffset; /* number of pixels offset in X direction */ 17512 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 17513 void *data; /* pointer to image data */ 17514 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 17515 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 17516 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 17517 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 17518 int depth; /* depth of image */ 17519 int bytes_per_line; /* accelarator to next line */ 17520 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 17521 arch_ulong red_mask; /* bits in z arrangment */ 17522 arch_ulong green_mask; 17523 arch_ulong blue_mask; 17524 XPointer obdata; /* hook for the object routines to hang on */ 17525 static struct F { /* image manipulation routines */ 17526 XImage* function( 17527 XDisplay* /* display */, 17528 Visual* /* visual */, 17529 uint /* depth */, 17530 int /* format */, 17531 int /* offset */, 17532 ubyte* /* data */, 17533 uint /* width */, 17534 uint /* height */, 17535 int /* bitmap_pad */, 17536 int /* bytes_per_line */) create_image; 17537 int function(XImage *) destroy_image; 17538 arch_ulong function(XImage *, int, int) get_pixel; 17539 int function(XImage *, int, int, arch_ulong) put_pixel; 17540 XImage* function(XImage *, int, int, uint, uint) sub_image; 17541 int function(XImage *, arch_long) add_pixel; 17542 } 17543 F f; 17544 } 17545 version(X86_64) static assert(XImage.sizeof == 136); 17546 else version(X86) static assert(XImage.sizeof == 88); 17547 17548 struct XCharStruct { 17549 short lbearing; /* origin to left edge of raster */ 17550 short rbearing; /* origin to right edge of raster */ 17551 short width; /* advance to next char's origin */ 17552 short ascent; /* baseline to top edge of raster */ 17553 short descent; /* baseline to bottom edge of raster */ 17554 ushort attributes; /* per char flags (not predefined) */ 17555 } 17556 17557 /* 17558 * To allow arbitrary information with fonts, there are additional properties 17559 * returned. 17560 */ 17561 struct XFontProp { 17562 Atom name; 17563 arch_ulong card32; 17564 } 17565 17566 alias Atom Font; 17567 17568 struct XFontStruct { 17569 XExtData *ext_data; /* Hook for extension to hang data */ 17570 Font fid; /* Font ID for this font */ 17571 uint direction; /* Direction the font is painted */ 17572 uint min_char_or_byte2; /* First character */ 17573 uint max_char_or_byte2; /* Last character */ 17574 uint min_byte1; /* First row that exists (for two-byte fonts) */ 17575 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 17576 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 17577 uint default_char; /* Char to print for undefined character */ 17578 int n_properties; /* How many properties there are */ 17579 XFontProp *properties; /* Pointer to array of additional properties*/ 17580 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 17581 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 17582 XCharStruct *per_char; /* first_char to last_char information */ 17583 int ascent; /* Max extent above baseline for spacing */ 17584 int descent; /* Max descent below baseline for spacing */ 17585 } 17586 17587 17588 /* 17589 * Definitions of specific events. 17590 */ 17591 struct XKeyEvent 17592 { 17593 int type; /* of event */ 17594 arch_ulong serial; /* # of last request processed by server */ 17595 Bool send_event; /* true if this came from a SendEvent request */ 17596 Display *display; /* Display the event was read from */ 17597 Window window; /* "event" window it is reported relative to */ 17598 Window root; /* root window that the event occurred on */ 17599 Window subwindow; /* child window */ 17600 Time time; /* milliseconds */ 17601 int x, y; /* pointer x, y coordinates in event window */ 17602 int x_root, y_root; /* coordinates relative to root */ 17603 KeyOrButtonMask state; /* key or button mask */ 17604 uint keycode; /* detail */ 17605 Bool same_screen; /* same screen flag */ 17606 } 17607 version(X86_64) static assert(XKeyEvent.sizeof == 96); 17608 alias XKeyEvent XKeyPressedEvent; 17609 alias XKeyEvent XKeyReleasedEvent; 17610 17611 struct XButtonEvent 17612 { 17613 int type; /* of event */ 17614 arch_ulong serial; /* # of last request processed by server */ 17615 Bool send_event; /* true if this came from a SendEvent request */ 17616 Display *display; /* Display the event was read from */ 17617 Window window; /* "event" window it is reported relative to */ 17618 Window root; /* root window that the event occurred on */ 17619 Window subwindow; /* child window */ 17620 Time time; /* milliseconds */ 17621 int x, y; /* pointer x, y coordinates in event window */ 17622 int x_root, y_root; /* coordinates relative to root */ 17623 KeyOrButtonMask state; /* key or button mask */ 17624 uint button; /* detail */ 17625 Bool same_screen; /* same screen flag */ 17626 } 17627 alias XButtonEvent XButtonPressedEvent; 17628 alias XButtonEvent XButtonReleasedEvent; 17629 17630 struct XMotionEvent{ 17631 int type; /* of event */ 17632 arch_ulong serial; /* # of last request processed by server */ 17633 Bool send_event; /* true if this came from a SendEvent request */ 17634 Display *display; /* Display the event was read from */ 17635 Window window; /* "event" window reported relative to */ 17636 Window root; /* root window that the event occurred on */ 17637 Window subwindow; /* child window */ 17638 Time time; /* milliseconds */ 17639 int x, y; /* pointer x, y coordinates in event window */ 17640 int x_root, y_root; /* coordinates relative to root */ 17641 KeyOrButtonMask state; /* key or button mask */ 17642 byte is_hint; /* detail */ 17643 Bool same_screen; /* same screen flag */ 17644 } 17645 alias XMotionEvent XPointerMovedEvent; 17646 17647 struct XCrossingEvent{ 17648 int type; /* of event */ 17649 arch_ulong serial; /* # of last request processed by server */ 17650 Bool send_event; /* true if this came from a SendEvent request */ 17651 Display *display; /* Display the event was read from */ 17652 Window window; /* "event" window reported relative to */ 17653 Window root; /* root window that the event occurred on */ 17654 Window subwindow; /* child window */ 17655 Time time; /* milliseconds */ 17656 int x, y; /* pointer x, y coordinates in event window */ 17657 int x_root, y_root; /* coordinates relative to root */ 17658 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 17659 NotifyDetail detail; 17660 /* 17661 * NotifyAncestor, NotifyVirtual, NotifyInferior, 17662 * NotifyNonlinear,NotifyNonlinearVirtual 17663 */ 17664 Bool same_screen; /* same screen flag */ 17665 Bool focus; /* Boolean focus */ 17666 KeyOrButtonMask state; /* key or button mask */ 17667 } 17668 alias XCrossingEvent XEnterWindowEvent; 17669 alias XCrossingEvent XLeaveWindowEvent; 17670 17671 struct XFocusChangeEvent{ 17672 int type; /* FocusIn or FocusOut */ 17673 arch_ulong serial; /* # of last request processed by server */ 17674 Bool send_event; /* true if this came from a SendEvent request */ 17675 Display *display; /* Display the event was read from */ 17676 Window window; /* window of event */ 17677 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 17678 NotifyGrab, NotifyUngrab */ 17679 NotifyDetail detail; 17680 /* 17681 * NotifyAncestor, NotifyVirtual, NotifyInferior, 17682 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 17683 * NotifyPointerRoot, NotifyDetailNone 17684 */ 17685 } 17686 alias XFocusChangeEvent XFocusInEvent; 17687 alias XFocusChangeEvent XFocusOutEvent; 17688 17689 enum CWBackPixmap = (1L<<0); 17690 enum CWBackPixel = (1L<<1); 17691 enum CWBorderPixmap = (1L<<2); 17692 enum CWBorderPixel = (1L<<3); 17693 enum CWBitGravity = (1L<<4); 17694 enum CWWinGravity = (1L<<5); 17695 enum CWBackingStore = (1L<<6); 17696 enum CWBackingPlanes = (1L<<7); 17697 enum CWBackingPixel = (1L<<8); 17698 enum CWOverrideRedirect = (1L<<9); 17699 enum CWSaveUnder = (1L<<10); 17700 enum CWEventMask = (1L<<11); 17701 enum CWDontPropagate = (1L<<12); 17702 enum CWColormap = (1L<<13); 17703 enum CWCursor = (1L<<14); 17704 17705 struct XWindowAttributes { 17706 int x, y; /* location of window */ 17707 int width, height; /* width and height of window */ 17708 int border_width; /* border width of window */ 17709 int depth; /* depth of window */ 17710 Visual *visual; /* the associated visual structure */ 17711 Window root; /* root of screen containing window */ 17712 int class_; /* InputOutput, InputOnly*/ 17713 int bit_gravity; /* one of the bit gravity values */ 17714 int win_gravity; /* one of the window gravity values */ 17715 int backing_store; /* NotUseful, WhenMapped, Always */ 17716 arch_ulong backing_planes; /* planes to be preserved if possible */ 17717 arch_ulong backing_pixel; /* value to be used when restoring planes */ 17718 Bool save_under; /* boolean, should bits under be saved? */ 17719 Colormap colormap; /* color map to be associated with window */ 17720 Bool map_installed; /* boolean, is color map currently installed*/ 17721 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 17722 arch_long all_event_masks; /* set of events all people have interest in*/ 17723 arch_long your_event_mask; /* my event mask */ 17724 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 17725 Bool override_redirect; /* boolean value for override-redirect */ 17726 Screen *screen; /* back pointer to correct screen */ 17727 } 17728 17729 enum IsUnmapped = 0; 17730 enum IsUnviewable = 1; 17731 enum IsViewable = 2; 17732 17733 struct XSetWindowAttributes { 17734 Pixmap background_pixmap;/* background, None, or ParentRelative */ 17735 arch_ulong background_pixel;/* background pixel */ 17736 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 17737 arch_ulong border_pixel;/* border pixel value */ 17738 int bit_gravity; /* one of bit gravity values */ 17739 int win_gravity; /* one of the window gravity values */ 17740 int backing_store; /* NotUseful, WhenMapped, Always */ 17741 arch_ulong backing_planes;/* planes to be preserved if possible */ 17742 arch_ulong backing_pixel;/* value to use in restoring planes */ 17743 Bool save_under; /* should bits under be saved? (popups) */ 17744 arch_long event_mask; /* set of events that should be saved */ 17745 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 17746 Bool override_redirect; /* boolean value for override_redirect */ 17747 Colormap colormap; /* color map to be associated with window */ 17748 Cursor cursor; /* cursor to be displayed (or None) */ 17749 } 17750 17751 17752 alias int Status; 17753 17754 17755 enum EventMask:int 17756 { 17757 NoEventMask =0, 17758 KeyPressMask =1<<0, 17759 KeyReleaseMask =1<<1, 17760 ButtonPressMask =1<<2, 17761 ButtonReleaseMask =1<<3, 17762 EnterWindowMask =1<<4, 17763 LeaveWindowMask =1<<5, 17764 PointerMotionMask =1<<6, 17765 PointerMotionHintMask =1<<7, 17766 Button1MotionMask =1<<8, 17767 Button2MotionMask =1<<9, 17768 Button3MotionMask =1<<10, 17769 Button4MotionMask =1<<11, 17770 Button5MotionMask =1<<12, 17771 ButtonMotionMask =1<<13, 17772 KeymapStateMask =1<<14, 17773 ExposureMask =1<<15, 17774 VisibilityChangeMask =1<<16, 17775 StructureNotifyMask =1<<17, 17776 ResizeRedirectMask =1<<18, 17777 SubstructureNotifyMask =1<<19, 17778 SubstructureRedirectMask=1<<20, 17779 FocusChangeMask =1<<21, 17780 PropertyChangeMask =1<<22, 17781 ColormapChangeMask =1<<23, 17782 OwnerGrabButtonMask =1<<24 17783 } 17784 17785 struct MwmHints { 17786 c_ulong flags; 17787 c_ulong functions; 17788 c_ulong decorations; 17789 c_long input_mode; 17790 c_ulong status; 17791 } 17792 17793 enum { 17794 MWM_HINTS_FUNCTIONS = (1L << 0), 17795 MWM_HINTS_DECORATIONS = (1L << 1), 17796 17797 MWM_FUNC_ALL = (1L << 0), 17798 MWM_FUNC_RESIZE = (1L << 1), 17799 MWM_FUNC_MOVE = (1L << 2), 17800 MWM_FUNC_MINIMIZE = (1L << 3), 17801 MWM_FUNC_MAXIMIZE = (1L << 4), 17802 MWM_FUNC_CLOSE = (1L << 5), 17803 17804 MWM_DECOR_ALL = (1L << 0), 17805 MWM_DECOR_BORDER = (1L << 1), 17806 MWM_DECOR_RESIZEH = (1L << 2), 17807 MWM_DECOR_TITLE = (1L << 3), 17808 MWM_DECOR_MENU = (1L << 4), 17809 MWM_DECOR_MINIMIZE = (1L << 5), 17810 MWM_DECOR_MAXIMIZE = (1L << 6), 17811 } 17812 17813 import core.stdc.config : c_long, c_ulong; 17814 17815 /* Size hints mask bits */ 17816 17817 enum USPosition = (1L << 0) /* user specified x, y */; 17818 enum USSize = (1L << 1) /* user specified width, height */; 17819 enum PPosition = (1L << 2) /* program specified position */; 17820 enum PSize = (1L << 3) /* program specified size */; 17821 enum PMinSize = (1L << 4) /* program specified minimum size */; 17822 enum PMaxSize = (1L << 5) /* program specified maximum size */; 17823 enum PResizeInc = (1L << 6) /* program specified resize increments */; 17824 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 17825 enum PBaseSize = (1L << 8); 17826 enum PWinGravity = (1L << 9); 17827 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 17828 struct XSizeHints { 17829 arch_long flags; /* marks which fields in this structure are defined */ 17830 int x, y; /* Obsolete */ 17831 int width, height; /* Obsolete */ 17832 int min_width, min_height; 17833 int max_width, max_height; 17834 int width_inc, height_inc; 17835 struct Aspect { 17836 int x; /* numerator */ 17837 int y; /* denominator */ 17838 } 17839 17840 Aspect min_aspect; 17841 Aspect max_aspect; 17842 int base_width, base_height; 17843 int win_gravity; 17844 /* this structure may be extended in the future */ 17845 } 17846 17847 17848 17849 enum EventType:int 17850 { 17851 KeyPress =2, 17852 KeyRelease =3, 17853 ButtonPress =4, 17854 ButtonRelease =5, 17855 MotionNotify =6, 17856 EnterNotify =7, 17857 LeaveNotify =8, 17858 FocusIn =9, 17859 FocusOut =10, 17860 KeymapNotify =11, 17861 Expose =12, 17862 GraphicsExpose =13, 17863 NoExpose =14, 17864 VisibilityNotify =15, 17865 CreateNotify =16, 17866 DestroyNotify =17, 17867 UnmapNotify =18, 17868 MapNotify =19, 17869 MapRequest =20, 17870 ReparentNotify =21, 17871 ConfigureNotify =22, 17872 ConfigureRequest =23, 17873 GravityNotify =24, 17874 ResizeRequest =25, 17875 CirculateNotify =26, 17876 CirculateRequest =27, 17877 PropertyNotify =28, 17878 SelectionClear =29, 17879 SelectionRequest =30, 17880 SelectionNotify =31, 17881 ColormapNotify =32, 17882 ClientMessage =33, 17883 MappingNotify =34, 17884 LASTEvent =35 /* must be bigger than any event # */ 17885 } 17886 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 17887 struct XKeymapEvent 17888 { 17889 int type; 17890 arch_ulong serial; /* # of last request processed by server */ 17891 Bool send_event; /* true if this came from a SendEvent request */ 17892 Display *display; /* Display the event was read from */ 17893 Window window; 17894 byte[32] key_vector; 17895 } 17896 17897 struct XExposeEvent 17898 { 17899 int type; 17900 arch_ulong serial; /* # of last request processed by server */ 17901 Bool send_event; /* true if this came from a SendEvent request */ 17902 Display *display; /* Display the event was read from */ 17903 Window window; 17904 int x, y; 17905 int width, height; 17906 int count; /* if non-zero, at least this many more */ 17907 } 17908 17909 struct XGraphicsExposeEvent{ 17910 int type; 17911 arch_ulong serial; /* # of last request processed by server */ 17912 Bool send_event; /* true if this came from a SendEvent request */ 17913 Display *display; /* Display the event was read from */ 17914 Drawable drawable; 17915 int x, y; 17916 int width, height; 17917 int count; /* if non-zero, at least this many more */ 17918 int major_code; /* core is CopyArea or CopyPlane */ 17919 int minor_code; /* not defined in the core */ 17920 } 17921 17922 struct XNoExposeEvent{ 17923 int type; 17924 arch_ulong serial; /* # of last request processed by server */ 17925 Bool send_event; /* true if this came from a SendEvent request */ 17926 Display *display; /* Display the event was read from */ 17927 Drawable drawable; 17928 int major_code; /* core is CopyArea or CopyPlane */ 17929 int minor_code; /* not defined in the core */ 17930 } 17931 17932 struct XVisibilityEvent{ 17933 int type; 17934 arch_ulong serial; /* # of last request processed by server */ 17935 Bool send_event; /* true if this came from a SendEvent request */ 17936 Display *display; /* Display the event was read from */ 17937 Window window; 17938 VisibilityNotify state; /* Visibility state */ 17939 } 17940 17941 struct XCreateWindowEvent{ 17942 int type; 17943 arch_ulong serial; /* # of last request processed by server */ 17944 Bool send_event; /* true if this came from a SendEvent request */ 17945 Display *display; /* Display the event was read from */ 17946 Window parent; /* parent of the window */ 17947 Window window; /* window id of window created */ 17948 int x, y; /* window location */ 17949 int width, height; /* size of window */ 17950 int border_width; /* border width */ 17951 Bool override_redirect; /* creation should be overridden */ 17952 } 17953 17954 struct XDestroyWindowEvent 17955 { 17956 int type; 17957 arch_ulong serial; /* # of last request processed by server */ 17958 Bool send_event; /* true if this came from a SendEvent request */ 17959 Display *display; /* Display the event was read from */ 17960 Window event; 17961 Window window; 17962 } 17963 17964 struct XUnmapEvent 17965 { 17966 int type; 17967 arch_ulong serial; /* # of last request processed by server */ 17968 Bool send_event; /* true if this came from a SendEvent request */ 17969 Display *display; /* Display the event was read from */ 17970 Window event; 17971 Window window; 17972 Bool from_configure; 17973 } 17974 17975 struct XMapEvent 17976 { 17977 int type; 17978 arch_ulong serial; /* # of last request processed by server */ 17979 Bool send_event; /* true if this came from a SendEvent request */ 17980 Display *display; /* Display the event was read from */ 17981 Window event; 17982 Window window; 17983 Bool override_redirect; /* Boolean, is override set... */ 17984 } 17985 17986 struct XMapRequestEvent 17987 { 17988 int type; 17989 arch_ulong serial; /* # of last request processed by server */ 17990 Bool send_event; /* true if this came from a SendEvent request */ 17991 Display *display; /* Display the event was read from */ 17992 Window parent; 17993 Window window; 17994 } 17995 17996 struct XReparentEvent 17997 { 17998 int type; 17999 arch_ulong serial; /* # of last request processed by server */ 18000 Bool send_event; /* true if this came from a SendEvent request */ 18001 Display *display; /* Display the event was read from */ 18002 Window event; 18003 Window window; 18004 Window parent; 18005 int x, y; 18006 Bool override_redirect; 18007 } 18008 18009 struct XConfigureEvent 18010 { 18011 int type; 18012 arch_ulong serial; /* # of last request processed by server */ 18013 Bool send_event; /* true if this came from a SendEvent request */ 18014 Display *display; /* Display the event was read from */ 18015 Window event; 18016 Window window; 18017 int x, y; 18018 int width, height; 18019 int border_width; 18020 Window above; 18021 Bool override_redirect; 18022 } 18023 18024 struct XGravityEvent 18025 { 18026 int type; 18027 arch_ulong serial; /* # of last request processed by server */ 18028 Bool send_event; /* true if this came from a SendEvent request */ 18029 Display *display; /* Display the event was read from */ 18030 Window event; 18031 Window window; 18032 int x, y; 18033 } 18034 18035 struct XResizeRequestEvent 18036 { 18037 int type; 18038 arch_ulong serial; /* # of last request processed by server */ 18039 Bool send_event; /* true if this came from a SendEvent request */ 18040 Display *display; /* Display the event was read from */ 18041 Window window; 18042 int width, height; 18043 } 18044 18045 struct XConfigureRequestEvent 18046 { 18047 int type; 18048 arch_ulong serial; /* # of last request processed by server */ 18049 Bool send_event; /* true if this came from a SendEvent request */ 18050 Display *display; /* Display the event was read from */ 18051 Window parent; 18052 Window window; 18053 int x, y; 18054 int width, height; 18055 int border_width; 18056 Window above; 18057 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 18058 arch_ulong value_mask; 18059 } 18060 18061 struct XCirculateEvent 18062 { 18063 int type; 18064 arch_ulong serial; /* # of last request processed by server */ 18065 Bool send_event; /* true if this came from a SendEvent request */ 18066 Display *display; /* Display the event was read from */ 18067 Window event; 18068 Window window; 18069 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 18070 } 18071 18072 struct XCirculateRequestEvent 18073 { 18074 int type; 18075 arch_ulong serial; /* # of last request processed by server */ 18076 Bool send_event; /* true if this came from a SendEvent request */ 18077 Display *display; /* Display the event was read from */ 18078 Window parent; 18079 Window window; 18080 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 18081 } 18082 18083 struct XPropertyEvent 18084 { 18085 int type; 18086 arch_ulong serial; /* # of last request processed by server */ 18087 Bool send_event; /* true if this came from a SendEvent request */ 18088 Display *display; /* Display the event was read from */ 18089 Window window; 18090 Atom atom; 18091 Time time; 18092 PropertyNotification state; /* NewValue, Deleted */ 18093 } 18094 18095 struct XSelectionClearEvent 18096 { 18097 int type; 18098 arch_ulong serial; /* # of last request processed by server */ 18099 Bool send_event; /* true if this came from a SendEvent request */ 18100 Display *display; /* Display the event was read from */ 18101 Window window; 18102 Atom selection; 18103 Time time; 18104 } 18105 18106 struct XSelectionRequestEvent 18107 { 18108 int type; 18109 arch_ulong serial; /* # of last request processed by server */ 18110 Bool send_event; /* true if this came from a SendEvent request */ 18111 Display *display; /* Display the event was read from */ 18112 Window owner; 18113 Window requestor; 18114 Atom selection; 18115 Atom target; 18116 Atom property; 18117 Time time; 18118 } 18119 18120 struct XSelectionEvent 18121 { 18122 int type; 18123 arch_ulong serial; /* # of last request processed by server */ 18124 Bool send_event; /* true if this came from a SendEvent request */ 18125 Display *display; /* Display the event was read from */ 18126 Window requestor; 18127 Atom selection; 18128 Atom target; 18129 Atom property; /* ATOM or None */ 18130 Time time; 18131 } 18132 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 18133 18134 struct XColormapEvent 18135 { 18136 int type; 18137 arch_ulong serial; /* # of last request processed by server */ 18138 Bool send_event; /* true if this came from a SendEvent request */ 18139 Display *display; /* Display the event was read from */ 18140 Window window; 18141 Colormap colormap; /* COLORMAP or None */ 18142 Bool new_; /* C++ */ 18143 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 18144 } 18145 version(X86_64) static assert(XColormapEvent.sizeof == 56); 18146 18147 struct XClientMessageEvent 18148 { 18149 int type; 18150 arch_ulong serial; /* # of last request processed by server */ 18151 Bool send_event; /* true if this came from a SendEvent request */ 18152 Display *display; /* Display the event was read from */ 18153 Window window; 18154 Atom message_type; 18155 int format; 18156 union Data{ 18157 byte[20] b; 18158 short[10] s; 18159 arch_ulong[5] l; 18160 } 18161 Data data; 18162 18163 } 18164 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 18165 18166 struct XMappingEvent 18167 { 18168 int type; 18169 arch_ulong serial; /* # of last request processed by server */ 18170 Bool send_event; /* true if this came from a SendEvent request */ 18171 Display *display; /* Display the event was read from */ 18172 Window window; /* unused */ 18173 MappingType request; /* one of MappingModifier, MappingKeyboard, 18174 MappingPointer */ 18175 int first_keycode; /* first keycode */ 18176 int count; /* defines range of change w. first_keycode*/ 18177 } 18178 18179 struct XErrorEvent 18180 { 18181 int type; 18182 Display *display; /* Display the event was read from */ 18183 XID resourceid; /* resource id */ 18184 arch_ulong serial; /* serial number of failed request */ 18185 ubyte error_code; /* error code of failed request */ 18186 ubyte request_code; /* Major op-code of failed request */ 18187 ubyte minor_code; /* Minor op-code of failed request */ 18188 } 18189 18190 struct XAnyEvent 18191 { 18192 int type; 18193 arch_ulong serial; /* # of last request processed by server */ 18194 Bool send_event; /* true if this came from a SendEvent request */ 18195 Display *display;/* Display the event was read from */ 18196 Window window; /* window on which event was requested in event mask */ 18197 } 18198 18199 union XEvent{ 18200 int type; /* must not be changed; first element */ 18201 XAnyEvent xany; 18202 XKeyEvent xkey; 18203 XButtonEvent xbutton; 18204 XMotionEvent xmotion; 18205 XCrossingEvent xcrossing; 18206 XFocusChangeEvent xfocus; 18207 XExposeEvent xexpose; 18208 XGraphicsExposeEvent xgraphicsexpose; 18209 XNoExposeEvent xnoexpose; 18210 XVisibilityEvent xvisibility; 18211 XCreateWindowEvent xcreatewindow; 18212 XDestroyWindowEvent xdestroywindow; 18213 XUnmapEvent xunmap; 18214 XMapEvent xmap; 18215 XMapRequestEvent xmaprequest; 18216 XReparentEvent xreparent; 18217 XConfigureEvent xconfigure; 18218 XGravityEvent xgravity; 18219 XResizeRequestEvent xresizerequest; 18220 XConfigureRequestEvent xconfigurerequest; 18221 XCirculateEvent xcirculate; 18222 XCirculateRequestEvent xcirculaterequest; 18223 XPropertyEvent xproperty; 18224 XSelectionClearEvent xselectionclear; 18225 XSelectionRequestEvent xselectionrequest; 18226 XSelectionEvent xselection; 18227 XColormapEvent xcolormap; 18228 XClientMessageEvent xclient; 18229 XMappingEvent xmapping; 18230 XErrorEvent xerror; 18231 XKeymapEvent xkeymap; 18232 arch_ulong[24] pad; 18233 } 18234 18235 18236 struct Display { 18237 XExtData *ext_data; /* hook for extension to hang data */ 18238 _XPrivate *private1; 18239 int fd; /* Network socket. */ 18240 int private2; 18241 int proto_major_version;/* major version of server's X protocol */ 18242 int proto_minor_version;/* minor version of servers X protocol */ 18243 char *vendor; /* vendor of the server hardware */ 18244 XID private3; 18245 XID private4; 18246 XID private5; 18247 int private6; 18248 XID function(Display*)resource_alloc;/* allocator function */ 18249 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 18250 int bitmap_unit; /* padding and data requirements */ 18251 int bitmap_pad; /* padding requirements on bitmaps */ 18252 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 18253 int nformats; /* number of pixmap formats in list */ 18254 ScreenFormat *pixmap_format; /* pixmap format list */ 18255 int private8; 18256 int release; /* release of the server */ 18257 _XPrivate *private9; 18258 _XPrivate *private10; 18259 int qlen; /* Length of input event queue */ 18260 arch_ulong last_request_read; /* seq number of last event read */ 18261 arch_ulong request; /* sequence number of last request. */ 18262 XPointer private11; 18263 XPointer private12; 18264 XPointer private13; 18265 XPointer private14; 18266 uint max_request_size; /* maximum number 32 bit words in request*/ 18267 _XrmHashBucketRec *db; 18268 int function (Display*)private15; 18269 char *display_name; /* "host:display" string used on this connect*/ 18270 int default_screen; /* default screen for operations */ 18271 int nscreens; /* number of screens on this server*/ 18272 Screen *screens; /* pointer to list of screens */ 18273 arch_ulong motion_buffer; /* size of motion buffer */ 18274 arch_ulong private16; 18275 int min_keycode; /* minimum defined keycode */ 18276 int max_keycode; /* maximum defined keycode */ 18277 XPointer private17; 18278 XPointer private18; 18279 int private19; 18280 byte *xdefaults; /* contents of defaults from server */ 18281 /* there is more to this structure, but it is private to Xlib */ 18282 } 18283 18284 // I got these numbers from a C program as a sanity test 18285 version(X86_64) { 18286 static assert(Display.sizeof == 296); 18287 static assert(XPointer.sizeof == 8); 18288 static assert(XErrorEvent.sizeof == 40); 18289 static assert(XAnyEvent.sizeof == 40); 18290 static assert(XMappingEvent.sizeof == 56); 18291 static assert(XEvent.sizeof == 192); 18292 } else version (AArch64) { 18293 // omit check for aarch64 18294 } else { 18295 static assert(Display.sizeof == 176); 18296 static assert(XPointer.sizeof == 4); 18297 static assert(XEvent.sizeof == 96); 18298 } 18299 18300 struct Depth 18301 { 18302 int depth; /* this depth (Z) of the depth */ 18303 int nvisuals; /* number of Visual types at this depth */ 18304 Visual *visuals; /* list of visuals possible at this depth */ 18305 } 18306 18307 alias void* GC; 18308 alias c_ulong VisualID; 18309 alias XID Colormap; 18310 alias XID Cursor; 18311 alias XID KeySym; 18312 alias uint KeyCode; 18313 enum None = 0; 18314 } 18315 18316 version(without_opengl) {} 18317 else { 18318 extern(C) nothrow @nogc { 18319 18320 18321 static if(!SdpyIsUsingIVGLBinds) { 18322 enum GLX_USE_GL= 1; /* support GLX rendering */ 18323 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 18324 enum GLX_LEVEL= 3; /* level in plane stacking */ 18325 enum GLX_RGBA= 4; /* true if RGBA mode */ 18326 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 18327 enum GLX_STEREO= 6; /* stereo buffering supported */ 18328 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 18329 enum GLX_RED_SIZE= 8; /* number of red component bits */ 18330 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 18331 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 18332 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 18333 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 18334 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 18335 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 18336 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 18337 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 18338 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 18339 18340 18341 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 18342 18343 18344 18345 enum GL_TRUE = 1; 18346 enum GL_FALSE = 0; 18347 } 18348 18349 alias XID GLXContextID; 18350 alias XID GLXPixmap; 18351 alias XID GLXDrawable; 18352 alias XID GLXPbuffer; 18353 alias XID GLXWindow; 18354 alias XID GLXFBConfigID; 18355 alias void* GLXContext; 18356 18357 } 18358 } 18359 18360 enum AllocNone = 0; 18361 18362 extern(C) { 18363 /* WARNING, this type not in Xlib spec */ 18364 extern(C) alias XIOErrorHandler = int function (Display* display); 18365 } 18366 18367 extern(C) nothrow 18368 alias XErrorHandler = int function(Display*, XErrorEvent*); 18369 18370 extern(C) nothrow @nogc { 18371 struct Screen{ 18372 XExtData *ext_data; /* hook for extension to hang data */ 18373 Display *display; /* back pointer to display structure */ 18374 Window root; /* Root window id. */ 18375 int width, height; /* width and height of screen */ 18376 int mwidth, mheight; /* width and height of in millimeters */ 18377 int ndepths; /* number of depths possible */ 18378 Depth *depths; /* list of allowable depths on the screen */ 18379 int root_depth; /* bits per pixel */ 18380 Visual *root_visual; /* root visual */ 18381 GC default_gc; /* GC for the root root visual */ 18382 Colormap cmap; /* default color map */ 18383 uint white_pixel; 18384 uint black_pixel; /* White and Black pixel values */ 18385 int max_maps, min_maps; /* max and min color maps */ 18386 int backing_store; /* Never, WhenMapped, Always */ 18387 bool save_unders; 18388 int root_input_mask; /* initial root input mask */ 18389 } 18390 18391 struct Visual 18392 { 18393 XExtData *ext_data; /* hook for extension to hang data */ 18394 VisualID visualid; /* visual id of this visual */ 18395 int class_; /* class of screen (monochrome, etc.) */ 18396 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 18397 int bits_per_rgb; /* log base 2 of distinct color values */ 18398 int map_entries; /* color map entries */ 18399 } 18400 18401 alias Display* _XPrivDisplay; 18402 18403 extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) @system { 18404 assert(dpy !is null); 18405 return &dpy.screens[scr]; 18406 } 18407 18408 extern(D) Window RootWindow(Display *dpy,int scr) { 18409 return ScreenOfDisplay(dpy,scr).root; 18410 } 18411 18412 struct XWMHints { 18413 arch_long flags; 18414 Bool input; 18415 int initial_state; 18416 Pixmap icon_pixmap; 18417 Window icon_window; 18418 int icon_x, icon_y; 18419 Pixmap icon_mask; 18420 XID window_group; 18421 } 18422 18423 struct XClassHint { 18424 char* res_name; 18425 char* res_class; 18426 } 18427 18428 extern(D) int DefaultScreen(Display *dpy) { 18429 return dpy.default_screen; 18430 } 18431 18432 extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 18433 extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 18434 extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 18435 extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 18436 extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 18437 extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 18438 18439 extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; } 18440 18441 enum int AnyPropertyType = 0; 18442 enum int Success = 0; 18443 18444 enum int RevertToNone = None; 18445 enum int PointerRoot = 1; 18446 enum Time CurrentTime = 0; 18447 enum int RevertToPointerRoot = PointerRoot; 18448 enum int RevertToParent = 2; 18449 18450 extern(D) int DefaultDepthOfDisplay(Display* dpy) { 18451 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 18452 } 18453 18454 extern(D) Visual* DefaultVisual(Display *dpy,int scr) { 18455 return ScreenOfDisplay(dpy,scr).root_visual; 18456 } 18457 18458 extern(D) GC DefaultGC(Display *dpy,int scr) { 18459 return ScreenOfDisplay(dpy,scr).default_gc; 18460 } 18461 18462 extern(D) uint BlackPixel(Display *dpy,int scr) { 18463 return ScreenOfDisplay(dpy,scr).black_pixel; 18464 } 18465 18466 extern(D) uint WhitePixel(Display *dpy,int scr) { 18467 return ScreenOfDisplay(dpy,scr).white_pixel; 18468 } 18469 18470 alias void* XFontSet; // i think 18471 struct XmbTextItem { 18472 char* chars; 18473 int nchars; 18474 int delta; 18475 XFontSet font_set; 18476 } 18477 18478 struct XTextItem { 18479 char* chars; 18480 int nchars; 18481 int delta; 18482 Font font; 18483 } 18484 18485 enum { 18486 GXclear = 0x0, /* 0 */ 18487 GXand = 0x1, /* src AND dst */ 18488 GXandReverse = 0x2, /* src AND NOT dst */ 18489 GXcopy = 0x3, /* src */ 18490 GXandInverted = 0x4, /* NOT src AND dst */ 18491 GXnoop = 0x5, /* dst */ 18492 GXxor = 0x6, /* src XOR dst */ 18493 GXor = 0x7, /* src OR dst */ 18494 GXnor = 0x8, /* NOT src AND NOT dst */ 18495 GXequiv = 0x9, /* NOT src XOR dst */ 18496 GXinvert = 0xa, /* NOT dst */ 18497 GXorReverse = 0xb, /* src OR NOT dst */ 18498 GXcopyInverted = 0xc, /* NOT src */ 18499 GXorInverted = 0xd, /* NOT src OR dst */ 18500 GXnand = 0xe, /* NOT src OR NOT dst */ 18501 GXset = 0xf, /* 1 */ 18502 } 18503 enum QueueMode : int { 18504 QueuedAlready, 18505 QueuedAfterReading, 18506 QueuedAfterFlush 18507 } 18508 18509 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 18510 18511 struct XPoint { 18512 short x; 18513 short y; 18514 } 18515 18516 enum CoordMode:int { 18517 CoordModeOrigin = 0, 18518 CoordModePrevious = 1 18519 } 18520 18521 enum PolygonShape:int { 18522 Complex = 0, 18523 Nonconvex = 1, 18524 Convex = 2 18525 } 18526 18527 struct XTextProperty { 18528 const(char)* value; /* same as Property routines */ 18529 Atom encoding; /* prop type */ 18530 int format; /* prop data format: 8, 16, or 32 */ 18531 arch_ulong nitems; /* number of data items in value */ 18532 } 18533 18534 version( X86_64 ) { 18535 static assert(XTextProperty.sizeof == 32); 18536 } 18537 18538 18539 struct XGCValues { 18540 int function_; /* logical operation */ 18541 arch_ulong plane_mask;/* plane mask */ 18542 arch_ulong foreground;/* foreground pixel */ 18543 arch_ulong background;/* background pixel */ 18544 int line_width; /* line width */ 18545 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 18546 int cap_style; /* CapNotLast, CapButt, 18547 CapRound, CapProjecting */ 18548 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 18549 int fill_style; /* FillSolid, FillTiled, 18550 FillStippled, FillOpaeueStippled */ 18551 int fill_rule; /* EvenOddRule, WindingRule */ 18552 int arc_mode; /* ArcChord, ArcPieSlice */ 18553 Pixmap tile; /* tile pixmap for tiling operations */ 18554 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 18555 int ts_x_origin; /* offset for tile or stipple operations */ 18556 int ts_y_origin; 18557 Font font; /* default text font for text operations */ 18558 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 18559 Bool graphics_exposures;/* boolean, should exposures be generated */ 18560 int clip_x_origin; /* origin for clipping */ 18561 int clip_y_origin; 18562 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 18563 int dash_offset; /* patterned/dashed line information */ 18564 char dashes; 18565 } 18566 18567 struct XColor { 18568 arch_ulong pixel; 18569 ushort red, green, blue; 18570 byte flags; 18571 byte pad; 18572 } 18573 18574 struct XRectangle { 18575 short x; 18576 short y; 18577 ushort width; 18578 ushort height; 18579 } 18580 18581 enum ClipByChildren = 0; 18582 enum IncludeInferiors = 1; 18583 18584 enum Atom XA_PRIMARY = 1; 18585 enum Atom XA_SECONDARY = 2; 18586 enum Atom XA_STRING = 31; 18587 enum Atom XA_CARDINAL = 6; 18588 enum Atom XA_WM_NAME = 39; 18589 enum Atom XA_ATOM = 4; 18590 enum Atom XA_WINDOW = 33; 18591 enum Atom XA_WM_HINTS = 35; 18592 enum int PropModeAppend = 2; 18593 enum int PropModeReplace = 0; 18594 enum int PropModePrepend = 1; 18595 18596 enum int CopyFromParent = 0; 18597 enum int InputOutput = 1; 18598 18599 // XWMHints 18600 enum InputHint = 1 << 0; 18601 enum StateHint = 1 << 1; 18602 enum IconPixmapHint = (1L << 2); 18603 enum IconWindowHint = (1L << 3); 18604 enum IconPositionHint = (1L << 4); 18605 enum IconMaskHint = (1L << 5); 18606 enum WindowGroupHint = (1L << 6); 18607 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 18608 enum XUrgencyHint = (1L << 8); 18609 18610 // GC Components 18611 enum GCFunction = (1L<<0); 18612 enum GCPlaneMask = (1L<<1); 18613 enum GCForeground = (1L<<2); 18614 enum GCBackground = (1L<<3); 18615 enum GCLineWidth = (1L<<4); 18616 enum GCLineStyle = (1L<<5); 18617 enum GCCapStyle = (1L<<6); 18618 enum GCJoinStyle = (1L<<7); 18619 enum GCFillStyle = (1L<<8); 18620 enum GCFillRule = (1L<<9); 18621 enum GCTile = (1L<<10); 18622 enum GCStipple = (1L<<11); 18623 enum GCTileStipXOrigin = (1L<<12); 18624 enum GCTileStipYOrigin = (1L<<13); 18625 enum GCFont = (1L<<14); 18626 enum GCSubwindowMode = (1L<<15); 18627 enum GCGraphicsExposures= (1L<<16); 18628 enum GCClipXOrigin = (1L<<17); 18629 enum GCClipYOrigin = (1L<<18); 18630 enum GCClipMask = (1L<<19); 18631 enum GCDashOffset = (1L<<20); 18632 enum GCDashList = (1L<<21); 18633 enum GCArcMode = (1L<<22); 18634 enum GCLastBit = 22; 18635 18636 18637 enum int WithdrawnState = 0; 18638 enum int NormalState = 1; 18639 enum int IconicState = 3; 18640 18641 } 18642 } else version (OSXCocoa) { 18643 18644 /+ 18645 DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do. 18646 +/ 18647 18648 private __gshared AppDelegate globalAppDelegate; 18649 18650 extern(Objective-C) 18651 class AppDelegate : NSObject, NSApplicationDelegate { 18652 override static AppDelegate alloc() @selector("alloc"); 18653 18654 18655 void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") { 18656 SimpleWindow.processAllCustomEvents(); 18657 } 18658 18659 override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") { 18660 immutable style = NSWindowStyleMask.resizable | 18661 NSWindowStyleMask.closable | 18662 NSWindowStyleMask.miniaturizable | 18663 NSWindowStyleMask.titled; 18664 18665 NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow); 18666 18667 { 18668 auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow); 18669 auto menu = NSMenu.alloc.init(MacString("Test2").borrow); 18670 mainMenu.setSubmenu(menu, item); 18671 18672 auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow); 18673 newItem.target = NSApp; 18674 auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow); 18675 newItem2.target = NSApp; 18676 } 18677 18678 { 18679 auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow); 18680 auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used 18681 mainMenu.setSubmenu(menu, item); 18682 18683 auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow); 18684 menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow); 18685 } 18686 18687 18688 NSApp.menu = mainMenu; 18689 18690 18691 // auto controller = ViewController.alloc.init; 18692 18693 // auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true); 18694 18695 /+ 18696 this.window = window; 18697 this.controller = controller; 18698 +/ 18699 } 18700 18701 override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") { 18702 NSApplication.shared_.activateIgnoringOtherApps(false); 18703 18704 sdpyPrintDebugString("before"); 18705 NSApp.stop(cast(void*) NSApp); // stop NSApp.run and let arsd.core event loop take over... 18706 sdpyPrintDebugString("after"); 18707 } 18708 override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") { 18709 return true; 18710 } 18711 } 18712 18713 extern(Objective-C) 18714 class SDWindowDelegate : NSObject, NSWindowDelegate { 18715 override static SDWindowDelegate alloc() @selector("alloc"); 18716 override SDWindowDelegate init() @selector("init"); 18717 18718 SimpleWindow simpleWindow; 18719 18720 override void windowWillClose(NSNotification notification) @selector("windowWillClose:") { 18721 auto window = cast(void*) notification.object; 18722 18723 // FIXME: do i need to release it? 18724 if(auto swp = window in SimpleWindow.nativeMapping) { 18725 auto sw = *swp; 18726 18727 sw._closed = true; 18728 18729 if (sw.visibilityChanged !is null && sw._visible) sw.visibilityChanged(false); 18730 18731 if (sw.onDestroyed !is null) try { sw.onDestroyed(); } catch (Exception e) {} // sorry 18732 SimpleWindow.nativeMapping.remove(window); 18733 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(cast(NSWindow) window); 18734 18735 bool anyImportant = false; 18736 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 18737 if(w.beingOpenKeepsAppOpen) { 18738 anyImportant = true; 18739 break; 18740 } 18741 if(!anyImportant) { 18742 EventLoop.quitApplication(); 18743 } 18744 18745 } 18746 } 18747 18748 override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") { 18749 if(simpleWindow.windowResized) { 18750 // FIXME: automaticallyScaleIfPossible behaviors 18751 18752 simpleWindow._width = cast(int) frameSize.width; 18753 simpleWindow._height = cast(int) frameSize.height; 18754 18755 simpleWindow.view.setFrameSize(frameSize); 18756 18757 /+ 18758 auto size = simpleWindow.view.frame.size; 18759 writeln(cast(int) size.width, "x", cast(int) size.height); 18760 +/ 18761 18762 simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height); 18763 18764 simpleWindow.windowResized(simpleWindow._width, simpleWindow._height); 18765 18766 // simpleWindow.view.setNeedsDisplay(true); 18767 } 18768 18769 return frameSize; 18770 } 18771 18772 /+ 18773 override void windowDidResize(NSNotification notification) @selector("windowDidResize:") { 18774 if(simpleWindow.windowResized) { 18775 auto window = simpleWindow.window; 18776 auto rect = window.contentRectForFrameRect(window.frame); 18777 import std.stdio; writeln(window.frame.size); 18778 simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height); 18779 } 18780 } 18781 +/ 18782 } 18783 18784 extern(Objective-C) 18785 class SDGraphicsView : NSView { 18786 SimpleWindow simpleWindow; 18787 18788 override static SDGraphicsView alloc() @selector("alloc"); 18789 override SDGraphicsView init() @selector("init");/* { 18790 super.init(); 18791 return this; 18792 }*/ 18793 18794 override void drawRect(NSRect rect) @selector("drawRect:") { 18795 auto curCtx = NSGraphicsContext.currentContext.graphicsPort; 18796 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 18797 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext)); 18798 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 18799 CGImageRelease(cgImage); 18800 } 18801 18802 extern(D) 18803 private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) { 18804 MouseEvent me; 18805 me.type = type; 18806 18807 auto pos = event.locationInWindow; 18808 18809 me.x = cast(int) pos.x; 18810 18811 // FIXME: 1-based things here might need fixup 18812 me.y = cast(int) (simpleWindow.height - pos.y); 18813 18814 me.dx = 0; // FIXME 18815 me.dy = 0; // FIXME 18816 18817 me.button = button; 18818 me.modifierState = cast(uint) event.modifierFlags; 18819 me.window = simpleWindow; 18820 18821 me.doubleClick = false; 18822 18823 if(simpleWindow && simpleWindow.handleMouseEvent) 18824 simpleWindow.handleMouseEvent(me); 18825 } 18826 18827 override void mouseDown(NSEvent event) @selector("mouseDown:") { 18828 // writeln(event.pressedMouseButtons); 18829 18830 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 18831 } 18832 override void mouseDragged(NSEvent event) @selector("mouseDragged:") { 18833 mouseHelper(event, MouseEventType.motion, MouseButton.left); 18834 } 18835 override void mouseUp(NSEvent event) @selector("mouseUp:") { 18836 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left); 18837 } 18838 override void mouseMoved(NSEvent event) @selector("mouseMoved:") { 18839 mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly 18840 } 18841 /+ 18842 // FIXME 18843 override void mouseEntered(NSEvent event) @selector("mouseEntered:") { 18844 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 18845 } 18846 override void mouseExited(NSEvent event) @selector("mouseExited:") { 18847 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 18848 } 18849 +/ 18850 18851 override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") { 18852 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right); 18853 } 18854 override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") { 18855 mouseHelper(event, MouseEventType.motion, MouseButton.right); 18856 } 18857 override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") { 18858 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right); 18859 } 18860 18861 override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") { 18862 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle); 18863 } 18864 override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") { 18865 mouseHelper(event, MouseEventType.motion, MouseButton.middle); 18866 } 18867 override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") { 18868 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle); 18869 } 18870 18871 override void scrollWheel(NSEvent event) @selector("scrollWheel:") { 18872 // import std.stdio; writeln(event.deltaY); 18873 } 18874 18875 override void keyDown(NSEvent event) @selector("keyDown:") { 18876 // the event may have multiple characters, and we send them all at once. 18877 if (simpleWindow.handleCharEvent) { 18878 auto chars = DeifiedNSString(event.characters); 18879 foreach (dchar dc; chars.str) 18880 simpleWindow.handleCharEvent(dc); 18881 } 18882 18883 keyHelper(event, true); 18884 } 18885 18886 override void keyUp(NSEvent event) @selector("keyUp:") { 18887 keyHelper(event, false); 18888 } 18889 18890 extern(D) 18891 private void keyHelper(NSEvent event, bool pressed) { 18892 if(simpleWindow.handleKeyEvent) { 18893 KeyEvent ev; 18894 ev.key = cast(Key) event.keyCode;// (event.specialKey ? event.specialKey : event.keyCode); 18895 ev.pressed = pressed; 18896 ev.hardwareCode = cast(ubyte) event.keyCode; 18897 ev.modifierState = cast(uint) event.modifierFlags; 18898 ev.window = simpleWindow; 18899 18900 simpleWindow.handleKeyEvent(ev); 18901 } 18902 } 18903 18904 override bool isFlipped() @selector("isFlipped") { 18905 return true; 18906 } 18907 override bool acceptsFirstResponder() @selector("acceptsFirstResponder") { 18908 return true; 18909 } 18910 18911 void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") { 18912 if(simpleWindow && simpleWindow.handlePulse) 18913 simpleWindow.handlePulse(); 18914 /+ 18915 setNeedsDisplay = true; 18916 +/ 18917 } 18918 } 18919 18920 private: 18921 alias const(void)* CFStringRef; 18922 alias const(void)* CFAllocatorRef; 18923 alias const(void)* CFTypeRef; 18924 alias const(void)* CGColorSpaceRef; 18925 alias const(void)* CGImageRef; 18926 alias ulong CGBitmapInfo; 18927 alias NSGraphicsContext CGContextRef; // actually CGContextRef should be a subclass... 18928 18929 alias NSPoint CGPoint; 18930 alias NSSize CGSize; 18931 alias NSRect CGRect; 18932 18933 struct CGAffineTransform { 18934 double a, b, c, d, tx, ty; 18935 } 18936 18937 enum NSApplicationActivationPolicyRegular = 0; 18938 enum NSBackingStoreBuffered = 2; 18939 enum kCFStringEncodingUTF8 = 0x08000100; 18940 18941 enum : size_t { 18942 NSBorderlessWindowMask = 0, 18943 NSTitledWindowMask = 1 << 0, 18944 NSClosableWindowMask = 1 << 1, 18945 NSMiniaturizableWindowMask = 1 << 2, 18946 NSResizableWindowMask = 1 << 3, 18947 NSTexturedBackgroundWindowMask = 1 << 8 18948 } 18949 18950 enum : ulong { 18951 kCGImageAlphaNone, 18952 kCGImageAlphaPremultipliedLast, 18953 kCGImageAlphaPremultipliedFirst, 18954 kCGImageAlphaLast, 18955 kCGImageAlphaFirst, 18956 kCGImageAlphaNoneSkipLast, 18957 kCGImageAlphaNoneSkipFirst 18958 } 18959 enum : ulong { 18960 kCGBitmapAlphaInfoMask = 0x1F, 18961 kCGBitmapFloatComponents = (1 << 8), 18962 kCGBitmapByteOrderMask = 0x7000, 18963 kCGBitmapByteOrderDefault = (0 << 12), 18964 kCGBitmapByteOrder16Little = (1 << 12), 18965 kCGBitmapByteOrder32Little = (2 << 12), 18966 kCGBitmapByteOrder16Big = (3 << 12), 18967 kCGBitmapByteOrder32Big = (4 << 12) 18968 } 18969 enum CGPathDrawingMode { 18970 kCGPathFill, 18971 kCGPathEOFill, 18972 kCGPathStroke, 18973 kCGPathFillStroke, 18974 kCGPathEOFillStroke 18975 } 18976 enum objc_AssociationPolicy : size_t { 18977 OBJC_ASSOCIATION_ASSIGN = 0, 18978 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 18979 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 18980 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 18981 OBJC_ASSOCIATION_COPY = 0x303 //01403 18982 } 18983 18984 extern(C) { 18985 CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo); 18986 void CGContextRelease(CGContextRef c); 18987 ubyte* CGBitmapContextGetData(CGContextRef c); 18988 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 18989 size_t CGBitmapContextGetWidth(CGContextRef c); 18990 size_t CGBitmapContextGetHeight(CGContextRef c); 18991 18992 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 18993 void CGColorSpaceRelease(CGColorSpaceRef cs); 18994 18995 alias void* CGFontRef; 18996 alias CTFontRef = NSFont; 18997 CGFontRef CTFontCopyGraphicsFont(CTFontRef font, void /*CTFontDescriptorRef*/ * attributes); 18998 18999 19000 void CGContextSetFont(CGContextRef c, CGFontRef font); 19001 void CGContextSetFontSize(CGContextRef c, CGFloat size); 19002 19003 void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha); 19004 void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha); 19005 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 19006 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length); 19007 void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count); 19008 void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat *lengths, size_t count); 19009 19010 void CGContextBeginPath(CGContextRef c); 19011 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 19012 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 19013 void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise); 19014 void CGContextAddRect(CGContextRef c, CGRect rect); 19015 void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count); 19016 void CGContextSaveGState(CGContextRef c); 19017 void CGContextRestoreGState(CGContextRef c); 19018 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding); 19019 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 19020 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 19021 19022 void CGImageRelease(CGImageRef image); 19023 } 19024 } else static assert(0, "Unsupported operating system"); 19025 19026 19027 version(OSXCocoa) { 19028 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 19029 // 19030 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 19031 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 19032 // 19033 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 19034 // Probably won't even fully compile right now 19035 19036 private enum double PI = 3.14159265358979323; 19037 19038 alias NSWindow NativeWindowHandle; 19039 alias void delegate(NSid) NativeEventHandler; 19040 19041 enum KEY_ESCAPE = 27; 19042 19043 mixin template NativeImageImplementation() { 19044 CGContextRef context; 19045 ubyte* rawData; 19046 19047 final: 19048 19049 void convertToRgbaBytes(ubyte[] where) @system { 19050 assert(where.length == this.width * this.height * 4); 19051 19052 // if rawData had a length.... 19053 //assert(rawData.length == where.length); 19054 for(long idx = 0; idx < where.length; idx += 4) { 19055 auto alpha = rawData[idx + 3]; 19056 if(alpha == 255) { 19057 where[idx + 0] = rawData[idx + 0]; // r 19058 where[idx + 1] = rawData[idx + 1]; // g 19059 where[idx + 2] = rawData[idx + 2]; // b 19060 where[idx + 3] = rawData[idx + 3]; // a 19061 } else { 19062 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 19063 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 19064 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 19065 where[idx + 3] = rawData[idx + 3]; // a 19066 19067 } 19068 } 19069 } 19070 19071 void setFromRgbaBytes(in ubyte[] where) @system { 19072 // FIXME: this is probably wrong 19073 assert(where.length == this.width * this.height * 4); 19074 19075 // if rawData had a length.... 19076 //assert(rawData.length == where.length); 19077 for(long idx = 0; idx < where.length; idx += 4) { 19078 auto alpha = where[idx + 3]; 19079 if(alpha == 255) { 19080 rawData[idx + 0] = where[idx + 0]; // r 19081 rawData[idx + 1] = where[idx + 1]; // g 19082 rawData[idx + 2] = where[idx + 2]; // b 19083 rawData[idx + 3] = where[idx + 3]; // a 19084 } else if(alpha == 0) { 19085 rawData[idx + 0] = 0; 19086 rawData[idx + 1] = 0; 19087 rawData[idx + 2] = 0; 19088 rawData[idx + 3] = 0; 19089 } else { 19090 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 19091 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 19092 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 19093 rawData[idx + 3] = where[idx + 3]; // a 19094 } 19095 } 19096 } 19097 19098 19099 void createImage(int width, int height, bool forcexshm=false, bool ignored = false) { 19100 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 19101 context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 19102 CGColorSpaceRelease(colorSpace); 19103 rawData = CGBitmapContextGetData(context); 19104 } 19105 void dispose() { 19106 CGContextRelease(context); 19107 } 19108 19109 void setPixel(int x, int y, Color c) @system { 19110 auto offset = (y * width + x) * 4; 19111 if (c.a == 255) { 19112 rawData[offset + 0] = c.r; 19113 rawData[offset + 1] = c.g; 19114 rawData[offset + 2] = c.b; 19115 rawData[offset + 3] = c.a; 19116 } else { 19117 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 19118 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 19119 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 19120 rawData[offset + 3] = c.a; 19121 } 19122 } 19123 } 19124 19125 mixin template NativeScreenPainterImplementation() { 19126 CGContextRef context; 19127 ubyte[4] _outlineComponents; 19128 NSView view; 19129 19130 Pen _activePen; 19131 Color _fillColor; 19132 Rectangle _clipRectangle; 19133 OperatingSystemFont _font; 19134 19135 OperatingSystemFont getFont() { 19136 if(_font is null) { 19137 static OperatingSystemFont _defaultFont; 19138 if(_defaultFont is null) { 19139 _defaultFont = new OperatingSystemFont(); 19140 _defaultFont.loadDefault(); 19141 } 19142 _font = _defaultFont; 19143 } 19144 19145 return _font; 19146 } 19147 19148 void create(PaintingHandle window) { 19149 // this.destiny = window; 19150 if(auto sw = cast(SimpleWindow) this.window) { 19151 context = sw.drawingContext; 19152 view = sw.view; 19153 } else { 19154 throw new NotYetImplementedException(); 19155 } 19156 } 19157 19158 void dispose() { 19159 view.setNeedsDisplay(true); 19160 } 19161 19162 bool manualInvalidations; 19163 void invalidateRect(Rectangle invalidRect) { } 19164 19165 // NotYetImplementedException 19166 void rasterOp(RasterOp op) { 19167 } 19168 void setClipRectangle(int, int, int, int) { 19169 } 19170 Size textSize(in char[] txt) { 19171 auto font = getFont(); 19172 return Size(castFnumToCnum(font.stringWidth(txt)), castFnumToCnum(font.height())); 19173 } 19174 19175 void setFont(OperatingSystemFont font) { 19176 _font = font; 19177 // font.font.setInContext(context); 19178 if(font) { 19179 // FIXME: should i free this thing? 19180 /+ 19181 auto f = CTFontCopyGraphicsFont(font.font, null); 19182 if(font.font is null) 19183 sdpyPrintDebugString("input is null"); 19184 if(f is null) 19185 sdpyPrintDebugString("f is null"); 19186 CGContextSetFont(context, f); 19187 +/ 19188 // CGContextSetFontSize(context, font.size); 19189 19190 // FIXME kinda hacky 19191 CGContextSelectFont(context, (font.loadedInfo.name ~ "\0").ptr, font.loadedInfo.size, 1); 19192 } else {} // FIMXE 19193 } 19194 int fontHeight() { 19195 auto font = getFont(); 19196 return castFnumToCnum(font.height); 19197 } 19198 19199 // end 19200 19201 void pen(Pen pen) { 19202 _activePen = pen; 19203 auto color = pen.color; // FIXME 19204 double alphaComponent = color.a/255.0f; 19205 CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 19206 19207 double[2] patternBuffer; 19208 double[] pattern; 19209 final switch(pen.style) { 19210 case Pen.Style.Solid: 19211 pattern = null; 19212 break; 19213 case Pen.Style.Dashed: 19214 patternBuffer[0] = 4; 19215 patternBuffer[1] = 1; 19216 pattern = patternBuffer[]; 19217 break; 19218 case Pen.Style.Dotted: 19219 patternBuffer[0] = 1; 19220 patternBuffer[1] = 1; 19221 pattern = patternBuffer[]; 19222 break; 19223 } 19224 19225 CGContextSetLineDash(context, 0, pattern.ptr, pattern.length); 19226 19227 if (color.a != 255) { 19228 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 19229 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 19230 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 19231 _outlineComponents[3] = color.a; 19232 } else { 19233 _outlineComponents[0] = color.r; 19234 _outlineComponents[1] = color.g; 19235 _outlineComponents[2] = color.b; 19236 _outlineComponents[3] = color.a; 19237 } 19238 } 19239 19240 @property void fillColor(Color color) { 19241 CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 19242 } 19243 19244 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 19245 // NotYetImplementedException for upper left/width/height 19246 auto cgImage = CGBitmapContextCreateImage(image.context); 19247 auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context)); 19248 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 19249 CGImageRelease(cgImage); 19250 } 19251 19252 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 19253 // FIXME: is this efficient? 19254 auto cgImage = CGBitmapContextCreateImage(s.handle); 19255 auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle)); 19256 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 19257 CGImageRelease(cgImage); 19258 } 19259 19260 19261 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 19262 // FIXME: alignment 19263 if (_outlineComponents[3] != 0) { 19264 CGContextSaveGState(context); 19265 auto invAlpha = 1.0f/_outlineComponents[3]; 19266 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 19267 _outlineComponents[1]*invAlpha, 19268 _outlineComponents[2]*invAlpha, 19269 _outlineComponents[3]/255.0f); 19270 19271 19272 19273 // FIXME: should we clip it to the bounding box? 19274 int textHeight = fontHeight; 19275 19276 auto lines = text.split('\n'); 19277 19278 const lineHeight = textHeight; 19279 textHeight *= lines.length; 19280 19281 int cy = y; 19282 19283 if(alignment & TextAlignment.VerticalBottom) { 19284 if(y2 <= 0) 19285 return; 19286 auto h = y2 - y; 19287 if(h > textHeight) { 19288 cy += h - textHeight; 19289 cy -= lineHeight / 2; 19290 } 19291 } else if(alignment & TextAlignment.VerticalCenter) { 19292 if(y2 <= 0) 19293 return; 19294 auto h = y2 - y; 19295 if(textHeight < h) { 19296 cy += (h - textHeight) / 2; 19297 //cy -= lineHeight / 4; 19298 } 19299 } 19300 19301 foreach(line; text.split('\n')) { 19302 int textWidth = this.textSize(line).width; 19303 19304 int px = x, py = cy; 19305 19306 if(alignment & TextAlignment.Center) { 19307 if(x2 <= 0) 19308 return; 19309 auto w = x2 - x; 19310 if(w > textWidth) 19311 px += (w - textWidth) / 2; 19312 } else if(alignment & TextAlignment.Right) { 19313 if(x2 <= 0) 19314 return; 19315 auto pos = x2 - textWidth; 19316 if(pos > x) 19317 px = pos; 19318 } 19319 19320 CGContextShowTextAtPoint(context, px, py + getFont.ascent /* this is cuz this picks baseline but i want bounding box */, line.ptr, line.length); 19321 19322 carry_on: 19323 cy += lineHeight + 4; 19324 } 19325 19326 // auto cfstr = cast(NSid)createCFString(text); 19327 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 19328 // NSPoint(x, y), null); 19329 // CFRelease(cfstr); 19330 CGContextRestoreGState(context); 19331 } 19332 } 19333 19334 void drawPixel(int x, int y) { 19335 auto rawData = CGBitmapContextGetData(context); 19336 auto width = CGBitmapContextGetWidth(context); 19337 auto height = CGBitmapContextGetHeight(context); 19338 auto offset = ((height - y - 1) * width + x) * 4; 19339 rawData[offset .. offset+4] = _outlineComponents; 19340 } 19341 19342 void drawLine(int x1, int y1, int x2, int y2) { 19343 CGPoint[2] linePoints; 19344 linePoints[0] = CGPoint(x1, y1); 19345 linePoints[1] = CGPoint(x2, y2); 19346 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 19347 } 19348 19349 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 19350 drawRectangle(upperLeft.x, upperLeft.y, lowerRight.x - upperLeft.x, lowerRight.y - upperLeft.y); // FIXME not rounded 19351 } 19352 19353 void drawRectangle(int x, int y, int width, int height) { 19354 CGContextBeginPath(context); 19355 // trying to align with actual pixels... 19356 auto rect = CGRect(CGPoint(x + 0.5, y + 0.5), CGSize(width - 1, height - 1)); 19357 CGContextAddRect(context, rect); 19358 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 19359 } 19360 19361 void drawEllipse(int x1, int y1, int x2, int y2) { 19362 CGContextBeginPath(context); 19363 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 19364 CGContextAddEllipseInRect(context, rect); 19365 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 19366 } 19367 19368 void drawArc(int x1, int y1, int width, int height, int start, int length) { 19369 // @@@BUG@@@ Does not support elliptic arc (width != height). 19370 CGContextBeginPath(context); 19371 int clockwise = 0; 19372 if(length < 0) { 19373 clockwise = 1; 19374 length = -length; 19375 } 19376 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 19377 start*PI/(180*64), (start+length)*PI/(180*64), clockwise); 19378 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 19379 } 19380 19381 void drawPolygon(Point[] intPoints) { 19382 CGContextBeginPath(context); 19383 CGPoint[16] pointsBuffer; 19384 CGPoint[] points; 19385 if(intPoints.length <= pointsBuffer.length) 19386 points = pointsBuffer[0 .. intPoints.length]; 19387 else 19388 points = new CGPoint[](intPoints.length); 19389 19390 foreach(idx, pt; intPoints) 19391 points[idx] = CGPoint(pt.x, pt.y); 19392 19393 CGContextAddLines(context, points.ptr, points.length); 19394 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 19395 } 19396 } 19397 19398 private bool appInitialized = false; 19399 void initializeApp() { 19400 if(appInitialized) 19401 return; 19402 synchronized { 19403 if(appInitialized) 19404 return; 19405 19406 auto app = NSApp(); // ensure the is initialized 19407 19408 auto dg = AppDelegate.alloc; 19409 globalAppDelegate = dg; 19410 NSApp.delegate_ = dg; 19411 19412 NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular); 19413 19414 appInitialized = true; 19415 } 19416 } 19417 19418 mixin template NativeSimpleWindowImplementation() { 19419 void setTitle(string title) { 19420 window.title = MacString(title).borrow; 19421 } 19422 19423 void moveResize (int x, int y, int w, int h) { 19424 //auto f = window.frame; 19425 // FIXME: finish 19426 sdpyPrintDebugString("moveResize not implemented"); 19427 } 19428 19429 void resize(int w, int h) { 19430 // FIXME: finish 19431 sdpyPrintDebugString("resize not implemented"); 19432 } 19433 19434 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 19435 initializeApp(); 19436 19437 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 19438 19439 auto window = NSWindow.alloc.initWithContentRect( 19440 contentRect, 19441 NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled, 19442 NSBackingStoreType.buffered, 19443 true 19444 ); 19445 19446 SimpleWindow.nativeMapping[cast(void*) window] = this; 19447 19448 window.title = MacString(title).borrow; 19449 19450 auto dg = SDWindowDelegate.alloc.init; 19451 dg.simpleWindow = this; 19452 window.delegate_ = dg; 19453 19454 auto view = SDGraphicsView.alloc.init; 19455 assert(view !is null); 19456 window.contentView = view; 19457 this.view = view; 19458 view.simpleWindow = this; 19459 19460 window.center(); 19461 19462 window.makeKeyAndOrderFront(null); 19463 19464 // no need to make a bitmap on mac since everything is double buffered already 19465 19466 // create area to draw on. 19467 createNewDrawingContext(width, height); 19468 19469 window.setBackgroundColor(NSColor.whiteColor); 19470 19471 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 19472 // show it 19473 view.setNeedsDisplay(true); 19474 } else { 19475 19476 view.setNeedsDisplay(true); 19477 // hide it 19478 //window.setIsVisible = false; 19479 } 19480 } 19481 19482 void createNewDrawingContext(int width, int height) { 19483 // FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay 19484 if(this.drawingContext) 19485 CGContextRelease(this.drawingContext); 19486 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 19487 this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 19488 CGColorSpaceRelease(colorSpace); 19489 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 19490 auto matrix = CGContextGetTextMatrix(drawingContext); 19491 matrix.c = -matrix.c; 19492 matrix.d = -matrix.d; 19493 CGContextSetTextMatrix(drawingContext, matrix); 19494 19495 } 19496 19497 void dispose() { 19498 closeWindow(); 19499 // window.release(); // closing the window does this automatically i think 19500 } 19501 void closeWindow() { 19502 window.close(); 19503 } 19504 19505 ScreenPainter getPainter(bool manualInvalidations) { 19506 return ScreenPainter(this, this.window, manualInvalidations); 19507 } 19508 19509 NSWindow window; 19510 NSView view; 19511 CGContextRef drawingContext; 19512 } 19513 } 19514 19515 version(without_opengl) {} else 19516 extern(System) nothrow @nogc { 19517 //enum uint GL_VERSION = 0x1F02; 19518 //const(char)* glGetString (/*GLenum*/uint); 19519 version(X11) { 19520 static if (!SdpyIsUsingIVGLBinds) { 19521 19522 enum GLX_X_RENDERABLE = 0x8012; 19523 enum GLX_DRAWABLE_TYPE = 0x8010; 19524 enum GLX_RENDER_TYPE = 0x8011; 19525 enum GLX_X_VISUAL_TYPE = 0x22; 19526 enum GLX_TRUE_COLOR = 0x8002; 19527 enum GLX_WINDOW_BIT = 0x00000001; 19528 enum GLX_RGBA_BIT = 0x00000001; 19529 enum GLX_COLOR_INDEX_BIT = 0x00000002; 19530 enum GLX_SAMPLE_BUFFERS = 0x186a0; 19531 enum GLX_SAMPLES = 0x186a1; 19532 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 19533 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 19534 } 19535 19536 // GLX_EXT_swap_control 19537 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 19538 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 19539 19540 //k8: ugly code to prevent warnings when sdpy is compiled into .a 19541 extern(System) { 19542 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 19543 } 19544 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 19545 19546 // this made public so we don't have to get it again and again 19547 public bool glXCreateContextAttribsARB_present () @system { 19548 if (glXCreateContextAttribsARBFn is cast(void*)1) { 19549 // get it 19550 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 19551 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 19552 } 19553 return (glXCreateContextAttribsARBFn !is null); 19554 } 19555 19556 // this made public so we don't have to get it again and again 19557 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) @system { 19558 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 19559 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 19560 } 19561 19562 // extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers 19563 extern(C) private __gshared int function(int) glXSwapIntervalMESA; 19564 19565 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 19566 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 19567 if (_glx_swapInterval_fn is null) { 19568 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 19569 if (_glx_swapInterval_fn is null) { 19570 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 19571 return; 19572 } 19573 version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); } 19574 } 19575 19576 if(glXSwapIntervalMESA is null) { 19577 // it seems to require both to actually take effect on many computers 19578 // idk why 19579 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA"); 19580 if(glXSwapIntervalMESA is null) 19581 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1; 19582 } 19583 19584 if(cast(void*) glXSwapIntervalMESA > cast(void*) 1) 19585 glXSwapIntervalMESA(wait ? 1 : 0); 19586 19587 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 19588 } 19589 } else version(Windows) { 19590 static if (!SdpyIsUsingIVGLBinds) { 19591 enum GL_TRUE = 1; 19592 enum GL_FALSE = 0; 19593 19594 public void* glbindGetProcAddress (const(char)* name) { 19595 void* res = wglGetProcAddress(name); 19596 if (res is null) { 19597 /+ 19598 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 19599 import core.sys.windows.windef, core.sys.windows.winbase; 19600 __gshared HINSTANCE dll = null; 19601 if (dll is null) { 19602 dll = LoadLibraryA("opengl32.dll"); 19603 if (dll is null) return null; // <32, but idc 19604 } 19605 res = GetProcAddress(dll, name); 19606 +/ 19607 res = GetProcAddress(gl.libHandle, name); 19608 } 19609 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 19610 return res; 19611 } 19612 } 19613 19614 19615 private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT; 19616 void wglSetVSync(bool wait) { 19617 if(wglSwapIntervalEXT is null) { 19618 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT"); 19619 if(wglSwapIntervalEXT is null) 19620 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1; 19621 } 19622 if(cast(void*) wglSwapIntervalEXT is cast(void*) 1) 19623 return; 19624 19625 wglSwapIntervalEXT(wait ? 1 : 0); 19626 } 19627 19628 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 19629 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 19630 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 19631 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 19632 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 19633 19634 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 19635 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 19636 19637 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 19638 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 19639 19640 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 19641 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 19642 19643 void wglInitOtherFunctions () { 19644 if (wglCreateContextAttribsARB is null) { 19645 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 19646 } 19647 } 19648 } 19649 19650 static if (!SdpyIsUsingIVGLBinds) { 19651 19652 interface GL { 19653 extern(System) @nogc nothrow: 19654 19655 void glGetIntegerv(int, void*); 19656 void glMatrixMode(int); 19657 void glPushMatrix(); 19658 void glLoadIdentity(); 19659 void glOrtho(double, double, double, double, double, double); 19660 void glFrustum(double, double, double, double, double, double); 19661 19662 void glPopMatrix(); 19663 void glEnable(int); 19664 void glDisable(int); 19665 void glClear(int); 19666 void glBegin(int); 19667 void glVertex2f(float, float); 19668 void glVertex3f(float, float, float); 19669 void glEnd(); 19670 void glColor3b(byte, byte, byte); 19671 void glColor3ub(ubyte, ubyte, ubyte); 19672 void glColor4b(byte, byte, byte, byte); 19673 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 19674 void glColor3i(int, int, int); 19675 void glColor3ui(uint, uint, uint); 19676 void glColor4i(int, int, int, int); 19677 void glColor4ui(uint, uint, uint, uint); 19678 void glColor3f(float, float, float); 19679 void glColor4f(float, float, float, float); 19680 void glTranslatef(float, float, float); 19681 void glScalef(float, float, float); 19682 version(X11) { 19683 void glSecondaryColor3b(byte, byte, byte); 19684 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 19685 void glSecondaryColor3i(int, int, int); 19686 void glSecondaryColor3ui(uint, uint, uint); 19687 void glSecondaryColor3f(float, float, float); 19688 } 19689 19690 void glDrawElements(int, int, int, void*); 19691 19692 void glRotatef(float, float, float, float); 19693 19694 uint glGetError(); 19695 19696 void glDeleteTextures(int, uint*); 19697 19698 19699 void glRasterPos2i(int, int); 19700 void glDrawPixels(int, int, uint, uint, void*); 19701 void glClearColor(float, float, float, float); 19702 19703 19704 void glPixelStorei(uint, int); 19705 19706 void glGenTextures(uint, uint*); 19707 void glBindTexture(int, int); 19708 void glTexParameteri(uint, uint, int); 19709 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 19710 void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*); 19711 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 19712 /*GLsizei*/int width, /*GLsizei*/int height, 19713 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 19714 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 19715 19716 void glLineWidth(int); 19717 19718 19719 void glTexCoord2f(float, float); 19720 void glVertex2i(int, int); 19721 void glBlendFunc (int, int); 19722 void glDepthFunc (int); 19723 void glViewport(int, int, int, int); 19724 19725 void glClearDepth(double); 19726 19727 void glReadBuffer(uint); 19728 void glReadPixels(int, int, int, int, int, int, void*); 19729 19730 void glScissor(GLint x, GLint y, GLsizei width, GLsizei height); 19731 19732 void glFlush(); 19733 void glFinish(); 19734 19735 version(Windows) { 19736 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 19737 HGLRC wglCreateContext(HDC); 19738 HGLRC wglCreateLayerContext(HDC, int); 19739 BOOL wglDeleteContext(HGLRC); 19740 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 19741 HGLRC wglGetCurrentContext(); 19742 HDC wglGetCurrentDC(); 19743 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 19744 PROC wglGetProcAddress(LPCSTR); 19745 BOOL wglMakeCurrent(HDC, HGLRC); 19746 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 19747 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 19748 BOOL wglShareLists(HGLRC, HGLRC); 19749 BOOL wglSwapLayerBuffers(HDC, UINT); 19750 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 19751 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 19752 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 19753 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 19754 } 19755 19756 } 19757 19758 interface GL3 { 19759 extern(System) @nogc nothrow: 19760 19761 void glGenVertexArrays(GLsizei, GLuint*); 19762 void glBindVertexArray(GLuint); 19763 void glDeleteVertexArrays(GLsizei, const(GLuint)*); 19764 void glGenerateMipmap(GLenum); 19765 void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 19766 void glStencilMask(GLuint); 19767 void glStencilFunc(GLenum, GLint, GLuint); 19768 void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 19769 void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 19770 GLuint glCreateProgram(); 19771 GLuint glCreateShader(GLenum); 19772 void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 19773 void glCompileShader(GLuint); 19774 void glGetShaderiv(GLuint, GLenum, GLint*); 19775 void glAttachShader(GLuint, GLuint); 19776 void glBindAttribLocation(GLuint, GLuint, const(GLchar)*); 19777 void glLinkProgram(GLuint); 19778 void glGetProgramiv(GLuint, GLenum, GLint*); 19779 void glDeleteProgram(GLuint); 19780 void glDeleteShader(GLuint); 19781 GLint glGetUniformLocation(GLuint, const(GLchar)*); 19782 void glGenBuffers(GLsizei, GLuint*); 19783 19784 void glUniform1f(GLint location, GLfloat v0); 19785 void glUniform2f(GLint location, GLfloat v0, GLfloat v1); 19786 void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); 19787 void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); 19788 void glUniform1i(GLint location, GLint v0); 19789 void glUniform2i(GLint location, GLint v0, GLint v1); 19790 void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2); 19791 void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); 19792 void glUniform1ui(GLint location, GLuint v0); 19793 void glUniform2ui(GLint location, GLuint v0, GLuint v1); 19794 void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2); 19795 void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); 19796 void glUniform1fv(GLint location, GLsizei count, const GLfloat *value); 19797 void glUniform2fv(GLint location, GLsizei count, const GLfloat *value); 19798 void glUniform3fv(GLint location, GLsizei count, const GLfloat *value); 19799 void glUniform4fv(GLint location, GLsizei count, const GLfloat *value); 19800 void glUniform1iv(GLint location, GLsizei count, const GLint *value); 19801 void glUniform2iv(GLint location, GLsizei count, const GLint *value); 19802 void glUniform3iv(GLint location, GLsizei count, const GLint *value); 19803 void glUniform4iv(GLint location, GLsizei count, const GLint *value); 19804 void glUniform1uiv(GLint location, GLsizei count, const GLuint *value); 19805 void glUniform2uiv(GLint location, GLsizei count, const GLuint *value); 19806 void glUniform3uiv(GLint location, GLsizei count, const GLuint *value); 19807 void glUniform4uiv(GLint location, GLsizei count, const GLuint *value); 19808 void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19809 void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19810 void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19811 void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19812 void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19813 void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19814 void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19815 void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19816 void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19817 19818 void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); 19819 void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); 19820 void glDrawArrays(GLenum, GLint, GLsizei); 19821 void glStencilOp(GLenum, GLenum, GLenum); 19822 void glUseProgram(GLuint); 19823 void glCullFace(GLenum); 19824 void glFrontFace(GLenum); 19825 void glActiveTexture(GLenum); 19826 void glBindBuffer(GLenum, GLuint); 19827 void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum); 19828 void glEnableVertexAttribArray(GLuint); 19829 void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 19830 void glUniform1i(GLint, GLint); 19831 void glUniform2fv(GLint, GLsizei, const(GLfloat)*); 19832 void glDisableVertexAttribArray(GLuint); 19833 void glDeleteBuffers(GLsizei, const(GLuint)*); 19834 void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); 19835 void glLogicOp (GLenum opcode); 19836 void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 19837 void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers); 19838 void glGenFramebuffers (GLsizei n, GLuint* framebuffers); 19839 GLenum glCheckFramebufferStatus (GLenum target); 19840 void glBindFramebuffer (GLenum target, GLuint framebuffer); 19841 } 19842 19843 interface GL4 { 19844 extern(System) @nogc nothrow: 19845 19846 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 19847 /*GLsizei*/int width, /*GLsizei*/int height, 19848 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 19849 } 19850 19851 interface GLU { 19852 extern(System) @nogc nothrow: 19853 19854 void gluLookAt(double, double, double, double, double, double, double, double, double); 19855 void gluPerspective(double, double, double, double); 19856 19857 char* gluErrorString(uint); 19858 } 19859 19860 19861 enum GL_RED = 0x1903; 19862 enum GL_ALPHA = 0x1906; 19863 19864 enum uint GL_FRONT = 0x0404; 19865 19866 enum uint GL_BLEND = 0x0be2; 19867 enum uint GL_LEQUAL = 0x0203; 19868 19869 19870 enum uint GL_RGB = 0x1907; 19871 enum uint GL_BGRA = 0x80e1; 19872 enum uint GL_RGBA = 0x1908; 19873 enum uint GL_RGBA8 = 0x8058; 19874 enum uint GL_TEXTURE_2D = 0x0DE1; 19875 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 19876 enum uint GL_NEAREST = 0x2600; 19877 enum uint GL_LINEAR = 0x2601; 19878 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 19879 enum uint GL_TEXTURE_WRAP_S = 0x2802; 19880 enum uint GL_TEXTURE_WRAP_T = 0x2803; 19881 enum uint GL_REPEAT = 0x2901; 19882 enum uint GL_CLAMP = 0x2900; 19883 enum uint GL_CLAMP_TO_EDGE = 0x812F; 19884 enum uint GL_CLAMP_TO_BORDER = 0x812D; 19885 enum uint GL_DECAL = 0x2101; 19886 enum uint GL_MODULATE = 0x2100; 19887 enum uint GL_TEXTURE_ENV = 0x2300; 19888 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 19889 enum uint GL_REPLACE = 0x1E01; 19890 enum uint GL_LIGHTING = 0x0B50; 19891 enum uint GL_DITHER = 0x0BD0; 19892 19893 enum uint GL_NO_ERROR = 0; 19894 19895 19896 19897 enum int GL_VIEWPORT = 0x0BA2; 19898 enum int GL_MODELVIEW = 0x1700; 19899 enum int GL_TEXTURE = 0x1702; 19900 enum int GL_PROJECTION = 0x1701; 19901 enum int GL_DEPTH_TEST = 0x0B71; 19902 19903 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 19904 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 19905 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 19906 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 19907 19908 enum int GL_POINTS = 0x0000; 19909 enum int GL_LINES = 0x0001; 19910 enum int GL_LINE_LOOP = 0x0002; 19911 enum int GL_LINE_STRIP = 0x0003; 19912 enum int GL_TRIANGLES = 0x0004; 19913 enum int GL_TRIANGLE_STRIP = 5; 19914 enum int GL_TRIANGLE_FAN = 6; 19915 enum int GL_QUADS = 7; 19916 enum int GL_QUAD_STRIP = 8; 19917 enum int GL_POLYGON = 9; 19918 19919 alias GLvoid = void; 19920 alias GLboolean = ubyte; 19921 alias GLint = int; 19922 alias GLuint = uint; 19923 alias GLenum = uint; 19924 alias GLchar = char; 19925 alias GLsizei = int; 19926 alias GLfloat = float; 19927 alias GLintptr = size_t; 19928 alias GLsizeiptr = ptrdiff_t; 19929 19930 19931 enum uint GL_INVALID_ENUM = 0x0500; 19932 19933 enum uint GL_ZERO = 0; 19934 enum uint GL_ONE = 1; 19935 19936 enum uint GL_BYTE = 0x1400; 19937 enum uint GL_UNSIGNED_BYTE = 0x1401; 19938 enum uint GL_SHORT = 0x1402; 19939 enum uint GL_UNSIGNED_SHORT = 0x1403; 19940 enum uint GL_INT = 0x1404; 19941 enum uint GL_UNSIGNED_INT = 0x1405; 19942 enum uint GL_FLOAT = 0x1406; 19943 enum uint GL_2_BYTES = 0x1407; 19944 enum uint GL_3_BYTES = 0x1408; 19945 enum uint GL_4_BYTES = 0x1409; 19946 enum uint GL_DOUBLE = 0x140A; 19947 19948 enum uint GL_STREAM_DRAW = 0x88E0; 19949 19950 enum uint GL_CCW = 0x0901; 19951 19952 enum uint GL_STENCIL_TEST = 0x0B90; 19953 enum uint GL_SCISSOR_TEST = 0x0C11; 19954 19955 enum uint GL_EQUAL = 0x0202; 19956 enum uint GL_NOTEQUAL = 0x0205; 19957 19958 enum uint GL_ALWAYS = 0x0207; 19959 enum uint GL_KEEP = 0x1E00; 19960 19961 enum uint GL_INCR = 0x1E02; 19962 19963 enum uint GL_INCR_WRAP = 0x8507; 19964 enum uint GL_DECR_WRAP = 0x8508; 19965 19966 enum uint GL_CULL_FACE = 0x0B44; 19967 enum uint GL_BACK = 0x0405; 19968 19969 enum uint GL_FRAGMENT_SHADER = 0x8B30; 19970 enum uint GL_VERTEX_SHADER = 0x8B31; 19971 19972 enum uint GL_COMPILE_STATUS = 0x8B81; 19973 enum uint GL_LINK_STATUS = 0x8B82; 19974 19975 enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893; 19976 19977 enum uint GL_STATIC_DRAW = 0x88E4; 19978 19979 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 19980 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 19981 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 19982 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 19983 19984 enum uint GL_GENERATE_MIPMAP = 0x8191; 19985 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 19986 19987 enum uint GL_TEXTURE0 = 0x84C0U; 19988 enum uint GL_TEXTURE1 = 0x84C1U; 19989 19990 enum uint GL_ARRAY_BUFFER = 0x8892; 19991 19992 enum uint GL_SRC_COLOR = 0x0300; 19993 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 19994 enum uint GL_SRC_ALPHA = 0x0302; 19995 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 19996 enum uint GL_DST_ALPHA = 0x0304; 19997 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 19998 enum uint GL_DST_COLOR = 0x0306; 19999 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 20000 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 20001 20002 enum uint GL_INVERT = 0x150AU; 20003 20004 enum uint GL_DEPTH_STENCIL = 0x84F9U; 20005 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 20006 20007 enum uint GL_FRAMEBUFFER = 0x8D40U; 20008 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 20009 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 20010 20011 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 20012 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 20013 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 20014 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 20015 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 20016 20017 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 20018 enum uint GL_CLEAR = 0x1500U; 20019 enum uint GL_COPY = 0x1503U; 20020 enum uint GL_XOR = 0x1506U; 20021 20022 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 20023 20024 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 20025 20026 } 20027 } 20028 20029 /++ 20030 History: 20031 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. 20032 +/ 20033 __gshared bool gluSuccessfullyLoaded = true; 20034 20035 version(without_opengl) {} else { 20036 static if(!SdpyIsUsingIVGLBinds) { 20037 version(Windows) { 20038 mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl; 20039 mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu; 20040 } else { 20041 mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl; 20042 mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu; 20043 } 20044 mixin DynamicLoadSupplementalOpenGL!(GL3) gl3; 20045 20046 20047 shared static this() { 20048 gl.loadDynamicLibrary(); 20049 20050 // FIXME: this is NOT actually required and should NOT fail if it is not loaded 20051 // unless those functions are actually used 20052 // go to mark b openGlLibrariesSuccessfullyLoaded = false; 20053 glu.loadDynamicLibrary(); 20054 } 20055 } 20056 } 20057 20058 /++ 20059 Convenience method for converting D arrays to opengl buffer data 20060 20061 I would LOVE to overload it with the original glBufferData, but D won't 20062 let me since glBufferData is a function pointer :( 20063 20064 Added: August 25, 2020 (version 8.5) 20065 +/ 20066 version(without_opengl) {} else 20067 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) { 20068 glBufferData(target, data.length, data.ptr, usage); 20069 } 20070 20071 /++ 20072 History: 20073 Added September 1, 2024 20074 +/ 20075 version(without_opengl) {} else 20076 void glBufferSubDataSlice(GLenum target, size_t offset, const(void[]) data, GLenum usage) { 20077 glBufferSubData(target, offset, data.length, data.ptr); 20078 } 20079 20080 /++ 20081 Convenience class for using opengl shaders. 20082 20083 Ensure that you've loaded opengl 3+ and set your active 20084 context before trying to use this. 20085 20086 Added: August 25, 2020 (version 8.5) 20087 +/ 20088 version(without_opengl) {} else 20089 final class OpenGlShader { 20090 private int shaderProgram_; 20091 private @property void shaderProgram(int a) { 20092 shaderProgram_ = a; 20093 } 20094 /// Get the program ID for use in OpenGL functions. 20095 public @property int shaderProgram() { 20096 return shaderProgram_; 20097 } 20098 20099 /++ 20100 20101 +/ 20102 static struct Source { 20103 uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc. 20104 string code; /// 20105 } 20106 20107 /++ 20108 Helper method to just compile some shader code and check for errors 20109 while you do glCreateShader, etc. on the outside yourself. 20110 20111 This just does `glShaderSource` and `glCompileShader` for the given code. 20112 20113 If you the OpenGlShader class constructor, you never need to call this yourself. 20114 +/ 20115 static void compile(int sid, Source code) { 20116 const(char)*[1] buffer; 20117 int[1] lengthBuffer; 20118 20119 buffer[0] = code.code.ptr; 20120 lengthBuffer[0] = cast(int) code.code.length; 20121 20122 glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr); 20123 glCompileShader(sid); 20124 20125 int success; 20126 glGetShaderiv(sid, GL_COMPILE_STATUS, &success); 20127 if(!success) { 20128 char[512] info; 20129 int len; 20130 glGetShaderInfoLog(sid, info.length, &len, info.ptr); 20131 20132 throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]); 20133 } 20134 } 20135 20136 /++ 20137 Calls `glLinkProgram` and throws if error a occurs. 20138 20139 If you the OpenGlShader class constructor, you never need to call this yourself. 20140 +/ 20141 static void link(int shaderProgram) { 20142 glLinkProgram(shaderProgram); 20143 int success; 20144 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 20145 if(!success) { 20146 char[512] info; 20147 int len; 20148 glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr); 20149 20150 throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]); 20151 } 20152 } 20153 20154 /++ 20155 Constructs the shader object by calling `glCreateProgram`, then 20156 compiling each given [Source], and finally, linking them together. 20157 20158 Throws: on compile or link failure. 20159 +/ 20160 this(Source[] codes...) { 20161 shaderProgram = glCreateProgram(); 20162 20163 int[16] shadersBufferStack; 20164 20165 int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 20166 shadersBufferStack[0 .. codes.length] : 20167 new int[](codes.length); 20168 20169 foreach(idx, code; codes) { 20170 shadersBuffer[idx] = glCreateShader(code.type); 20171 20172 compile(shadersBuffer[idx], code); 20173 20174 glAttachShader(shaderProgram, shadersBuffer[idx]); 20175 } 20176 20177 link(shaderProgram); 20178 20179 foreach(s; shadersBuffer) 20180 glDeleteShader(s); 20181 } 20182 20183 /// Calls `glUseProgram(this.shaderProgram)` 20184 void use() { 20185 glUseProgram(this.shaderProgram); 20186 } 20187 20188 /// Deletes the program. 20189 void delete_() { 20190 glDeleteProgram(shaderProgram); 20191 shaderProgram = 0; 20192 } 20193 20194 /++ 20195 [OpenGlShader.uniforms].name gives you one of these. 20196 20197 You can get the id out of it or just assign 20198 +/ 20199 static struct Uniform { 20200 /// the id passed to glUniform* 20201 int id; 20202 20203 /// Assigns the 4 floats. You will probably have to call this via the .opAssign name 20204 void opAssign(float x, float y, float z, float w) { 20205 if(id != -1) 20206 glUniform4f(id, x, y, z, w); 20207 } 20208 20209 void opAssign(float x) { 20210 if(id != -1) 20211 glUniform1f(id, x); 20212 } 20213 20214 void opAssign(float x, float y) { 20215 if(id != -1) 20216 glUniform2f(id, x, y); 20217 } 20218 20219 void opAssign(T)(T t) { 20220 t.glUniform(id); 20221 } 20222 } 20223 20224 static struct UniformsHelper { 20225 OpenGlShader _shader; 20226 20227 @property Uniform opDispatch(string name)() { 20228 auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); 20229 // FIXME: decide what to do here; the exception is liable to be swallowed by the event syste 20230 //if(i == -1) 20231 //throw new Exception("Could not find uniform " ~ name); 20232 return Uniform(i); 20233 } 20234 20235 @property void opDispatch(string name, T)(T t) { 20236 Uniform f = this.opDispatch!name; 20237 t.glUniform(f); 20238 } 20239 } 20240 20241 /++ 20242 Gives access to the uniforms through dot access. 20243 `OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo"); 20244 +/ 20245 @property UniformsHelper uniforms() { return UniformsHelper(this); } 20246 } 20247 20248 version(without_opengl) {} else { 20249 /++ 20250 A static container of experimental types and value constructors for opengl 3+ shaders. 20251 20252 20253 You can declare variables like: 20254 20255 ``` 20256 OGL.vec3f something; 20257 ``` 20258 20259 But generally it would be used with [OpenGlShader]'s uniform helpers like 20260 20261 ``` 20262 shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific 20263 ``` 20264 20265 This is still extremely experimental, not very useful at this point, and thus subject to change at random. 20266 20267 20268 History: 20269 Added December 7, 2021. Not yet stable. 20270 +/ 20271 final class OGL { 20272 static: 20273 20274 private template typeFromSpecifier(string specifier) { 20275 static if(specifier == "f") 20276 alias typeFromSpecifier = GLfloat; 20277 else static if(specifier == "i") 20278 alias typeFromSpecifier = GLint; 20279 else static if(specifier == "ui") 20280 alias typeFromSpecifier = GLuint; 20281 else static assert(0, "I don't know this ogl type suffix " ~ specifier); 20282 } 20283 20284 private template CommonType(T...) { 20285 static if(T.length == 1) 20286 alias CommonType = T[0]; 20287 else static if(is(typeof(true ? T[0].init : T[1].init) C)) 20288 alias CommonType = CommonType!(C, T[2 .. $]); 20289 } 20290 20291 private template typesToSpecifier(T...) { 20292 static if(is(CommonType!T == float)) 20293 enum typesToSpecifier = "f"; 20294 else static if(is(CommonType!T == int)) 20295 enum typesToSpecifier = "i"; 20296 else static if(is(CommonType!T == uint)) 20297 enum typesToSpecifier = "ui"; 20298 else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof); 20299 } 20300 20301 private template genNames(size_t dim, size_t dim2 = 0) { 20302 string helper() { 20303 string s; 20304 if(dim2) { 20305 static if(__VERSION__ < 2102) 20306 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = void;"; // stupid compiler bug 20307 else 20308 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = 0;"; 20309 } else { 20310 if(dim > 0) s ~= "type x = 0;"; 20311 if(dim > 1) s ~= "type y = 0;"; 20312 if(dim > 2) s ~= "type z = 0;"; 20313 if(dim > 3) s ~= "type w = 0;"; 20314 } 20315 20316 s ~= "this(typeof(this.tupleof) args) { this.tupleof = args; }"; 20317 if(dim2) 20318 s ~= "this(type["~(dim*dim2).stringof~"] t) { (cast(typeof(t)) this.matrix)[] = t[]; }"; 20319 20320 return s; 20321 } 20322 20323 enum genNames = helper(); 20324 } 20325 20326 // there's vec, arrays of vec, mat, and arrays of mat 20327 template opDispatch(string name) 20328 if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat")) 20329 { 20330 static if(name[4] == 'x') { 20331 enum dimX = cast(int) (name[3] - '0'); 20332 static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]); 20333 20334 enum dimY = cast(int) (name[5] - '0'); 20335 static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]); 20336 20337 enum isArray = name[$ - 1] == 'v'; 20338 enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $]; 20339 alias type = typeFromSpecifier!typeSpecifier; 20340 } else { 20341 enum dim = cast(int) (name[3] - '0'); 20342 static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]); 20343 enum isArray = name[$ - 1] == 'v'; 20344 enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $]; 20345 alias type = typeFromSpecifier!typeSpecifier; 20346 } 20347 20348 align(1) 20349 struct opDispatch { 20350 align(1): 20351 static if(name[4] == 'x') 20352 mixin(genNames!(dimX, dimY)); 20353 else 20354 mixin(genNames!dim); 20355 20356 private void glUniform(OpenGlShader.Uniform assignTo) { 20357 glUniform(assignTo.id); 20358 } 20359 private void glUniform(int assignTo) { 20360 static if(name[4] == 'x') { 20361 static if(name[3] == name[5]) { 20362 // import std.stdio; writeln(name, " ", this.matrix, dimX, " ", dimY); 20363 mixin("glUniformMatrix" ~ name[5 .. $] ~ "v")(assignTo, 1, true, &this.matrix[0][0]); 20364 } else { 20365 mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, 1, false, this.matrix.ptr); 20366 } 20367 } else 20368 mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof); 20369 } 20370 } 20371 } 20372 20373 auto vec(T...)(T members) { 20374 return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members); 20375 } 20376 } 20377 20378 void checkGlError() { 20379 auto error = glGetError(); 20380 int[] errors; 20381 string[] errorStrings; 20382 while(error != GL_NO_ERROR) { 20383 errors ~= error; 20384 switch(error) { 20385 case 0x0500: errorStrings ~= "GL_INVALID_ENUM"; break; 20386 case 0x0501: errorStrings ~= "GL_INVALID_VALUE"; break; 20387 case 0x0502: errorStrings ~= "GL_INVALID_OPERATION"; break; 20388 case 0x0503: errorStrings ~= "GL_STACK_OVERFLOW"; break; 20389 case 0x0504: errorStrings ~= "GL_STACK_UNDERFLOW"; break; 20390 case 0x0505: errorStrings ~= "GL_OUT_OF_MEMORY"; break; 20391 default: errorStrings ~= "idk"; 20392 } 20393 error = glGetError(); 20394 } 20395 if(errors.length) 20396 throw ArsdException!"glGetError"(errors, errorStrings); 20397 } 20398 20399 /++ 20400 A matrix for simple uses that easily integrates with [OpenGlShader]. 20401 20402 Might not be useful to you since it only as some simple functions and 20403 probably isn't that fast. 20404 20405 Note it uses an inline static array for its storage, so copying it 20406 may be expensive. 20407 +/ 20408 struct BasicMatrix(int columns, int rows, T = float) { 20409 static import core.stdc.math; 20410 static if(is(T == float)) { 20411 alias cos = core.stdc.math.cosf; 20412 alias sin = core.stdc.math.sinf; 20413 } else { 20414 alias cos = core.stdc.math.cos; 20415 alias sin = core.stdc.math.sin; 20416 } 20417 20418 T[columns * rows] data = 0.0; 20419 20420 /++ 20421 20422 +/ 20423 this(T[columns * rows] data) { 20424 this.data = data; 20425 } 20426 20427 /++ 20428 Basic operations that operate *in place*. 20429 +/ 20430 static if(columns == 4 && rows == 4) 20431 void translate(T x, T y, T z) { 20432 BasicMatrix m = [ 20433 1, 0, 0, x, 20434 0, 1, 0, y, 20435 0, 0, 1, z, 20436 0, 0, 0, 1 20437 ]; 20438 20439 this *= m; 20440 } 20441 20442 /// ditto 20443 static if(columns == 4 && rows == 4) 20444 void scale(T x, T y, T z) { 20445 BasicMatrix m = [ 20446 x, 0, 0, 0, 20447 0, y, 0, 0, 20448 0, 0, z, 0, 20449 0, 0, 0, 1 20450 ]; 20451 20452 this *= m; 20453 } 20454 20455 /// ditto 20456 static if(columns == 4 && rows == 4) 20457 void rotateX(T theta) { 20458 BasicMatrix m = [ 20459 1, 0, 0, 0, 20460 0, cos(theta), -sin(theta), 0, 20461 0, sin(theta), cos(theta), 0, 20462 0, 0, 0, 1 20463 ]; 20464 20465 this *= m; 20466 } 20467 20468 /// ditto 20469 static if(columns == 4 && rows == 4) 20470 void rotateY(T theta) { 20471 BasicMatrix m = [ 20472 cos(theta), 0, sin(theta), 0, 20473 0, 1, 0, 0, 20474 -sin(theta), 0, cos(theta), 0, 20475 0, 0, 0, 1 20476 ]; 20477 20478 this *= m; 20479 } 20480 20481 /// ditto 20482 static if(columns == 4 && rows == 4) 20483 void rotateZ(T theta) { 20484 BasicMatrix m = [ 20485 cos(theta), -sin(theta), 0, 0, 20486 sin(theta), cos(theta), 0, 0, 20487 0, 0, 1, 0, 20488 0, 0, 0, 1 20489 ]; 20490 20491 this *= m; 20492 } 20493 20494 /++ 20495 20496 +/ 20497 static if(columns == rows) 20498 static BasicMatrix identity() { 20499 BasicMatrix m; 20500 foreach(i; 0 .. columns) 20501 m.data[0 + i + i * columns] = 1.0; 20502 return m; 20503 } 20504 20505 static if(columns == rows) 20506 void loadIdentity() { 20507 this = identity(); 20508 } 20509 20510 static if(columns == 4 && rows == 4) 20511 static BasicMatrix ortho(T l, T r, T b, T t, T n, T f) { 20512 return BasicMatrix([ 20513 2/(r-l), 0, 0, -(r+l)/(r-l), 20514 0, 2/(t-b), 0, -(t+b)/(t-b), 20515 0, 0, -2/(f-n), -(f+n)/(f-n), 20516 0, 0, 0, 1 20517 ]); 20518 } 20519 20520 static if(columns == 4 && rows == 4) 20521 void loadOrtho(T l, T r, T b, T t, T n, T f) { 20522 this = ortho(l, r, b, t, n, f); 20523 } 20524 20525 void opOpAssign(string op : "+")(const BasicMatrix rhs) { 20526 this.data[] += rhs.data; 20527 } 20528 void opOpAssign(string op : "-")(const BasicMatrix rhs) { 20529 this.data[] -= rhs.data; 20530 } 20531 void opOpAssign(string op : "*")(const T rhs) { 20532 this.data[] *= rhs; 20533 } 20534 void opOpAssign(string op : "/")(const T rhs) { 20535 this.data[] /= rhs; 20536 } 20537 void opOpAssign(string op : "*", BM : BasicMatrix!(rhsColumns, rhsRows, rhsT), int rhsColumns, int rhsRows, rhsT)(const BM rhs) { 20538 static assert(columns == rhsRows); 20539 auto multiplySize = columns; 20540 20541 auto tmp = this.data; // copy cuz it is a value type 20542 20543 int idx = 0; 20544 foreach(r; 0 .. rows) 20545 foreach(c; 0 .. columns) { 20546 T sum = 0.0; 20547 20548 foreach(i; 0 .. multiplySize) 20549 sum += this.data[r * columns + i] * rhs.data[i * rhsColumns + c]; 20550 20551 tmp[idx++] = sum; 20552 } 20553 20554 this.data = tmp; 20555 } 20556 } 20557 20558 unittest { 20559 auto m = BasicMatrix!(2, 2)([ 20560 1, 2, 20561 3, 4 20562 ]); 20563 20564 auto m2 = BasicMatrix!(2, 2)([ 20565 5, 6, 20566 7, 8 20567 ]); 20568 20569 //import std.conv; 20570 m *= m2; 20571 assert(m.data == [ 20572 19, 22, 20573 43, 50 20574 ]);//, to!string(m.data)); 20575 } 20576 20577 20578 20579 class GlObjectBase { 20580 protected uint _vao; 20581 protected uint _elementsCount; 20582 20583 protected uint element_buffer; 20584 20585 void gen() { 20586 glGenVertexArrays(1, &_vao); 20587 } 20588 20589 void bind() { 20590 glBindVertexArray(_vao); 20591 } 20592 20593 void dispose() { 20594 glDeleteVertexArrays(1, &_vao); 20595 } 20596 20597 void draw() { 20598 bind(); 20599 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer); 20600 glDrawElements(GL_TRIANGLES, _elementsCount, GL_UNSIGNED_INT, null); 20601 } 20602 } 20603 20604 /++ 20605 20606 +/ 20607 class GlObject(T) : GlObjectBase { 20608 protected uint VBO; 20609 20610 this(T[] arr, uint[] indices) { 20611 gen(); 20612 bind(); 20613 20614 glGenBuffers(1, &VBO); 20615 glGenBuffers(1, &element_buffer); 20616 20617 glBindBuffer(GL_ARRAY_BUFFER, VBO); 20618 glBufferDataSlice(GL_ARRAY_BUFFER, arr, GL_STATIC_DRAW); 20619 20620 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer); 20621 glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); 20622 _elementsCount = cast(int) indices.length; 20623 20624 foreach(int idx, memberName; __traits(allMembers, T)) { 20625 static if(memberName != "__ctor") { 20626 static if(is(typeof(__traits(getMember, T, memberName)) == float[N], size_t N)) { 20627 glVertexAttribPointer(idx, N, GL_FLOAT, GL_FALSE, T.sizeof, cast(void*) __traits(getMember, T, memberName).offsetof); 20628 glEnableVertexAttribArray(idx); 20629 } else static assert(0); } 20630 } 20631 } 20632 20633 static string generateShaderDefinitions() { 20634 string code; 20635 20636 foreach(idx, memberName; __traits(allMembers, T)) { 20637 // never use stringof ladies and gents it has a LU thing at the end of it 20638 static if(memberName != "__ctor") 20639 code ~= "layout (location = " ~ idx.stringof[0..$-2] ~ ") in " ~ typeToGl!(typeof(__traits(getMember, T, memberName))) ~ " " ~ memberName ~ ";\n"; 20640 } 20641 20642 return code; 20643 } 20644 } 20645 20646 private string typeToGl(T)() { 20647 static if(is(T == float[4])) 20648 return "vec4"; 20649 else static if(is(T == float[3])) 20650 return "vec3"; 20651 else static if(is(T == float[2])) 20652 return "vec2"; 20653 else static assert(0, T.stringof); 20654 } 20655 20656 20657 } 20658 20659 version(Emscripten) { 20660 20661 } else version(linux) { 20662 version(with_eventloop) {} else { 20663 private int epollFd = -1; 20664 void prepareEventLoop() { 20665 if(epollFd != -1) 20666 return; // already initialized, no need to do it again 20667 import ep = core.sys.linux.epoll; 20668 20669 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 20670 if(epollFd == -1) 20671 throw new Exception("epoll create failure"); 20672 } 20673 } 20674 } else version(Posix) { 20675 void prepareEventLoop() {} 20676 } 20677 20678 version(X11) { 20679 import core.stdc.locale : LC_ALL; // rdmd fix 20680 __gshared bool sdx_isUTF8Locale; 20681 20682 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 20683 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 20684 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 20685 // anal magic is here. I (Ketmar) hope you like it. 20686 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 20687 // always return correct unicode symbols. The detection is here 'cause user can change locale 20688 // later. 20689 20690 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 20691 shared static this () @system { 20692 if(!librariesSuccessfullyLoaded) 20693 return; 20694 20695 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 20696 20697 // this doesn't hurt; it may add some locking, but the speed is still 20698 // allows doing 60 FPS videogames; also, ignore the result, as most 20699 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 20700 // never seen this failing). 20701 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 20702 20703 setlocale(LC_ALL, ""); 20704 // check if out locale is UTF-8 20705 auto lct = setlocale(LC_CTYPE, null); 20706 if (lct is null) { 20707 sdx_isUTF8Locale = false; 20708 } else { 20709 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 20710 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 20711 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 20712 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 20713 { 20714 sdx_isUTF8Locale = true; 20715 break; 20716 } 20717 } 20718 } 20719 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 20720 } 20721 } 20722 20723 /++ 20724 $(PITFALL This is not yet stable and may break in future versions without notice.) 20725 20726 History: 20727 Added February 19, 2021 20728 +/ 20729 /// Group: drag_and_drop 20730 interface DropHandler { 20731 /++ 20732 Called when the drag enters the handler's area. 20733 +/ 20734 DragAndDropAction dragEnter(DropPackage*); 20735 /++ 20736 Called when the drag leaves the handler's area or is 20737 cancelled. You should free your resources when this is called. 20738 +/ 20739 void dragLeave(); 20740 /++ 20741 Called continually as the drag moves over the handler's area. 20742 20743 Returns: feedback to the dragger 20744 +/ 20745 DropParameters dragOver(Point pt); 20746 /++ 20747 The user dropped the data and you should process it now. You can 20748 access the data through the given [DropPackage]. 20749 +/ 20750 void drop(scope DropPackage*); 20751 /++ 20752 Called when the drop is complete. You should free whatever temporary 20753 resources you were using. It is often reasonable to simply forward 20754 this call to [dragLeave]. 20755 +/ 20756 void finish(); 20757 20758 /++ 20759 Parameters returned by [DropHandler.drop]. 20760 +/ 20761 static struct DropParameters { 20762 /++ 20763 Acceptable action over this area. 20764 +/ 20765 DragAndDropAction action; 20766 /++ 20767 Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again. 20768 20769 If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources. 20770 +/ 20771 Rectangle consistentWithin; 20772 } 20773 } 20774 20775 /++ 20776 History: 20777 Added February 19, 2021 20778 +/ 20779 /// Group: drag_and_drop 20780 enum DragAndDropAction { 20781 none = 0, 20782 copy, 20783 move, 20784 link, 20785 ask, 20786 custom 20787 } 20788 20789 /++ 20790 An opaque structure representing dropped data. It contains 20791 private, platform-specific data that your `drop` function 20792 should simply forward. 20793 20794 $(PITFALL This is not yet stable and may break in future versions without notice.) 20795 20796 History: 20797 Added February 19, 2021 20798 +/ 20799 /// Group: drag_and_drop 20800 struct DropPackage { 20801 /++ 20802 Lists the available formats as magic numbers. You should compare these 20803 against looked-up formats (see [DraggableData.getFormatId]) you know you support and can 20804 understand the passed data. 20805 +/ 20806 DraggableData.FormatId[] availableFormats() { 20807 version(X11) { 20808 return xFormats; 20809 } else version(Windows) { 20810 if(pDataObj is null) 20811 return null; 20812 20813 typeof(return) ret; 20814 20815 IEnumFORMATETC ef; 20816 if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) { 20817 FORMATETC fmt; 20818 ULONG fetched; 20819 while(ef.Next(1, &fmt, &fetched) == S_OK) { 20820 if(fetched == 0) 20821 break; 20822 20823 if(fmt.lindex != -1) 20824 continue; 20825 if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT) 20826 continue; 20827 if(!(fmt.tymed & TYMED.TYMED_HGLOBAL)) 20828 continue; 20829 20830 ret ~= fmt.cfFormat; 20831 } 20832 } 20833 20834 return ret; 20835 } else throw new NotYetImplementedException(); 20836 } 20837 20838 /++ 20839 Gets data from the drop and optionally accepts it. 20840 20841 Returns: 20842 void because the data is fed asynchronously through the `dg` parameter. 20843 20844 Params: 20845 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. 20846 20847 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. 20848 20849 Calling `getData` again after accepting a drop is not permitted. 20850 20851 format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format. 20852 20853 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. 20854 20855 Throws: 20856 if `format` was not compatible with the [availableFormats] or if the drop has already been accepted. 20857 20858 History: 20859 Included in first release of [DropPackage]. 20860 +/ 20861 void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) { 20862 version(X11) { 20863 20864 auto display = XDisplayConnection.get(); 20865 auto selectionAtom = GetAtom!"XdndSelection"(display); 20866 auto best = format; 20867 20868 static class X11GetSelectionHandler_Drop : X11GetSelectionHandler { 20869 20870 XDisplay* display; 20871 Atom selectionAtom; 20872 DraggableData.FormatId best; 20873 DraggableData.FormatId format; 20874 void delegate(scope ubyte[] data) dg; 20875 DragAndDropAction acceptedAction; 20876 Window sourceWindow; 20877 SimpleWindow win; 20878 this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) { 20879 this.display = display; 20880 this.win = win; 20881 this.sourceWindow = sourceWindow; 20882 this.format = format; 20883 this.selectionAtom = selectionAtom; 20884 this.best = best; 20885 this.dg = dg; 20886 this.acceptedAction = acceptedAction; 20887 } 20888 20889 20890 mixin X11GetSelectionHandler_Basics; 20891 20892 void handleData(Atom target, in ubyte[] data) { 20893 //if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 20894 20895 dg(cast(ubyte[]) data); 20896 20897 if(acceptedAction != DragAndDropAction.none) { 20898 auto display = XDisplayConnection.get; 20899 20900 XClientMessageEvent xclient; 20901 20902 xclient.type = EventType.ClientMessage; 20903 xclient.window = sourceWindow; 20904 xclient.message_type = GetAtom!"XdndFinished"(display); 20905 xclient.format = 32; 20906 xclient.data.l[0] = win.impl.window; 20907 xclient.data.l[1] = 1; // drop successful 20908 xclient.data.l[2] = dndActionAtom(display, acceptedAction); 20909 20910 XSendEvent( 20911 display, 20912 sourceWindow, 20913 false, 20914 EventMask.NoEventMask, 20915 cast(XEvent*) &xclient 20916 ); 20917 20918 XFlush(display); 20919 } 20920 } 20921 20922 Atom findBestFormat(Atom[] answer) { 20923 Atom best = None; 20924 foreach(option; answer) { 20925 if(option == format) { 20926 best = option; 20927 break; 20928 } 20929 /* 20930 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 20931 best = option; 20932 break; 20933 } else if(option == XA_STRING) { 20934 best = option; 20935 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 20936 best = option; 20937 } 20938 */ 20939 } 20940 return best; 20941 } 20942 } 20943 20944 win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction); 20945 20946 XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp); 20947 20948 } else version(Windows) { 20949 20950 // clean up like DragLeave 20951 // pass effect back up 20952 20953 FORMATETC t; 20954 assert(format >= 0 && format <= ushort.max); 20955 t.cfFormat = cast(ushort) format; 20956 t.lindex = -1; 20957 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 20958 t.tymed = TYMED.TYMED_HGLOBAL; 20959 20960 STGMEDIUM m; 20961 20962 if(pDataObj.GetData(&t, &m) != S_OK) { 20963 // fail 20964 } else { 20965 // succeed, take the data and clean up 20966 20967 // FIXME: ensure it is legit HGLOBAL 20968 auto handle = m.hGlobal; 20969 20970 if(handle) { 20971 auto sz = GlobalSize(handle); 20972 if(auto ptr = cast(ubyte*) GlobalLock(handle)) { 20973 scope(exit) GlobalUnlock(handle); 20974 scope(exit) GlobalFree(handle); 20975 20976 auto data = ptr[0 .. sz]; 20977 20978 dg(data); 20979 } 20980 } 20981 } 20982 } 20983 } 20984 20985 private: 20986 20987 version(X11) { 20988 SimpleWindow win; 20989 Window sourceWindow; 20990 Time dataTimestamp; 20991 20992 Atom[] xFormats; 20993 } 20994 version(Windows) { 20995 IDataObject pDataObj; 20996 } 20997 } 20998 20999 /++ 21000 A generic helper base class for making a drop handler with a preference list of custom types. 21001 This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own 21002 droppers too. 21003 21004 It assumes the whole window it used, but you can subclass to change that. 21005 21006 $(PITFALL This is not yet stable and may break in future versions without notice.) 21007 21008 History: 21009 Added February 19, 2021 21010 +/ 21011 /// Group: drag_and_drop 21012 class GenericDropHandlerBase : DropHandler { 21013 // no fancy state here so no need to do anything here 21014 void finish() { } 21015 void dragLeave() { } 21016 21017 private DragAndDropAction acceptedAction; 21018 private DraggableData.FormatId acceptedFormat; 21019 private void delegate(scope ubyte[]) acceptedHandler; 21020 21021 struct FormatHandler { 21022 DraggableData.FormatId format; 21023 void delegate(scope ubyte[]) handler; 21024 } 21025 21026 protected abstract FormatHandler[] formatHandlers(); 21027 21028 DragAndDropAction dragEnter(DropPackage* pkg) { 21029 debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); } 21030 foreach(fmt; formatHandlers()) 21031 foreach(f; pkg.availableFormats()) 21032 if(f == fmt.format) { 21033 acceptedFormat = f; 21034 acceptedHandler = fmt.handler; 21035 return acceptedAction = DragAndDropAction.copy; 21036 } 21037 return acceptedAction = DragAndDropAction.none; 21038 } 21039 DropParameters dragOver(Point pt) { 21040 return DropParameters(acceptedAction); 21041 } 21042 21043 void drop(scope DropPackage* dropPackage) { 21044 if(!acceptedFormat || acceptedHandler is null) { 21045 debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); } 21046 return; // prolly shouldn't happen anyway... 21047 } 21048 21049 dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler); 21050 } 21051 } 21052 21053 /++ 21054 A simple handler for making your window accept drops of plain text. 21055 21056 $(PITFALL This is not yet stable and may break in future versions without notice.) 21057 21058 History: 21059 Added February 22, 2021 21060 +/ 21061 /// Group: drag_and_drop 21062 class TextDropHandler : GenericDropHandlerBase { 21063 private void delegate(in char[] text) dg; 21064 21065 /++ 21066 21067 +/ 21068 this(void delegate(in char[] text) dg) { 21069 this.dg = dg; 21070 } 21071 21072 protected override FormatHandler[] formatHandlers() { 21073 version(X11) 21074 return [ 21075 FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator), 21076 FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator), 21077 ]; 21078 else version(Windows) 21079 return [ 21080 FormatHandler(CF_UNICODETEXT, &translator), 21081 ]; 21082 else throw new NotYetImplementedException(); 21083 } 21084 21085 private void translator(scope ubyte[] data) { 21086 version(X11) 21087 dg(cast(char[]) data); 21088 else version(Windows) 21089 dg(makeUtf8StringFromWindowsString(cast(wchar[]) data)); 21090 } 21091 } 21092 21093 /++ 21094 A simple handler for making your window accept drops of files, issued to you as file names. 21095 21096 $(PITFALL This is not yet stable and may break in future versions without notice.) 21097 21098 History: 21099 Added February 22, 2021 21100 +/ 21101 /// Group: drag_and_drop 21102 21103 class FilesDropHandler : GenericDropHandlerBase { 21104 private void delegate(in char[][]) dg; 21105 21106 /++ 21107 21108 +/ 21109 this(void delegate(in char[][] fileNames) dg) { 21110 this.dg = dg; 21111 } 21112 21113 protected override FormatHandler[] formatHandlers() { 21114 version(X11) 21115 return [ 21116 FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator), 21117 ]; 21118 else version(Windows) 21119 return [ 21120 FormatHandler(CF_HDROP, &translator), 21121 ]; 21122 else throw new NotYetImplementedException(); 21123 } 21124 21125 private void translator(scope ubyte[] data) @system { 21126 version(X11) { 21127 char[] listString = cast(char[]) data; 21128 char[][16] buffer; 21129 int count; 21130 char[][] result = buffer[]; 21131 21132 void commit(char[] s) { 21133 if(count == result.length) 21134 result.length += 16; 21135 if(s.length > 7 && s[0 ..7] == "file://") 21136 s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding 21137 result[count++] = s; 21138 } 21139 21140 size_t last; 21141 foreach(idx, char c; listString) { 21142 if(c == '\n') { 21143 commit(listString[last .. idx - 1]); // a \r 21144 last = idx + 1; // a \n 21145 } 21146 } 21147 21148 if(last < listString.length) { 21149 commit(listString[last .. $]); 21150 } 21151 21152 // FIXME: they are uris now, should I translate it to local file names? 21153 // of course the host name is supposed to be there cuz of X rokking... 21154 21155 dg(result[0 .. count]); 21156 } else version(Windows) { 21157 21158 static struct DROPFILES { 21159 DWORD pFiles; 21160 POINT pt; 21161 BOOL fNC; 21162 BOOL fWide; 21163 } 21164 21165 21166 const(char)[][16] buffer; 21167 int count; 21168 const(char)[][] result = buffer[]; 21169 size_t last; 21170 21171 void commitA(in char[] stuff) { 21172 if(count == result.length) 21173 result.length += 16; 21174 result[count++] = stuff; 21175 } 21176 21177 void commitW(in wchar[] stuff) { 21178 commitA(makeUtf8StringFromWindowsString(stuff)); 21179 } 21180 21181 void magic(T)(T chars) { 21182 size_t idx; 21183 while(chars[idx]) { 21184 last = idx; 21185 while(chars[idx]) { 21186 idx++; 21187 } 21188 static if(is(T == char*)) 21189 commitA(chars[last .. idx]); 21190 else 21191 commitW(chars[last .. idx]); 21192 idx++; 21193 } 21194 } 21195 21196 auto df = cast(DROPFILES*) data.ptr; 21197 if(df.fWide) { 21198 wchar* chars = cast(wchar*) (data.ptr + df.pFiles); 21199 magic(chars); 21200 } else { 21201 char* chars = cast(char*) (data.ptr + df.pFiles); 21202 magic(chars); 21203 } 21204 dg(result[0 .. count]); 21205 } 21206 else throw new NotYetImplementedException(); 21207 } 21208 } 21209 21210 /++ 21211 Interface to describe data being dragged. See also [draggable] helper function. 21212 21213 $(PITFALL This is not yet stable and may break in future versions without notice.) 21214 21215 History: 21216 Added February 19, 2021 21217 +/ 21218 interface DraggableData { 21219 version(X11) 21220 alias FormatId = Atom; 21221 else 21222 alias FormatId = uint; 21223 /++ 21224 Gets the platform-specific FormatId associated with the given named format. 21225 21226 This may be a MIME type, but may also be other various strings defined by the 21227 programs you want to interoperate with. 21228 21229 FIXME: sdpy needs to offer data adapter things that look for compatible formats 21230 and convert it to some particular type for you. 21231 +/ 21232 static FormatId getFormatId(string name)() { 21233 version(X11) 21234 return GetAtom!name(XDisplayConnection.get); 21235 else version(Windows) { 21236 static UINT cache; 21237 if(!cache) 21238 cache = RegisterClipboardFormatA(name); 21239 return cache; 21240 } else 21241 throw new NotYetImplementedException(); 21242 } 21243 21244 /++ 21245 Looks up a string to represent the name for the given format, if there is one. 21246 21247 You should avoid using this function because it is slow. It is provided more for 21248 debugging than for primary use. 21249 +/ 21250 static string getFormatName(FormatId format) { 21251 version(X11) { 21252 if(format == 0) 21253 return "None"; 21254 else 21255 return getAtomName(format, XDisplayConnection.get); 21256 } else version(Windows) { 21257 switch(format) { 21258 case CF_UNICODETEXT: return "CF_UNICODETEXT"; 21259 case CF_DIBV5: return "CF_DIBV5"; 21260 case CF_RIFF: return "CF_RIFF"; 21261 case CF_WAVE: return "CF_WAVE"; 21262 case CF_HDROP: return "CF_HDROP"; 21263 default: 21264 char[1024] name; 21265 auto count = GetClipboardFormatNameA(format, name.ptr, name.length); 21266 return name[0 .. count].idup; 21267 } 21268 } else throw new NotYetImplementedException(); 21269 } 21270 21271 FormatId[] availableFormats(); 21272 // Return the slice of data you filled, empty slice if done. 21273 // this is to support the incremental thing 21274 ubyte[] getData(FormatId format, return scope ubyte[] data); 21275 21276 size_t dataLength(FormatId format); 21277 } 21278 21279 /++ 21280 $(PITFALL This is not yet stable and may break in future versions without notice.) 21281 21282 History: 21283 Added February 19, 2021 21284 +/ 21285 DraggableData draggable(string s) { 21286 version(X11) 21287 return new class X11SetSelectionHandler_Text, DraggableData { 21288 this() { 21289 super(s); 21290 } 21291 21292 override FormatId[] availableFormats() { 21293 return X11SetSelectionHandler_Text.availableFormats(); 21294 } 21295 21296 override ubyte[] getData(FormatId format, return scope ubyte[] data) { 21297 return X11SetSelectionHandler_Text.getData(format, data); 21298 } 21299 21300 size_t dataLength(FormatId format) { 21301 return s.length; 21302 } 21303 }; 21304 else version(Windows) 21305 return new class DraggableData { 21306 FormatId[] availableFormats() { 21307 return [CF_UNICODETEXT]; 21308 } 21309 21310 ubyte[] getData(FormatId format, return scope ubyte[] data) { 21311 return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 21312 } 21313 21314 size_t dataLength(FormatId format) { 21315 return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof; 21316 } 21317 }; 21318 else 21319 throw new NotYetImplementedException(); 21320 } 21321 21322 /++ 21323 $(PITFALL This is not yet stable and may break in future versions without notice.) 21324 21325 History: 21326 Added February 19, 2021 21327 +/ 21328 /// Group: drag_and_drop 21329 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) 21330 in { 21331 assert(window !is null); 21332 assert(handler !is null); 21333 } 21334 do 21335 { 21336 version(X11) { 21337 auto sh = cast(X11SetSelectionHandler) handler; 21338 if(sh is null) { 21339 // gotta make my own adapter. 21340 sh = new class X11SetSelectionHandler { 21341 mixin X11SetSelectionHandler_Basics; 21342 21343 Atom[] availableFormats() { return handler.availableFormats(); } 21344 ubyte[] getData(Atom format, return scope ubyte[] data) { 21345 return handler.getData(format, data); 21346 } 21347 21348 // since the drop selection is only ever used once it isn't important 21349 // to reset it. 21350 void done() {} 21351 }; 21352 } 21353 return doDragDropX11(window, sh, action); 21354 } else version(Windows) { 21355 return doDragDropWindows(window, handler, action); 21356 } else throw new NotYetImplementedException(); 21357 } 21358 21359 version(Windows) 21360 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) { 21361 IDataObject obj = new class IDataObject { 21362 ULONG refCount; 21363 ULONG AddRef() { 21364 return ++refCount; 21365 } 21366 ULONG Release() { 21367 return --refCount; 21368 } 21369 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21370 if (IID_IUnknown == *riid) { 21371 *ppv = cast(void*) cast(IUnknown) this; 21372 } 21373 else if (IID_IDataObject == *riid) { 21374 *ppv = cast(void*) cast(IDataObject) this; 21375 } 21376 else { 21377 *ppv = null; 21378 return E_NOINTERFACE; 21379 } 21380 21381 AddRef(); 21382 return NOERROR; 21383 } 21384 21385 HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) { 21386 // writeln("Advise"); 21387 return E_NOTIMPL; 21388 } 21389 HRESULT DUnadvise(DWORD dwConnection) { 21390 return E_NOTIMPL; 21391 } 21392 HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) { 21393 // writeln("EnumDAdvise"); 21394 return OLE_E_ADVISENOTSUPPORTED; 21395 } 21396 // tell what formats it supports 21397 21398 FORMATETC[] types; 21399 this() { 21400 FORMATETC t; 21401 foreach(ty; handler.availableFormats()) { 21402 assert(ty <= ushort.max && ty >= 0); 21403 t.cfFormat = cast(ushort) ty; 21404 t.lindex = -1; 21405 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 21406 t.tymed = TYMED.TYMED_HGLOBAL; 21407 } 21408 types ~= t; 21409 } 21410 HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) { 21411 if(dwDirection == DATADIR.DATADIR_GET) { 21412 *ppenumFormatEtc = new class IEnumFORMATETC { 21413 ULONG refCount; 21414 ULONG AddRef() { 21415 return ++refCount; 21416 } 21417 ULONG Release() { 21418 return --refCount; 21419 } 21420 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21421 if (IID_IUnknown == *riid) { 21422 *ppv = cast(void*) cast(IUnknown) this; 21423 } 21424 else if (IID_IEnumFORMATETC == *riid) { 21425 *ppv = cast(void*) cast(IEnumFORMATETC) this; 21426 } 21427 else { 21428 *ppv = null; 21429 return E_NOINTERFACE; 21430 } 21431 21432 AddRef(); 21433 return NOERROR; 21434 } 21435 21436 21437 int pos; 21438 this() { 21439 pos = 0; 21440 } 21441 21442 HRESULT Clone(IEnumFORMATETC* ppenum) { 21443 // writeln("clone"); 21444 return E_NOTIMPL; // FIXME 21445 } 21446 21447 // Caller is responsible for freeing memory 21448 HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) { 21449 // fetched may be null if celt is one 21450 if(celt != 1) 21451 return E_NOTIMPL; // FIXME 21452 21453 if(celt + pos > types.length) 21454 return S_FALSE; 21455 21456 *rgelt = types[pos++]; 21457 21458 if(pceltFetched !is null) 21459 *pceltFetched = 1; 21460 21461 // writeln("ok celt ", celt); 21462 return S_OK; 21463 } 21464 21465 HRESULT Reset() { 21466 pos = 0; 21467 return S_OK; 21468 } 21469 21470 HRESULT Skip(ULONG celt) { 21471 if(celt + pos <= types.length) { 21472 pos += celt; 21473 return S_OK; 21474 } 21475 return S_FALSE; 21476 } 21477 }; 21478 21479 return S_OK; 21480 } else 21481 return E_NOTIMPL; 21482 } 21483 // given a format, return the format you'd prefer to use cuz it is identical 21484 HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) { 21485 // FIXME: prolly could be better but meh 21486 // writeln("gcf: ", *pformatectIn); 21487 *pformatetcOut = *pformatectIn; 21488 return S_OK; 21489 } 21490 HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21491 foreach(ty; types) { 21492 if(ty == *pformatetcIn) { 21493 auto format = ty.cfFormat; 21494 // writeln("A: ", *pformatetcIn, "\nB: ", ty); 21495 STGMEDIUM medium; 21496 medium.tymed = TYMED.TYMED_HGLOBAL; 21497 21498 auto sz = handler.dataLength(format); 21499 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz); 21500 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 21501 if(auto data = cast(wchar*) GlobalLock(handle)) { 21502 auto slice = data[0 .. sz]; 21503 scope(exit) 21504 GlobalUnlock(handle); 21505 21506 handler.getData(format, cast(ubyte[]) slice[]); 21507 } 21508 21509 21510 medium.hGlobal = handle; // FIXME 21511 *pmedium = medium; 21512 return S_OK; 21513 } 21514 } 21515 return DV_E_FORMATETC; 21516 } 21517 HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21518 // writeln("GDH: ", *pformatetcIn); 21519 return E_NOTIMPL; // FIXME 21520 } 21521 HRESULT QueryGetData(FORMATETC* pformatetc) { 21522 auto search = *pformatetc; 21523 search.tymed &= TYMED.TYMED_HGLOBAL; 21524 foreach(ty; types) 21525 if(ty == search) { 21526 // writeln("QueryGetData ", search, " ", types[0]); 21527 return S_OK; 21528 } 21529 if(pformatetc.cfFormat==CF_UNICODETEXT) { 21530 //writeln("QueryGetData FALSE ", search, " ", types[0]); 21531 } 21532 return S_FALSE; 21533 } 21534 HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) { 21535 // writeln("SetData: "); 21536 return E_NOTIMPL; 21537 } 21538 }; 21539 21540 21541 IDropSource src = new class IDropSource { 21542 ULONG refCount; 21543 ULONG AddRef() { 21544 return ++refCount; 21545 } 21546 ULONG Release() { 21547 return --refCount; 21548 } 21549 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21550 if (IID_IUnknown == *riid) { 21551 *ppv = cast(void*) cast(IUnknown) this; 21552 } 21553 else if (IID_IDropSource == *riid) { 21554 *ppv = cast(void*) cast(IDropSource) this; 21555 } 21556 else { 21557 *ppv = null; 21558 return E_NOINTERFACE; 21559 } 21560 21561 AddRef(); 21562 return NOERROR; 21563 } 21564 21565 int QueryContinueDrag(int fEscapePressed, uint grfKeyState) { 21566 if(fEscapePressed) 21567 return DRAGDROP_S_CANCEL; 21568 if(!(grfKeyState & MK_LBUTTON)) 21569 return DRAGDROP_S_DROP; 21570 return S_OK; 21571 } 21572 21573 int GiveFeedback(uint dwEffect) { 21574 return DRAGDROP_S_USEDEFAULTCURSORS; 21575 } 21576 }; 21577 21578 DWORD effect; 21579 21580 if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect."); 21581 21582 DROPEFFECT de = win32DragAndDropAction(action); 21583 21584 // I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time 21585 // but still prolly a FIXME 21586 21587 auto ret = DoDragDrop(obj, src, de, &effect); 21588 /+ 21589 if(ret == DRAGDROP_S_DROP) 21590 writeln("drop ", effect); 21591 else if(ret == DRAGDROP_S_CANCEL) 21592 writeln("cancel"); 21593 else if(ret == S_OK) 21594 writeln("ok"); 21595 else writeln(ret); 21596 +/ 21597 21598 return ret; 21599 } 21600 21601 version(Windows) 21602 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) { 21603 DROPEFFECT de; 21604 21605 with(DragAndDropAction) 21606 with(DROPEFFECT) 21607 final switch(action) { 21608 case none: de = DROPEFFECT_NONE; break; 21609 case copy: de = DROPEFFECT_COPY; break; 21610 case move: de = DROPEFFECT_MOVE; break; 21611 case link: de = DROPEFFECT_LINK; break; 21612 case ask: throw new Exception("ask not implemented yet"); 21613 case custom: throw new Exception("custom not implemented yet"); 21614 } 21615 21616 return de; 21617 } 21618 21619 21620 /++ 21621 History: 21622 Added February 19, 2021 21623 +/ 21624 /// Group: drag_and_drop 21625 void enableDragAndDrop(SimpleWindow window, DropHandler handler) { 21626 version(X11) { 21627 auto display = XDisplayConnection.get; 21628 21629 Atom atom = 5; // right??? 21630 21631 XChangeProperty( 21632 display, 21633 window.impl.window, 21634 GetAtom!"XdndAware"(display), 21635 XA_ATOM, 21636 32 /* bits */, 21637 PropModeReplace, 21638 &atom, 21639 1); 21640 21641 window.dropHandler = handler; 21642 } else version(Windows) { 21643 21644 initDnd(); 21645 21646 auto dropTarget = new class (handler) IDropTarget { 21647 DropHandler handler; 21648 this(DropHandler handler) { 21649 this.handler = handler; 21650 } 21651 ULONG refCount; 21652 ULONG AddRef() { 21653 return ++refCount; 21654 } 21655 ULONG Release() { 21656 return --refCount; 21657 } 21658 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21659 if (IID_IUnknown == *riid) { 21660 *ppv = cast(void*) cast(IUnknown) this; 21661 } 21662 else if (IID_IDropTarget == *riid) { 21663 *ppv = cast(void*) cast(IDropTarget) this; 21664 } 21665 else { 21666 *ppv = null; 21667 return E_NOINTERFACE; 21668 } 21669 21670 AddRef(); 21671 return NOERROR; 21672 } 21673 21674 21675 // /////////////////// 21676 21677 HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21678 DropPackage dropPackage = DropPackage(pDataObj); 21679 *pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage)); 21680 return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter 21681 } 21682 21683 HRESULT DragLeave() { 21684 handler.dragLeave(); 21685 // release the IDataObject if needed 21686 return S_OK; 21687 } 21688 21689 HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21690 auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates 21691 21692 *pdwEffect = win32DragAndDropAction(res.action); 21693 // same as DragEnter basically 21694 return S_OK; 21695 } 21696 21697 HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21698 DropPackage pkg = DropPackage(pDataObj); 21699 handler.drop(&pkg); 21700 21701 return S_OK; 21702 } 21703 }; 21704 // Windows can hold on to the handler and try to call it 21705 // during which time the GC can't see it. so important to 21706 // manually manage this. At some point i'll FIXME and make 21707 // all my com instances manually managed since they supposed 21708 // to respect the refcount. 21709 import core.memory; 21710 GC.addRoot(cast(void*) dropTarget); 21711 21712 if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK) 21713 throw new WindowsApiException("RegisterDragDrop", GetLastError()); 21714 21715 window.dropHandler = handler; 21716 } else throw new NotYetImplementedException(); 21717 } 21718 21719 21720 21721 static if(UsingSimpledisplayX11) { 21722 21723 enum _NET_WM_STATE_ADD = 1; 21724 enum _NET_WM_STATE_REMOVE = 0; 21725 enum _NET_WM_STATE_TOGGLE = 2; 21726 21727 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases. 21728 void demandAttention(SimpleWindow window, bool needs = true) { 21729 demandAttention(window.impl.window, needs); 21730 } 21731 21732 /// ditto 21733 void demandAttention(Window window, bool needs = true) { 21734 setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs); 21735 } 21736 21737 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) { 21738 auto display = XDisplayConnection.get(); 21739 if(atom == None) 21740 return; // non-failure error 21741 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 21742 21743 XClientMessageEvent xclient; 21744 21745 xclient.type = EventType.ClientMessage; 21746 xclient.window = window; 21747 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 21748 xclient.format = 32; 21749 xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 21750 xclient.data.l[1] = atom; 21751 xclient.data.l[2] = atom2; 21752 xclient.data.l[3] = 1; 21753 // [3] == source. 0 == unknown, 1 == app, 2 == else 21754 21755 XSendEvent( 21756 display, 21757 RootWindow(display, DefaultScreen(display)), 21758 false, 21759 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 21760 cast(XEvent*) &xclient 21761 ); 21762 21763 /+ 21764 XChangeProperty( 21765 display, 21766 window.impl.window, 21767 GetAtom!"_NET_WM_STATE"(display), 21768 XA_ATOM, 21769 32 /* bits */, 21770 PropModeAppend, 21771 &atom, 21772 1); 21773 +/ 21774 } 21775 21776 private Atom dndActionAtom(Display* display, DragAndDropAction action) { 21777 Atom actionAtom; 21778 with(DragAndDropAction) 21779 final switch(action) { 21780 case none: actionAtom = None; break; 21781 case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break; 21782 case move: actionAtom = GetAtom!"XdndActionMove"(display); break; 21783 case link: actionAtom = GetAtom!"XdndActionLink"(display); break; 21784 case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break; 21785 case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break; 21786 } 21787 21788 return actionAtom; 21789 } 21790 21791 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) { 21792 // FIXME: I need to show user feedback somehow. 21793 auto display = XDisplayConnection.get; 21794 21795 auto actionAtom = dndActionAtom(display, action); 21796 assert(actionAtom, "Don't use action none to accept a drop"); 21797 21798 setX11Selection!"XdndSelection"(window, handler, null); 21799 21800 auto oldKeyHandler = window.handleKeyEvent; 21801 scope(exit) window.handleKeyEvent = oldKeyHandler; 21802 21803 auto oldCharHandler = window.handleCharEvent; 21804 scope(exit) window.handleCharEvent = oldCharHandler; 21805 21806 auto oldMouseHandler = window.handleMouseEvent; 21807 scope(exit) window.handleMouseEvent = oldMouseHandler; 21808 21809 Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child 21810 21811 import core.sys.posix.sys.time; 21812 timeval tv; 21813 gettimeofday(&tv, null); 21814 21815 Time dataTimestamp = cast(Time) ( tv.tv_sec * 1000 + tv.tv_usec / 1000 ); 21816 21817 Time lastMouseTimestamp; 21818 21819 bool dnding = true; 21820 Window lastIn = None; 21821 21822 void leave() { 21823 if(lastIn == None) 21824 return; 21825 21826 XEvent ev; 21827 ev.xclient.type = EventType.ClientMessage; 21828 ev.xclient.window = lastIn; 21829 ev.xclient.message_type = GetAtom!("XdndLeave", true)(display); 21830 ev.xclient.format = 32; 21831 ev.xclient.data.l[0] = window.impl.window; 21832 21833 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21834 XFlush(display); 21835 21836 lastIn = None; 21837 } 21838 21839 void enter(Window w) { 21840 assert(lastIn == None); 21841 21842 lastIn = w; 21843 21844 XEvent ev; 21845 ev.xclient.type = EventType.ClientMessage; 21846 ev.xclient.window = lastIn; 21847 ev.xclient.message_type = GetAtom!("XdndEnter", true)(display); 21848 ev.xclient.format = 32; 21849 ev.xclient.data.l[0] = window.impl.window; 21850 ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types 21851 21852 auto types = handler.availableFormats(); 21853 assert(types.length > 0); 21854 21855 ev.xclient.data.l[2] = types[0]; 21856 if(types.length > 1) 21857 ev.xclient.data.l[3] = types[1]; 21858 if(types.length > 2) 21859 ev.xclient.data.l[4] = types[2]; 21860 21861 // FIXME: other types?!?!? and make sure we skip TARGETS 21862 21863 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21864 XFlush(display); 21865 } 21866 21867 void position(int rootX, int rootY) { 21868 assert(lastIn != None); 21869 21870 XEvent ev; 21871 ev.xclient.type = EventType.ClientMessage; 21872 ev.xclient.window = lastIn; 21873 ev.xclient.message_type = GetAtom!("XdndPosition", true)(display); 21874 ev.xclient.format = 32; 21875 ev.xclient.data.l[0] = window.impl.window; 21876 ev.xclient.data.l[1] = 0; // reserved 21877 ev.xclient.data.l[2] = (rootX << 16) | rootY; 21878 ev.xclient.data.l[3] = dataTimestamp; 21879 ev.xclient.data.l[4] = actionAtom; 21880 21881 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21882 XFlush(display); 21883 21884 } 21885 21886 void drop() { 21887 XEvent ev; 21888 ev.xclient.type = EventType.ClientMessage; 21889 ev.xclient.window = lastIn; 21890 ev.xclient.message_type = GetAtom!("XdndDrop", true)(display); 21891 ev.xclient.format = 32; 21892 ev.xclient.data.l[0] = window.impl.window; 21893 ev.xclient.data.l[1] = 0; // reserved 21894 ev.xclient.data.l[2] = dataTimestamp; 21895 21896 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21897 XFlush(display); 21898 21899 lastIn = None; 21900 dnding = false; 21901 } 21902 21903 // fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler 21904 // but idk if i should... 21905 21906 window.setEventHandlers( 21907 delegate(KeyEvent ev) { 21908 if(ev.pressed == true && ev.key == Key.Escape) { 21909 // cancel 21910 dnding = false; 21911 } 21912 }, 21913 delegate(MouseEvent ev) { 21914 if(ev.timestamp < lastMouseTimestamp) 21915 return; 21916 21917 lastMouseTimestamp = ev.timestamp; 21918 21919 if(ev.type == MouseEventType.motion) { 21920 auto display = XDisplayConnection.get; 21921 auto root = RootWindow(display, DefaultScreen(display)); 21922 21923 Window topWindow; 21924 int rootX, rootY; 21925 21926 XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow); 21927 21928 if(topWindow == None) 21929 return; 21930 21931 top: 21932 if(auto result = topWindow in eligibility) { 21933 auto dropWindow = *result; 21934 if(dropWindow == None) { 21935 leave(); 21936 return; 21937 } 21938 21939 if(dropWindow != lastIn) { 21940 leave(); 21941 enter(dropWindow); 21942 position(rootX, rootY); 21943 } else { 21944 position(rootX, rootY); 21945 } 21946 } else { 21947 // determine eligibility 21948 auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM); 21949 if(data.length == 1) { 21950 // in case there is no WM or it isn't reparenting 21951 eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh 21952 } else { 21953 21954 Window tryScanChildren(Window search, int maxRecurse) { 21955 // could be reparenting window manager, so gotta check the next few children too 21956 Window child; 21957 int x; 21958 int y; 21959 XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child); 21960 21961 if(child == None) 21962 return None; 21963 auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM); 21964 if(data.length == 1) { 21965 return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh 21966 } else { 21967 if(maxRecurse) 21968 return tryScanChildren(child, maxRecurse - 1); 21969 else 21970 return None; 21971 } 21972 21973 } 21974 21975 // if a WM puts more than 3 layers on it, like wtf is it doing, screw that. 21976 auto topResult = tryScanChildren(topWindow, 3); 21977 // it is easy to have a false negative due to the mouse going over a WM 21978 // child window like the close button if separate from the frame... so I 21979 // can't really cache negatives, :( 21980 if(topResult != None) { 21981 eligibility[topWindow] = topResult; 21982 goto top; // reload to do the positioning iff eligibility changed lest we endless loop 21983 } 21984 } 21985 21986 } 21987 21988 } else if(ev.type == MouseEventType.buttonReleased) { 21989 drop(); 21990 dnding = false; 21991 } 21992 } 21993 ); 21994 21995 window.grabInput(); 21996 scope(exit) 21997 window.releaseInputGrab(); 21998 21999 22000 EventLoop.get.run(() => dnding); 22001 22002 return 0; 22003 } 22004 22005 /// X-specific 22006 TrueColorImage getWindowNetWmIcon(Window window) { 22007 try { 22008 auto display = XDisplayConnection.get; 22009 22010 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 22011 22012 if (data.length > arch_ulong.sizeof * 2) { 22013 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 22014 // these are an array of rgba images that we have to convert into pixmaps ourself 22015 22016 int width = cast(int) meta[0]; 22017 int height = cast(int) meta[1]; 22018 22019 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 22020 22021 static if(arch_ulong.sizeof == 4) { 22022 bytes = bytes[0 .. width * height * 4]; 22023 alias imageData = bytes; 22024 } else static if(arch_ulong.sizeof == 8) { 22025 bytes = bytes[0 .. width * height * 8]; 22026 auto imageData = new ubyte[](4 * width * height); 22027 } else static assert(0); 22028 22029 22030 22031 // this returns ARGB. Remember it is little-endian so 22032 // we have BGRA 22033 // our thing uses RGBA, which in little endian, is ABGR 22034 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 22035 auto r = bytes[idx + 2]; 22036 auto g = bytes[idx + 1]; 22037 auto b = bytes[idx + 0]; 22038 auto a = bytes[idx + 3]; 22039 22040 imageData[idx2 + 0] = r; 22041 imageData[idx2 + 1] = g; 22042 imageData[idx2 + 2] = b; 22043 imageData[idx2 + 3] = a; 22044 } 22045 22046 return new TrueColorImage(width, height, imageData); 22047 } 22048 22049 return null; 22050 } catch(Exception e) { 22051 return null; 22052 } 22053 } 22054 22055 } /* UsingSimpledisplayX11 */ 22056 22057 22058 void loadBinNameToWindowClassName () { 22059 import core.stdc.stdlib : realloc; 22060 version(linux) { 22061 // args[0] MAY be empty, so we'll just use this 22062 import core.sys.posix.unistd : readlink; 22063 char[1024] ebuf = void; // 1KB should be enough for everyone! 22064 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 22065 if (len < 1) return; 22066 } else /*version(Windows)*/ { 22067 import core.runtime : Runtime; 22068 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 22069 auto ebuf = Runtime.args[0]; 22070 auto len = ebuf.length; 22071 } 22072 auto pos = len; 22073 while (pos > 0 && ebuf[pos-1] != '/') --pos; 22074 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 22075 if (sdpyWindowClassStr is null) return; // oops 22076 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 22077 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 22078 } 22079 22080 /++ 22081 An interface representing a font that is drawn with custom facilities. 22082 22083 You might want [OperatingSystemFont] instead, which represents 22084 a font loaded and drawn by functions native to the operating system. 22085 22086 WARNING: I might still change this. 22087 +/ 22088 interface DrawableFont : MeasurableFont { 22089 /++ 22090 Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string. 22091 22092 Implementations must use the painter's fillColor to draw a rectangle behind the string, 22093 then use the outlineColor to draw the string. It might alpha composite if there's a transparent 22094 fill color, but that's up to the implementation. 22095 +/ 22096 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 22097 22098 /++ 22099 Requests that the given string is added to the image cache. You should only do this rarely, but 22100 if you have a string that you know will be used over and over again, adding it to a cache can 22101 improve things (assuming the implementation actually has a cache; it is also valid for an implementation 22102 to implement this as a do-nothing method). 22103 +/ 22104 void cacheString(SimpleWindow window, Color foreground, Color background, string text); 22105 } 22106 22107 /++ 22108 Loads a true type font using [arsd.ttf] that can be drawn as images on windows 22109 through a [ScreenPainter]. That module must be compiled in if you choose to use this function. 22110 22111 You should also consider [OperatingSystemFont], which loads and draws a font with 22112 facilities native to the user's operating system. You might also consider 22113 [arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind 22114 of game, as they have their own ways to draw text too. 22115 22116 Be warned: this can be slow, especially on remote connections to the X server, since 22117 it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface 22118 offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to 22119 experiment in your specific case. 22120 22121 Please note that the return type of [DrawableFont] also includes an implementation of 22122 [MeasurableFont]. 22123 +/ 22124 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 22125 import arsd.ttf; 22126 static class ArsdTtfFont : DrawableFont { 22127 TtfFont font; 22128 int size; 22129 this(in ubyte[] data, int size) { 22130 font = TtfFont(data); 22131 this.size = size; 22132 22133 22134 auto scale = stbtt_ScaleForPixelHeight(&font.font, size); 22135 int ascent_, descent_, line_gap; 22136 stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap); 22137 22138 int advance, lsb; 22139 stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb); 22140 xWidth = cast(int) (advance * scale); 22141 stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb); 22142 MWidth = cast(int) (advance * scale); 22143 } 22144 22145 private int ascent_; 22146 private int descent_; 22147 private int xWidth; 22148 private int MWidth; 22149 22150 bool isMonospace() { 22151 return xWidth == MWidth; 22152 } 22153 int averageWidth() { 22154 return xWidth; 22155 } 22156 int height() { 22157 return size; 22158 } 22159 int ascent() { 22160 return ascent_; 22161 } 22162 int descent() { 22163 return descent_; 22164 } 22165 22166 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 22167 int width, height; 22168 font.getStringSize(s, size, width, height); 22169 return width; 22170 } 22171 22172 22173 22174 Sprite[string] cache; 22175 22176 void cacheString(SimpleWindow window, Color foreground, Color background, string text) { 22177 auto sprite = new Sprite(window, stringToImage(foreground, background, text)); 22178 cache[text] = sprite; 22179 } 22180 22181 Image stringToImage(Color fg, Color bg, in char[] text) { 22182 int width, height; 22183 auto data = font.renderString(text, size, width, height); 22184 auto image = new TrueColorImage(width, height); 22185 int pos = 0; 22186 foreach(y; 0 .. height) 22187 foreach(x; 0 .. width) { 22188 fg.a = data[0]; 22189 bg.a = 255; 22190 auto color = alphaBlend(fg, bg); 22191 image.imageData.bytes[pos++] = color.r; 22192 image.imageData.bytes[pos++] = color.g; 22193 image.imageData.bytes[pos++] = color.b; 22194 image.imageData.bytes[pos++] = data[0]; 22195 data = data[1 .. $]; 22196 } 22197 assert(data.length == 0); 22198 22199 return Image.fromMemoryImage(image); 22200 } 22201 22202 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 22203 Sprite sprite = (text in cache) ? *(text in cache) : null; 22204 22205 auto fg = painter.impl._outlineColor; 22206 auto bg = painter.impl._fillColor; 22207 22208 if(sprite !is null) { 22209 auto w = cast(SimpleWindow) painter.window; 22210 assert(w !is null); 22211 22212 sprite.drawAt(painter, upperLeft); 22213 } else { 22214 painter.drawImage(upperLeft, stringToImage(fg, bg, text)); 22215 } 22216 } 22217 } 22218 22219 return new ArsdTtfFont(data, size); 22220 } 22221 22222 class NotYetImplementedException : Exception { 22223 this(string file = __FILE__, size_t line = __LINE__) { 22224 super("Not yet implemented", file, line); 22225 } 22226 } 22227 22228 /// 22229 __gshared bool librariesSuccessfullyLoaded = true; 22230 /// 22231 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 22232 22233 private mixin template DynamicLoadSupplementalOpenGL(Iface) { 22234 // mixin(staticForeachReplacement!Iface); 22235 static foreach(name; __traits(derivedMembers, Iface)) 22236 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 22237 22238 void loadDynamicLibrary() @nogc { 22239 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 22240 } 22241 22242 void loadDynamicLibraryForReal() { 22243 foreach(name; __traits(derivedMembers, Iface)) { 22244 mixin("alias tmp = " ~ name ~ ";"); 22245 tmp = cast(typeof(tmp)) glbindGetProcAddress(name); 22246 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL"); 22247 } 22248 } 22249 } 22250 22251 /+ 22252 private const(char)[] staticForeachReplacement(Iface)() pure { 22253 /* 22254 // just this for gdc 9.... 22255 // when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease 22256 22257 static foreach(name; __traits(derivedMembers, Iface)) 22258 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 22259 */ 22260 22261 char[] code = new char[](__traits(derivedMembers, Iface).length * 64); 22262 size_t pos; 22263 22264 void append(in char[] what) { 22265 if(pos + what.length > code.length) 22266 code.length = (code.length * 3) / 2; 22267 code[pos .. pos + what.length] = what[]; 22268 pos += what.length; 22269 } 22270 22271 foreach(name; __traits(derivedMembers, Iface)) { 22272 append(`__gshared typeof(&__traits(getMember, Iface, "`); 22273 append(name); 22274 append(`")) `); 22275 append(name); 22276 append(";"); 22277 } 22278 22279 return code[0 .. pos]; 22280 } 22281 +/ 22282 22283 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) { 22284 //mixin(staticForeachReplacement!Iface); 22285 static foreach(name; __traits(derivedMembers, Iface)) 22286 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 22287 22288 private __gshared void* libHandle; 22289 private __gshared bool attempted; 22290 22291 void loadDynamicLibrary() @nogc { 22292 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 22293 } 22294 22295 bool loadAttempted() { 22296 return attempted; 22297 } 22298 bool loadSuccessful() { 22299 return libHandle !is null; 22300 } 22301 22302 void loadDynamicLibraryForReal() { 22303 attempted = true; 22304 version(Posix) { 22305 import core.sys.posix.dlfcn; 22306 version(OSX) { 22307 version(X11) 22308 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 22309 else 22310 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 22311 } else { 22312 version(apitrace) { 22313 if(library == "GL" || library == "GLX") { 22314 libHandle = dlopen("glxtrace.so", RTLD_NOW); 22315 if(libHandle is null) { 22316 assert(false, "Failed to load `glxtrace.so`."); 22317 } 22318 } 22319 else { 22320 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 22321 } 22322 } 22323 else { 22324 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 22325 } 22326 if(libHandle is null) { 22327 libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW); 22328 } 22329 } 22330 22331 static void* loadsym(void* l, const char* name) { 22332 import core.stdc.stdlib; 22333 if(l is null) 22334 return &abort; 22335 return dlsym(l, name); 22336 } 22337 } else version(Windows) { 22338 import core.sys.windows.winbase; 22339 libHandle = LoadLibrary(library ~ ".dll"); 22340 static void* loadsym(void* l, const char* name) { 22341 import core.stdc.stdlib; 22342 if(l is null) 22343 return &abort; 22344 return GetProcAddress(l, name); 22345 } 22346 } 22347 if(libHandle is null) { 22348 success = false; 22349 //throw new Exception("load failure of library " ~ library); 22350 } 22351 foreach(name; __traits(derivedMembers, Iface)) { 22352 mixin("alias tmp = " ~ name ~ ";"); 22353 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 22354 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 22355 } 22356 } 22357 22358 void unloadDynamicLibrary() { 22359 version(Posix) { 22360 import core.sys.posix.dlfcn; 22361 dlclose(libHandle); 22362 } else version(Windows) { 22363 import core.sys.windows.winbase; 22364 FreeLibrary(libHandle); 22365 } 22366 foreach(name; __traits(derivedMembers, Iface)) 22367 mixin(name ~ " = null;"); 22368 } 22369 } 22370 22371 // version(X11) 22372 /++ 22373 Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"]. 22374 22375 $(WARNING 22376 This function is exempted from stability guarantees. 22377 ) 22378 +/ 22379 float customScalingFactorForMonitor(int monitorNumber) @system { 22380 import core.stdc.stdlib; 22381 auto val = getenv("ARSD_SCALING_FACTOR"); 22382 22383 // FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given 22384 if(val is null) 22385 return 1.0; 22386 22387 char[16] buffer = 0; 22388 int pos; 22389 22390 const(char)* at = val; 22391 22392 foreach(item; 0 .. monitorNumber + 1) { 22393 if(*at == 0) 22394 break; // reuse the last number when we at the end of the string 22395 pos = 0; 22396 while(pos + 1 < buffer.length && *at && *at != ';') { 22397 buffer[pos++] = *at; 22398 at++; 22399 } 22400 if(*at) 22401 at++; // skip the semicolon 22402 buffer[pos] = 0; 22403 } 22404 22405 //sdpyPrintDebugString(buffer[0 .. pos]); 22406 22407 import core.stdc.math; 22408 auto f = atof(buffer.ptr); 22409 22410 if(f <= 0.0 || isnan(f) || isinf(f)) 22411 return 1.0; 22412 22413 return f; 22414 } 22415 22416 void guiAbortProcess(string msg) { 22417 import core.stdc.stdlib; 22418 version(Windows) { 22419 WCharzBuffer t = WCharzBuffer(msg); 22420 MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0); 22421 } else { 22422 import core.stdc.stdio; 22423 fwrite(msg.ptr, 1, msg.length, stderr); 22424 msg = "\n"; 22425 fwrite(msg.ptr, 1, msg.length, stderr); 22426 fflush(stderr); 22427 } 22428 22429 abort(); 22430 } 22431 22432 private int minInternal(int a, int b) { 22433 return (a < b) ? a : b; 22434 } 22435 22436 private alias scriptable = arsd_jsvar_compatible;