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 module arsd.simpledisplay; 815 816 import arsd.core; 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 version(OSX) version(DigitalMars) version=OSXCocoa; 1166 1167 1168 version(OSXCocoa) { 1169 version=without_opengl; 1170 version=allow_unimplemented_features; 1171 // version=OSXCocoa; 1172 // pragma(linkerDirective, "-framework Cocoa"); 1173 } 1174 1175 version(without_opengl) { 1176 enum SdpyIsUsingIVGLBinds = false; 1177 } else /*version(Posix)*/ { 1178 static if (__traits(compiles, (){import iv.glbinds;})) { 1179 enum SdpyIsUsingIVGLBinds = true; 1180 public import iv.glbinds; 1181 //pragma(msg, "SDPY: using iv.glbinds"); 1182 } else { 1183 enum SdpyIsUsingIVGLBinds = false; 1184 } 1185 //} else { 1186 // enum SdpyIsUsingIVGLBinds = false; 1187 } 1188 1189 1190 version(Windows) { 1191 //import core.sys.windows.windows; 1192 import core.sys.windows.winnls; 1193 import core.sys.windows.windef; 1194 import core.sys.windows.basetyps; 1195 import core.sys.windows.winbase; 1196 import core.sys.windows.winuser; 1197 import core.sys.windows.shellapi; 1198 import core.sys.windows.wingdi; 1199 static import gdi = core.sys.windows.wingdi; // so i 1200 1201 pragma(lib, "gdi32"); 1202 pragma(lib, "user32"); 1203 1204 // for AlphaBlend... a breaking change.... 1205 version(CRuntime_DigitalMars) { } else 1206 pragma(lib, "msimg32"); 1207 1208 // core.sys.windows.dwmapi 1209 private { 1210 /++ 1211 See_also: 1212 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmgetwindowattribute 1213 +/ 1214 extern extern(Windows) HRESULT DwmGetWindowAttribute( 1215 HWND hwnd, 1216 DWORD dwAttribute, 1217 PVOID pvAttribute, 1218 DWORD cbAttribute 1219 ) nothrow @nogc; 1220 1221 /++ 1222 See_also: 1223 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute 1224 +/ 1225 extern extern(Windows) HRESULT DwmSetWindowAttribute( 1226 HWND hwnd, 1227 DWORD dwAttribute, 1228 LPCVOID pvAttribute, 1229 DWORD cbAttribute 1230 ) nothrow @nogc; 1231 1232 /++ 1233 See_also: 1234 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute 1235 +/ 1236 enum DWMWINDOWATTRIBUTE { 1237 // incomplete, only declare what we need 1238 1239 /++ 1240 Usage: 1241 pvAttribute → `DWM_WINDOW_CORNER_PREFERENCE*` 1242 1243 $(NOTE 1244 Requires Windows 11 or later. 1245 ) 1246 +/ 1247 DWMWA_WINDOW_CORNER_PREFERENCE = 33, 1248 } 1249 1250 /++ 1251 See_also: 1252 https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference 1253 +/ 1254 enum DWM_WINDOW_CORNER_PREFERENCE { 1255 /// System decision 1256 DWMWCP_DEFAULT = 0, 1257 1258 /// Never 1259 DWMWCP_DONOTROUND = 1, 1260 1261 // If "appropriate" 1262 DWMWCP_ROUND = 2, 1263 1264 // If "appropriate", but smaller radius 1265 DWMWCP_ROUNDSMALL = 3 1266 } 1267 1268 bool fromDWM( 1269 DWM_WINDOW_CORNER_PREFERENCE value, 1270 out CornerStyle result, 1271 ) @safe pure nothrow @nogc { 1272 switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) { 1273 case DWMWCP_DEFAULT: 1274 result = CornerStyle.automatic; 1275 return true; 1276 case DWMWCP_DONOTROUND: 1277 result = CornerStyle.rectangular; 1278 return true; 1279 case DWMWCP_ROUND: 1280 result = CornerStyle.rounded; 1281 return true; 1282 case DWMWCP_ROUNDSMALL: 1283 result = CornerStyle.roundedSlightly; 1284 return true; 1285 default: 1286 return false; 1287 } 1288 } 1289 1290 bool toDWM( 1291 CornerStyle value, 1292 out DWM_WINDOW_CORNER_PREFERENCE result, 1293 ) @safe pure nothrow @nogc { 1294 final switch (value) with (DWM_WINDOW_CORNER_PREFERENCE) { 1295 case CornerStyle.automatic: 1296 result = DWMWCP_DEFAULT; 1297 return true; 1298 case CornerStyle.rectangular: 1299 result = DWMWCP_DONOTROUND; 1300 return true; 1301 case CornerStyle.rounded: 1302 result = DWMWCP_ROUND; 1303 return true; 1304 case CornerStyle.roundedSlightly: 1305 result = DWMWCP_ROUNDSMALL; 1306 return true; 1307 } 1308 } 1309 1310 pragma(lib, "dwmapi"); 1311 } 1312 } else version (linux) { 1313 //k8: this is hack for rdmd. sorry. 1314 static import core.sys.linux.epoll; 1315 static import core.sys.linux.timerfd; 1316 } 1317 1318 1319 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off. 1320 1321 // http://wiki.dlang.org/Simpledisplay.d 1322 1323 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led 1324 1325 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl 1326 // but can i control the scroll lock led 1327 1328 1329 // Note: if you are using Image on X, you might want to do: 1330 /* 1331 static if(UsingSimpledisplayX11) { 1332 if(!Image.impl.xshmAvailable) { 1333 // the images will use the slower XPutImage, you might 1334 // want to consider an alternative method to get better speed 1335 } 1336 } 1337 1338 If the shared memory extension is available though, simpledisplay uses it 1339 for a significant speed boost whenever you draw large Images. 1340 */ 1341 1342 // 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. 1343 1344 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()! 1345 1346 /* 1347 Biggest FIXME: 1348 make sure the key event numbers match between X and Windows OR provide symbolic constants on each system 1349 1350 clean up opengl contexts when their windows close 1351 1352 fix resizing the bitmaps/pixmaps 1353 */ 1354 1355 // BTW on Windows: 1356 // -L/SUBSYSTEM:WINDOWS:5.0 1357 // to dmd will make a nice windows binary w/o a console if you want that. 1358 1359 /* 1360 Stuff to add: 1361 1362 use multibyte functions everywhere we can 1363 1364 OpenGL windows 1365 more event stuff 1366 extremely basic windows w/ no decoration for tooltips, splash screens, etc. 1367 1368 1369 resizeEvent 1370 and make the windows non-resizable by default, 1371 or perhaps stretched (if I can find something in X like StretchBlt) 1372 1373 take a screenshot function! 1374 1375 Pens and brushes? 1376 Maybe a global event loop? 1377 1378 Mouse deltas 1379 Key items 1380 */ 1381 1382 /* 1383 From MSDN: 1384 1385 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate. 1386 1387 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. 1388 1389 */ 1390 1391 version(linux) { 1392 version = X11; 1393 version(without_libnotify) { 1394 // we cool 1395 } 1396 else 1397 version = libnotify; 1398 } 1399 1400 version(libnotify) { 1401 pragma(lib, "dl"); 1402 import core.sys.posix.dlfcn; 1403 1404 void delegate()[int] libnotify_action_delegates; 1405 int libnotify_action_delegates_count; 1406 extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) { 1407 auto idx = cast(int) user_data; 1408 if(auto dgptr = idx in libnotify_action_delegates) { 1409 (*dgptr)(); 1410 libnotify_action_delegates.remove(idx); 1411 } 1412 } 1413 1414 struct C_DynamicLibrary { 1415 void* handle; 1416 this(string name) { 1417 handle = dlopen((name ~ "\0").ptr, RTLD_NOW); 1418 if(handle is null) 1419 throw new Exception("dlopen"); 1420 } 1421 1422 void close() { 1423 dlclose(handle); 1424 } 1425 1426 ~this() { 1427 // close 1428 } 1429 1430 // FIXME: this looks up by name every time.... 1431 template call(string func, Ret, Args...) { 1432 extern(C) Ret function(Args) fptr; 1433 typeof(fptr) call() { 1434 fptr = cast(typeof(fptr)) dlsym(handle, func); 1435 return fptr; 1436 } 1437 } 1438 } 1439 1440 C_DynamicLibrary* libnotify; 1441 } 1442 1443 version(OSX) { 1444 version(OSXCocoa) {} 1445 else { version = X11; } 1446 } 1447 //version = OSXCocoa; // this was written by KennyTM 1448 version(FreeBSD) 1449 version = X11; 1450 version(Solaris) 1451 version = X11; 1452 1453 version(X11) { 1454 version(without_xft) {} 1455 else version=with_xft; 1456 } 1457 1458 void featureNotImplemented()() { 1459 version(allow_unimplemented_features) 1460 throw new NotYetImplementedException(); 1461 else 1462 static assert(0); 1463 } 1464 1465 // these are so the static asserts don't trigger unless you want to 1466 // add support to it for an OS 1467 version(Windows) 1468 version = with_timer; 1469 version(linux) 1470 version = with_timer; 1471 version(OSXCocoa) 1472 version = with_timer; 1473 1474 version(with_timer) 1475 enum bool SimpledisplayTimerAvailable = true; 1476 else 1477 enum bool SimpledisplayTimerAvailable = false; 1478 1479 /// 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. 1480 version(Windows) 1481 enum bool UsingSimpledisplayWindows = true; 1482 else 1483 enum bool UsingSimpledisplayWindows = false; 1484 1485 /// 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. 1486 version(X11) 1487 enum bool UsingSimpledisplayX11 = true; 1488 else 1489 enum bool UsingSimpledisplayX11 = false; 1490 1491 /// 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. 1492 version(OSXCocoa) 1493 enum bool UsingSimpledisplayCocoa = true; 1494 else 1495 enum bool UsingSimpledisplayCocoa = false; 1496 1497 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception. 1498 version(Windows) 1499 enum multipleWindowsSupported = true; 1500 else version(X11) 1501 enum multipleWindowsSupported = true; 1502 else version(OSXCocoa) 1503 enum multipleWindowsSupported = true; 1504 else 1505 static assert(0); 1506 1507 version(without_opengl) 1508 enum bool OpenGlEnabled = false; 1509 else 1510 enum bool OpenGlEnabled = true; 1511 1512 /++ 1513 Adds the necessary pragmas to your application to use the Windows gui subsystem. 1514 If you mix this in above your `main` function, you no longer need to use the linker 1515 flags explicitly. It does the necessary version blocks for various compilers and runtimes. 1516 1517 It does nothing if not compiling for Windows, so you need not version it out yourself. 1518 1519 Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and 1520 stderr writeln. It will fail and throw an exception. 1521 1522 This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`. 1523 1524 History: 1525 Added November 24, 2021 (dub v10.4) 1526 +/ 1527 mixin template EnableWindowsSubsystem() { 1528 version(Windows) 1529 version(CRuntime_Microsoft) { 1530 pragma(linkerDirective, "/subsystem:windows"); 1531 version(LDC) 1532 pragma(linkerDirective, "/entry:wmainCRTStartup"); 1533 else 1534 pragma(linkerDirective, "/entry:mainCRTStartup"); 1535 } 1536 } 1537 1538 1539 /++ 1540 After selecting a type from [WindowTypes], you may further customize 1541 its behavior by setting one or more of these flags. 1542 1543 1544 The different window types have different meanings of `normal`. If the 1545 window type already is a good match for what you want to do, you should 1546 just use [WindowFlags.normal], the default, which will do the right thing 1547 for your users. 1548 1549 The window flags will not always be honored by the operating system 1550 and window managers; they are hints, not commands. 1551 +/ 1552 enum WindowFlags : int { 1553 normal = 0, /// 1554 skipTaskbar = 1, /// 1555 alwaysOnTop = 2, /// 1556 alwaysOnBottom = 4, /// 1557 cannotBeActivated = 8, /// 1558 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. 1559 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. 1560 /++ 1561 Sets the window as a short-lived child of its parent, but unlike an ordinary child, 1562 it is still a top-level window. This should NOT be set separately for most window types. 1563 1564 A transient window will not keep the application open if its main window closes. 1565 1566 $(PITFALL This may not be correctly implemented and its behavior is subject to change.) 1567 1568 1569 From the ICCM: 1570 1571 $(BLOCKQUOTE 1572 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. 1573 1574 $(CITE https://tronche.com/gui/x/icccm/sec-4.html) 1575 ) 1576 1577 So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag. 1578 1579 History: 1580 Added February 23, 2021 but not yet stabilized. 1581 +/ 1582 transient = 64, 1583 /++ 1584 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. 1585 1586 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 1587 1588 History: 1589 Added April 1, 2022 1590 +/ 1591 managesChildWindowFocus = 128, 1592 1593 dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually. 1594 } 1595 1596 /++ 1597 When creating a window, you can pass a type to SimpleWindow's constructor, 1598 then further customize the window by changing `WindowFlags`. 1599 1600 1601 You should mostly only need [normal], [undecorated], and [eventOnly] for normal 1602 use. The others are there to build a foundation for a higher level GUI toolkit, 1603 but are themselves not as high level as you might think from their names. 1604 1605 This list is based on the EMWH spec for X11. 1606 http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896 1607 +/ 1608 enum WindowTypes : int { 1609 /// An ordinary application window. 1610 normal, 1611 /// 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. 1612 undecorated, 1613 /// 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. 1614 eventOnly, 1615 /// A drop down menu, such as from a menu bar 1616 dropdownMenu, 1617 /// A popup menu, such as from a right click 1618 popupMenu, 1619 /// A popup bubble notification 1620 notification, 1621 /* 1622 menu, /// a tearable menu bar 1623 splashScreen, /// a loading splash screen for your application 1624 tooltip, /// A tiny window showing temporary help text or something. 1625 comboBoxDropdown, 1626 toolbar 1627 */ 1628 /// a dialog box of some sort 1629 dialog, 1630 /// a child nested inside the parent. You must pass a parent window to the ctor 1631 nestedChild, 1632 1633 /++ 1634 The type you get when you pass in an existing browser handle, which means most 1635 of simpledisplay's fancy things will not be done since they were never set up. 1636 1637 Using this to the main SimpleWindow constructor explicitly will trigger an assertion 1638 failure; you should use the existing handle constructor. 1639 1640 History: 1641 Added November 17, 2022 (previously it would have type `normal`) 1642 +/ 1643 minimallyWrapped 1644 } 1645 1646 1647 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call 1648 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features 1649 private __gshared char* sdpyWindowClassStr = null; 1650 private __gshared bool sdpyOpenGLContextAllowFallback = false; 1651 1652 /** 1653 Set OpenGL context version to use. This has no effect on non-OpenGL windows. 1654 You may want to change context version if you want to use advanced shaders or 1655 other modern OpenGL techinques. This setting doesn't affect already created 1656 windows. You may use version 2.1 as your default, which should be supported 1657 by any box since 2006, so seems to be a reasonable choice. 1658 1659 Note that by default version is set to `0`, which forces SimpleDisplay to use 1660 old context creation code without any version specified. This is the safest 1661 way to init OpenGL, but it may not give you access to advanced features. 1662 1663 See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL 1664 */ 1665 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); } 1666 1667 /** 1668 Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed 1669 pipeline functions, and without "compatible" mode you won't be able to use 1670 your old non-shader-based code with such contexts. By default SimpleDisplay 1671 creates compatible context, so you can gradually upgrade your OpenGL code if 1672 you want to (or leave it as is, as it should "just work"). 1673 */ 1674 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; } 1675 1676 /** 1677 Set to `true` to allow creating OpenGL context with lower version than requested 1678 instead of throwing. If fallback was activated (or legacy OpenGL was requested), 1679 `openGLContextFallbackActivated()` will return `true`. 1680 */ 1681 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; } 1682 1683 /** 1684 After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context. 1685 */ 1686 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); } 1687 1688 /++ 1689 History: 1690 Added April 24, 2023 (dub v11.0) 1691 +/ 1692 version(without_opengl) {} else 1693 auto openGLCurrentContext() { 1694 version(Windows) 1695 return wglGetCurrentContext(); 1696 else 1697 return glXGetCurrentContext(); 1698 } 1699 1700 1701 /** 1702 Set window class name for all following `new SimpleWindow()` calls. 1703 1704 WARNING! For Windows, you should set your class name before creating any 1705 window, and NEVER change it after that! 1706 */ 1707 void sdpyWindowClass (const(char)[] v) { 1708 import core.stdc.stdlib : realloc; 1709 if (v.length == 0) v = "SimpleDisplayWindow"; 1710 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1); 1711 if (sdpyWindowClassStr is null) return; // oops 1712 sdpyWindowClassStr[0..v.length+1] = 0; 1713 sdpyWindowClassStr[0..v.length] = v[]; 1714 } 1715 1716 /** 1717 Get current window class name. 1718 */ 1719 string sdpyWindowClass () @trusted { 1720 if (sdpyWindowClassStr is null) return null; 1721 foreach (immutable idx; 0..size_t.max-1) { 1722 if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup; 1723 } 1724 return null; 1725 } 1726 1727 /++ 1728 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. 1729 1730 If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0. 1731 +/ 1732 float[2] getDpi() { 1733 float[2] dpi; 1734 version(Windows) { 1735 HDC screen = GetDC(null); 1736 dpi[0] = GetDeviceCaps(screen, LOGPIXELSX); 1737 dpi[1] = GetDeviceCaps(screen, LOGPIXELSY); 1738 } else version(X11) { 1739 auto display = XDisplayConnection.get; 1740 auto screen = DefaultScreen(display); 1741 1742 void fallback() { 1743 /+ 1744 // 25.4 millimeters in an inch... 1745 dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4; 1746 dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4; 1747 +/ 1748 1749 // the physical size isn't actually as important as the logical size since this is 1750 // all about scaling really 1751 dpi[0] = 96; 1752 dpi[1] = 96; 1753 } 1754 1755 auto xft = getXftDpi(); 1756 if(xft is float.init) 1757 fallback(); 1758 else { 1759 dpi[0] = xft; 1760 dpi[1] = xft; 1761 } 1762 } 1763 1764 return dpi; 1765 } 1766 1767 version(X11) 1768 float getXftDpi() { 1769 auto display = XDisplayConnection.get; 1770 1771 char* resourceString = XResourceManagerString(display); 1772 XrmInitialize(); 1773 1774 if (resourceString) { 1775 auto db = XrmGetStringDatabase(resourceString); 1776 XrmValue value; 1777 char* type; 1778 if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) { 1779 if (value.addr) { 1780 import core.stdc.stdlib; 1781 return atof(cast(char*) value.addr); 1782 } 1783 } 1784 } 1785 1786 return float.init; 1787 } 1788 1789 /++ 1790 Implementation used by [SimpleWindow.takeScreenshot]. 1791 1792 Params: 1793 handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen. 1794 width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target. 1795 height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target. 1796 x = the x-offset of the image to capture, from the left. 1797 y = the y-offset of the image to capture, from the top. 1798 1799 History: 1800 Added on March 14, 2021 1801 1802 Documented public on September 23, 2021 with full support for null params (dub 10.3) 1803 1804 +/ 1805 TrueColorImage trueColorImageFromNativeHandle(PaintingHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) { 1806 TrueColorImage got; 1807 version(X11) { 1808 auto display = XDisplayConnection.get; 1809 if(handle == 0) 1810 handle = RootWindow(display, DefaultScreen(display)); 1811 1812 if(width == 0 || height == 0) { 1813 Window root; 1814 int xpos, ypos; 1815 uint widthret, heightret, borderret, depthret; 1816 XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret); 1817 1818 if(width == 0) 1819 width = widthret; 1820 if(height == 0) 1821 height = heightret; 1822 } 1823 1824 auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap); 1825 1826 // https://github.com/adamdruppe/arsd/issues/98 1827 1828 auto i = new Image(image); 1829 got = i.toTrueColorImage(); 1830 1831 XDestroyImage(image); 1832 } else version(Windows) { 1833 auto hdc = GetDC(handle); 1834 scope(exit) ReleaseDC(handle, hdc); 1835 1836 if(width == 0 || height == 0) { 1837 BITMAP bmHeader; 1838 auto bm = GetCurrentObject(hdc, OBJ_BITMAP); 1839 GetObject(bm, BITMAP.sizeof, &bmHeader); 1840 if(width == 0) 1841 width = bmHeader.bmWidth; 1842 if(height == 0) 1843 height = bmHeader.bmHeight; 1844 } 1845 1846 auto i = new Image(width, height); 1847 HDC hdcMem = CreateCompatibleDC(hdc); 1848 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 1849 BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY); 1850 SelectObject(hdcMem, hbmOld); 1851 DeleteDC(hdcMem); 1852 1853 got = i.toTrueColorImage(); 1854 } else featureNotImplemented(); 1855 1856 return got; 1857 } 1858 1859 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE); 1860 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow; 1861 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi; 1862 1863 version(Windows) 1864 shared static this() { 1865 auto lib = LoadLibrary("User32.dll"); 1866 if(lib is null) 1867 return; 1868 //scope(exit) 1869 //FreeLibrary(lib); 1870 1871 SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext"); 1872 1873 if(SetProcessDpiAwarenessContext is null) 1874 return; 1875 1876 enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4; 1877 if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { 1878 //writeln(GetLastError()); 1879 } 1880 1881 GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow"); 1882 SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi"); 1883 } 1884 1885 /++ 1886 Blocking mode for event loop calls associated with a window instance. 1887 1888 History: 1889 Added December 8, 2021 (dub v10.5). Prior to that, all calls to 1890 `window.eventLoop` were the same as calls to `EventLoop.get.run`; that 1891 is, all would block until the application quit. 1892 1893 That behavior can still be achieved here with `untilApplicationQuits`, 1894 or explicitly calling the top-level `EventLoop.get.run` function. 1895 +/ 1896 enum BlockingMode { 1897 /++ 1898 The event loop call will block until the whole application is ready 1899 to quit if it is the only one running, but if it is nested inside 1900 another one, it will only block until the window you're calling it on 1901 closes. 1902 +/ 1903 automatic = 0x00, 1904 /++ 1905 The event loop call will only return when the whole application 1906 is ready to quit. This usually means all windows have been closed. 1907 1908 This is appropriate for your main application event loop. 1909 +/ 1910 untilApplicationQuits = 0x01, 1911 /++ 1912 The event loop will return when the window you're calling it on 1913 closes. If there are other windows still open, they may be destroyed 1914 unless you have another event loop running later. 1915 1916 This might be appropriate for a modal dialog box loop. Remember that 1917 other windows are still processing input though, so you can end up 1918 with a lengthy call stack if this happens in a loop, similar to a 1919 recursive function (well, it literally is a recursive function, just 1920 not an obvious looking one). 1921 +/ 1922 untilWindowCloses = 0x02, 1923 /++ 1924 If an event loop is already running, this call will immediately 1925 return, allowing the existing loop to handle it. If not, this call 1926 will block until the condition you bitwise-or into the flag. 1927 1928 The default is to block until the application quits, same as with 1929 the `automatic` setting (since if it were nested, which triggers until 1930 window closes in automatic, this flag would instead not block at all), 1931 but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`, 1932 it will only nest until the window closes. You might want that if you are 1933 going to open two windows simultaneously and want closing just one of them 1934 to trigger the event loop return. 1935 +/ 1936 onlyIfNotNested = 0x10, 1937 } 1938 1939 /++ 1940 Window corner visuals preference 1941 +/ 1942 enum CornerStyle { 1943 /++ 1944 Use the default style automatically applied by the system or its window manager/compositor. 1945 +/ 1946 automatic, 1947 1948 /++ 1949 Prefer rectangular window corners 1950 +/ 1951 rectangular, 1952 1953 /++ 1954 Prefer rounded window corners 1955 +/ 1956 rounded, 1957 1958 /++ 1959 Prefer slightly-rounded window corners 1960 +/ 1961 roundedSlightly, 1962 } 1963 1964 /++ 1965 The flagship window class. 1966 1967 1968 SimpleWindow tries to make ordinary windows very easy to create and use without locking you 1969 out of more advanced or complex features of the underlying windowing system. 1970 1971 For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")` 1972 and get a suitable window to work with. 1973 1974 From there, you can opt into additional features, like custom resizability and OpenGL support 1975 with the next two constructor arguments. Or, if you need even more, you can set a window type 1976 and customization flags with the final two constructor arguments. 1977 1978 If none of that works for you, you can also create a window using native function calls, then 1979 wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember, 1980 though, if you do this, managing the window is still your own responsibility! Notably, you 1981 will need to destroy it yourself. 1982 +/ 1983 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { 1984 1985 /++ 1986 Copies the window's current state into a [TrueColorImage]. 1987 1988 Be warned: this can be a very slow operation 1989 1990 History: 1991 Actually implemented on March 14, 2021 1992 +/ 1993 TrueColorImage takeScreenshot() { 1994 version(Windows) 1995 return trueColorImageFromNativeHandle(impl.hwnd, _width, _height); 1996 else version(OSXCocoa) 1997 throw new NotYetImplementedException(); 1998 else 1999 return trueColorImageFromNativeHandle(impl.window, _width, _height); 2000 } 2001 2002 /++ 2003 Returns the actual logical DPI for the window on its current display monitor. If the window 2004 straddles monitors, it will return the value of one or the other in a platform-defined manner. 2005 2006 Please note this function may return zero if it doesn't know the answer! 2007 2008 2009 On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10), 2010 or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up). 2011 2012 On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems, 2013 this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the 2014 window primarily resides on by checking the center point of the window against the monitor map. 2015 2016 Returns: 2017 0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It 2018 assumes the X and Y dpi are the same. 2019 2020 History: 2021 Added November 26, 2021 (dub v10.4) 2022 2023 It said "physical dpi" in the description prior to July 29, 2022, but the behavior was 2024 always a logical value on Windows and usually one on Linux too, so now the docs reflect 2025 that. 2026 2027 Bugs: 2028 Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically 2029 just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to 2030 set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor 2031 and 1.5 on the secondary monitor. 2032 2033 The local dpi is not necessarily related to the physical dpi of the monitor. The name 2034 is a historical misnomer - the real thing of interest is the scale factor and due to 2035 compatibility concerns the scale would modify dpi values to trick applications. But since 2036 that's the terminology common out there, I used it too. 2037 2038 See_Also: 2039 [getDpi] gives the value provided for the default monitor. Not necessarily the same 2040 as this since the window many be on a different monitor, but it is a reasonable fallback 2041 to use if `actualDpi` returns 0. 2042 2043 [onDpiChanged] is changed when `actualDpi` has changed. 2044 +/ 2045 int actualDpi() { 2046 version(X11) bool useFallbackDpi = false; 2047 if(!actualDpiLoadAttempted) { 2048 // FIXME: do the actual monitor we are on 2049 // and on X this is a good chance to load the monitor map. 2050 version(Windows) { 2051 if(GetDpiForWindow) 2052 actualDpi_ = GetDpiForWindow(impl.hwnd); 2053 } else version(X11) { 2054 if(!xRandrInfoLoadAttemped) { 2055 xRandrInfoLoadAttemped = true; 2056 if(!XRandrLibrary.attempted) { 2057 XRandrLibrary.loadDynamicLibrary(); 2058 } 2059 2060 if(XRandrLibrary.loadSuccessful) { 2061 auto display = XDisplayConnection.get; 2062 int scratch; 2063 int major, minor; 2064 if(!XRRQueryExtension(display, &xrrEventBase, &scratch)) 2065 goto fallback; 2066 2067 XRRQueryVersion(display, &major, &minor); 2068 if(major <= 1 && minor < 5) 2069 goto fallback; 2070 2071 int count; 2072 XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count); 2073 if(monitors is null) 2074 goto fallback; 2075 scope(exit) XRRFreeMonitors(monitors); 2076 2077 MonitorInfo.info = MonitorInfo.info[0 .. 0]; 2078 MonitorInfo.info.assumeSafeAppend(); 2079 foreach(idx, monitor; monitors[0 .. count]) { 2080 MonitorInfo.info ~= MonitorInfo( 2081 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 2082 Size(monitor.mwidth, monitor.mheight), 2083 cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0]) 2084 ); 2085 2086 /+ 2087 if(monitor.mwidth == 0 || monitor.mheight == 0) 2088 // unknown physical size, just guess 96 to avoid divide by zero 2089 MonitorInfo.info ~= MonitorInfo( 2090 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 2091 Size(monitor.mwidth, monitor.mheight), 2092 96 2093 ); 2094 else 2095 // and actual thing 2096 MonitorInfo.info ~= MonitorInfo( 2097 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 2098 Size(monitor.mwidth, monitor.mheight), 2099 minInternal( 2100 // millimeter to int then rounding up. 2101 cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5), 2102 cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5) 2103 ) 2104 ); 2105 +/ 2106 } 2107 // writeln("Here", MonitorInfo.info); 2108 } 2109 } 2110 2111 if(XRandrLibrary.loadSuccessful) { 2112 updateActualDpi(true); 2113 // writeln("updated"); 2114 2115 if(!requestedInput) { 2116 // this is what requests live updates should the configuration change 2117 // each time you select input, it sends an initial event, so very important 2118 // to not get into a loop of selecting input, getting event, updating data, 2119 // and reselecting input... 2120 requestedInput = true; 2121 XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask); 2122 // writeln("requested input"); 2123 } 2124 } else { 2125 fallback: 2126 // make sure we disable events that aren't coming 2127 xrrEventBase = -1; 2128 // best guess... respect the custom scaling user command to some extent at least though 2129 useFallbackDpi = true; 2130 } 2131 } else version(OSXCocoa) { 2132 actualDpi_ = cast(int)(96 * customScalingFactorForMonitor(0)); // FIXME 2133 } 2134 actualDpiLoadAttempted = true; 2135 } else version(X11) if(MonitorInfo.info.length == 0) { 2136 useFallbackDpi = true; 2137 } 2138 2139 version(X11) 2140 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... 2141 actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0)); 2142 return actualDpi_; 2143 } 2144 2145 private int actualDpi_; 2146 private bool actualDpiLoadAttempted; 2147 2148 version(X11) private { 2149 bool requestedInput; 2150 static bool xRandrInfoLoadAttemped; 2151 struct MonitorInfo { 2152 Rectangle position; 2153 Size size; 2154 int dpi; 2155 2156 static MonitorInfo[] info; 2157 } 2158 bool screenPositionKnown; 2159 int screenPositionX; 2160 int screenPositionY; 2161 void updateActualDpi(bool loadingNow = false) { 2162 if(!loadingNow && !actualDpiLoadAttempted) 2163 actualDpi(); // just to make it do the load 2164 foreach(idx, m; MonitorInfo.info) { 2165 if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) { 2166 bool changed = actualDpi_ && actualDpi_ != m.dpi; 2167 actualDpi_ = m.dpi; 2168 // writeln("monitor ", idx); 2169 if(changed && onDpiChanged) 2170 onDpiChanged(); 2171 break; 2172 } 2173 } 2174 } 2175 } 2176 2177 /++ 2178 Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors 2179 or if the window is moved to a new remote connection or a monitor is hot-swapped. 2180 2181 History: 2182 Added November 26, 2021 (dub v10.4) 2183 2184 See_Also: 2185 [actualDpi] 2186 +/ 2187 void delegate() onDpiChanged; 2188 2189 version(X11) { 2190 void recreateAfterDisconnect() { 2191 if(!stateDiscarded) return; 2192 2193 if(_parent !is null && _parent.stateDiscarded) 2194 _parent.recreateAfterDisconnect(); 2195 2196 bool wasHidden = hidden; 2197 2198 activeScreenPainter = null; // should already be done but just to confirm 2199 2200 actualDpi_ = 0; 2201 actualDpiLoadAttempted = false; 2202 xRandrInfoLoadAttemped = false; 2203 2204 impl.createWindow(_width, _height, _title, openglMode, _parent); 2205 2206 if(auto dh = dropHandler) { 2207 dropHandler = null; 2208 enableDragAndDrop(this, dh); 2209 } 2210 2211 if(recreateAdditionalConnectionState) 2212 recreateAdditionalConnectionState(); 2213 2214 hidden = wasHidden; 2215 stateDiscarded = false; 2216 } 2217 2218 bool stateDiscarded; 2219 void discardConnectionState() { 2220 if(XDisplayConnection.display) 2221 impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway 2222 if(discardAdditionalConnectionState) 2223 discardAdditionalConnectionState(); 2224 stateDiscarded = true; 2225 } 2226 2227 void delegate() discardAdditionalConnectionState; 2228 void delegate() recreateAdditionalConnectionState; 2229 2230 } 2231 2232 private DropHandler dropHandler; 2233 2234 SimpleWindow _parent; 2235 bool beingOpenKeepsAppOpen = true; 2236 /++ 2237 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. 2238 2239 The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them. 2240 2241 Params: 2242 2243 width = the width of the window's client area, in pixels 2244 height = the height of the window's client area, in pixels 2245 title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property. 2246 opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window. 2247 resizable = [Resizability] has three options: 2248 $(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.) 2249 $(P `fixedSize` will not allow the user to resize the window.) 2250 $(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.) 2251 windowType = The type of window you want to make. 2252 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. 2253 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". 2254 +/ 2255 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) { 2256 claimGuiThread(); 2257 version(sdpy_thread_checks) assert(thisIsGuiThread); 2258 this._width = this._virtualWidth = width; 2259 this._height = this._virtualHeight = height; 2260 this.openglMode = opengl; 2261 version(X11) { 2262 // auto scale not implemented except with opengl and even there it is kinda weird 2263 if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no) 2264 resizable = Resizability.fixedSize; 2265 } 2266 this.resizability = resizable; 2267 this.windowType = windowType; 2268 this.customizationFlags = customizationFlags; 2269 this._title = (title is null ? "D Application" : title); 2270 this._parent = parent; 2271 impl.createWindow(width, height, this._title, opengl, parent); 2272 2273 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient)) 2274 beingOpenKeepsAppOpen = false; 2275 } 2276 2277 /// ditto 2278 this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 2279 this(width, height, title, opengl, resizable, windowType, customizationFlags, parent); 2280 } 2281 2282 /// Same as above, except using the `Size` struct instead of separate width and height. 2283 this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) { 2284 this(size.width, size.height, title, opengl, resizable); 2285 } 2286 2287 /// ditto 2288 this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) { 2289 this(size, title, opengl, resizable); 2290 } 2291 2292 2293 /++ 2294 Creates a window based on the given [Image]. It's client area 2295 width and height is equal to the image. (A window's client area 2296 is the drawable space inside; it excludes the title bar, etc.) 2297 2298 Windows based on images will not be resizable and do not use OpenGL. 2299 2300 It will draw the image in upon creation, but this will be overwritten 2301 upon any draws, including the initial window visible event. 2302 2303 You probably do not want to use this and it may be removed from 2304 the library eventually, or I might change it to be a "permanent" 2305 background image; one that is automatically drawn on it before any 2306 other drawing event. idk. 2307 +/ 2308 this(Image image, string title = null) { 2309 this(image.width, image.height, title); 2310 this.image = image; 2311 } 2312 2313 /++ 2314 Wraps a native window handle with very little additional processing - notably no destruction 2315 this is incomplete so don't use it for much right now. The purpose of this is to make native 2316 windows created through the low level API (so you can use platform-specific options and 2317 other details SimpleWindow does not expose) available to the event loop wrappers. 2318 +/ 2319 this(NativeWindowHandle nativeWindow) { 2320 windowType = WindowTypes.minimallyWrapped; 2321 version(Windows) 2322 impl.hwnd = nativeWindow; 2323 else version(X11) { 2324 impl.window = nativeWindow; 2325 if(nativeWindow) 2326 display = XDisplayConnection.get(); // get initial display to not segfault 2327 } else version(OSXCocoa) { 2328 if(nativeWindow !is NullWindow) throw new NotYetImplementedException(); 2329 } else featureNotImplemented(); 2330 // FIXME: set the size correctly 2331 _width = 1; 2332 _height = 1; 2333 if(nativeWindow) 2334 nativeMapping[cast(void*) nativeWindow] = this; 2335 2336 beingOpenKeepsAppOpen = false; 2337 2338 if(nativeWindow) 2339 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 2340 _suppressDestruction = true; // so it doesn't try to close 2341 } 2342 2343 /++ 2344 Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created. 2345 The delegate will be called when the window manager asks you to take focus. 2346 2347 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 2348 2349 History: 2350 Added April 1, 2022 (dub v10.8) 2351 +/ 2352 SimpleWindow delegate() setRequestedInputFocus; 2353 2354 /// Experimental, do not use yet 2355 /++ 2356 Grabs exclusive input from the user until you release it with 2357 [releaseInputGrab]. 2358 2359 2360 Note: it is extremely rude to do this without good reason. 2361 Reasons may include doing some kind of mouse drag operation 2362 or popping up a temporary menu that should get events and will 2363 be dismissed at ease by the user clicking away. 2364 2365 Params: 2366 keyboard = do you want to grab keyboard input? 2367 mouse = grab mouse input? 2368 confine = confine the mouse cursor to inside this window? 2369 2370 History: 2371 Prior to March 11, 2021, grabbing the keyboard would always also 2372 set the X input focus. Now, it only focuses if it is a non-transient 2373 window and otherwise manages the input direction internally. 2374 2375 This means spurious focus/blur events will no longer be sent and the 2376 application will not steal focus from other applications (which the 2377 window manager may have rejected anyway). 2378 +/ 2379 void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) { 2380 static if(UsingSimpledisplayX11) { 2381 XSync(XDisplayConnection.get, 0); 2382 if(keyboard) { 2383 if(isTransient && _parent) { 2384 /* 2385 FIXME: 2386 setting the keyboard focus is not actually that helpful, what I more likely want 2387 is the events from the parent window to be sent over here if we're transient. 2388 */ 2389 2390 _parent.inputProxy = this; 2391 } else { 2392 2393 SimpleWindow setTo; 2394 if(setRequestedInputFocus !is null) 2395 setTo = setRequestedInputFocus(); 2396 if(setTo is null) 2397 setTo = this; 2398 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2399 } 2400 } 2401 if(mouse) { 2402 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 2403 EventMask.PointerMotionMask // FIXME: not efficient 2404 | EventMask.ButtonPressMask 2405 | EventMask.ButtonReleaseMask 2406 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 2407 ) 2408 { 2409 XSync(XDisplayConnection.get, 0); 2410 import core.stdc.stdio; 2411 printf("Grab input failed %d\n", res); 2412 //throw new Exception("Grab input failed"); 2413 } else { 2414 // cool 2415 } 2416 } 2417 2418 } else version(Windows) { 2419 // FIXME: keyboard? 2420 SetCapture(impl.hwnd); 2421 if(confine) { 2422 RECT rcClip; 2423 //RECT rcOldClip; 2424 //GetClipCursor(&rcOldClip); 2425 GetWindowRect(hwnd, &rcClip); 2426 ClipCursor(&rcClip); 2427 } 2428 } else version(OSXCocoa) { 2429 // throw new NotYetImplementedException(); 2430 } else static assert(0); 2431 } 2432 2433 private Point imePopupLocation = Point(0, 0); 2434 2435 /++ 2436 Sets the location for the IME (input method editor) to pop up when the user activates it. 2437 2438 Bugs: 2439 Not implemented outside X11. 2440 +/ 2441 void setIMEPopupLocation(Point location) { 2442 static if(UsingSimpledisplayX11) { 2443 imePopupLocation = location; 2444 updateIMEPopupLocation(); 2445 } else { 2446 // this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least 2447 // throw new NotYetImplementedException(); 2448 } 2449 } 2450 2451 /// ditto 2452 void setIMEPopupLocation(int x, int y) { 2453 return setIMEPopupLocation(Point(x, y)); 2454 } 2455 2456 // we need to remind XIM of where we wanted to place the IME whenever the window moves 2457 // so this function gets called in setIMEPopupLocation as well as whenever the window 2458 // receives a ConfigureNotify event 2459 private void updateIMEPopupLocation() { 2460 static if(UsingSimpledisplayX11) { 2461 if (xic is null) { 2462 return; 2463 } 2464 2465 XPoint nspot; 2466 nspot.x = cast(short) imePopupLocation.x; 2467 nspot.y = cast(short) imePopupLocation.y; 2468 XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null); 2469 XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null); 2470 XFree(preeditAttr); 2471 } 2472 } 2473 2474 private bool imeFocused = true; 2475 2476 /++ 2477 Tells the IME whether or not an input field is currently focused in the window. 2478 2479 Bugs: 2480 Not implemented outside X11. 2481 +/ 2482 void setIMEFocused(bool value) { 2483 imeFocused = value; 2484 updateIMEFocused(); 2485 } 2486 2487 // used to focus/unfocus the IC if necessary when the window gains/loses focus 2488 private void updateIMEFocused() { 2489 static if(UsingSimpledisplayX11) { 2490 if (xic is null) { 2491 return; 2492 } 2493 2494 if (focused && imeFocused) { 2495 XSetICFocus(xic); 2496 } else { 2497 XUnsetICFocus(xic); 2498 } 2499 } 2500 } 2501 2502 /++ 2503 Returns the native window. 2504 2505 History: 2506 Added November 5, 2021 (dub v10.4). Prior to that, you'd have 2507 to access it through the `impl` member (which is semi-supported 2508 but platform specific and here it is simple enough to offer an accessor). 2509 2510 Bugs: 2511 Not implemented outside Windows or X11. 2512 +/ 2513 NativeWindowHandle nativeWindowHandle() { 2514 version(X11) 2515 return impl.window; 2516 else version(Windows) 2517 return impl.hwnd; 2518 else 2519 throw new NotYetImplementedException(); 2520 } 2521 2522 private bool isTransient() { 2523 with(WindowTypes) 2524 final switch(windowType) { 2525 case normal, undecorated, eventOnly: 2526 case nestedChild, minimallyWrapped: 2527 return (customizationFlags & WindowFlags.transient) ? true : false; 2528 case dropdownMenu, popupMenu, notification, dialog: 2529 return true; 2530 } 2531 } 2532 2533 private SimpleWindow inputProxy; 2534 2535 /++ 2536 Releases the grab acquired by [grabInput]. 2537 +/ 2538 void releaseInputGrab() { 2539 static if(UsingSimpledisplayX11) { 2540 XUngrabPointer(XDisplayConnection.get, CurrentTime); 2541 if(_parent) 2542 _parent.inputProxy = null; 2543 } else version(Windows) { 2544 ReleaseCapture(); 2545 ClipCursor(null); 2546 } else version(OSXCocoa) { 2547 // throw new NotYetImplementedException(); 2548 } else static assert(0); 2549 } 2550 2551 /++ 2552 Sets the input focus to this window. 2553 2554 You shouldn't call this very often - please let the user control the input focus. 2555 +/ 2556 void focus() { 2557 static if(UsingSimpledisplayX11) { 2558 SimpleWindow setTo; 2559 if(setRequestedInputFocus !is null) 2560 setTo = setRequestedInputFocus(); 2561 if(setTo is null) 2562 setTo = this; 2563 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2564 } else version(Windows) { 2565 SetFocus(this.impl.hwnd); 2566 } else version(OSXCocoa) { 2567 throw new NotYetImplementedException(); 2568 } else static assert(0); 2569 } 2570 2571 /++ 2572 Requests attention from the user for this window. 2573 2574 2575 The typical result of this function is to change the color 2576 of the taskbar icon, though it may be tweaked on specific 2577 platforms. 2578 2579 It is meant to unobtrusively tell the user that something 2580 relevant to them happened in the background and they should 2581 check the window when they get a chance. Upon receiving the 2582 keyboard focus, the window will automatically return to its 2583 natural state. 2584 2585 If the window already has the keyboard focus, this function 2586 may do nothing, because the user is presumed to already be 2587 giving the window attention. 2588 2589 Implementation_note: 2590 2591 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 2592 atom on X11 and the FlashWindow function on Windows. 2593 +/ 2594 void requestAttention() { 2595 if(_focused) 2596 return; 2597 2598 version(Windows) { 2599 FLASHWINFO info; 2600 info.cbSize = info.sizeof; 2601 info.hwnd = impl.hwnd; 2602 info.dwFlags = FLASHW_TRAY; 2603 info.uCount = 1; 2604 2605 FlashWindowEx(&info); 2606 2607 } else version(X11) { 2608 demandingAttention = true; 2609 demandAttention(this, true); 2610 } else version(OSXCocoa) { 2611 throw new NotYetImplementedException(); 2612 } else static assert(0); 2613 } 2614 2615 private bool _focused; 2616 2617 version(X11) private bool demandingAttention; 2618 2619 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 2620 /// You'll have to call `close()` manually if you set this delegate. 2621 void delegate () closeQuery; 2622 2623 /// This will be called when window visibility was changed. 2624 void delegate (bool becomesVisible) visibilityChanged; 2625 2626 /// This will be called when window becomes visible for the first time. 2627 /// You can do OpenGL initialization here. Note that in X11 you can't call 2628 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 2629 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 2630 /// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization. 2631 private bool _visibleForTheFirstTimeCalled; 2632 void delegate () visibleForTheFirstTime; 2633 2634 /// Returns true if the window has been closed. 2635 final @property bool closed() { return _closed; } 2636 2637 private final @property bool notClosed() { return !_closed; } 2638 2639 /// Returns true if the window is focused. 2640 final @property bool focused() { return _focused; } 2641 2642 private bool _visible; 2643 /// Returns true if the window is visible (mapped). 2644 final @property bool visible() { return _visible; } 2645 2646 /// Closes the window. If there are no more open windows, the event loop will terminate. 2647 void close() { 2648 if (!_closed) { 2649 runInGuiThread( { 2650 if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued 2651 if (onClosing !is null) onClosing(); 2652 impl.closeWindow(); 2653 _closed = true; 2654 } ); 2655 } 2656 } 2657 2658 /++ 2659 `close` is one of the few methods that can be called from other threads. This `shared` overload reflects that. 2660 2661 History: 2662 Overload added on March 7, 2021. 2663 +/ 2664 void close() shared { 2665 (cast() this).close(); 2666 } 2667 2668 /++ 2669 2670 +/ 2671 void maximize() { 2672 version(Windows) 2673 ShowWindow(impl.hwnd, SW_MAXIMIZE); 2674 else version(X11) { 2675 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get)); 2676 2677 // also note _NET_WM_STATE_FULLSCREEN 2678 } 2679 2680 } 2681 2682 private bool _fullscreen; 2683 version(Windows) 2684 private WINDOWPLACEMENT g_wpPrev; 2685 2686 /// not fully implemented but planned for a future release 2687 void fullscreen(bool yes) { 2688 version(Windows) { 2689 g_wpPrev.length = WINDOWPLACEMENT.sizeof; 2690 DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE); 2691 if (dwStyle & WS_OVERLAPPEDWINDOW) { 2692 MONITORINFO mi; 2693 mi.cbSize = MONITORINFO.sizeof; 2694 if (GetWindowPlacement(hwnd, &g_wpPrev) && 2695 GetMonitorInfo(MonitorFromWindow(hwnd, 2696 MONITOR_DEFAULTTOPRIMARY), &mi)) { 2697 SetWindowLong(hwnd, GWL_STYLE, 2698 dwStyle & ~WS_OVERLAPPEDWINDOW); 2699 SetWindowPos(hwnd, HWND_TOP, 2700 mi.rcMonitor.left, mi.rcMonitor.top, 2701 mi.rcMonitor.right - mi.rcMonitor.left, 2702 mi.rcMonitor.bottom - mi.rcMonitor.top, 2703 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2704 } 2705 } else { 2706 SetWindowLong(hwnd, GWL_STYLE, 2707 dwStyle | WS_OVERLAPPEDWINDOW); 2708 SetWindowPlacement(hwnd, &g_wpPrev); 2709 SetWindowPos(hwnd, null, 0, 0, 0, 0, 2710 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | 2711 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2712 } 2713 2714 } else version(X11) { 2715 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes); 2716 } 2717 2718 _fullscreen = yes; 2719 2720 } 2721 2722 bool fullscreen() { 2723 return _fullscreen; 2724 } 2725 2726 /++ 2727 Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead. 2728 2729 +/ 2730 void minimize() { 2731 version(Windows) 2732 ShowWindow(impl.hwnd, SW_MINIMIZE); 2733 //else version(X11) 2734 //setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true); 2735 } 2736 2737 /// Alias for `hidden = false` 2738 void show() { 2739 hidden = false; 2740 } 2741 2742 /// Alias for `hidden = true` 2743 void hide() { 2744 hidden = true; 2745 } 2746 2747 /// Hide cursor when it enters the window. 2748 void hideCursor() { 2749 version(OSXCocoa) throw new NotYetImplementedException(); else 2750 if (!_closed) impl.hideCursor(); 2751 } 2752 2753 /// Don't hide cursor when it enters the window. 2754 void showCursor() { 2755 version(OSXCocoa) throw new NotYetImplementedException(); else 2756 if (!_closed) impl.showCursor(); 2757 } 2758 2759 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 2760 * 2761 * Please remember that the cursor is a shared resource that should usually be left to the user's 2762 * control. Try to think for other approaches before using this function. 2763 * 2764 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 2765 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 2766 * receive "mouse moved here" event. 2767 */ 2768 bool warpMouse (int x, int y) { 2769 version(X11) { 2770 if (!_closed) { impl.warpMouse(x, y); return true; } 2771 } else version(Windows) { 2772 if (!_closed) { 2773 POINT point; 2774 point.x = x; 2775 point.y = y; 2776 if(ClientToScreen(impl.hwnd, &point)) { 2777 SetCursorPos(point.x, point.y); 2778 return true; 2779 } 2780 } 2781 } 2782 return false; 2783 } 2784 2785 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 2786 void sendDummyEvent () { 2787 version(X11) { 2788 if (!_closed) { impl.sendDummyEvent(); } 2789 } 2790 } 2791 2792 /// Set window minimal size. 2793 void setMinSize (int minwidth, int minheight) { 2794 version(OSXCocoa) throw new NotYetImplementedException(); else 2795 if (!_closed) impl.setMinSize(minwidth, minheight); 2796 } 2797 2798 /// Set window maximal size. 2799 void setMaxSize (int maxwidth, int maxheight) { 2800 version(OSXCocoa) throw new NotYetImplementedException(); else 2801 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 2802 } 2803 2804 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 2805 /// Currently only supported on X11. 2806 void setResizeGranularity (int granx, int grany) { 2807 version(OSXCocoa) throw new NotYetImplementedException(); else 2808 if (!_closed) impl.setResizeGranularity(granx, grany); 2809 } 2810 2811 /// Move window. 2812 void move(int x, int y) { 2813 version(OSXCocoa) throw new NotYetImplementedException(); else 2814 if (!_closed) impl.move(x, y); 2815 } 2816 2817 /// ditto 2818 void move(Point p) { 2819 version(OSXCocoa) throw new NotYetImplementedException(); else 2820 if (!_closed) impl.move(p.x, p.y); 2821 } 2822 2823 /++ 2824 Resize window. 2825 2826 Note that the width and height of the window are NOT instantly 2827 updated - it waits for the window manager to approve the resize 2828 request, which means you must return to the event loop before the 2829 width and height are actually changed. 2830 +/ 2831 void resize(int w, int h) { 2832 if(!_closed && _fullscreen) fullscreen = false; 2833 version(OSXCocoa) throw new NotYetImplementedException(); else 2834 if (!_closed) impl.resize(w, h); 2835 } 2836 2837 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 2838 void moveResize (int x, int y, int w, int h) { 2839 if(!_closed && _fullscreen) fullscreen = false; 2840 version(OSXCocoa) throw new NotYetImplementedException(); else 2841 if (!_closed) impl.moveResize(x, y, w, h); 2842 } 2843 2844 private bool _hidden; 2845 2846 /// Returns true if the window is hidden. 2847 final @property bool hidden() { 2848 return _hidden; 2849 } 2850 2851 /// Shows or hides the window based on the bool argument. 2852 final @property void hidden(bool b) { 2853 _hidden = b; 2854 version(Windows) { 2855 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 2856 } else version(X11) { 2857 if(b) 2858 //XUnmapWindow(impl.display, impl.window); 2859 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 2860 else 2861 XMapWindow(impl.display, impl.window); 2862 } else version(OSXCocoa) { 2863 // throw new NotYetImplementedException(); 2864 } else static assert(0); 2865 } 2866 2867 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 2868 void opacity(double opacity) @property 2869 in { 2870 assert(opacity >= 0 && opacity <= 1); 2871 } do { 2872 version (Windows) { 2873 impl.setOpacity(cast(ubyte)(255 * opacity)); 2874 } else version (X11) { 2875 impl.setOpacity(cast(uint)(uint.max * opacity)); 2876 } else throw new NotYetImplementedException(); 2877 } 2878 2879 /++ 2880 Sets your event handlers, without entering the event loop. Useful if you 2881 have multiple windows - set the handlers on each window, then only do 2882 [eventLoop] on your main window or call `EventLoop.get.run();`. 2883 2884 This assigns the given handlers to [handleKeyEvent], [handleCharEvent], 2885 [handlePulse], and [handleMouseEvent] automatically based on the provide 2886 delegate signatures. 2887 +/ 2888 void setEventHandlers(T...)(T eventHandlers) { 2889 // FIXME: add more events 2890 foreach(handler; eventHandlers) { 2891 static if(__traits(compiles, handleKeyEvent = handler)) { 2892 handleKeyEvent = handler; 2893 } else static if(__traits(compiles, handleCharEvent = handler)) { 2894 handleCharEvent = handler; 2895 } else static if(__traits(compiles, handlePulse = handler)) { 2896 handlePulse = handler; 2897 } else static if(__traits(compiles, handleMouseEvent = handler)) { 2898 handleMouseEvent = handler; 2899 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 2900 } 2901 } 2902 2903 /++ 2904 The event loop automatically returns when the window is closed 2905 pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 2906 pulse timer is created. The event loop will block until an event 2907 arrives or the pulse timer goes off. 2908 2909 The given `eventHandlers` are passed to [setEventHandlers], which in turn 2910 assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and 2911 [handleMouseEvent], based on the signature of delegates you provide. 2912 2913 Give one with no parameters to set a timer pulse handler. Give one that 2914 takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler, 2915 and one that takes `dchar` for a char event handler. You can use as many 2916 or as few handlers as you need for your application. 2917 2918 Bugs: 2919 2920 $(PITFALL 2921 You should always have one event loop live for your application. 2922 If you make two windows in sequence, the second call to eventLoop 2923 might fail: 2924 2925 --- 2926 // don't do this! 2927 auto window = new SimpleWindow(); 2928 window.eventLoop(0); 2929 2930 auto window2 = new SimpleWindow(); 2931 window2.eventLoop(0); // problematic! might crash 2932 --- 2933 2934 simpledisplay's current implementation assumes that final cleanup is 2935 done when the event loop refcount reaches zero. So after the first 2936 eventLoop returns, when there isn't already another one active, it assumes 2937 the program will exit soon and cleans up. 2938 2939 This is arguably a bug that it doesn't reinitialize, and I'll probably change 2940 it eventually, but in the mean time, there's an easy solution: 2941 2942 --- 2943 // do this 2944 EventLoop mainEventLoop = EventLoop.get; // just add this line 2945 2946 auto window = new SimpleWindow(); 2947 window.eventLoop(0); 2948 2949 auto window2 = new SimpleWindow(); 2950 window2.eventLoop(0); // perfectly fine since mainEventLoop still alive 2951 --- 2952 2953 By adding a top-level reference to the event loop, it ensures the final cleanup 2954 is not performed until it goes out of scope too, letting the individual window loops 2955 work without trouble despite the bug. 2956 ) 2957 2958 History: 2959 The overload without `pulseTimeout` was added on December 8, 2021. 2960 2961 On December 9, 2021, the default blocking mode (which is now configurable 2962 because [eventLoopWithBlockingMode] was added) switched from 2963 [BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This 2964 should almost never be noticeable to you since the typical simpledisplay 2965 paradigm has been (and I still recommend) to have one `eventLoop` call. 2966 2967 See_Also: 2968 [eventLoopWithBlockingMode] 2969 +/ 2970 final int eventLoop(T...)( 2971 long pulseTimeout, /// set to zero if you don't want a pulse. 2972 T eventHandlers) /// delegate list like std.concurrency.receive 2973 { 2974 return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers); 2975 } 2976 2977 /// ditto 2978 final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate)) 2979 { 2980 return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers); 2981 } 2982 2983 /++ 2984 This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`. 2985 2986 History: 2987 Added December 8, 2021 (dub v10.5) 2988 2989 Previously, this implementation was right inside [eventLoop], but when I wanted 2990 to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I 2991 just renamed it instead of adding as an overload. Besides, the new name makes it 2992 easier to remember the order and avoids ambiguity between two int-like params anyway. 2993 2994 See_Also: 2995 [SimpleWindow.eventLoop], [EventLoop] 2996 2997 Bugs: 2998 The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop. 2999 +/ 3000 final int eventLoopWithBlockingMode(T...)( 3001 BlockingMode blockingMode, /// when you want this function to block until 3002 long pulseTimeout, /// set to zero if you don't want a pulse. 3003 T eventHandlers) /// delegate list like std.concurrency.receive 3004 { 3005 setEventHandlers(eventHandlers); 3006 3007 version(with_eventloop) { 3008 // delegates event loop to my other module 3009 version(X11) 3010 XFlush(display); 3011 3012 import arsd.eventloop; 3013 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 3014 scope(exit) clearInterval(handle); 3015 3016 loop(); 3017 return 0; 3018 } else version(OSXCocoa) { 3019 // FIXME 3020 if (handlePulse !is null && pulseTimeout != 0) { 3021 timer = NSTimer.schedule(pulseTimeout*1e-3, 3022 cast(NSid) view, sel_registerName("simpledisplay_pulse:"), 3023 null, true); 3024 } 3025 3026 view.setNeedsDisplay(true); 3027 3028 NSApp.run(); 3029 return 0; 3030 } else { 3031 EventLoop el = EventLoop(pulseTimeout, handlePulse); 3032 3033 if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1) 3034 return 0; 3035 3036 return el.run( 3037 ((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ? 3038 null : 3039 &this.notClosed 3040 ); 3041 } 3042 } 3043 3044 /++ 3045 This lets you draw on the window (or its backing buffer) using basic 3046 2D primitives. 3047 3048 Be sure to call this in a limited scope because your changes will not 3049 actually appear on the window until ScreenPainter's destructor runs. 3050 3051 Returns: an instance of [ScreenPainter], which has the drawing methods 3052 on it to draw on this window. 3053 3054 Params: 3055 manualInvalidations = if you set this to true, you will need to 3056 set the invalid rectangle on the painter yourself. If false, it 3057 assumes the whole window has been redrawn each time you draw. 3058 3059 Only invalidated rectangles are blitted back to the window when 3060 the destructor runs. Doing this yourself can reduce flickering 3061 of child windows. 3062 3063 History: 3064 The `manualInvalidations` parameter overload was added on 3065 December 30, 2021 (dub v10.5) 3066 +/ 3067 ScreenPainter draw() { 3068 return draw(false); 3069 } 3070 /// ditto 3071 ScreenPainter draw(bool manualInvalidations) { 3072 return impl.getPainter(manualInvalidations); 3073 } 3074 3075 // This is here to implement the interface we use for various native handlers. 3076 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 3077 3078 // maps native window handles to SimpleWindow instances, if there are any 3079 // you shouldn't need this, but it is public in case you do in a native event handler or something 3080 // mac uses void* cuz NSObject opHash won't pick up in typeinfo 3081 version(OSXCocoa) 3082 public __gshared SimpleWindow[void*] nativeMapping; 3083 else 3084 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 3085 3086 // the size the user requested in the constructor, in automatic scale modes it always pretends to be this size 3087 private int _virtualWidth; 3088 private int _virtualHeight; 3089 3090 /// Width of the window's drawable client area, in pixels. 3091 @scriptable 3092 final @property int width() const pure nothrow @safe @nogc { 3093 if(resizability == Resizability.automaticallyScaleIfPossible) 3094 return _virtualWidth; 3095 else 3096 return _width; 3097 } 3098 3099 /// Height of the window's drawable client area, in pixels. 3100 @scriptable 3101 final @property int height() const pure nothrow @safe @nogc { 3102 if(resizability == Resizability.automaticallyScaleIfPossible) 3103 return _virtualHeight; 3104 else 3105 return _height; 3106 } 3107 3108 /++ 3109 Returns the actual size of the window, bypassing the logical 3110 illusions of [Resizability.automaticallyScaleIfPossible]. 3111 3112 History: 3113 Added November 11, 2022 (dub v10.10) 3114 +/ 3115 final @property Size actualWindowSize() const pure nothrow @safe @nogc { 3116 return Size(_width, _height); 3117 } 3118 3119 3120 private int _width; 3121 private int _height; 3122 3123 // HACK: making the best of some copy constructor woes with refcounting 3124 private ScreenPainterImplementation* activeScreenPainter_; 3125 3126 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 3127 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 3128 3129 private OpenGlOptions openglMode; 3130 private Resizability resizability; 3131 private WindowTypes windowType; 3132 private int customizationFlags; 3133 3134 /// `true` if OpenGL was initialized for this window. 3135 @property bool isOpenGL () const pure nothrow @safe @nogc { 3136 version(without_opengl) 3137 return false; 3138 else 3139 return (openglMode == OpenGlOptions.yes); 3140 } 3141 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 3142 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 3143 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 3144 3145 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 3146 /// to call this, as it's not recommended to share window between threads. 3147 void mtLock () { 3148 version(X11) { 3149 XLockDisplay(this.display); 3150 } 3151 } 3152 3153 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 3154 /// to call this, as it's not recommended to share window between threads. 3155 void mtUnlock () { 3156 version(X11) { 3157 XUnlockDisplay(this.display); 3158 } 3159 } 3160 3161 /// Emit a beep to get user's attention. 3162 void beep () { 3163 version(X11) { 3164 XBell(this.display, 100); 3165 } else version(Windows) { 3166 MessageBeep(0xFFFFFFFF); 3167 } 3168 } 3169 3170 3171 3172 version(without_opengl) {} else { 3173 3174 /// 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`. 3175 void delegate() redrawOpenGlScene; 3176 3177 /// This will allow you to change OpenGL vsync state. 3178 final @property void vsync (bool wait) { 3179 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3180 version(X11) { 3181 setAsCurrentOpenGlContext(); 3182 glxSetVSync(display, impl.window, wait); 3183 } else version(Windows) { 3184 setAsCurrentOpenGlContext(); 3185 wglSetVSync(wait); 3186 } 3187 } 3188 3189 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 3190 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 3191 /// enough without waiting 'em to finish their frame business. 3192 bool useGLFinish = true; 3193 3194 // FIXME: it should schedule it for the end of the current iteration of the event loop... 3195 /// 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. 3196 void redrawOpenGlSceneNow() { 3197 version(X11) if (!this._visible) return; // no need to do this if window is invisible 3198 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3199 if(redrawOpenGlScene is null) 3200 return; 3201 3202 this.mtLock(); 3203 scope(exit) this.mtUnlock(); 3204 3205 this.setAsCurrentOpenGlContext(); 3206 3207 redrawOpenGlScene(); 3208 3209 this.swapOpenGlBuffers(); 3210 // 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. 3211 if (useGLFinish) glFinish(); 3212 } 3213 3214 private bool redrawOpenGlSceneSoonSet = false; 3215 private static class RedrawOpenGlSceneEvent { 3216 SimpleWindow w; 3217 this(SimpleWindow w) { this.w = w; } 3218 } 3219 private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent; 3220 /++ 3221 Queues an opengl redraw as soon as the other pending events are cleared. 3222 +/ 3223 void redrawOpenGlSceneSoon() { 3224 if(redrawOpenGlScene is null) 3225 return; 3226 3227 if(!redrawOpenGlSceneSoonSet) { 3228 redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this); 3229 this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); }); 3230 redrawOpenGlSceneSoonSet = true; 3231 } 3232 this.postEvent(redrawOpenGlSceneEvent, true); 3233 } 3234 3235 3236 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3237 void setAsCurrentOpenGlContext() { 3238 assert(openglMode == OpenGlOptions.yes); 3239 version(X11) { 3240 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 3241 throw new Exception("glXMakeCurrent"); 3242 } else version(Windows) { 3243 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3244 if (!wglMakeCurrent(ghDC, ghRC)) 3245 throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too 3246 } 3247 } 3248 3249 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3250 /// This doesn't throw, returning success flag instead. 3251 bool setAsCurrentOpenGlContextNT() nothrow { 3252 assert(openglMode == OpenGlOptions.yes); 3253 version(X11) { 3254 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 3255 } else version(Windows) { 3256 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3257 return wglMakeCurrent(ghDC, ghRC) ? true : false; 3258 } 3259 } 3260 3261 /// 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. 3262 /// This doesn't throw, returning success flag instead. 3263 bool releaseCurrentOpenGlContext() nothrow { 3264 assert(openglMode == OpenGlOptions.yes); 3265 version(X11) { 3266 return (glXMakeCurrent(display, 0, null) != 0); 3267 } else version(Windows) { 3268 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3269 return wglMakeCurrent(ghDC, null) ? true : false; 3270 } 3271 } 3272 3273 /++ 3274 simpledisplay always uses double buffering, usually automatically. This 3275 manually swaps the OpenGL buffers. You should only use this if you are NOT 3276 using the [redrawOpenGlScene] delegate. 3277 3278 3279 You must not this yourself if you use [redrawOpenGlScene] because simpledisplay will do it 3280 for you after calling your `redrawOpenGlScene`. Please note that once you swap 3281 buffers, the contents become undefined - the implementation, in the OpenGL driver 3282 or the desktop compositor, may not actually just swap two buffers. The back buffer's 3283 contents are $(B undefined) after calling this function. 3284 3285 See: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-swapbuffers 3286 and https://linux.die.net/man/3/glxswapbuffers 3287 3288 Remember that this may throw an exception, which you can catch in a multithreaded 3289 application to keep your thread from dying from an unhandled exception. 3290 +/ 3291 void swapOpenGlBuffers() { 3292 assert(openglMode == OpenGlOptions.yes); 3293 version(X11) { 3294 if (!this._visible) return; // no need to do this if window is invisible 3295 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3296 glXSwapBuffers(display, impl.window); 3297 } else version(Windows) { 3298 SwapBuffers(ghDC); 3299 } 3300 } 3301 } 3302 3303 /++ 3304 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 3305 3306 3307 --- 3308 auto window = new SimpleWindow(100, 100, "First title"); 3309 window.title = "A new title"; 3310 --- 3311 3312 You may call this function at any time. 3313 +/ 3314 @property void title(string title) { 3315 _title = title; 3316 version(OSXCocoa) throw new NotYetImplementedException(); else 3317 impl.setTitle(title); 3318 } 3319 3320 private string _title; 3321 3322 /// Gets the title 3323 @property string title() { 3324 if(_title is null) 3325 _title = getRealTitle(); 3326 return _title; 3327 } 3328 3329 /++ 3330 Get the title as set by the window manager. 3331 May not match what you attempted to set. 3332 +/ 3333 string getRealTitle() { 3334 static if(is(typeof(impl.getTitle()))) 3335 return impl.getTitle(); 3336 else 3337 return null; 3338 } 3339 3340 // don't use this generally it is not yet really released 3341 version(X11) 3342 @property Image secret_icon() { 3343 return secret_icon_inner; 3344 } 3345 private Image secret_icon_inner; 3346 3347 3348 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing. 3349 @property void icon(MemoryImage icon) { 3350 if(icon is null) 3351 return; 3352 auto tci = icon.getAsTrueColorImage(); 3353 version(Windows) { 3354 winIcon = new WindowsIcon(icon); 3355 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 3356 } else version(X11) { 3357 secret_icon_inner = Image.fromMemoryImage(icon); 3358 // FIXME: ensure this is correct 3359 auto display = XDisplayConnection.get; 3360 arch_ulong[] buffer; 3361 buffer ~= icon.width; 3362 buffer ~= icon.height; 3363 foreach(c; tci.imageData.colors) { 3364 arch_ulong b; 3365 b |= c.a << 24; 3366 b |= c.r << 16; 3367 b |= c.g << 8; 3368 b |= c.b; 3369 buffer ~= b; 3370 } 3371 3372 XChangeProperty( 3373 display, 3374 impl.window, 3375 GetAtom!("_NET_WM_ICON", true)(display), 3376 GetAtom!"CARDINAL"(display), 3377 32 /* bits */, 3378 0 /*PropModeReplace*/, 3379 buffer.ptr, 3380 cast(int) buffer.length); 3381 } else version(OSXCocoa) { 3382 throw new NotYetImplementedException(); 3383 } else static assert(0); 3384 } 3385 3386 version(Windows) 3387 private WindowsIcon winIcon; 3388 3389 bool _suppressDestruction; 3390 3391 ~this() { 3392 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 3393 if(_suppressDestruction) 3394 return; 3395 impl.dispose(); 3396 } 3397 3398 private bool _closed; 3399 3400 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 3401 /* 3402 ScreenPainter drawTransiently() { 3403 return impl.getPainter(); 3404 } 3405 */ 3406 3407 /// Draws an image on the window. This is meant to provide quick look 3408 /// of a static image generated elsewhere. 3409 @property void image(Image i) { 3410 /+ 3411 version(Windows) { 3412 BITMAP bm; 3413 HDC hdc = GetDC(hwnd); 3414 HDC hdcMem = CreateCompatibleDC(hdc); 3415 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 3416 3417 GetObject(i.handle, bm.sizeof, &bm); 3418 3419 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 3420 3421 SelectObject(hdcMem, hbmOld); 3422 DeleteDC(hdcMem); 3423 ReleaseDC(hwnd, hdc); 3424 3425 /* 3426 RECT r; 3427 r.right = i.width; 3428 r.bottom = i.height; 3429 InvalidateRect(hwnd, &r, false); 3430 */ 3431 } else 3432 version(X11) { 3433 if(!destroyed) { 3434 if(i.usingXshm) 3435 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 3436 else 3437 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 3438 } 3439 } else 3440 version(OSXCocoa) { 3441 draw().drawImage(Point(0, 0), i); 3442 setNeedsDisplay(view, true); 3443 } else static assert(0); 3444 +/ 3445 auto painter = this.draw; 3446 painter.drawImage(Point(0, 0), i); 3447 } 3448 3449 /++ 3450 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 3451 3452 --- 3453 window.cursor = GenericCursor.Help; 3454 // now the window mouse cursor is set to a generic help 3455 --- 3456 3457 +/ 3458 @property void cursor(MouseCursor cursor) { 3459 version(OSXCocoa) 3460 {} // featureNotImplemented(); 3461 else 3462 if(this.impl.curHidden <= 0) { 3463 static if(UsingSimpledisplayX11) { 3464 auto ch = cursor.cursorHandle; 3465 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 3466 } else version(Windows) { 3467 auto ch = cursor.cursorHandle; 3468 impl.currentCursor = ch; 3469 SetCursor(ch); // redraw without waiting for mouse movement to update 3470 } else featureNotImplemented(); 3471 } 3472 3473 } 3474 3475 /// What follows are the event handlers. These are set automatically 3476 /// by the eventLoop function, but are still public so you can change 3477 /// them later. wasPressed == true means key down. false == key up. 3478 3479 /// Handles a low-level keyboard event. Settable through setEventHandlers. 3480 void delegate(KeyEvent ke) handleKeyEvent; 3481 3482 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 3483 void delegate(dchar c) handleCharEvent; 3484 3485 /// Handles a timer pulse. Settable through setEventHandlers. 3486 void delegate() handlePulse; 3487 3488 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 3489 void delegate(bool) onFocusChange; 3490 3491 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 3492 * Sometimes it is easier to setup the delegate instead of subclassing. */ 3493 void delegate() onClosing; 3494 3495 /** Called when we received destroy notification. At this stage we cannot do much with our window 3496 * (as it is already dead, and it's native handle cannot be used), but we still can do some 3497 * last minute cleanup. */ 3498 void delegate() onDestroyed; 3499 3500 static if (UsingSimpledisplayX11) 3501 /** Called when Expose event comes. See Xlib manual to understand the arguments. 3502 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 3503 * You will probably never need to setup this handler, it is for very low-level stuff. 3504 * 3505 * WARNING! Xlib is multithread-locked when this handles is called! */ 3506 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 3507 3508 //version(Windows) 3509 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 3510 3511 private { 3512 int lastMouseX = int.min; 3513 int lastMouseY = int.min; 3514 void mdx(ref MouseEvent ev) { 3515 if(lastMouseX == int.min || lastMouseY == int.min) { 3516 ev.dx = 0; 3517 ev.dy = 0; 3518 } else { 3519 ev.dx = ev.x - lastMouseX; 3520 ev.dy = ev.y - lastMouseY; 3521 } 3522 3523 lastMouseX = ev.x; 3524 lastMouseY = ev.y; 3525 } 3526 } 3527 3528 /// Mouse event handler. Settable through setEventHandlers. 3529 void delegate(MouseEvent) handleMouseEvent; 3530 3531 /// use to redraw child widgets if you use system apis to add stuff 3532 void delegate() paintingFinished; 3533 3534 void delegate() paintingFinishedDg() { 3535 return paintingFinished; 3536 } 3537 3538 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 3539 /// for this to ever happen. 3540 void delegate(int width, int height) windowResized; 3541 3542 /++ 3543 Platform specific - handle any native message this window gets. 3544 3545 Note: this is called *in addition to* other event handlers, unless you either: 3546 3547 1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded. 3548 3549 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. 3550 3551 On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`. 3552 3553 On X, it takes the form of `int delegate(XEvent)`. 3554 3555 History: 3556 In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead. 3557 3558 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. 3559 +/ 3560 NativeEventHandler handleNativeEvent_; 3561 3562 @property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe { 3563 return handleNativeEvent_; 3564 } 3565 @property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe { 3566 handleNativeEvent_ = neh; 3567 } 3568 3569 version(Windows) 3570 // compatibility shim with the old deprecated way 3571 // in this one, if you return 0, it means you must return. otherwise the ret value is ignored. 3572 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) { 3573 handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) { 3574 auto ret = dg(h, m, w, l); 3575 if(ret == 0) 3576 r = 1; 3577 return ret; 3578 }; 3579 } 3580 3581 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 3582 /// If you used to use handleNativeEvent depending on it being static, just change it to use 3583 /// this instead and it will work the same way. 3584 __gshared NativeEventHandler handleNativeGlobalEvent; 3585 3586 // private: 3587 /// The native implementation is available, but you shouldn't use it unless you are 3588 /// familiar with the underlying operating system, don't mind depending on it, and 3589 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 3590 /// do what you need to do with handleNativeEvent instead. 3591 /// 3592 /// This is likely to eventually change to be just a struct holding platform-specific 3593 /// handles instead of a template mixin at some point because I'm not happy with the 3594 /// code duplication here (ironically). 3595 mixin NativeSimpleWindowImplementation!() impl; 3596 3597 /** 3598 This is in-process one-way (from anything to window) event sending mechanics. 3599 It is thread-safe, so it can be used in multi-threaded applications to send, 3600 for example, "wake up and repaint" events when thread completed some operation. 3601 This will allow to avoid using timer pulse to check events with synchronization, 3602 'cause event handler will be called in UI thread. You can stop guessing which 3603 pulse frequency will be enough for your app. 3604 Note that events handlers may be called in arbitrary order, i.e. last registered 3605 handler can be called first, and vice versa. 3606 */ 3607 public: 3608 /** Is our custom event queue empty? Can be used in simple cases to prevent 3609 * "spamming" window with events it can't cope with. 3610 * It is safe to call this from non-UI threads. 3611 */ 3612 @property bool eventQueueEmpty() () { 3613 synchronized(this) { 3614 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 3615 } 3616 return true; 3617 } 3618 3619 /** Does our custom event queue contains at least one with the given type? 3620 * Can be used in simple cases to prevent "spamming" window with events 3621 * it can't cope with. 3622 * It is safe to call this from non-UI threads. 3623 */ 3624 @property bool eventQueued(ET:Object) () { 3625 synchronized(this) { 3626 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 3627 if (!o.doProcess) { 3628 if (cast(ET)(o.evt)) return true; 3629 } 3630 } 3631 } 3632 return false; 3633 } 3634 3635 /++ 3636 Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds. 3637 3638 History: 3639 Added May 12, 2021 3640 +/ 3641 void delegate(Exception e) nothrow eventUncaughtException; 3642 3643 /** Add listener for custom event. Can be used like this: 3644 * 3645 * --------------------- 3646 * auto eid = win.addEventListener((MyStruct evt) { ... }); 3647 * ... 3648 * win.removeEventListener(eid); 3649 * --------------------- 3650 * 3651 * Returns: 0 on failure (should never happen, so ignore it) 3652 * 3653 * $(WARNING Don't use this method in object destructors!) 3654 * 3655 * $(WARNING It is better to register all event handlers and don't remove 'em, 3656 * 'cause if event handler id counter will overflow, you won't be able 3657 * to register any more events.) 3658 */ 3659 uint addEventListener(ET:Object) (void delegate (ET) dg) { 3660 if (dg is null) return 0; // ignore empty handlers 3661 synchronized(this) { 3662 //FIXME: abort on overflow? 3663 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 3664 EventHandlerEntry e; 3665 e.dg = delegate (Object o) { 3666 if (auto co = cast(ET)o) { 3667 try { 3668 dg(co); 3669 } catch (Exception e) { 3670 // sorry! 3671 if(eventUncaughtException) 3672 eventUncaughtException(e); 3673 } 3674 return true; 3675 } 3676 return false; 3677 }; 3678 e.id = lastUsedHandlerId; 3679 auto optr = eventHandlers.ptr; 3680 eventHandlers ~= e; 3681 if (eventHandlers.ptr !is optr) { 3682 import core.memory : GC; 3683 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 3684 } 3685 return lastUsedHandlerId; 3686 } 3687 } 3688 3689 /// Remove event listener. It is safe to pass invalid event id here. 3690 /// $(WARNING Don't use this method in object destructors!) 3691 void removeEventListener() (uint id) { 3692 if (id == 0 || id > lastUsedHandlerId) return; 3693 synchronized(this) { 3694 foreach (immutable idx; 0..eventHandlers.length) { 3695 if (eventHandlers[idx].id == id) { 3696 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 3697 eventHandlers[$-1].dg = null; 3698 eventHandlers.length -= 1; 3699 eventHandlers.assumeSafeAppend; 3700 return; 3701 } 3702 } 3703 } 3704 } 3705 3706 /// Post event to queue. It is safe to call this from non-UI threads. 3707 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 3708 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3709 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3710 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) { 3711 if (this.closed) return false; // closed windows can't handle events 3712 3713 // remove all events of type `ET` 3714 void removeAllET () { 3715 uint eidx = 0, ec = eventQueueUsed; 3716 auto eptr = eventQueue.ptr; 3717 while (eidx < ec) { 3718 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 3719 if (cast(ET)eptr.evt !is null) { 3720 // i found her! 3721 if (inCustomEventProcessor) { 3722 // if we're in custom event processing loop, processor will clear it for us 3723 eptr.evt = null; 3724 ++eidx; 3725 ++eptr; 3726 } else { 3727 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3728 ec = --eventQueueUsed; 3729 // clear last event (it is already copied) 3730 eventQueue.ptr[ec].evt = null; 3731 } 3732 } else { 3733 ++eidx; 3734 ++eptr; 3735 } 3736 } 3737 } 3738 3739 if (evt is null) { 3740 if (replace) { synchronized(this) removeAllET(); } 3741 // ignore empty events, they can't be handled anyway 3742 return false; 3743 } 3744 3745 // add events even if no event FD/event object created yet 3746 synchronized(this) { 3747 if (replace) removeAllET(); 3748 if (eventQueueUsed == uint.max) return false; // just in case 3749 if (eventQueueUsed < eventQueue.length) { 3750 eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs); 3751 } else { 3752 if (eventQueue.capacity == eventQueue.length) { 3753 // need to reallocate; do a trick to ensure that old array is cleared 3754 auto oarr = eventQueue; 3755 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3756 // just in case, do yet another check 3757 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 3758 import core.memory : GC; 3759 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 3760 } else { 3761 auto optr = eventQueue.ptr; 3762 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3763 assert(eventQueue.ptr is optr); 3764 } 3765 ++eventQueueUsed; 3766 assert(eventQueueUsed == eventQueue.length); 3767 } 3768 if (!eventWakeUp()) { 3769 // can't wake up event processor, so there is no reason to keep the event 3770 assert(eventQueueUsed > 0); 3771 eventQueue[--eventQueueUsed].evt = null; 3772 return false; 3773 } 3774 return true; 3775 } 3776 } 3777 3778 /// Post event to queue. It is safe to call this from non-UI threads. 3779 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3780 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3781 bool postEvent(ET:Object) (ET evt, bool replace=false) { 3782 return postTimeout!ET(evt, 0, replace); 3783 } 3784 3785 private: 3786 private import core.time : MonoTime; 3787 3788 version(Posix) { 3789 __gshared int customEventFDRead = -1; 3790 __gshared int customEventFDWrite = -1; 3791 __gshared int customSignalFD = -1; 3792 } else version(Windows) { 3793 __gshared HANDLE customEventH = null; 3794 } 3795 3796 // wake up event processor 3797 static bool eventWakeUp () { 3798 version(X11) { 3799 import core.sys.posix.unistd : write; 3800 ulong n = 1; 3801 if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof); 3802 return true; 3803 } else version(Windows) { 3804 if (customEventH !is null) SetEvent(customEventH); 3805 return true; 3806 } else version(OSXCocoa) { 3807 if(globalAppDelegate) 3808 globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false); 3809 return true; 3810 } else { 3811 // not implemented for other OSes 3812 return false; 3813 } 3814 } 3815 3816 static struct QueuedEvent { 3817 Object evt; 3818 bool timed = false; 3819 MonoTime hittime = MonoTime.zero; 3820 bool doProcess = false; // process event at the current iteration (internal flag) 3821 3822 this (Object aevt, uint toutmsecs) { 3823 evt = aevt; 3824 if (toutmsecs > 0) { 3825 import core.time : msecs; 3826 timed = true; 3827 hittime = MonoTime.currTime+toutmsecs.msecs; 3828 } 3829 } 3830 } 3831 3832 alias CustomEventHandler = bool delegate (Object o) nothrow; 3833 static struct EventHandlerEntry { 3834 CustomEventHandler dg; 3835 uint id; 3836 } 3837 3838 uint lastUsedHandlerId; 3839 EventHandlerEntry[] eventHandlers; 3840 QueuedEvent[] eventQueue = null; 3841 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 3842 bool inCustomEventProcessor = false; // required to properly remove events 3843 3844 // process queued events and call custom event handlers 3845 // this will not process events posted from called handlers (such events are postponed for the next iteration) 3846 void processCustomEvents () @system { 3847 bool hasSomethingToDo = false; 3848 uint ecount; 3849 bool ocep; 3850 synchronized(this) { 3851 ocep = inCustomEventProcessor; 3852 inCustomEventProcessor = true; 3853 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 3854 auto ctt = MonoTime.currTime; 3855 bool hasEmpty = false; 3856 // mark events to process (this is required for `eventQueued()`) 3857 foreach (ref qe; eventQueue[0..ecount]) { 3858 if (qe.evt is null) { hasEmpty = true; continue; } 3859 if (qe.timed) { 3860 qe.doProcess = (qe.hittime <= ctt); 3861 } else { 3862 qe.doProcess = true; 3863 } 3864 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 3865 } 3866 if (!hasSomethingToDo) { 3867 // remove empty events 3868 if (hasEmpty) { 3869 uint eidx = 0, ec = eventQueueUsed; 3870 auto eptr = eventQueue.ptr; 3871 while (eidx < ec) { 3872 if (eptr.evt is null) { 3873 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3874 ec = --eventQueueUsed; 3875 eventQueue.ptr[ec].evt = null; // make GC life easier 3876 } else { 3877 ++eidx; 3878 ++eptr; 3879 } 3880 } 3881 } 3882 inCustomEventProcessor = ocep; 3883 return; 3884 } 3885 } 3886 // process marked events 3887 uint efree = 0; // non-processed events will be put at this index 3888 EventHandlerEntry[] eh; 3889 Object evt; 3890 foreach (immutable eidx; 0..ecount) { 3891 synchronized(this) { 3892 if (!eventQueue[eidx].doProcess) { 3893 // skip this event 3894 assert(efree <= eidx); 3895 if (efree != eidx) { 3896 // copy this event to queue start 3897 eventQueue[efree] = eventQueue[eidx]; 3898 eventQueue[eidx].evt = null; // just in case 3899 } 3900 ++efree; 3901 continue; 3902 } 3903 evt = eventQueue[eidx].evt; 3904 eventQueue[eidx].evt = null; // in case event handler will hit GC 3905 if (evt is null) continue; // just in case 3906 // try all handlers; this can be slow, but meh... 3907 eh = eventHandlers; 3908 } 3909 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 3910 evt = null; 3911 eh = null; 3912 } 3913 synchronized(this) { 3914 // move all unprocessed events to queue top; efree holds first "free index" 3915 foreach (immutable eidx; ecount..eventQueueUsed) { 3916 assert(efree <= eidx); 3917 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 3918 ++efree; 3919 } 3920 eventQueueUsed = efree; 3921 // wake up event processor on next event loop iteration if we have more queued events 3922 // also, remove empty events 3923 bool awaken = false; 3924 uint eidx = 0, ec = eventQueueUsed; 3925 auto eptr = eventQueue.ptr; 3926 while (eidx < ec) { 3927 if (eptr.evt is null) { 3928 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3929 ec = --eventQueueUsed; 3930 eventQueue.ptr[ec].evt = null; // make GC life easier 3931 } else { 3932 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 3933 ++eidx; 3934 ++eptr; 3935 } 3936 } 3937 inCustomEventProcessor = ocep; 3938 } 3939 } 3940 3941 // for all windows in nativeMapping 3942 package static void processAllCustomEvents () @system { 3943 3944 cleanupQueue.process(); 3945 3946 justCommunication.processCustomEvents(); 3947 3948 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3949 if (sw is null || sw.closed) continue; 3950 sw.processCustomEvents(); 3951 } 3952 3953 runPendingRunInGuiThreadDelegates(); 3954 } 3955 3956 // 0: infinite (i.e. no scheduled events in queue) 3957 uint eventQueueTimeoutMSecs () { 3958 synchronized(this) { 3959 if (eventQueueUsed == 0) return 0; 3960 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3961 uint res = int.max; 3962 auto ctt = MonoTime.currTime; 3963 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 3964 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3965 if (qe.doProcess) continue; // just in case 3966 if (!qe.timed) return 1; // minimal 3967 if (qe.hittime <= ctt) return 1; // minimal 3968 auto tms = (qe.hittime-ctt).total!"msecs"; 3969 if (tms < 1) tms = 1; // safety net 3970 if (tms >= int.max) tms = int.max-1; // and another safety net 3971 if (res > tms) res = cast(uint)tms; 3972 } 3973 return (res >= int.max ? 0 : res); 3974 } 3975 } 3976 3977 // for all windows in nativeMapping 3978 static uint eventAllQueueTimeoutMSecs () { 3979 uint res = uint.max; 3980 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3981 if (sw is null || sw.closed) continue; 3982 uint to = sw.eventQueueTimeoutMSecs(); 3983 if (to && to < res) { 3984 res = to; 3985 if (to == 1) break; // can't have less than this 3986 } 3987 } 3988 return (res >= int.max ? 0 : res); 3989 } 3990 3991 version(X11) { 3992 ResizeEvent pendingResizeEvent; 3993 } 3994 3995 /++ 3996 When in opengl mode and automatically resizing, it will set the opengl viewport to stretch. 3997 3998 If you work with multiple opengl contexts and/or threads, this might be more trouble than it is 3999 worth so you can disable it by setting this to `true`. 4000 4001 History: 4002 Added November 13, 2022. 4003 +/ 4004 public bool suppressAutoOpenglViewport = false; 4005 private void updateOpenglViewportIfNeeded(int width, int height) { 4006 if(suppressAutoOpenglViewport) return; 4007 4008 version(without_opengl) {} else 4009 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 4010 // writeln(width, " ", height); 4011 setAsCurrentOpenGlContextNT(); 4012 glViewport(0, 0, width, height); 4013 } 4014 } 4015 4016 // TODO: Implement on non-Windows platforms (where available). 4017 private CornerStyle _fauxCornerStyle = CornerStyle.automatic; 4018 4019 /++ 4020 Style of the window's corners 4021 4022 $(WARNING 4023 Currently only implemented on Windows targets. 4024 Has no visual effect elsewhere. 4025 4026 Windows: Requires Windows 11 or later. 4027 ) 4028 4029 History: 4030 Added September 09, 2024. 4031 +/ 4032 public CornerStyle cornerStyle() @trusted { 4033 version(Windows) { 4034 DWM_WINDOW_CORNER_PREFERENCE dwmCorner; 4035 const apiResult = DwmGetWindowAttribute( 4036 this.hwnd, 4037 DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, 4038 &dwmCorner, 4039 typeof(dwmCorner).sizeof 4040 ); 4041 4042 if (apiResult != S_OK) { 4043 // Unsupported? 4044 if (apiResult == E_INVALIDARG) { 4045 // Feature unsupported; Windows version probably too old. 4046 // Requires Windows 11 (build 22000) or later. 4047 return _fauxCornerStyle; 4048 } 4049 4050 throw new WindowsApiException("DwmGetWindowAttribute", apiResult); 4051 } 4052 4053 CornerStyle corner; 4054 if (!dwmCorner.fromDWM(corner)) { 4055 throw ArsdException!"DwmGetWindowAttribute unfamiliar corner preference"(dwmCorner); 4056 } 4057 return corner; 4058 } else { 4059 return _fauxCornerStyle; 4060 } 4061 } 4062 4063 /// ditto 4064 public void cornerStyle(const CornerStyle corner) @trusted { 4065 version(Windows) { 4066 DWM_WINDOW_CORNER_PREFERENCE dwmCorner; 4067 if (!corner.toDWM(dwmCorner)) { 4068 assert(false, "This should have been impossible because of a final switch."); 4069 } 4070 4071 const apiResult = DwmSetWindowAttribute( 4072 this.hwnd, 4073 DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, 4074 &dwmCorner, 4075 typeof(dwmCorner).sizeof 4076 ); 4077 4078 if (apiResult != S_OK) { 4079 // Unsupported? 4080 if (apiResult == E_INVALIDARG) { 4081 // Feature unsupported; Windows version probably too old. 4082 // Requires Windows 11 (build 22000) or later. 4083 _fauxCornerStyle = corner; 4084 return; 4085 } 4086 4087 throw new WindowsApiException("DwmSetWindowAttribute", apiResult); 4088 } 4089 } else { 4090 _fauxCornerStyle = corner; 4091 } 4092 } 4093 } 4094 4095 version(OSXCocoa) 4096 enum NSWindow NullWindow = null; 4097 else 4098 enum NullWindow = NativeWindowHandle.init; 4099 4100 /++ 4101 Magic pseudo-window for just posting events to a global queue. 4102 4103 Not entirely supported, I might delete it at any time. 4104 4105 Added Nov 5, 2021. 4106 +/ 4107 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow); 4108 4109 /* Drag and drop support { */ 4110 version(X11) { 4111 4112 } else version(Windows) { 4113 import core.sys.windows.uuid; 4114 import core.sys.windows.ole2; 4115 import core.sys.windows.oleidl; 4116 import core.sys.windows.objidl; 4117 import core.sys.windows.wtypes; 4118 4119 pragma(lib, "ole32"); 4120 void initDnd() { 4121 auto err = OleInitialize(null); 4122 if(err != S_OK && err != S_FALSE) 4123 throw new Exception("init");//err); 4124 } 4125 } 4126 /* } End drag and drop support */ 4127 4128 4129 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 4130 /// See [GenericCursor]. 4131 class MouseCursor { 4132 int osId; 4133 bool isStockCursor; 4134 private this(int osId) { 4135 this.osId = osId; 4136 this.isStockCursor = true; 4137 } 4138 4139 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 4140 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 4141 4142 version(Windows) { 4143 HCURSOR cursor_; 4144 HCURSOR cursorHandle() { 4145 if(cursor_ is null) 4146 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 4147 return cursor_; 4148 } 4149 4150 } else static if(UsingSimpledisplayX11) { 4151 Cursor cursor_ = None; 4152 int xDisplaySequence; 4153 4154 Cursor cursorHandle() { 4155 if(this.osId == None) 4156 return None; 4157 4158 // we need to reload if we on a new X connection 4159 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 4160 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 4161 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 4162 } 4163 return cursor_; 4164 } 4165 } 4166 } 4167 4168 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 4169 // https://tronche.com/gui/x/xlib/appendix/b/ 4170 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 4171 /// 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. 4172 enum GenericCursorType { 4173 Default, /// The default arrow pointer. 4174 Wait, /// A cursor indicating something is loading and the user must wait. 4175 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 4176 Help, /// A cursor indicating the user can get help about the pointer location. 4177 Cross, /// A crosshair. 4178 Text, /// An i-beam shape, typically used to indicate text selection is possible. 4179 Move, /// Pointer indicating movement is possible. May also be used as SizeAll. 4180 UpArrow, /// An arrow pointing straight up. 4181 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 4182 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 4183 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator). 4184 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator). 4185 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator). 4186 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator). 4187 4188 } 4189 4190 /* 4191 X_plus == css cell == Windows ? 4192 */ 4193 4194 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 4195 static struct GenericCursor { 4196 static: 4197 /// 4198 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 4199 static MouseCursor mc; 4200 4201 auto type = __traits(getMember, GenericCursorType, str); 4202 4203 if(mc is null) { 4204 4205 version(Windows) { 4206 int osId; 4207 final switch(type) { 4208 case GenericCursorType.Default: osId = IDC_ARROW; break; 4209 case GenericCursorType.Wait: osId = IDC_WAIT; break; 4210 case GenericCursorType.Hand: osId = IDC_HAND; break; 4211 case GenericCursorType.Help: osId = IDC_HELP; break; 4212 case GenericCursorType.Cross: osId = IDC_CROSS; break; 4213 case GenericCursorType.Text: osId = IDC_IBEAM; break; 4214 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 4215 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 4216 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 4217 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 4218 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 4219 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 4220 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 4221 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 4222 } 4223 } else static if(UsingSimpledisplayX11) { 4224 int osId; 4225 final switch(type) { 4226 case GenericCursorType.Default: osId = None; break; 4227 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 4228 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 4229 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 4230 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 4231 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 4232 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 4233 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 4234 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 4235 4236 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 4237 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 4238 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 4239 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 4240 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 4241 } 4242 4243 } else { 4244 int osId; 4245 // featureNotImplemented(); 4246 } 4247 4248 mc = new MouseCursor(osId); 4249 } 4250 return mc; 4251 } 4252 } 4253 4254 4255 /++ 4256 If you want to get more control over the event loop, you can use this. 4257 4258 Typically though, you can just call [SimpleWindow.eventLoop] which forwards 4259 to `EventLoop.get.run`. 4260 +/ 4261 struct EventLoop { 4262 @disable this(); 4263 4264 /// Gets a reference to an existing event loop 4265 static EventLoop get() { 4266 return EventLoop(0, null); 4267 } 4268 4269 static void quitApplication() { 4270 version(use_arsd_core) { 4271 import arsd.core; 4272 ICoreEventLoop.exitApplication(); 4273 } 4274 EventLoop.get().exit(); 4275 } 4276 4277 private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi 4278 4279 /// Construct an application-global event loop for yourself 4280 /// See_Also: [SimpleWindow.setEventHandlers] 4281 this(long pulseTimeout, void delegate() handlePulse) { 4282 synchronized(monitor) { 4283 if(impl is null) { 4284 claimGuiThread(); 4285 version(sdpy_thread_checks) assert(thisIsGuiThread); 4286 impl = new EventLoopImpl(pulseTimeout, handlePulse); 4287 } else { 4288 if(pulseTimeout) { 4289 impl.pulseTimeout = pulseTimeout; 4290 impl.handlePulse = handlePulse; 4291 } 4292 } 4293 impl.refcount++; 4294 } 4295 } 4296 4297 ~this() { 4298 if(impl is null) 4299 return; 4300 impl.refcount--; 4301 if(impl.refcount == 0) { 4302 impl.dispose(); 4303 if(thisIsGuiThread) 4304 guiThreadFinalize(); 4305 } 4306 4307 } 4308 4309 this(this) { 4310 if(impl is null) 4311 return; 4312 impl.refcount++; 4313 } 4314 4315 /// Runs the event loop until the whileCondition, if present, returns false 4316 int run(bool delegate() whileCondition = null) { 4317 assert(impl !is null); 4318 impl.notExited = true; 4319 return impl.run(whileCondition); 4320 } 4321 4322 /// Exits the event loop, but allows you to reenter it again later (in contrast with quitApplication, which tries to terminate the program) 4323 void exit() { 4324 assert(impl !is null); 4325 impl.notExited = false; 4326 4327 version(use_arsd_core) { 4328 import arsd.core; 4329 ICoreEventLoop.exitApplication(); 4330 } 4331 } 4332 4333 version(linux) 4334 ref void delegate(int) signalHandler() { 4335 assert(impl !is null); 4336 return impl.signalHandler; 4337 } 4338 4339 __gshared static EventLoopImpl* impl; 4340 } 4341 4342 version(linux) 4343 void delegate(int, int) globalHupHandler; 4344 4345 version(Posix) 4346 void makeNonBlocking(int fd) { 4347 import fcntl = core.sys.posix.fcntl; 4348 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 4349 if(flags == -1) 4350 throw new Exception("fcntl get"); 4351 flags |= fcntl.O_NONBLOCK; 4352 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 4353 if(s == -1) 4354 throw new Exception("fcntl set"); 4355 } 4356 4357 struct EventLoopImpl { 4358 int refcount; 4359 4360 bool notExited = true; 4361 4362 version(linux) { 4363 static import ep = core.sys.linux.epoll; 4364 static import unix = core.sys.posix.unistd; 4365 static import err = core.stdc.errno; 4366 import core.sys.linux.timerfd; 4367 4368 void delegate(int) signalHandler; 4369 } 4370 4371 version(X11) { 4372 int pulseFd = -1; 4373 version(linux) ep.epoll_event[16] events = void; 4374 } else version(Windows) { 4375 Timer pulser; 4376 HANDLE[] handles; 4377 } 4378 4379 4380 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 4381 /// to call this, as it's not recommended to share window between threads. 4382 void mtLock () { 4383 version(X11) { 4384 XLockDisplay(this.display); 4385 } 4386 } 4387 4388 version(X11) 4389 auto display() { return XDisplayConnection.get; } 4390 4391 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 4392 /// to call this, as it's not recommended to share window between threads. 4393 void mtUnlock () { 4394 version(X11) { 4395 XUnlockDisplay(this.display); 4396 } 4397 } 4398 4399 version(with_eventloop) 4400 void initialize(long pulseTimeout) {} 4401 else 4402 void initialize(long pulseTimeout) @system { 4403 version(Windows) { 4404 if(pulseTimeout && handlePulse !is null) 4405 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 4406 4407 if (customEventH is null) { 4408 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 4409 if (customEventH !is null) { 4410 handles ~= customEventH; 4411 } else { 4412 // this is something that should not be; better be safe than sorry 4413 throw new Exception("can't create eventfd for custom event processing"); 4414 } 4415 } 4416 4417 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 4418 } 4419 4420 version(linux) { 4421 prepareEventLoop(); 4422 { 4423 auto display = XDisplayConnection.get; 4424 // adding Xlib file 4425 ep.epoll_event ev = void; 4426 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4427 ev.events = ep.EPOLLIN; 4428 ev.data.fd = display.fd; 4429 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 4430 throw new Exception("add x fd");// ~ to!string(epollFd)); 4431 displayFd = display.fd; 4432 } 4433 4434 if(pulseTimeout && handlePulse !is null) { 4435 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 4436 if(pulseFd == -1) 4437 throw new Exception("pulse timer create failed"); 4438 4439 itimerspec value; 4440 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 4441 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4442 4443 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 4444 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4445 4446 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 4447 throw new Exception("couldn't make pulse timer"); 4448 4449 ep.epoll_event ev = void; 4450 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4451 ev.events = ep.EPOLLIN; 4452 ev.data.fd = pulseFd; 4453 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 4454 } 4455 4456 // eventfd for custom events 4457 if (customEventFDWrite == -1) { 4458 customEventFDWrite = eventfd(0, 0); 4459 customEventFDRead = customEventFDWrite; 4460 if (customEventFDRead >= 0) { 4461 ep.epoll_event ev = void; 4462 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4463 ev.events = ep.EPOLLIN; 4464 ev.data.fd = customEventFDRead; 4465 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev); 4466 } else { 4467 // this is something that should not be; better be safe than sorry 4468 throw new Exception("can't create eventfd for custom event processing"); 4469 } 4470 } 4471 4472 if (customSignalFD == -1) { 4473 import core.sys.linux.sys.signalfd; 4474 4475 sigset_t sigset; 4476 auto err = sigemptyset(&sigset); 4477 assert(!err); 4478 err = sigaddset(&sigset, SIGINT); 4479 assert(!err); 4480 err = sigaddset(&sigset, SIGHUP); 4481 assert(!err); 4482 err = sigprocmask(SIG_BLOCK, &sigset, null); 4483 assert(!err); 4484 4485 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 4486 assert(customSignalFD != -1); 4487 4488 ep.epoll_event ev = void; 4489 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4490 ev.events = ep.EPOLLIN; 4491 ev.data.fd = customSignalFD; 4492 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 4493 } 4494 } else version(Posix) { 4495 prepareEventLoop(); 4496 if (customEventFDRead == -1) { 4497 int[2] bfr; 4498 import core.sys.posix.unistd; 4499 auto ret = pipe(bfr); 4500 if(ret == -1) throw new Exception("pipe"); 4501 customEventFDRead = bfr[0]; 4502 customEventFDWrite = bfr[1]; 4503 } 4504 4505 } 4506 4507 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 4508 4509 version(linux) { 4510 this.mtLock(); 4511 scope(exit) this.mtUnlock(); 4512 XPending(display); // no, really 4513 } 4514 4515 disposed = false; 4516 } 4517 4518 bool disposed = true; 4519 version(X11) 4520 int displayFd = -1; 4521 4522 version(with_eventloop) 4523 void dispose() {} 4524 else 4525 void dispose() @system { 4526 disposed = true; 4527 version(X11) { 4528 if(pulseFd != -1) { 4529 import unix = core.sys.posix.unistd; 4530 unix.close(pulseFd); 4531 pulseFd = -1; 4532 } 4533 4534 version(linux) 4535 if(displayFd != -1) { 4536 // 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 4537 ep.epoll_event ev = void; 4538 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4539 ev.events = ep.EPOLLIN; 4540 ev.data.fd = displayFd; 4541 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 4542 displayFd = -1; 4543 } 4544 4545 } else version(Windows) { 4546 if(pulser !is null) { 4547 pulser.destroy(); 4548 pulser = null; 4549 } 4550 if (customEventH !is null) { 4551 CloseHandle(customEventH); 4552 customEventH = null; 4553 } 4554 } 4555 } 4556 4557 this(long pulseTimeout, void delegate() handlePulse) { 4558 this.pulseTimeout = pulseTimeout; 4559 this.handlePulse = handlePulse; 4560 initialize(pulseTimeout); 4561 } 4562 4563 private long pulseTimeout; 4564 void delegate() handlePulse; 4565 4566 ~this() { 4567 dispose(); 4568 } 4569 4570 version(Posix) 4571 ref int customEventFDRead() { return SimpleWindow.customEventFDRead; } 4572 version(Posix) 4573 ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; } 4574 version(linux) 4575 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 4576 version(Windows) 4577 ref auto customEventH() { return SimpleWindow.customEventH; } 4578 4579 version(X11) { 4580 bool doXPending() { 4581 bool done = false; 4582 4583 this.mtLock(); 4584 scope(exit) this.mtUnlock(); 4585 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 4586 while(!done && XPending(display)) { 4587 done = doXNextEvent(this.display); 4588 } 4589 4590 return done; 4591 } 4592 void doXNextEventVoid() { 4593 doXPending(); 4594 } 4595 } 4596 4597 version(with_eventloop) { 4598 int loopHelper(bool delegate() whileCondition) { 4599 // FIXME: whileCondition 4600 import arsd.eventloop; 4601 loop(); 4602 return 0; 4603 } 4604 } else 4605 int loopHelper(bool delegate() whileCondition) { 4606 version(X11) { 4607 bool done = false; 4608 4609 XFlush(display); 4610 insideXEventLoop = true; 4611 scope(exit) insideXEventLoop = false; 4612 4613 version(use_arsd_core) { 4614 import arsd.core; 4615 auto el = getThisThreadEventLoop(EventLoopType.Ui); 4616 4617 static bool loopInitialized = false; 4618 if(!loopInitialized) { 4619 el.addDelegateOnLoopIteration(&doXNextEventVoid, 0); 4620 el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 0); 4621 4622 if(customSignalFD != -1) 4623 cast(void) el.addCallbackOnFdReadable(customSignalFD, new CallbackHelper(() { 4624 version(linux) { 4625 import core.sys.linux.sys.signalfd; 4626 import core.sys.posix.unistd : read; 4627 signalfd_siginfo info; 4628 read(customSignalFD, &info, info.sizeof); 4629 4630 auto sig = info.ssi_signo; 4631 4632 if(EventLoop.get.signalHandler !is null) { 4633 EventLoop.get.signalHandler()(sig); 4634 } else { 4635 EventLoop.get.exit(); 4636 } 4637 } 4638 })); 4639 4640 if(display.fd != -1) 4641 cast(void) el.addCallbackOnFdReadable(display.fd, new CallbackHelper(() { 4642 this.mtLock(); 4643 scope(exit) this.mtUnlock(); 4644 while(!done && XPending(display)) { 4645 done = doXNextEvent(this.display); 4646 } 4647 })); 4648 4649 if(pulseFd != -1) 4650 cast(void) el.addCallbackOnFdReadable(pulseFd, new CallbackHelper(() { 4651 long expirationCount; 4652 // 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... 4653 4654 handlePulse(); 4655 4656 // read just to clear the buffer so poll doesn't trigger again 4657 // BTW I read AFTER the pulse because if the pulse handler takes 4658 // a lot of time to execute, we don't want the app to get stuck 4659 // in a loop of timer hits without a chance to do anything else 4660 // 4661 // IOW handlePulse happens at most once per pulse interval. 4662 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4663 })); 4664 4665 if(customEventFDRead != -1) 4666 cast(void) el.addCallbackOnFdReadable(customEventFDRead, new CallbackHelper(() { 4667 // we have some custom events; process 'em 4668 import core.sys.posix.unistd : read; 4669 ulong n; 4670 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4671 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4672 //SimpleWindow.processAllCustomEvents(); 4673 })); 4674 4675 // FIXME: posix fds 4676 // FIXME up? 4677 4678 4679 loopInitialized = true; 4680 } 4681 4682 el.run(() => !whileCondition()); 4683 } else version(linux) { 4684 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 4685 bool forceXPending = false; 4686 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4687 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 4688 { 4689 this.mtLock(); 4690 scope(exit) this.mtUnlock(); 4691 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 4692 } 4693 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 4694 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 4695 if(nfds == -1) { 4696 if(err.errno == err.EINTR) { 4697 //if(forceXPending) goto xpending; 4698 continue; // interrupted by signal, just try again 4699 } 4700 throw new Exception("epoll wait failure"); 4701 } 4702 // writeln(nfds, " ", events[0].data.fd); 4703 4704 SimpleWindow.processAllCustomEvents(); // anyway 4705 //version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 4706 foreach(idx; 0 .. nfds) { 4707 if(done) break; 4708 auto fd = events[idx].data.fd; 4709 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 4710 auto flags = events[idx].events; 4711 if(flags & ep.EPOLLIN) { 4712 if (fd == customSignalFD) { 4713 version(linux) { 4714 import core.sys.linux.sys.signalfd; 4715 import core.sys.posix.unistd : read; 4716 signalfd_siginfo info; 4717 read(customSignalFD, &info, info.sizeof); 4718 4719 auto sig = info.ssi_signo; 4720 4721 if(EventLoop.get.signalHandler !is null) { 4722 EventLoop.get.signalHandler()(sig); 4723 } else { 4724 EventLoop.get.exit(); 4725 } 4726 } 4727 } else if(fd == display.fd) { 4728 version(sdddd) { writeln("X EVENT PENDING!"); } 4729 this.mtLock(); 4730 scope(exit) this.mtUnlock(); 4731 while(!done && XPending(display)) { 4732 done = doXNextEvent(this.display); 4733 } 4734 forceXPending = false; 4735 } else if(fd == pulseFd) { 4736 long expirationCount; 4737 // 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... 4738 4739 handlePulse(); 4740 4741 // read just to clear the buffer so poll doesn't trigger again 4742 // BTW I read AFTER the pulse because if the pulse handler takes 4743 // a lot of time to execute, we don't want the app to get stuck 4744 // in a loop of timer hits without a chance to do anything else 4745 // 4746 // IOW handlePulse happens at most once per pulse interval. 4747 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4748 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 4749 } else if (fd == customEventFDRead) { 4750 // we have some custom events; process 'em 4751 import core.sys.posix.unistd : read; 4752 ulong n; 4753 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4754 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4755 //SimpleWindow.processAllCustomEvents(); 4756 4757 forceXPending = true; 4758 } else { 4759 // some other timer 4760 version(sdddd) { writeln("unknown fd: ", fd); } 4761 4762 if(Timer* t = fd in Timer.mapping) 4763 (*t).trigger(); 4764 4765 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4766 (*pfr).ready(flags); 4767 4768 // we don't know what the user did in this timer, so we need to assume that 4769 // there's X data to be flushed and potentially processed 4770 forceXPending = true; 4771 4772 // or i might add support for other FDs too 4773 // but for now it is just timer 4774 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 4775 } 4776 } 4777 if(flags & ep.EPOLLHUP) { 4778 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4779 (*pfr).hup(flags); 4780 if(globalHupHandler) 4781 globalHupHandler(fd, flags); 4782 } 4783 /+ 4784 } else { 4785 // not interested in OUT, we are just reading here. 4786 // 4787 // error or hup might also be reported 4788 // but it shouldn't here since we are only 4789 // using a few types of FD and Xlib will report 4790 // if it dies. 4791 // so instead of thoughtfully handling it, I'll 4792 // just throw. for now at least 4793 4794 throw new Exception("epoll did something else"); 4795 } 4796 +/ 4797 } 4798 // if we won't call `XPending()` here, libX may delay some internal event delivery. 4799 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 4800 xpending: 4801 if (!done && forceXPending) { 4802 done = doXPending(); 4803 } 4804 } 4805 } else { 4806 // Generic fallback: yes to simple pulse support, 4807 // but NO timer support! 4808 4809 // FIXME: we could probably support the POSIX timer_create 4810 // signal-based option, but I'm in no rush to write it since 4811 // I prefer the fd-based functions. 4812 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 4813 4814 import core.sys.posix.poll; 4815 4816 pollfd[] pfds; 4817 pollfd[32] pfdsBuffer; 4818 auto len = PosixFdReader.mapping.length + 2; 4819 // FIXME: i should just reuse the buffer 4820 if(len < pfdsBuffer.length) 4821 pfds = pfdsBuffer[0 .. len]; 4822 else 4823 pfds = new pollfd[](len); 4824 4825 pfds[0].fd = display.fd; 4826 pfds[0].events = POLLIN; 4827 pfds[0].revents = 0; 4828 4829 int slot = 1; 4830 4831 if(customEventFDRead != -1) { 4832 pfds[slot].fd = customEventFDRead; 4833 pfds[slot].events = POLLIN; 4834 pfds[slot].revents = 0; 4835 4836 slot++; 4837 } 4838 4839 foreach(fd, obj; PosixFdReader.mapping) { 4840 if(!obj.enabled) continue; 4841 pfds[slot].fd = fd; 4842 pfds[slot].events = POLLIN; 4843 pfds[slot].revents = 0; 4844 4845 slot++; 4846 } 4847 4848 auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1); 4849 if(ret == -1) throw new Exception("poll"); 4850 4851 if(ret == 0) { 4852 // FIXME it may not necessarily time out if events keep coming 4853 if(handlePulse !is null) 4854 handlePulse(); 4855 } else { 4856 foreach(s; 0 .. slot) { 4857 if(pfds[s].revents == 0) continue; 4858 4859 if(pfds[s].fd == display.fd) { 4860 while(!done && XPending(display)) { 4861 this.mtLock(); 4862 scope(exit) this.mtUnlock(); 4863 done = doXNextEvent(this.display); 4864 } 4865 } else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) { 4866 4867 import core.sys.posix.unistd : read; 4868 ulong n; 4869 read(customEventFDRead, &n, n.sizeof); 4870 SimpleWindow.processAllCustomEvents(); 4871 } else { 4872 auto obj = PosixFdReader.mapping[pfds[s].fd]; 4873 if(pfds[s].revents & POLLNVAL) { 4874 obj.dispose(); 4875 } else { 4876 obj.ready(pfds[s].revents); 4877 } 4878 } 4879 4880 ret--; 4881 if(ret == 0) break; 4882 } 4883 } 4884 } 4885 } 4886 } 4887 4888 version(Windows) { 4889 4890 version(use_arsd_core) { 4891 import arsd.core; 4892 auto el = getThisThreadEventLoop(EventLoopType.Ui); 4893 static bool loopInitialized = false; 4894 if(!loopInitialized) { 4895 el.addDelegateOnLoopIteration(&SimpleWindow.processAllCustomEvents, 0); 4896 el.addDelegateOnLoopIteration(function() { eventLoopRound++; }, 0); 4897 loopInitialized = true; 4898 } 4899 el.run(() => !whileCondition()); 4900 } else { 4901 int ret = -1; 4902 MSG message; 4903 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 4904 eventLoopRound++; 4905 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4906 auto waitResult = MsgWaitForMultipleObjectsEx( 4907 cast(int) handles.length, handles.ptr, 4908 (wto == 0 ? INFINITE : wto), /* timeout */ 4909 0x04FF, /* QS_ALLINPUT */ 4910 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 4911 4912 SimpleWindow.processAllCustomEvents(); // anyway 4913 enum WAIT_OBJECT_0 = 0; 4914 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 4915 auto h = handles[waitResult - WAIT_OBJECT_0]; 4916 if(auto e = h in WindowsHandleReader.mapping) { 4917 (*e).ready(); 4918 } 4919 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 4920 // message ready 4921 int count; 4922 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 4923 ret = GetMessage(&message, null, 0, 0); 4924 if(ret == -1) 4925 throw new WindowsApiException("GetMessage", GetLastError()); 4926 TranslateMessage(&message); 4927 DispatchMessage(&message); 4928 4929 count++; 4930 if(count > 10) 4931 break; // take the opportunity to catch up on other events 4932 4933 if(ret == 0) { // WM_QUIT 4934 EventLoop.quitApplication(); 4935 break; 4936 } 4937 } 4938 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 4939 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 4940 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 4941 // timeout, should never happen since we aren't using it 4942 } else if(waitResult == 0xFFFFFFFF) { 4943 // failed 4944 throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError()); 4945 } else { 4946 // idk.... 4947 } 4948 } 4949 } 4950 4951 // return message.wParam; 4952 return 0; 4953 } else { 4954 return 0; 4955 } 4956 } 4957 4958 int run(bool delegate() whileCondition = null) { 4959 if(disposed) 4960 initialize(this.pulseTimeout); 4961 4962 version(X11) { 4963 try { 4964 return loopHelper(whileCondition); 4965 } catch(XDisconnectException e) { 4966 if(e.userRequested) { 4967 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 4968 item.discardConnectionState(); 4969 XCloseDisplay(XDisplayConnection.display); 4970 } 4971 4972 XDisplayConnection.display = null; 4973 4974 this.dispose(); 4975 4976 throw e; 4977 } 4978 } else { 4979 return loopHelper(whileCondition); 4980 } 4981 } 4982 } 4983 4984 4985 /++ 4986 Provides an icon on the system notification area (also known as the system tray). 4987 4988 4989 If a notification area is not available with the NotificationIcon object is created, 4990 it will silently succeed and simply attempt to create one when an area becomes available. 4991 4992 4993 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for 4994 Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency 4995 with true color was added at that time. I was just too lazy to write the fallback. 4996 4997 If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest 4998 you use arsd 10.x when targeting Windows XP. 4999 +/ 5000 version(OSXCocoa) {} else // NotYetImplementedException 5001 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 5002 5003 version(X11) { 5004 void recreateAfterDisconnect() { 5005 stateDiscarded = false; 5006 clippixmap = None; 5007 throw new Exception("NOT IMPLEMENTED"); 5008 } 5009 5010 bool stateDiscarded; 5011 void discardConnectionState() { 5012 stateDiscarded = true; 5013 } 5014 } 5015 5016 5017 version(X11) { 5018 Image img; 5019 5020 NativeEventHandler getNativeEventHandler() { 5021 return delegate int(XEvent e) { 5022 switch(e.type) { 5023 case EventType.Expose: 5024 //case EventType.VisibilityNotify: 5025 redraw(); 5026 break; 5027 case EventType.ClientMessage: 5028 version(sddddd) { 5029 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 5030 writeln("\t", e.xclient.format); 5031 writeln("\t", e.xclient.data.l); 5032 } 5033 break; 5034 case EventType.ButtonPress: 5035 auto event = e.xbutton; 5036 if (onClick !is null || onClickEx !is null) { 5037 MouseButton mb = cast(MouseButton)0; 5038 switch (event.button) { 5039 case 1: mb = MouseButton.left; break; // left 5040 case 2: mb = MouseButton.middle; break; // middle 5041 case 3: mb = MouseButton.right; break; // right 5042 case 4: mb = MouseButton.wheelUp; break; // scroll up 5043 case 5: mb = MouseButton.wheelDown; break; // scroll down 5044 case 6: break; // scroll left... 5045 case 7: break; // scroll right... 5046 case 8: mb = MouseButton.backButton; break; 5047 case 9: mb = MouseButton.forwardButton; break; 5048 default: 5049 } 5050 if (mb) { 5051 try { onClick()(mb); } catch (Exception) {} 5052 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 5053 } 5054 } 5055 break; 5056 case EventType.EnterNotify: 5057 if (onEnter !is null) { 5058 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 5059 } 5060 break; 5061 case EventType.LeaveNotify: 5062 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 5063 break; 5064 case EventType.DestroyNotify: 5065 active = false; 5066 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 5067 break; 5068 case EventType.ConfigureNotify: 5069 auto event = e.xconfigure; 5070 this.width = event.width; 5071 this.height = event.height; 5072 // writeln(width, " x " , height, " @ ", event.x, " ", event.y); 5073 redraw(); 5074 break; 5075 default: return 1; 5076 } 5077 return 1; 5078 }; 5079 } 5080 5081 /* private */ void hideBalloon() { 5082 balloon.close(); 5083 version(with_timer) 5084 timer.destroy(); 5085 balloon = null; 5086 version(with_timer) 5087 timer = null; 5088 } 5089 5090 void redraw() { 5091 if (!active) return; 5092 5093 auto display = XDisplayConnection.get; 5094 GC gc; 5095 5096 // from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap 5097 5098 int gc_depth(int depth, Display *dpy, Window root, GC *gc) { 5099 Visual *visual; 5100 XVisualInfo vis_info; 5101 XSetWindowAttributes win_attr; 5102 c_ulong win_mask; 5103 5104 if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) { 5105 assert(0); 5106 // return 1; 5107 } 5108 5109 visual = vis_info.visual; 5110 5111 win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone); 5112 win_attr.background_pixel = 0; 5113 win_attr.border_pixel = 0; 5114 5115 win_mask = CWBackPixel | CWColormap | CWBorderPixel; 5116 5117 *gc = XCreateGC(dpy, nativeHandle, 0, null); 5118 5119 return 0; 5120 } 5121 5122 if(useAlpha) 5123 gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc); 5124 else 5125 gc = DefaultGC(display, DefaultScreen(display)); 5126 5127 XClearWindow(display, nativeHandle); 5128 5129 if(!useAlpha && img !is null) 5130 XSetClipMask(display, gc, clippixmap); 5131 5132 /+ 5133 XSetForeground(display, gc, 5134 cast(uint) 0 << 16 | 5135 cast(uint) 0 << 8 | 5136 cast(uint) 0); 5137 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 5138 +/ 5139 5140 if (img is null) { 5141 XSetForeground(display, gc, 5142 cast(uint) 0 << 16 | 5143 cast(uint) 127 << 8 | 5144 cast(uint) 0); 5145 XFillArc(display, nativeHandle, 5146 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 5147 } else { 5148 int dx = 0; 5149 int dy = 0; 5150 if(width > img.width) 5151 dx = (width - img.width) / 2; 5152 if(height > img.height) 5153 dy = (height - img.height) / 2; 5154 // writeln(img.width, " ", img.height, " vs ", width, " ", height); 5155 XSetClipOrigin(display, gc, dx, dy); 5156 5157 int max(int a, int b) { 5158 if(a > b) return a; else return b; 5159 } 5160 5161 if (img.usingXshm) 5162 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false); 5163 else 5164 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height)); 5165 } 5166 XSetClipMask(display, gc, None); 5167 flushGui(); 5168 } 5169 5170 static Window getTrayOwner() { 5171 auto display = XDisplayConnection.get; 5172 auto i = cast(int) DefaultScreen(display); 5173 if(i < 10 && i >= 0) { 5174 static Atom atom; 5175 if(atom == None) 5176 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 5177 return XGetSelectionOwner(display, atom); 5178 } 5179 return None; 5180 } 5181 5182 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 5183 auto to = getTrayOwner(); 5184 auto display = XDisplayConnection.get; 5185 XEvent ev; 5186 ev.xclient.type = EventType.ClientMessage; 5187 ev.xclient.window = to; 5188 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 5189 ev.xclient.format = 32; 5190 ev.xclient.data.l[0] = CurrentTime; 5191 ev.xclient.data.l[1] = message; 5192 ev.xclient.data.l[2] = d1; 5193 ev.xclient.data.l[3] = d2; 5194 ev.xclient.data.l[4] = d3; 5195 5196 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 5197 } 5198 5199 private static NotificationAreaIcon[] activeIcons; 5200 5201 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 5202 private void newManager() { 5203 close(); 5204 createXWin(); 5205 5206 if(this.clippixmap) 5207 XFreePixmap(XDisplayConnection.get, clippixmap); 5208 if(this.originalMemoryImage) 5209 this.icon = this.originalMemoryImage; 5210 else if(this.img) 5211 this.icon = this.img; 5212 } 5213 5214 private bool useAlpha = false; 5215 5216 private void createXWin () { 5217 // create window 5218 auto display = XDisplayConnection.get; 5219 5220 // to check for MANAGER on root window to catch new/changed tray owners 5221 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 5222 // so if a thing does appear, we can handle it 5223 foreach(ai; activeIcons) 5224 if(ai is this) 5225 goto alreadythere; 5226 activeIcons ~= this; 5227 alreadythere: 5228 5229 // and check for an existing tray 5230 auto trayOwner = getTrayOwner(); 5231 if(trayOwner == None) 5232 return; 5233 //throw new Exception("No notification area found"); 5234 5235 Visual* v = cast(Visual*) CopyFromParent; 5236 5237 // GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales 5238 // from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send 5239 // a resize event later. 5240 width = 22; 5241 height = 22; 5242 5243 // if they system gave us a 32 bit visual we need to switch to it too 5244 int depth = 24; 5245 5246 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 5247 if(visualProp !is null) { 5248 c_ulong[] info = cast(c_ulong[]) visualProp; 5249 if(info.length == 1) { 5250 auto vid = info[0]; 5251 int returned; 5252 XVisualInfo t; 5253 t.visualid = vid; 5254 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 5255 if(got !is null) { 5256 if(returned == 1) { 5257 v = got.visual; 5258 depth = got.depth; 5259 // writeln("using special visual ", got.depth); 5260 // writeln(depth); 5261 } 5262 XFree(got); 5263 } 5264 } 5265 } 5266 5267 int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect; 5268 XSetWindowAttributes attr; 5269 attr.background_pixel = 0; 5270 attr.border_pixel = 0; 5271 attr.override_redirect = 0; 5272 if(v !is cast(Visual*) CopyFromParent) { 5273 attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone); 5274 CWFlags |= CWColormap; 5275 if(depth == 32) 5276 useAlpha = true; 5277 else 5278 goto plain; 5279 } else { 5280 plain: 5281 attr.background_pixmap = 1 /* ParentRelative */; 5282 CWFlags |= CWBackPixmap; 5283 } 5284 5285 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr); 5286 5287 assert(nativeWindow); 5288 5289 if(!useAlpha) 5290 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 5291 5292 nativeHandle = nativeWindow; 5293 5294 ///+ 5295 arch_ulong[2] info; 5296 info[0] = 0; 5297 info[1] = 1; 5298 5299 string title = this.name is null ? "simpledisplay.d program" : this.name; 5300 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 5301 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 5302 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 5303 5304 XChangeProperty( 5305 display, 5306 nativeWindow, 5307 GetAtom!("_XEMBED_INFO", true)(display), 5308 GetAtom!("_XEMBED_INFO", true)(display), 5309 32 /* bits */, 5310 0 /*PropModeReplace*/, 5311 info.ptr, 5312 2); 5313 5314 import core.sys.posix.unistd; 5315 arch_ulong pid = getpid(); 5316 5317 XChangeProperty( 5318 display, 5319 nativeWindow, 5320 GetAtom!("_NET_WM_PID", true)(display), 5321 XA_CARDINAL, 5322 32 /* bits */, 5323 0 /*PropModeReplace*/, 5324 &pid, 5325 1); 5326 5327 updateNetWmIcon(); 5328 5329 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 5330 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 5331 XClassHint klass; 5332 XWMHints wh; 5333 XSizeHints size; 5334 klass.res_name = sdpyWindowClassStr; 5335 klass.res_class = sdpyWindowClassStr; 5336 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 5337 } 5338 5339 // believe it or not, THIS is what xfce needed for the 9999 issue 5340 XSizeHints sh; 5341 c_long spr; 5342 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 5343 sh.flags |= PMaxSize | PMinSize; 5344 // FIXME maybe nicer resizing 5345 sh.min_width = 16; 5346 sh.min_height = 16; 5347 sh.max_width = 22; 5348 sh.max_height = 22; 5349 XSetWMNormalHints(display, nativeWindow, &sh); 5350 5351 5352 //+/ 5353 5354 5355 XSelectInput(display, nativeWindow, 5356 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 5357 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 5358 5359 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 5360 // XMapWindow(display, nativeWindow); // to demo it w/o a tray 5361 5362 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 5363 active = true; 5364 } 5365 5366 void updateNetWmIcon() { 5367 if(img is null) return; 5368 auto display = XDisplayConnection.get; 5369 // FIXME: ensure this is correct 5370 arch_ulong[] buffer; 5371 auto imgMi = img.toTrueColorImage; 5372 buffer ~= imgMi.width; 5373 buffer ~= imgMi.height; 5374 foreach(c; imgMi.imageData.colors) { 5375 arch_ulong b; 5376 b |= c.a << 24; 5377 b |= c.r << 16; 5378 b |= c.g << 8; 5379 b |= c.b; 5380 buffer ~= b; 5381 } 5382 5383 XChangeProperty( 5384 display, 5385 nativeHandle, 5386 GetAtom!"_NET_WM_ICON"(display), 5387 GetAtom!"CARDINAL"(display), 5388 32 /* bits */, 5389 0 /*PropModeReplace*/, 5390 buffer.ptr, 5391 cast(int) buffer.length); 5392 } 5393 5394 5395 5396 private SimpleWindow balloon; 5397 version(with_timer) 5398 private Timer timer; 5399 5400 private Window nativeHandle; 5401 private Pixmap clippixmap = None; 5402 private int width = 16; 5403 private int height = 16; 5404 private bool active = false; 5405 5406 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 5407 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 5408 void delegate () onLeave; /// X11 only. 5409 5410 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 5411 5412 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 5413 void getWindowRect (out int x, out int y, out int width, out int height) { 5414 if (!active) { width = 1; height = 1; return; } // 1: just in case 5415 Window dummyw; 5416 auto dpy = XDisplayConnection.get; 5417 //XWindowAttributes xwa; 5418 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 5419 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 5420 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 5421 width = this.width; 5422 height = this.height; 5423 } 5424 } 5425 5426 /+ 5427 What I actually want from this: 5428 5429 * set / change: icon, tooltip 5430 * handle: mouse click, right click 5431 * show: notification bubble. 5432 +/ 5433 5434 version(Windows) { 5435 WindowsIcon win32Icon; 5436 HWND hwnd; 5437 5438 NOTIFYICONDATAW data; 5439 5440 NativeEventHandler getNativeEventHandler() { 5441 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 5442 if(msg == WM_USER) { 5443 auto event = LOWORD(lParam); 5444 auto iconId = HIWORD(lParam); 5445 //auto x = GET_X_LPARAM(wParam); 5446 //auto y = GET_Y_LPARAM(wParam); 5447 switch(event) { 5448 case WM_LBUTTONDOWN: 5449 onClick()(MouseButton.left); 5450 break; 5451 case WM_RBUTTONDOWN: 5452 onClick()(MouseButton.right); 5453 break; 5454 case WM_MBUTTONDOWN: 5455 onClick()(MouseButton.middle); 5456 break; 5457 case WM_MOUSEMOVE: 5458 // sent, we could use it. 5459 break; 5460 case WM_MOUSEWHEEL: 5461 // NOT SENT 5462 break; 5463 //case NIN_KEYSELECT: 5464 //case NIN_SELECT: 5465 //break; 5466 default: {} 5467 } 5468 } 5469 return 0; 5470 }; 5471 } 5472 5473 enum NIF_SHOWTIP = 0x00000080; 5474 5475 private static struct NOTIFYICONDATAW { 5476 DWORD cbSize; 5477 HWND hWnd; 5478 UINT uID; 5479 UINT uFlags; 5480 UINT uCallbackMessage; 5481 HICON hIcon; 5482 WCHAR[128] szTip; 5483 DWORD dwState; 5484 DWORD dwStateMask; 5485 WCHAR[256] szInfo; 5486 union { 5487 UINT uTimeout; 5488 UINT uVersion; 5489 } 5490 WCHAR[64] szInfoTitle; 5491 DWORD dwInfoFlags; 5492 GUID guidItem; 5493 HICON hBalloonIcon; 5494 } 5495 5496 } 5497 5498 /++ 5499 Note that on Windows, only left, right, and middle buttons are sent. 5500 Mouse wheel buttons are NOT set, so don't rely on those events if your 5501 program is meant to be used on Windows too. 5502 +/ 5503 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 5504 // The canonical constructor for Windows needs the MemoryImage, so it is here, 5505 // but on X, we need an Image, so its canonical ctor is there. They should 5506 // forward to each other though. 5507 version(X11) { 5508 this.name = name; 5509 this.onClick = onClick; 5510 createXWin(); 5511 this.icon = icon; 5512 } else version(Windows) { 5513 this.onClick = onClick; 5514 this.win32Icon = new WindowsIcon(icon); 5515 5516 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 5517 5518 static bool registered = false; 5519 if(!registered) { 5520 WNDCLASSEX wc; 5521 wc.cbSize = wc.sizeof; 5522 wc.hInstance = hInstance; 5523 wc.lpfnWndProc = &WndProc; 5524 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 5525 if(!RegisterClassExW(&wc)) 5526 throw new WindowsApiException("RegisterClass", GetLastError()); 5527 registered = true; 5528 } 5529 5530 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 5531 if(hwnd is null) 5532 throw new WindowsApiException("CreateWindow", GetLastError()); 5533 5534 data.cbSize = data.sizeof; 5535 data.hWnd = hwnd; 5536 data.uID = cast(uint) cast(void*) this; 5537 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 5538 // NIF_INFO means show balloon 5539 data.uCallbackMessage = WM_USER; 5540 data.hIcon = this.win32Icon.hIcon; 5541 data.szTip = ""; // FIXME 5542 data.dwState = 0; // NIS_HIDDEN; // windows vista 5543 data.dwStateMask = NIS_HIDDEN; // windows vista 5544 5545 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 5546 5547 5548 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 5549 5550 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 5551 } else version(OSXCocoa) { 5552 throw new NotYetImplementedException(); 5553 } else static assert(0); 5554 } 5555 5556 /// ditto 5557 this(string name, Image icon, void delegate(MouseButton button) onClick) { 5558 version(X11) { 5559 this.onClick = onClick; 5560 this.name = name; 5561 createXWin(); 5562 this.icon = icon; 5563 } else version(Windows) { 5564 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 5565 } else version(OSXCocoa) { 5566 throw new NotYetImplementedException(); 5567 } else static assert(0); 5568 } 5569 5570 version(X11) { 5571 /++ 5572 X-specific extension (for now at least) 5573 +/ 5574 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5575 this.onClickEx = onClickEx; 5576 createXWin(); 5577 if (icon !is null) this.icon = icon; 5578 } 5579 5580 /// ditto 5581 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5582 this.onClickEx = onClickEx; 5583 createXWin(); 5584 this.icon = icon; 5585 } 5586 } 5587 5588 private void delegate (MouseButton button) onClick_; 5589 5590 /// 5591 @property final void delegate(MouseButton) onClick() { 5592 if(onClick_ is null) 5593 onClick_ = delegate void(MouseButton) {}; 5594 return onClick_; 5595 } 5596 5597 /// ditto 5598 @property final void onClick(void delegate(MouseButton) handler) { 5599 // I made this a property setter so we can wrap smaller arg 5600 // delegates and just forward all to onClickEx or something. 5601 onClick_ = handler; 5602 } 5603 5604 5605 string name_; 5606 @property void name(string n) { 5607 name_ = n; 5608 } 5609 5610 @property string name() { 5611 return name_; 5612 } 5613 5614 private MemoryImage originalMemoryImage; 5615 5616 /// 5617 @property void icon(MemoryImage i) { 5618 version(X11) { 5619 this.originalMemoryImage = i; 5620 if (!active) return; 5621 if (i !is null) { 5622 this.img = Image.fromMemoryImage(i, useAlpha, false); 5623 if(!useAlpha) 5624 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 5625 // writeln("using pixmap ", clippixmap); 5626 updateNetWmIcon(); 5627 redraw(); 5628 } else { 5629 if (this.img !is null) { 5630 this.img = null; 5631 redraw(); 5632 } 5633 } 5634 } else version(Windows) { 5635 this.win32Icon = new WindowsIcon(i); 5636 5637 data.uFlags = NIF_ICON; 5638 data.hIcon = this.win32Icon.hIcon; 5639 5640 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5641 } else version(OSXCocoa) { 5642 throw new NotYetImplementedException(); 5643 } else static assert(0); 5644 } 5645 5646 /// ditto 5647 @property void icon (Image i) { 5648 version(X11) { 5649 if (!active) return; 5650 if (i !is img) { 5651 originalMemoryImage = null; 5652 img = i; 5653 redraw(); 5654 } 5655 } else version(Windows) { 5656 this.icon(i is null ? null : i.toTrueColorImage()); 5657 } else version(OSXCocoa) { 5658 throw new NotYetImplementedException(); 5659 } else static assert(0); 5660 } 5661 5662 /++ 5663 Shows a balloon notification. You can only show one balloon at a time, if you call 5664 it twice while one is already up, the first balloon will be replaced. 5665 5666 5667 The user is free to block notifications and they will automatically disappear after 5668 a timeout period. 5669 5670 Params: 5671 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 5672 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 5673 icon = the icon to display with the notification. If null, it uses your existing icon. 5674 onclick = delegate called if the user clicks the balloon. (not yet implemented) 5675 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 5676 +/ 5677 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 5678 bool useCustom = true; 5679 version(libnotify) { 5680 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 5681 try { 5682 if(!active) return; 5683 5684 if(libnotify is null) { 5685 libnotify = new C_DynamicLibrary("libnotify.so"); 5686 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 5687 } 5688 5689 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 5690 5691 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 5692 5693 if(onclick) { 5694 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 5695 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); 5696 libnotify_action_delegates_count++; 5697 } 5698 5699 // FIXME icon 5700 5701 // set hint image-data 5702 // set default action for onclick 5703 5704 void* error; 5705 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 5706 5707 useCustom = false; 5708 } catch(Exception e) { 5709 5710 } 5711 } 5712 5713 version(X11) { 5714 if(useCustom) { 5715 if(!active) return; 5716 if(balloon) { 5717 hideBalloon(); 5718 } 5719 // I know there are two specs for this, but one is never 5720 // implemented by any window manager I have ever seen, and 5721 // the other is a bloated mess and too complicated for simpledisplay... 5722 // so doing my own little window instead. 5723 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 5724 5725 int x, y, width, height; 5726 getWindowRect(x, y, width, height); 5727 5728 int bx = x - balloon.width; 5729 int by = y - balloon.height; 5730 if(bx < 0) 5731 bx = x + width + balloon.width; 5732 if(by < 0) 5733 by = y + height; 5734 5735 // just in case, make sure it is actually on scren 5736 if(bx < 0) 5737 bx = 0; 5738 if(by < 0) 5739 by = 0; 5740 5741 balloon.move(bx, by); 5742 auto painter = balloon.draw(); 5743 painter.fillColor = Color(220, 220, 220); 5744 painter.outlineColor = Color.black; 5745 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 5746 auto iconWidth = icon is null ? 0 : icon.width; 5747 if(icon) 5748 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 5749 iconWidth += 6; // margin around the icon 5750 5751 // draw a close button 5752 painter.outlineColor = Color(44, 44, 44); 5753 painter.fillColor = Color(255, 255, 255); 5754 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 5755 painter.pen = Pen(Color.black, 3); 5756 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 5757 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 5758 painter.pen = Pen(Color.black, 1); 5759 painter.fillColor = Color(220, 220, 220); 5760 5761 // Draw the title and message 5762 painter.drawText(Point(4 + iconWidth, 4), title); 5763 painter.drawLine( 5764 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 5765 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 5766 ); 5767 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 5768 5769 balloon.setEventHandlers( 5770 (MouseEvent ev) { 5771 if(ev.type == MouseEventType.buttonPressed) { 5772 if(ev.x > balloon.width - 16 && ev.y < 16) 5773 hideBalloon(); 5774 else if(onclick) 5775 onclick(); 5776 } 5777 } 5778 ); 5779 balloon.show(); 5780 5781 version(with_timer) 5782 timer = new Timer(timeout, &hideBalloon); 5783 else {} // FIXME 5784 } 5785 } else version(Windows) { 5786 enum NIF_INFO = 0x00000010; 5787 5788 data.uFlags = NIF_INFO; 5789 5790 // FIXME: go back to the last valid unicode code point 5791 if(title.length > 40) 5792 title = title[0 .. 40]; 5793 if(message.length > 220) 5794 message = message[0 .. 220]; 5795 5796 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 5797 enum NIIF_LARGE_ICON = 0x00000020; 5798 enum NIIF_NOSOUND = 0x00000010; 5799 enum NIIF_USER = 0x00000004; 5800 enum NIIF_ERROR = 0x00000003; 5801 enum NIIF_WARNING = 0x00000002; 5802 enum NIIF_INFO = 0x00000001; 5803 enum NIIF_NONE = 0; 5804 5805 WCharzBuffer t = WCharzBuffer(title); 5806 WCharzBuffer m = WCharzBuffer(message); 5807 5808 t.copyInto(data.szInfoTitle); 5809 m.copyInto(data.szInfo); 5810 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 5811 5812 if(icon !is null) { 5813 auto i = new WindowsIcon(icon); 5814 data.hBalloonIcon = i.hIcon; 5815 data.dwInfoFlags |= NIIF_USER; 5816 } 5817 5818 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5819 } else version(OSXCocoa) { 5820 throw new NotYetImplementedException(); 5821 } else static assert(0); 5822 } 5823 5824 /// 5825 //version(Windows) 5826 void show() { 5827 version(X11) { 5828 if(!hidden) 5829 return; 5830 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 5831 hidden = false; 5832 } else version(Windows) { 5833 data.uFlags = NIF_STATE; 5834 data.dwState = 0; // NIS_HIDDEN; // windows vista 5835 data.dwStateMask = NIS_HIDDEN; // windows vista 5836 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5837 } else version(OSXCocoa) { 5838 throw new NotYetImplementedException(); 5839 } else static assert(0); 5840 } 5841 5842 version(X11) 5843 bool hidden = false; 5844 5845 /// 5846 //version(Windows) 5847 void hide() { 5848 version(X11) { 5849 if(hidden) 5850 return; 5851 hidden = true; 5852 XUnmapWindow(XDisplayConnection.get, nativeHandle); 5853 } else version(Windows) { 5854 data.uFlags = NIF_STATE; 5855 data.dwState = NIS_HIDDEN; // windows vista 5856 data.dwStateMask = NIS_HIDDEN; // windows vista 5857 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5858 } else version(OSXCocoa) { 5859 throw new NotYetImplementedException(); 5860 } else static assert(0); 5861 } 5862 5863 /// 5864 void close () { 5865 version(X11) { 5866 if (active) { 5867 active = false; // event handler will set this too, but meh 5868 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 5869 XDestroyWindow(XDisplayConnection.get, nativeHandle); 5870 flushGui(); 5871 } 5872 } else version(Windows) { 5873 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 5874 } else version(OSXCocoa) { 5875 throw new NotYetImplementedException(); 5876 } else static assert(0); 5877 } 5878 5879 ~this() { 5880 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 5881 version(X11) 5882 if(clippixmap != None) 5883 XFreePixmap(XDisplayConnection.get, clippixmap); 5884 close(); 5885 } 5886 } 5887 5888 version(X11) 5889 /// Call `XFreePixmap` on the return value. 5890 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 5891 char[] data = new char[](i.width * i.height / 8 + 2); 5892 data[] = 0; 5893 5894 int bitOffset = 0; 5895 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 5896 ubyte v = c.a > 128 ? 1 : 0; 5897 data[bitOffset / 8] |= v << (bitOffset%8); 5898 bitOffset++; 5899 } 5900 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 5901 return handle; 5902 } 5903 5904 5905 // basic functions to make timers 5906 /** 5907 A timer that will trigger your function on a given interval. 5908 5909 5910 You create a timer with an interval and a callback. It will continue 5911 to fire on the interval until it is destroyed. 5912 5913 There are currently no one-off timers (instead, just create one and 5914 destroy it when it is triggered) nor are there pause/resume functions - 5915 the timer must again be destroyed and recreated if you want to pause it. 5916 5917 --- 5918 auto timer = new Timer(50, { it happened!; }); 5919 timer.destroy(); 5920 --- 5921 5922 Timers can only be expected to fire when the event loop is running and only 5923 once per iteration through the event loop. 5924 5925 History: 5926 Prior to December 9, 2020, a timer pulse set too high with a handler too 5927 slow could lock up the event loop. It now guarantees other things will 5928 get a chance to run between timer calls, even if that means not keeping up 5929 with the requested interval. 5930 */ 5931 version(with_timer) { 5932 version(use_arsd_core) 5933 alias Timer = arsd.core.Timer; // FIXME should probably wrap it for a stable api 5934 else 5935 class Timer { 5936 // FIXME: needs pause and unpause 5937 // FIXME: I might add overloads for ones that take a count of 5938 // how many elapsed since last time (on Windows, it will divide 5939 // the ticks thing given, on Linux it is just available) and 5940 // maybe one that takes an instance of the Timer itself too 5941 /// Create a timer with a callback when it triggers. 5942 this(int intervalInMilliseconds, void delegate() onPulse) @trusted { 5943 assert(onPulse !is null); 5944 5945 this.intervalInMilliseconds = intervalInMilliseconds; 5946 this.onPulse = onPulse; 5947 5948 version(Windows) { 5949 /* 5950 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5951 if(handle == 0) 5952 throw new WindowsApiException("SetTimer", GetLastError()); 5953 */ 5954 5955 // thanks to Archival 998 for the WaitableTimer blocks 5956 handle = CreateWaitableTimer(null, false, null); 5957 long initialTime = -intervalInMilliseconds; 5958 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5959 throw new WindowsApiException("SetWaitableTimer", GetLastError()); 5960 5961 mapping[handle] = this; 5962 5963 } else version(linux) { 5964 static import ep = core.sys.linux.epoll; 5965 5966 import core.sys.linux.timerfd; 5967 5968 fd = timerfd_create(CLOCK_MONOTONIC, 0); 5969 if(fd == -1) 5970 throw new Exception("timer create failed"); 5971 5972 mapping[fd] = this; 5973 5974 itimerspec value = makeItimerspec(intervalInMilliseconds); 5975 5976 if(timerfd_settime(fd, 0, &value, null) == -1) 5977 throw new Exception("couldn't make pulse timer"); 5978 5979 version(with_eventloop) { 5980 import arsd.eventloop; 5981 addFileEventListeners(fd, &trigger, null, null); 5982 } else { 5983 prepareEventLoop(); 5984 5985 ep.epoll_event ev = void; 5986 ev.events = ep.EPOLLIN; 5987 ev.data.fd = fd; 5988 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5989 } 5990 } else featureNotImplemented(); 5991 } 5992 5993 private int intervalInMilliseconds; 5994 5995 // just cuz I sometimes call it this. 5996 alias dispose = destroy; 5997 5998 /// Stop and destroy the timer object. 5999 void destroy() { 6000 version(Windows) { 6001 staticDestroy(handle); 6002 handle = null; 6003 } else version(linux) { 6004 staticDestroy(fd); 6005 fd = -1; 6006 } else featureNotImplemented(); 6007 } 6008 6009 version(Windows) 6010 static void staticDestroy(HANDLE handle) { 6011 if(handle) { 6012 // KillTimer(null, handle); 6013 CancelWaitableTimer(cast(void*)handle); 6014 mapping.remove(handle); 6015 CloseHandle(handle); 6016 } 6017 } 6018 else version(linux) 6019 static void staticDestroy(int fd) @system { 6020 if(fd != -1) { 6021 import unix = core.sys.posix.unistd; 6022 static import ep = core.sys.linux.epoll; 6023 6024 version(with_eventloop) { 6025 import arsd.eventloop; 6026 removeFileEventListeners(fd); 6027 } else { 6028 ep.epoll_event ev = void; 6029 ev.events = ep.EPOLLIN; 6030 ev.data.fd = fd; 6031 6032 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 6033 } 6034 unix.close(fd); 6035 mapping.remove(fd); 6036 } 6037 } 6038 6039 ~this() { 6040 version(Windows) { if(handle) 6041 cleanupQueue.queue!staticDestroy(handle); 6042 } else version(linux) { if(fd != -1) 6043 cleanupQueue.queue!staticDestroy(fd); 6044 } 6045 } 6046 6047 void changeTime(int intervalInMilliseconds) 6048 { 6049 this.intervalInMilliseconds = intervalInMilliseconds; 6050 version(Windows) 6051 { 6052 if(handle) 6053 { 6054 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 6055 long initialTime = -intervalInMilliseconds; 6056 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 6057 throw new WindowsApiException("couldn't change pulse timer", GetLastError()); 6058 } 6059 } else version(linux) { 6060 import core.sys.linux.timerfd; 6061 6062 itimerspec value = makeItimerspec(intervalInMilliseconds); 6063 if(timerfd_settime(fd, 0, &value, null) == -1) { 6064 throw new Exception("couldn't change pulse timer"); 6065 } 6066 } else { 6067 assert(false, "Timer.changeTime(int) is not implemented for this platform"); 6068 } 6069 } 6070 6071 6072 private: 6073 6074 void delegate() onPulse; 6075 6076 int lastEventLoopRoundTriggered; 6077 6078 version(linux) { 6079 static auto makeItimerspec(int intervalInMilliseconds) { 6080 import core.sys.linux.timerfd; 6081 6082 itimerspec value; 6083 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 6084 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 6085 6086 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 6087 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 6088 6089 return value; 6090 } 6091 } 6092 6093 void trigger() { 6094 version(linux) { 6095 import unix = core.sys.posix.unistd; 6096 long val; 6097 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 6098 } else version(Windows) { 6099 if(this.lastEventLoopRoundTriggered == eventLoopRound) 6100 return; // never try to actually run faster than the event loop 6101 lastEventLoopRoundTriggered = eventLoopRound; 6102 } else featureNotImplemented(); 6103 6104 onPulse(); 6105 } 6106 6107 version(Windows) 6108 void rearm() { 6109 6110 } 6111 6112 version(Windows) 6113 extern(Windows) 6114 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 6115 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 6116 if(Timer* t = timer in mapping) { 6117 try 6118 (*t).trigger(); 6119 catch(Exception e) { sdpy_abort(e); assert(0); } 6120 } 6121 } 6122 6123 version(Windows) { 6124 //UINT_PTR handle; 6125 //static Timer[UINT_PTR] mapping; 6126 HANDLE handle; 6127 __gshared Timer[HANDLE] mapping; 6128 } else version(linux) { 6129 int fd = -1; 6130 __gshared Timer[int] mapping; 6131 } else version(OSXCocoa) { 6132 } else static assert(0, "timer not supported"); 6133 } 6134 } 6135 6136 version(Windows) 6137 private int eventLoopRound; 6138 6139 version(Windows) 6140 /// 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 6141 class WindowsHandleReader { 6142 /// 6143 this(void delegate() onReady, HANDLE handle) { 6144 this.onReady = onReady; 6145 this.handle = handle; 6146 6147 mapping[handle] = this; 6148 6149 enable(); 6150 } 6151 6152 version(use_arsd_core) 6153 ICoreEventLoop.UnregisterToken unregisterToken; 6154 6155 /// 6156 void enable() { 6157 version(use_arsd_core) { 6158 unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnHandleReady(handle, new CallbackHelper(&ready)); 6159 } else { 6160 auto el = EventLoop.get().impl; 6161 el.handles ~= handle; 6162 } 6163 } 6164 6165 /// 6166 void disable() { 6167 version(use_arsd_core) { 6168 unregisterToken.unregister(); 6169 } else { 6170 auto el = EventLoop.get().impl; 6171 for(int i = 0; i < el.handles.length; i++) { 6172 if(el.handles[i] is handle) { 6173 el.handles[i] = el.handles[$-1]; 6174 el.handles = el.handles[0 .. $-1]; 6175 return; 6176 } 6177 } 6178 } 6179 } 6180 6181 void dispose() { 6182 disable(); 6183 if(handle) 6184 mapping.remove(handle); 6185 handle = null; 6186 } 6187 6188 void ready() { 6189 if(onReady) 6190 onReady(); 6191 } 6192 6193 HANDLE handle; 6194 void delegate() onReady; 6195 6196 __gshared WindowsHandleReader[HANDLE] mapping; 6197 } 6198 6199 version(Posix) 6200 /// Lets you add files to the event loop for reading. Use at your own risk. 6201 class PosixFdReader { 6202 /// 6203 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 6204 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 6205 } 6206 6207 /// 6208 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 6209 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 6210 } 6211 6212 /// 6213 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 6214 this.onReady = onReady; 6215 this.fd = fd; 6216 this.captureWrites = captureWrites; 6217 this.captureReads = captureReads; 6218 6219 mapping[fd] = this; 6220 6221 version(with_eventloop) { 6222 import arsd.eventloop; 6223 addFileEventListeners(fd, &readyel); 6224 } else { 6225 enable(); 6226 } 6227 } 6228 6229 bool captureReads; 6230 bool captureWrites; 6231 6232 version(use_arsd_core) { 6233 import arsd.core; 6234 ICoreEventLoop.UnregisterToken unregisterToken; 6235 } 6236 6237 version(with_eventloop) {} else 6238 /// 6239 void enable() @system { 6240 enabled = true; 6241 6242 version(use_arsd_core) { 6243 unregisterToken = getThisThreadEventLoop(EventLoopType.Ui).addCallbackOnFdReadable(fd, new CallbackHelper( 6244 () { onReady(fd, true, false); } 6245 )); 6246 // FIXME: what if it is writeable? 6247 6248 } else version(linux) { 6249 prepareEventLoop(); 6250 static import ep = core.sys.linux.epoll; 6251 ep.epoll_event ev = void; 6252 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 6253 // writeln("enable ", fd, " ", captureReads, " ", captureWrites); 6254 ev.data.fd = fd; 6255 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 6256 } else { 6257 6258 } 6259 } 6260 6261 version(with_eventloop) {} else 6262 /// 6263 void disable() @system { 6264 enabled = false; 6265 6266 version(use_arsd_core) { 6267 unregisterToken.unregister(); 6268 } else 6269 version(linux) { 6270 prepareEventLoop(); 6271 static import ep = core.sys.linux.epoll; 6272 ep.epoll_event ev = void; 6273 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 6274 // writeln("disable ", fd, " ", captureReads, " ", captureWrites); 6275 ev.data.fd = fd; 6276 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 6277 } 6278 } 6279 6280 version(with_eventloop) {} else 6281 /// 6282 void dispose() { 6283 if(enabled) 6284 disable(); 6285 if(fd != -1) 6286 mapping.remove(fd); 6287 fd = -1; 6288 } 6289 6290 void delegate(int, bool, bool) onReady; 6291 6292 version(with_eventloop) 6293 void readyel() { 6294 onReady(fd, true, true); 6295 } 6296 6297 void ready(uint flags) { 6298 version(linux) { 6299 static import ep = core.sys.linux.epoll; 6300 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 6301 } else { 6302 import core.sys.posix.poll; 6303 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 6304 } 6305 } 6306 6307 void hup(uint flags) { 6308 if(onHup) 6309 onHup(); 6310 } 6311 6312 void delegate() onHup; 6313 6314 int fd = -1; 6315 private bool enabled; 6316 __gshared PosixFdReader[int] mapping; 6317 } 6318 6319 // basic functions to access the clipboard 6320 /+ 6321 6322 6323 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 6324 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 6325 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 6326 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 6327 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx 6328 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 6329 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx 6330 6331 +/ 6332 6333 /++ 6334 this does a delegate because it is actually an async call on X... 6335 the receiver may never be called if the clipboard is empty or unavailable 6336 gets plain text from the clipboard. 6337 +/ 6338 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) @system { 6339 version(Windows) { 6340 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 6341 if(OpenClipboard(hwndOwner) == 0) 6342 throw new WindowsApiException("OpenClipboard", GetLastError()); 6343 scope(exit) 6344 CloseClipboard(); 6345 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat 6346 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 6347 6348 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 6349 scope(exit) 6350 GlobalUnlock(dataHandle); 6351 6352 // FIXME: CR/LF conversions 6353 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 6354 int len = 0; 6355 auto d = data; 6356 while(*d) { 6357 d++; 6358 len++; 6359 } 6360 string s; 6361 s.reserve(len); 6362 foreach(dchar ch; data[0 .. len]) { 6363 s ~= ch; 6364 } 6365 receiver(s); 6366 } 6367 } 6368 } else version(X11) { 6369 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 6370 } else version(OSXCocoa) { 6371 throw new NotYetImplementedException(); 6372 } else static assert(0); 6373 } 6374 6375 // FIXME: a clipboard listener might be cool btw 6376 6377 /++ 6378 this does a delegate because it is actually an async call on X... 6379 the receiver may never be called if the clipboard is empty or unavailable 6380 gets image from the clipboard. 6381 6382 templated because it introduces an optional dependency on arsd.bmp 6383 +/ 6384 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) { 6385 version(Windows) { 6386 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 6387 if(OpenClipboard(hwndOwner) == 0) 6388 throw new WindowsApiException("OpenClipboard", GetLastError()); 6389 scope(exit) 6390 CloseClipboard(); 6391 if(auto dataHandle = GetClipboardData(CF_DIBV5)) { 6392 if(auto data = cast(ubyte*) GlobalLock(dataHandle)) { 6393 scope(exit) 6394 GlobalUnlock(dataHandle); 6395 6396 auto len = GlobalSize(dataHandle); 6397 6398 import arsd.bmp; 6399 auto img = readBmp(data[0 .. len], false); 6400 receiver(img); 6401 } 6402 } 6403 } else version(X11) { 6404 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 6405 } else version(OSXCocoa) { 6406 throw new NotYetImplementedException(); 6407 } else static assert(0); 6408 } 6409 6410 /// Copies some text to the clipboard. 6411 void setClipboardText(SimpleWindow clipboardOwner, string text) { 6412 assert(clipboardOwner !is null); 6413 version(Windows) { 6414 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6415 throw new WindowsApiException("OpenClipboard", GetLastError()); 6416 scope(exit) 6417 CloseClipboard(); 6418 EmptyClipboard(); 6419 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6420 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 6421 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6422 if(auto data = cast(wchar*) GlobalLock(handle)) { 6423 auto slice = data[0 .. sz]; 6424 scope(failure) 6425 GlobalUnlock(handle); 6426 6427 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6428 6429 GlobalUnlock(handle); 6430 SetClipboardData(CF_UNICODETEXT, handle); 6431 } 6432 } else version(X11) { 6433 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 6434 } else version(OSXCocoa) { 6435 throw new NotYetImplementedException(); 6436 } else static assert(0); 6437 } 6438 6439 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) { 6440 assert(clipboardOwner !is null); 6441 version(Windows) { 6442 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6443 throw new WindowsApiException("OpenClipboard", GetLastError()); 6444 scope(exit) 6445 CloseClipboard(); 6446 EmptyClipboard(); 6447 6448 6449 import arsd.bmp; 6450 ubyte[] mdata; 6451 mdata.reserve(img.width * img.height); 6452 void sink(ubyte b) { 6453 mdata ~= b; 6454 } 6455 writeBmpIndirect(img, &sink, false); 6456 6457 auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length); 6458 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6459 if(auto data = cast(ubyte*) GlobalLock(handle)) { 6460 auto slice = data[0 .. mdata.length]; 6461 scope(failure) 6462 GlobalUnlock(handle); 6463 6464 slice[] = mdata[]; 6465 6466 GlobalUnlock(handle); 6467 SetClipboardData(CF_DIB, handle); 6468 } 6469 } else version(X11) { 6470 static class X11SetSelectionHandler_Image : X11SetSelectionHandler { 6471 mixin X11SetSelectionHandler_Basics; 6472 private const(ubyte)[] mdata; 6473 private const(ubyte)[] mdata_original; 6474 this(MemoryImage img) { 6475 import arsd.bmp; 6476 6477 mdata.reserve(img.width * img.height); 6478 void sink(ubyte b) { 6479 mdata ~= b; 6480 } 6481 writeBmpIndirect(img, &sink, true); 6482 6483 mdata_original = mdata; 6484 } 6485 6486 Atom[] availableFormats() { 6487 auto display = XDisplayConnection.get; 6488 return [ 6489 GetAtom!"image/bmp"(display), 6490 GetAtom!"TARGETS"(display) 6491 ]; 6492 } 6493 6494 ubyte[] getData(Atom format, return scope ubyte[] data) { 6495 if(mdata.length < data.length) { 6496 data[0 .. mdata.length] = mdata[]; 6497 auto ret = data[0 .. mdata.length]; 6498 mdata = mdata[$..$]; 6499 return ret; 6500 } else { 6501 data[] = mdata[0 .. data.length]; 6502 mdata = mdata[data.length .. $]; 6503 return data[]; 6504 } 6505 } 6506 6507 void done() { 6508 mdata = mdata_original; 6509 } 6510 } 6511 6512 setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img)); 6513 } else version(OSXCocoa) { 6514 throw new NotYetImplementedException(); 6515 } else static assert(0); 6516 } 6517 6518 6519 version(X11) { 6520 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 6521 6522 private __gshared Atom*[] interredAtoms; // for discardAndRecreate 6523 6524 // FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all. 6525 /// Platform-specific for X11. 6526 /// History: On February 21, 2021, I changed the default value of `create` to be true. 6527 @property Atom GetAtom(string name, bool create = true)(Display* display) { 6528 __gshared static Atom a; 6529 if(!a) { 6530 a = XInternAtom(display, name, !create); 6531 // FIXME: might need to synchronize this and attach it to the actual object 6532 interredAtoms ~= &a; 6533 } 6534 if(a == None) 6535 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 6536 return a; 6537 } 6538 6539 /// Platform-specific for X11 - gets atom names as a string. 6540 string getAtomName(Atom atom, Display* display) { 6541 auto got = XGetAtomName(display, atom); 6542 scope(exit) XFree(got); 6543 import core.stdc.string; 6544 string s = got[0 .. strlen(got)].idup; 6545 return s; 6546 } 6547 6548 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later. 6549 void setPrimarySelection(SimpleWindow window, string text) { 6550 setX11Selection!"PRIMARY"(window, text); 6551 } 6552 6553 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later. 6554 void setSecondarySelection(SimpleWindow window, string text) { 6555 setX11Selection!"SECONDARY"(window, text); 6556 } 6557 6558 interface X11SetSelectionHandler { 6559 // should include TARGETS right now 6560 Atom[] availableFormats(); 6561 // Return the slice of data you filled, empty slice if done. 6562 // this is to support the incremental thing 6563 ubyte[] getData(Atom format, return scope ubyte[] data); 6564 6565 void done(); 6566 6567 void handleRequest(XEvent); 6568 6569 bool matchesIncr(Window, Atom); 6570 void sendMoreIncr(XPropertyEvent*); 6571 } 6572 6573 mixin template X11SetSelectionHandler_Basics() { 6574 Window incrWindow; 6575 Atom incrAtom; 6576 Atom selectionAtom; 6577 Atom formatAtom; 6578 ubyte[] toSend; 6579 bool matchesIncr(Window w, Atom a) { 6580 return incrAtom && incrAtom == a && w == incrWindow; 6581 } 6582 void sendMoreIncr(XPropertyEvent* event) { 6583 auto display = XDisplayConnection.get; 6584 6585 XChangeProperty (display, 6586 incrWindow, 6587 incrAtom, 6588 formatAtom, 6589 8 /* bits */, PropModeReplace, 6590 toSend.ptr, cast(int) toSend.length); 6591 6592 if(toSend.length != 0) { 6593 toSend = this.getData(formatAtom, toSend[]); 6594 } else { 6595 this.done(); 6596 incrWindow = None; 6597 incrAtom = None; 6598 selectionAtom = None; 6599 formatAtom = None; 6600 toSend = null; 6601 } 6602 } 6603 void handleRequest(XEvent ev) { 6604 6605 auto display = XDisplayConnection.get; 6606 6607 XSelectionRequestEvent* event = &ev.xselectionrequest; 6608 XSelectionEvent selectionEvent; 6609 selectionEvent.type = EventType.SelectionNotify; 6610 selectionEvent.display = event.display; 6611 selectionEvent.requestor = event.requestor; 6612 selectionEvent.selection = event.selection; 6613 selectionEvent.time = event.time; 6614 selectionEvent.target = event.target; 6615 6616 bool supportedType() { 6617 foreach(t; this.availableFormats()) 6618 if(t == event.target) 6619 return true; 6620 return false; 6621 } 6622 6623 if(event.property == None) { 6624 selectionEvent.property = event.target; 6625 6626 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6627 XFlush(display); 6628 } if(event.target == GetAtom!"TARGETS"(display)) { 6629 /* respond with the supported types */ 6630 auto tlist = this.availableFormats(); 6631 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length); 6632 selectionEvent.property = event.property; 6633 6634 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6635 XFlush(display); 6636 } else if(supportedType()) { 6637 auto buffer = new ubyte[](1024 * 64); 6638 auto toSend = this.getData(event.target, buffer[]); 6639 6640 if(toSend.length < 32 * 1024) { 6641 // small enough to send directly... 6642 selectionEvent.property = event.property; 6643 XChangeProperty (display, 6644 selectionEvent.requestor, 6645 selectionEvent.property, 6646 event.target, 6647 8 /* bits */, 0 /* PropModeReplace */, 6648 toSend.ptr, cast(int) toSend.length); 6649 6650 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6651 XFlush(display); 6652 } else { 6653 // large, let's send incrementally 6654 arch_ulong l = toSend.length; 6655 6656 // if I wanted other events from this window don't want to clear that out.... 6657 XWindowAttributes xwa; 6658 XGetWindowAttributes(display, selectionEvent.requestor, &xwa); 6659 6660 XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask)); 6661 6662 incrWindow = event.requestor; 6663 incrAtom = event.property; 6664 formatAtom = event.target; 6665 selectionAtom = event.selection; 6666 this.toSend = toSend; 6667 6668 selectionEvent.property = event.property; 6669 XChangeProperty (display, 6670 selectionEvent.requestor, 6671 selectionEvent.property, 6672 GetAtom!"INCR"(display), 6673 32 /* bits */, PropModeReplace, 6674 &l, 1); 6675 6676 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6677 XFlush(display); 6678 } 6679 //if(after) 6680 //after(); 6681 } else { 6682 debug(sdpy_clip) { 6683 writeln("Unsupported data ", getAtomName(event.target, display)); 6684 } 6685 selectionEvent.property = None; // I don't know how to handle this type... 6686 XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent); 6687 XFlush(display); 6688 } 6689 } 6690 } 6691 6692 class X11SetSelectionHandler_Text : X11SetSelectionHandler { 6693 mixin X11SetSelectionHandler_Basics; 6694 private const(ubyte)[] text; 6695 private const(ubyte)[] text_original; 6696 this(string text) { 6697 this.text = cast(const ubyte[]) text; 6698 this.text_original = this.text; 6699 } 6700 Atom[] availableFormats() { 6701 auto display = XDisplayConnection.get; 6702 return [ 6703 GetAtom!"UTF8_STRING"(display), 6704 GetAtom!"text/plain"(display), 6705 XA_STRING, 6706 GetAtom!"TARGETS"(display) 6707 ]; 6708 } 6709 6710 ubyte[] getData(Atom format, return scope ubyte[] data) { 6711 if(text.length < data.length) { 6712 data[0 .. text.length] = text[]; 6713 return data[0 .. text.length]; 6714 } else { 6715 data[] = text[0 .. data.length]; 6716 text = text[data.length .. $]; 6717 return data[]; 6718 } 6719 } 6720 6721 void done() { 6722 text = text_original; 6723 } 6724 } 6725 6726 /// 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?!) 6727 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 6728 setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after); 6729 } 6730 6731 private __gshared bool mightShortCircuitClipboard; 6732 6733 void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) { 6734 assert(window !is null); 6735 6736 auto display = XDisplayConnection.get(); 6737 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6738 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6739 else Atom a = GetAtom!atomName(display); 6740 6741 if(mightShortCircuitClipboard) 6742 if(auto ptr = a in window.impl.setSelectionHandlers) { 6743 // we already have it, don't even need to inform the X server 6744 // sdpyPrintDebugString("short circuit in set"); 6745 *ptr = data; 6746 return; 6747 } 6748 6749 // we don't have it, tell X we want it 6750 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 6751 window.impl.setSelectionHandlers[a] = data; 6752 mightShortCircuitClipboard = true; 6753 } 6754 6755 /+ 6756 /++ 6757 History: 6758 Added September 28, 2024 6759 +/ 6760 bool hasX11Selection(string atomName)(SimpleWindow window) { 6761 auto display = XDisplayConnection.get(); 6762 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6763 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6764 else Atom a = GetAtom!atomName(display); 6765 6766 if(a in window.impl.setSelectionHandlers) 6767 return true; 6768 else 6769 return false; 6770 } 6771 +/ 6772 6773 /// 6774 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 6775 getX11Selection!"PRIMARY"(window, handler); 6776 } 6777 6778 // added July 28, 2020 6779 // undocumented as experimental tho 6780 interface X11GetSelectionHandler { 6781 void handleData(Atom target, in ubyte[] data); 6782 Atom findBestFormat(Atom[] answer); 6783 6784 void prepareIncremental(Window, Atom); 6785 bool matchesIncr(Window, Atom); 6786 void handleIncrData(Atom, in ubyte[] data); 6787 } 6788 6789 mixin template X11GetSelectionHandler_Basics() { 6790 Window incrWindow; 6791 Atom incrAtom; 6792 6793 void prepareIncremental(Window w, Atom a) { 6794 incrWindow = w; 6795 incrAtom = a; 6796 } 6797 bool matchesIncr(Window w, Atom a) { 6798 return incrWindow == w && incrAtom == a; 6799 } 6800 6801 Atom incrFormatAtom; 6802 ubyte[] incrData; 6803 void handleIncrData(Atom format, in ubyte[] data) { 6804 incrFormatAtom = format; 6805 6806 if(data.length) 6807 incrData ~= data; 6808 else 6809 handleData(incrFormatAtom, incrData); 6810 6811 } 6812 } 6813 6814 static class X11GetSelectionHandler_Text : X11GetSelectionHandler { 6815 this(void delegate(in char[]) handler) { 6816 this.handler = handler; 6817 } 6818 6819 mixin X11GetSelectionHandler_Basics; 6820 6821 void delegate(in char[]) handler; 6822 6823 void handleData(Atom target, in ubyte[] data) { 6824 // import std.stdio; writeln(target, " ", data); 6825 if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 6826 handler(cast(const char[]) data); 6827 else if(target == None && data is null) 6828 handler(null); // no suitable selection exists 6829 } 6830 6831 Atom findBestFormat(Atom[] answer) { 6832 Atom best = None; 6833 foreach(option; answer) { 6834 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 6835 best = option; 6836 break; 6837 } else if(option == XA_STRING) { 6838 best = option; 6839 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 6840 best = option; 6841 } 6842 } 6843 return best; 6844 } 6845 } 6846 6847 /// 6848 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) { 6849 assert(window !is null); 6850 6851 auto display = XDisplayConnection.get(); 6852 6853 static if (atomName == "PRIMARY") Atom atom = XA_PRIMARY; 6854 else static if (atomName == "SECONDARY") Atom atom = XA_SECONDARY; 6855 else Atom atom = GetAtom!atomName(display); 6856 6857 if(mightShortCircuitClipboard) 6858 if(auto ptr = atom in window.impl.setSelectionHandlers) { 6859 if(auto txt = (cast(X11SetSelectionHandler_Text) *ptr)) { 6860 // we already have it! short circuit everything 6861 6862 // sdpyPrintDebugString("short circuit in get"); 6863 handler(cast(char[]) txt.text_original); 6864 return; 6865 } 6866 } 6867 6868 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler); 6869 6870 auto target = GetAtom!"TARGETS"(display); 6871 6872 // SDD_DATA is "simpledisplay.d data" 6873 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp); 6874 } 6875 6876 /// Gets the image on the clipboard, if there is one. Added July 2020. 6877 /// only supports bmps. using this function will import arsd.bmp. 6878 void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) { 6879 assert(window !is null); 6880 6881 auto display = XDisplayConnection.get(); 6882 auto atom = GetAtom!atomName(display); 6883 6884 static class X11GetSelectionHandler_Image : X11GetSelectionHandler { 6885 this(void delegate(MemoryImage) handler) { 6886 this.handler = handler; 6887 } 6888 6889 mixin X11GetSelectionHandler_Basics; 6890 6891 void delegate(MemoryImage) handler; 6892 6893 void handleData(Atom target, in ubyte[] data) { 6894 if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6895 import arsd.bmp; 6896 handler(readBmp(data)); 6897 } 6898 } 6899 6900 Atom findBestFormat(Atom[] answer) { 6901 Atom best = None; 6902 foreach(option; answer) { 6903 if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6904 best = option; 6905 } 6906 } 6907 return best; 6908 } 6909 6910 } 6911 6912 6913 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler); 6914 6915 auto target = GetAtom!"TARGETS"(display); 6916 6917 // SDD_DATA is "simpledisplay.d data" 6918 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 6919 } 6920 6921 6922 /// 6923 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 6924 Atom actualType; 6925 int actualFormat; 6926 arch_ulong actualItems; 6927 arch_ulong bytesRemaining; 6928 void* data; 6929 6930 auto display = XDisplayConnection.get(); 6931 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 6932 if(actualFormat == 0) 6933 return null; 6934 else { 6935 int byteLength; 6936 if(actualFormat == 32) { 6937 // 32 means it is a C long... which is variable length 6938 actualFormat = cast(int) arch_long.sizeof * 8; 6939 } 6940 6941 // then it is just a bit count 6942 byteLength = cast(int) (actualItems * actualFormat / 8); 6943 6944 auto d = new ubyte[](byteLength); 6945 d[] = cast(ubyte[]) data[0 .. byteLength]; 6946 XFree(data); 6947 return d; 6948 } 6949 } 6950 return null; 6951 } 6952 6953 /* defined in the systray spec */ 6954 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 6955 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 6956 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 6957 6958 6959 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 6960 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 6961 public class GlobalHotkey { 6962 KeyEvent key; 6963 void delegate () handler; 6964 6965 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 6966 6967 /// Create from initialzed KeyEvent object 6968 this (KeyEvent akey, void delegate () ahandler=null) { 6969 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 6970 key = akey; 6971 handler = ahandler; 6972 } 6973 6974 /// Create from emacs-like key name ("C-M-Y", etc.) 6975 this (const(char)[] akey, void delegate () ahandler=null) { 6976 key = KeyEvent.parse(akey); 6977 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 6978 handler = ahandler; 6979 } 6980 6981 } 6982 6983 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6984 //conwriteln("failed to grab key"); 6985 GlobalHotkeyManager.ghfailed = true; 6986 return 0; 6987 } 6988 6989 private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6990 Image.impl.xshmfailed = true; 6991 return 0; 6992 } 6993 6994 private __gshared int errorHappened; 6995 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6996 import core.stdc.stdio; 6997 char[265] buffer; 6998 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 6999 debug printf("X Error %d: %s / Serial: %lld, Opcode: %d.%d, XID: 0x%llx\n", evt.error_code, buffer.ptr, evt.serial, evt.request_code, evt.minor_code, evt.resourceid); 7000 errorHappened = true; 7001 return 0; 7002 } 7003 7004 /++ 7005 Global hotkey manager. It contains static methods to manage global hotkeys. 7006 7007 --- 7008 try { 7009 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 7010 } catch (Exception e) { 7011 conwriteln("ERROR registering hotkey!"); 7012 } 7013 EventLoop.get.run(); 7014 --- 7015 7016 The key strings are based on Emacs. In practical terms, 7017 `M` means `alt` and `H` means the Windows logo key. `C` 7018 is `ctrl`. 7019 7020 $(WARNING 7021 This is X-specific right now. If you are on 7022 Windows, try [registerHotKey] instead. 7023 7024 We will probably merge these into a single 7025 interface later. 7026 ) 7027 +/ 7028 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 7029 version(X11) { 7030 void recreateAfterDisconnect() { 7031 throw new Exception("NOT IMPLEMENTED"); 7032 } 7033 void discardConnectionState() { 7034 throw new Exception("NOT IMPLEMENTED"); 7035 } 7036 } 7037 7038 private static immutable uint[8] masklist = [ 0, 7039 KeyOrButtonMask.LockMask, 7040 KeyOrButtonMask.Mod2Mask, 7041 KeyOrButtonMask.Mod3Mask, 7042 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 7043 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 7044 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 7045 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 7046 ]; 7047 private __gshared GlobalHotkeyManager ghmanager; 7048 private __gshared bool ghfailed = false; 7049 7050 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 7051 if (modmask == 0) return false; 7052 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 7053 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 7054 return true; 7055 } 7056 7057 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 7058 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 7059 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 7060 return modmask; 7061 } 7062 7063 private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) { 7064 uint keycode = cast(uint)ke.key; 7065 auto dpy = XDisplayConnection.get; 7066 return XKeysymToKeycode(dpy, keycode); 7067 } 7068 7069 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 7070 7071 private __gshared GlobalHotkey[ulong] globalHotkeyList; 7072 7073 NativeEventHandler getNativeEventHandler () { 7074 return delegate int (XEvent e) { 7075 if (e.type != EventType.KeyPress) return 1; 7076 auto kev = cast(const(XKeyEvent)*)&e; 7077 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 7078 if (auto ghkp = hash in globalHotkeyList) { 7079 try { 7080 ghkp.doHandle(); 7081 } catch (Exception e) { 7082 import core.stdc.stdio : stderr, fprintf; 7083 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 7084 } 7085 } 7086 return 1; 7087 }; 7088 } 7089 7090 private this () { 7091 auto dpy = XDisplayConnection.get; 7092 auto root = RootWindow(dpy, DefaultScreen(dpy)); 7093 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 7094 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 7095 } 7096 7097 /// Register new global hotkey with initialized `GlobalHotkey` object. 7098 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 7099 static void register (GlobalHotkey gh) { 7100 if (gh is null) return; 7101 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 7102 7103 auto dpy = XDisplayConnection.get; 7104 immutable keycode = keyEvent2KeyCode(gh.key); 7105 7106 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 7107 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 7108 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 7109 XSync(dpy, 0/*False*/); 7110 7111 Window root = RootWindow(dpy, DefaultScreen(dpy)); 7112 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 7113 ghfailed = false; 7114 foreach (immutable uint ormask; masklist[]) { 7115 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 7116 } 7117 XSync(dpy, 0/*False*/); 7118 XSetErrorHandler(savedErrorHandler); 7119 7120 if (ghfailed) { 7121 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 7122 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 7123 XSync(dpy, 0/*False*/); 7124 XSetErrorHandler(savedErrorHandler); 7125 throw new Exception("cannot register global hotkey"); 7126 } 7127 7128 globalHotkeyList[hash] = gh; 7129 } 7130 7131 /// Ditto 7132 static void register (const(char)[] akey, void delegate () ahandler) { 7133 register(new GlobalHotkey(akey, ahandler)); 7134 } 7135 7136 private static void removeByHash (ulong hash) { 7137 if (auto ghp = hash in globalHotkeyList) { 7138 auto dpy = XDisplayConnection.get; 7139 immutable keycode = keyEvent2KeyCode(ghp.key); 7140 Window root = RootWindow(dpy, DefaultScreen(dpy)); 7141 XSync(dpy, 0/*False*/); 7142 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 7143 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 7144 XSync(dpy, 0/*False*/); 7145 XSetErrorHandler(savedErrorHandler); 7146 globalHotkeyList.remove(hash); 7147 } 7148 } 7149 7150 /// Register new global hotkey with previously used `GlobalHotkey` object. 7151 /// It is safe to unregister unknown or invalid hotkey. 7152 static void unregister (GlobalHotkey gh) { 7153 //TODO: add second AA for faster search? prolly doesn't worth it. 7154 if (gh is null) return; 7155 foreach (const ref kv; globalHotkeyList.byKeyValue) { 7156 if (kv.value is gh) { 7157 removeByHash(kv.key); 7158 return; 7159 } 7160 } 7161 } 7162 7163 /// Ditto. 7164 static void unregister (const(char)[] key) { 7165 auto kev = KeyEvent.parse(key); 7166 immutable keycode = keyEvent2KeyCode(kev); 7167 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 7168 } 7169 } 7170 } 7171 7172 version(Windows) { 7173 /++ 7174 See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications. 7175 7176 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). 7177 +/ 7178 void sendSyntheticInput(wstring s) { 7179 INPUT[] inputs; 7180 inputs.reserve(s.length * 2); 7181 7182 foreach(wchar c; s) { 7183 INPUT input; 7184 input.type = INPUT_KEYBOARD; 7185 input.ki.wScan = c; 7186 input.ki.dwFlags = KEYEVENTF_UNICODE; 7187 inputs ~= input; 7188 7189 input.ki.dwFlags |= KEYEVENTF_KEYUP; 7190 inputs ~= input; 7191 } 7192 7193 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 7194 throw new WindowsApiException("SendInput", GetLastError()); 7195 } 7196 7197 } 7198 7199 7200 // global hotkey helper function 7201 7202 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these. 7203 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) @system { 7204 __gshared int hotkeyId = 0; 7205 int id = ++hotkeyId; 7206 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 7207 throw new Exception("RegisterHotKey"); 7208 7209 __gshared void delegate()[WPARAM][HWND] handlers; 7210 7211 handlers[window.impl.hwnd][id] = handler; 7212 7213 int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler; 7214 7215 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 7216 switch(msg) { 7217 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 7218 case WM_HOTKEY: 7219 if(auto list = hwnd in handlers) { 7220 if(auto h = wParam in *list) { 7221 (*h)(); 7222 return 0; 7223 } 7224 } 7225 goto default; 7226 default: 7227 } 7228 if(oldHandler) 7229 return oldHandler(hwnd, msg, wParam, lParam, mustReturn); 7230 return 1; // pass it on 7231 }; 7232 7233 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 7234 oldHandler = window.handleNativeEvent; 7235 window.handleNativeEvent = nativeEventHandler; 7236 } 7237 7238 return id; 7239 } 7240 7241 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey]. 7242 void unregisterHotKey(SimpleWindow window, int id) { 7243 if(!UnregisterHotKey(window.impl.hwnd, id)) 7244 throw new WindowsApiException("UnregisterHotKey", GetLastError()); 7245 } 7246 } 7247 7248 version (X11) { 7249 pragma(lib, "dl"); 7250 import core.sys.posix.dlfcn; 7251 } 7252 7253 /++ 7254 Allows for sending synthetic input to the X server via the Xtst 7255 extension or on Windows using SendInput. 7256 7257 Please remember user input is meant to be user - don't use this 7258 if you have some other alternative! 7259 7260 History: 7261 Added May 17, 2020 with the X implementation. 7262 7263 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.) 7264 Bugs: 7265 All methods on OSX Cocoa will throw not yet implemented exceptions. 7266 +/ 7267 struct SyntheticInput { 7268 @disable this(); 7269 7270 private int* refcount; 7271 7272 version(X11) { 7273 private void* lib; 7274 7275 private extern(C) { 7276 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 7277 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 7278 } 7279 } 7280 7281 /// The dummy param must be 0. 7282 this(int dummy) { 7283 version(X11) { 7284 lib = dlopen("libXtst.so", RTLD_NOW); 7285 if(lib is null) 7286 throw new Exception("cannot load xtest lib extension"); 7287 scope(failure) 7288 dlclose(lib); 7289 7290 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 7291 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 7292 7293 if(XTestFakeKeyEvent is null) 7294 throw new Exception("No XTestFakeKeyEvent"); 7295 if(XTestFakeButtonEvent is null) 7296 throw new Exception("No XTestFakeButtonEvent"); 7297 } 7298 7299 refcount = new int; 7300 *refcount = 1; 7301 } 7302 7303 this(this) { 7304 if(refcount) 7305 *refcount += 1; 7306 } 7307 7308 ~this() { 7309 if(refcount) { 7310 *refcount -= 1; 7311 if(*refcount == 0) 7312 // I commented this because if I close the lib before 7313 // XCloseDisplay, it is liable to segfault... so just 7314 // gonna keep it loaded if it is loaded, no big deal 7315 // anyway. 7316 {} // dlclose(lib); 7317 } 7318 } 7319 7320 /++ 7321 Simulates typing a string into the keyboard. 7322 7323 Bugs: 7324 On X11, this ONLY works with basic ascii! On Windows, it can handle more. 7325 7326 Not implemented except on Windows and X11. 7327 +/ 7328 void sendSyntheticInput(string s) { 7329 version(Windows) { 7330 INPUT[] inputs; 7331 inputs.reserve(s.length * 2); 7332 7333 auto ei = GetMessageExtraInfo(); 7334 7335 foreach(wchar c; s) { 7336 INPUT input; 7337 input.type = INPUT_KEYBOARD; 7338 input.ki.wScan = c; 7339 input.ki.dwFlags = KEYEVENTF_UNICODE; 7340 input.ki.dwExtraInfo = ei; 7341 inputs ~= input; 7342 7343 input.ki.dwFlags |= KEYEVENTF_KEYUP; 7344 inputs ~= input; 7345 } 7346 7347 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 7348 throw new WindowsApiException("SendInput", GetLastError()); 7349 } 7350 } else version(X11) { 7351 int delay = 0; 7352 foreach(ch; s) { 7353 pressKey(cast(Key) ch, true, delay); 7354 pressKey(cast(Key) ch, false, delay); 7355 delay += 5; 7356 } 7357 } else throw new NotYetImplementedException(); 7358 } 7359 7360 /++ 7361 Sends a fake press or release key event. 7362 7363 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 7364 7365 Bugs: 7366 The `delay` parameter is not implemented yet on Windows. 7367 7368 Not implemented except on Windows and X11. 7369 +/ 7370 void pressKey(Key key, bool pressed, int delay = 0) { 7371 version(Windows) { 7372 INPUT input; 7373 input.type = INPUT_KEYBOARD; 7374 input.ki.wVk = cast(ushort) key; 7375 7376 input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP; 7377 input.ki.dwExtraInfo = GetMessageExtraInfo(); 7378 7379 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7380 throw new WindowsApiException("SendInput", GetLastError()); 7381 } 7382 } else version(X11) { 7383 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 7384 } else throw new NotYetImplementedException(); 7385 } 7386 7387 /++ 7388 Sends a fake mouse button press or release event. 7389 7390 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 7391 7392 `pressed` param must be `true` if button is `wheelUp` or `wheelDown`. 7393 7394 Bugs: 7395 The `delay` parameter is not implemented yet on Windows. 7396 7397 The backButton and forwardButton will throw NotYetImplementedException on Windows. 7398 7399 All arguments will throw NotYetImplementedException on OSX Cocoa. 7400 +/ 7401 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 7402 version(Windows) { 7403 INPUT input; 7404 input.type = INPUT_MOUSE; 7405 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7406 7407 // input.mi.mouseData for a wheel event 7408 7409 switch(button) { 7410 case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break; 7411 case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break; 7412 case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break; 7413 case MouseButton.wheelUp: 7414 case MouseButton.wheelDown: 7415 input.mi.dwFlags = MOUSEEVENTF_WHEEL; 7416 input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120; 7417 break; 7418 case MouseButton.backButton: throw new NotYetImplementedException(); 7419 case MouseButton.forwardButton: throw new NotYetImplementedException(); 7420 default: 7421 } 7422 7423 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7424 throw new WindowsApiException("SendInput", GetLastError()); 7425 } 7426 } else version(X11) { 7427 int btn; 7428 7429 switch(button) { 7430 case MouseButton.left: btn = 1; break; 7431 case MouseButton.middle: btn = 2; break; 7432 case MouseButton.right: btn = 3; break; 7433 case MouseButton.wheelUp: btn = 4; break; 7434 case MouseButton.wheelDown: btn = 5; break; 7435 case MouseButton.backButton: btn = 8; break; 7436 case MouseButton.forwardButton: btn = 9; break; 7437 default: 7438 } 7439 7440 assert(btn); 7441 7442 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 7443 } else throw new NotYetImplementedException(); 7444 } 7445 7446 /// 7447 static void moveMouseArrowBy(int dx, int dy) { 7448 version(Windows) { 7449 INPUT input; 7450 input.type = INPUT_MOUSE; 7451 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7452 input.mi.dx = dx; 7453 input.mi.dy = dy; 7454 input.mi.dwFlags = MOUSEEVENTF_MOVE; 7455 7456 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7457 throw new WindowsApiException("SendInput", GetLastError()); 7458 } 7459 } else version(X11) { 7460 auto disp = XDisplayConnection.get(); 7461 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 7462 XFlush(disp); 7463 } else throw new NotYetImplementedException(); 7464 } 7465 7466 /// 7467 static void moveMouseArrowTo(int x, int y) { 7468 version(Windows) { 7469 INPUT input; 7470 input.type = INPUT_MOUSE; 7471 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7472 input.mi.dx = x; 7473 input.mi.dy = y; 7474 input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; 7475 7476 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7477 throw new WindowsApiException("SendInput", GetLastError()); 7478 } 7479 } else version(X11) { 7480 auto disp = XDisplayConnection.get(); 7481 auto root = RootWindow(disp, DefaultScreen(disp)); 7482 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 7483 XFlush(disp); 7484 } else throw new NotYetImplementedException(); 7485 } 7486 } 7487 7488 7489 7490 /++ 7491 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 7492 7493 See_Also: 7494 $(LIST 7495 *[ScreenPainter] 7496 *[ScreenPainter.rasterOp] 7497 ) 7498 +/ 7499 enum RasterOp { 7500 normal, /// Replaces the pixel. 7501 xor, /// Uses bitwise xor to draw. 7502 } 7503 7504 // being phobos-free keeps the size WAY down 7505 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 7506 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 7507 package(arsd) const(wchar)* toWStringz(string s) { 7508 wstring r; 7509 foreach(dchar c; s) 7510 r ~= c; 7511 r ~= '\0'; 7512 return r.ptr; 7513 } 7514 private string[] split(in void[] a, char c) { 7515 string[] ret; 7516 size_t previous = 0; 7517 foreach(i, char ch; cast(ubyte[]) a) { 7518 if(ch == c) { 7519 ret ~= cast(string) a[previous .. i]; 7520 previous = i + 1; 7521 } 7522 } 7523 if(previous != a.length) 7524 ret ~= cast(string) a[previous .. $]; 7525 return ret; 7526 } 7527 7528 version(without_opengl) { 7529 enum OpenGlOptions { 7530 no, 7531 } 7532 } else { 7533 /++ 7534 Determines if you want an OpenGL context created on the new window. 7535 7536 7537 See more: [#topics-3d|in the 3d topic]. 7538 7539 --- 7540 import arsd.simpledisplay; 7541 void main() { 7542 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 7543 7544 // Set up the matrix 7545 window.setAsCurrentOpenGlContext(); // make this window active 7546 7547 // This is called on each frame, we will draw our scene 7548 window.redrawOpenGlScene = delegate() { 7549 7550 }; 7551 7552 window.eventLoop(0); 7553 } 7554 --- 7555 +/ 7556 enum OpenGlOptions { 7557 no, /// No OpenGL context is created 7558 yes, /// Yes, create an OpenGL context 7559 } 7560 7561 version(X11) { 7562 static if (!SdpyIsUsingIVGLBinds) { 7563 7564 7565 struct __GLXFBConfigRec {} 7566 alias GLXFBConfig = __GLXFBConfigRec*; 7567 7568 //pragma(lib, "GL"); 7569 //pragma(lib, "GLU"); 7570 interface GLX { 7571 extern(C) nothrow @nogc { 7572 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 7573 const int *attrib_list); 7574 7575 void glXCopyContext(Display *dpy, GLXContext src, 7576 GLXContext dst, arch_ulong mask); 7577 7578 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 7579 GLXContext share_list, Bool direct); 7580 7581 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 7582 Pixmap pixmap); 7583 7584 void glXDestroyContext(Display *dpy, GLXContext ctx); 7585 7586 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 7587 7588 int glXGetConfig(Display *dpy, XVisualInfo *vis, 7589 int attrib, int *value); 7590 7591 GLXContext glXGetCurrentContext(); 7592 7593 GLXDrawable glXGetCurrentDrawable(); 7594 7595 Bool glXIsDirect(Display *dpy, GLXContext ctx); 7596 7597 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 7598 GLXContext ctx); 7599 7600 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 7601 7602 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 7603 7604 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 7605 7606 void glXUseXFont(Font font, int first, int count, int list_base); 7607 7608 void glXWaitGL(); 7609 7610 void glXWaitX(); 7611 7612 7613 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 7614 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 7615 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 7616 7617 char* glXQueryExtensionsString (Display*, int); 7618 void* glXGetProcAddress (const(char)*); 7619 7620 } 7621 } 7622 7623 version(OSX) 7624 mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx; 7625 else 7626 mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx; 7627 shared static this() { 7628 glx.loadDynamicLibrary(); 7629 } 7630 7631 alias glbindGetProcAddress = glXGetProcAddress; 7632 } 7633 } else version(Windows) { 7634 /* it is done below by interface GL */ 7635 } else 7636 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."); 7637 } 7638 7639 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 7640 alias Resizablity = Resizability; 7641 7642 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 7643 enum Resizability { 7644 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. 7645 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. 7646 /++ 7647 $(PITFALL 7648 Planned for the future but not implemented. 7649 ) 7650 7651 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. 7652 7653 History: 7654 Added November 11, 2022, but not yet implemented and may not be for some time. 7655 +/ 7656 /*@__future*/ allowResizingMaintainingAspectRatio, 7657 /++ 7658 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. 7659 7660 History: 7661 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. 7662 7663 Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all. 7664 +/ 7665 automaticallyScaleIfPossible, 7666 } 7667 /// ditto 7668 alias Resizeability = Resizability; 7669 7670 7671 /++ 7672 Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 7673 +/ 7674 enum TextAlignment : uint { 7675 Left = 0, /// 7676 Center = 1, /// 7677 Right = 2, /// 7678 7679 VerticalTop = 0, /// 7680 VerticalCenter = 4, /// 7681 VerticalBottom = 8, /// 7682 } 7683 7684 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 7685 alias Rectangle = arsd.color.Rectangle; 7686 7687 7688 /++ 7689 Keyboard press and release events. 7690 +/ 7691 struct KeyEvent { 7692 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 7693 Key key; 7694 ubyte hardwareCode; /// A platform and hardware specific code for the key 7695 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 7696 7697 deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character; 7698 7699 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 7700 7701 SimpleWindow window; /// associated Window 7702 7703 /++ 7704 A view into the upcoming buffer holding coming character events that are sent if and only if neither 7705 the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))` 7706 to predict if char events are actually coming.. 7707 7708 Only available on X systems since this information is not given ahead of time elsewhere. 7709 (Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.) 7710 7711 I'm adding this because it is useful to the terminal emulator, but given its platform specificness 7712 and potential quirks I'd recommend avoiding it. 7713 7714 History: 7715 Added April 26, 2021 (dub v9.5) 7716 +/ 7717 version(X11) 7718 dchar[] charsPossible; 7719 7720 // convert key event to simplified string representation a-la emacs 7721 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 7722 uint dpos = 0; 7723 void put (const(char)[] s...) nothrow @trusted { 7724 static if (growdest) { 7725 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 7726 } else { 7727 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 7728 } 7729 } 7730 7731 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 7732 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 7733 } 7734 7735 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 7736 7737 // put modifiers 7738 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 7739 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 7740 putMod(ModifierState.alt, Key.Alt, "Alt+"); 7741 putMod(ModifierState.windows, Key.Shift, "Windows+"); 7742 putMod(ModifierState.shift, Key.Shift, "Shift+"); 7743 7744 if (this.key) { 7745 foreach (string kn; __traits(allMembers, Key)) { 7746 if (this.key == __traits(getMember, Key, kn)) { 7747 // HACK! 7748 static if (kn == "N0") put("0"); 7749 else static if (kn == "N1") put("1"); 7750 else static if (kn == "N2") put("2"); 7751 else static if (kn == "N3") put("3"); 7752 else static if (kn == "N4") put("4"); 7753 else static if (kn == "N5") put("5"); 7754 else static if (kn == "N6") put("6"); 7755 else static if (kn == "N7") put("7"); 7756 else static if (kn == "N8") put("8"); 7757 else static if (kn == "N9") put("9"); 7758 else put(kn); 7759 return dest[0..dpos]; 7760 } 7761 } 7762 put("Unknown"); 7763 } else { 7764 if (dpos && dest[dpos-1] == '+') --dpos; 7765 } 7766 return dest[0..dpos]; 7767 } 7768 7769 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 7770 7771 /** Parse string into key name with modifiers. It accepts things like: 7772 * 7773 * C-H-1 -- emacs style (ctrl, and windows, and 1) 7774 * 7775 * Ctrl+Win+1 -- windows style 7776 * 7777 * Ctrl-Win-1 -- '-' is a valid delimiter too 7778 * 7779 * Ctrl Win 1 -- and space 7780 * 7781 * and even "Win + 1 + Ctrl". 7782 */ 7783 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 7784 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 7785 7786 // remove trailing spaces 7787 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 7788 7789 // tokens delimited by blank, '+', or '-' 7790 // null on eol 7791 const(char)[] getToken () nothrow @trusted @nogc { 7792 // remove leading spaces and delimiters 7793 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 7794 if (name.length == 0) return null; // oops, no more tokens 7795 // get token 7796 size_t epos = 0; 7797 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 7798 assert(epos > 0 && epos <= name.length); 7799 auto res = name[0..epos]; 7800 name = name[epos..$]; 7801 return res; 7802 } 7803 7804 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 7805 if (s0.length != s1.length) return false; 7806 foreach (immutable ci, char c0; s0) { 7807 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 7808 char c1 = s1[ci]; 7809 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 7810 if (c0 != c1) return false; 7811 } 7812 return true; 7813 } 7814 7815 if (ignoreModsOut !is null) *ignoreModsOut = false; 7816 if (updown !is null) *updown = -1; 7817 KeyEvent res; 7818 res.key = cast(Key)0; // just in case 7819 const(char)[] tk, tkn; // last token 7820 bool allowEmascStyle = true; 7821 bool ignoreModifiers = false; 7822 tokenloop: for (;;) { 7823 tk = tkn; 7824 tkn = getToken(); 7825 //k8: yay, i took "Bloody Mess" trait from Fallout! 7826 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 7827 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 7828 if (allowEmascStyle && tkn.length != 0) { 7829 if (tk.length == 1) { 7830 char mdc = tk[0]; 7831 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 7832 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7833 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7834 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7835 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7836 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 7837 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 7838 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 7839 } 7840 } 7841 allowEmascStyle = false; 7842 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7843 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7844 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7845 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7846 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 7847 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 7848 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 7849 if (tk.length == 0) continue; 7850 // try key name 7851 if (res.key == 0) { 7852 // little hack 7853 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 7854 final switch (tk[0]) { 7855 case '0': tk = "N0"; break; 7856 case '1': tk = "N1"; break; 7857 case '2': tk = "N2"; break; 7858 case '3': tk = "N3"; break; 7859 case '4': tk = "N4"; break; 7860 case '5': tk = "N5"; break; 7861 case '6': tk = "N6"; break; 7862 case '7': tk = "N7"; break; 7863 case '8': tk = "N8"; break; 7864 case '9': tk = "N9"; break; 7865 } 7866 } 7867 foreach (string kn; __traits(allMembers, Key)) { 7868 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 7869 } 7870 } 7871 // unknown or duplicate key name, get out of here 7872 break; 7873 } 7874 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 7875 return res; // something 7876 } 7877 7878 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 7879 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 7880 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 7881 if (kk == k) { mask |= mst; kk = cast(Key)0; } 7882 } 7883 bool ignoreMods; 7884 int updown; 7885 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 7886 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 7887 if (this.key != ke.key) { 7888 // things like "ctrl+alt" are complicated 7889 uint tkm = this.modifierState&modmask; 7890 uint kkm = ke.modifierState&modmask; 7891 Key tk = this.key; 7892 // ke 7893 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 7894 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 7895 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 7896 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 7897 // this 7898 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 7899 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 7900 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 7901 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 7902 return (tk == ke.key && tkm == kkm); 7903 } 7904 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 7905 } 7906 } 7907 7908 /// Sets the application name. 7909 @property string ApplicationName(string name) { 7910 return _applicationName = name; 7911 } 7912 7913 string _applicationName; 7914 7915 /// ditto 7916 @property string ApplicationName() { 7917 if(_applicationName is null) { 7918 import core.runtime; 7919 return Runtime.args[0]; 7920 } 7921 return _applicationName; 7922 } 7923 7924 7925 /// Type of a [MouseEvent]. 7926 enum MouseEventType : int { 7927 motion = 0, /// The mouse moved inside the window 7928 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 7929 buttonReleased = 2, /// A mouse button was released 7930 } 7931 7932 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 7933 /++ 7934 Listen for this on your event listeners if you are interested in mouse action. 7935 7936 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. 7937 7938 Examples: 7939 7940 This will draw boxes on the window with the mouse as you hold the left button. 7941 --- 7942 import arsd.simpledisplay; 7943 7944 void main() { 7945 auto window = new SimpleWindow(); 7946 7947 window.eventLoop(0, 7948 (MouseEvent ev) { 7949 if(ev.modifierState & ModifierState.leftButtonDown) { 7950 auto painter = window.draw(); 7951 painter.fillColor = Color.red; 7952 painter.outlineColor = Color.black; 7953 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 7954 } 7955 } 7956 ); 7957 } 7958 --- 7959 +/ 7960 struct MouseEvent { 7961 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 7962 7963 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. 7964 int y; /// Current Y position of the cursor when the event fired. 7965 7966 int dx; /// Change in X position since last report 7967 int dy; /// Change in Y position since last report 7968 7969 MouseButton button; /// See [MouseButton] 7970 int modifierState; /// See [ModifierState] 7971 7972 version(X11) 7973 private Time timestamp; 7974 7975 /// Returns a linear representation of mouse button, 7976 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 7977 /// 7978 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 7979 @property ubyte buttonLinear() const { 7980 import core.bitop; 7981 if(button == 0) 7982 return 0; 7983 return (bsf(button) + 1) & 0b1111; 7984 } 7985 7986 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 7987 7988 SimpleWindow window; /// The window in which the event happened. 7989 7990 Point globalCoordinates() { 7991 Point p; 7992 if(window is null) 7993 throw new Exception("wtf"); 7994 static if(UsingSimpledisplayX11) { 7995 Window child; 7996 XTranslateCoordinates( 7997 XDisplayConnection.get, 7998 window.impl.window, 7999 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 8000 x, y, &p.x, &p.y, &child); 8001 return p; 8002 } else version(Windows) { 8003 POINT[1] points; 8004 points[0].x = x; 8005 points[0].y = y; 8006 MapWindowPoints( 8007 window.impl.hwnd, 8008 null, 8009 points.ptr, 8010 points.length 8011 ); 8012 p.x = points[0].x; 8013 p.y = points[0].y; 8014 8015 return p; 8016 } else version(OSXCocoa) { 8017 throw new NotYetImplementedException(); 8018 } else static assert(0); 8019 } 8020 8021 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 8022 8023 /** 8024 can contain emacs-like modifier prefix 8025 case-insensitive names: 8026 lmbX/leftX 8027 rmbX/rightX 8028 mmbX/middleX 8029 wheelX 8030 motion (no prefix allowed) 8031 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 8032 */ 8033 static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 8034 if (str.length == 0) return false; // just in case 8035 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 8036 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 8037 auto anchor = str; 8038 uint mods = 0; // uint.max == any 8039 // interesting bits in kmod 8040 uint kmodmask = 8041 ModifierState.shift| 8042 ModifierState.ctrl| 8043 ModifierState.alt| 8044 ModifierState.windows| 8045 ModifierState.leftButtonDown| 8046 ModifierState.middleButtonDown| 8047 ModifierState.rightButtonDown| 8048 0; 8049 uint lastButt = uint.max; // otherwise, bit 31 means "down" 8050 bool wasButtons = false; 8051 while (str.length) { 8052 if (str.ptr[0] <= ' ') { 8053 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 8054 continue; 8055 } 8056 // one-letter modifier? 8057 if (str.length >= 2 && str.ptr[1] == '-') { 8058 switch (str.ptr[0]) { 8059 case '*': // "any" modifier (cannot be undone) 8060 mods = mods.max; 8061 break; 8062 case 'C': case 'c': // emacs "ctrl" 8063 if (mods != mods.max) mods |= ModifierState.ctrl; 8064 break; 8065 case 'M': case 'm': // emacs "meta" 8066 if (mods != mods.max) mods |= ModifierState.alt; 8067 break; 8068 case 'S': case 's': // emacs "shift" 8069 if (mods != mods.max) mods |= ModifierState.shift; 8070 break; 8071 case 'H': case 'h': // emacs "hyper" (aka winkey) 8072 if (mods != mods.max) mods |= ModifierState.windows; 8073 break; 8074 default: 8075 return false; // unknown modifier 8076 } 8077 str = str[2..$]; 8078 continue; 8079 } 8080 // word 8081 char[16] buf = void; // locased 8082 auto wep = 0; 8083 while (str.length) { 8084 immutable char ch = str.ptr[0]; 8085 if (ch <= ' ' || ch == '-') break; 8086 str = str[1..$]; 8087 if (wep > buf.length) return false; // too long 8088 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 8089 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 8090 else return false; // invalid char 8091 } 8092 if (wep == 0) return false; // just in case 8093 uint bnum; 8094 enum UpDown { None = -1, Up, Down, Any } 8095 auto updown = UpDown.None; // 0: up; 1: down 8096 switch (buf[0..wep]) { 8097 // left button 8098 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 8099 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 8100 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 8101 case "lmb": case "left": bnum = 0; break; 8102 // middle button 8103 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 8104 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 8105 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 8106 case "mmb": case "middle": bnum = 1; break; 8107 // right button 8108 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 8109 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 8110 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 8111 case "rmb": case "right": bnum = 2; break; 8112 // wheel 8113 case "wheelup": updown = UpDown.Up; goto case "wheel"; 8114 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 8115 case "wheelany": updown = UpDown.Any; goto case "wheel"; 8116 case "wheel": bnum = 3; break; 8117 // motion 8118 case "motion": bnum = 7; break; 8119 // unknown 8120 default: return false; 8121 } 8122 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 8123 // parse possible "-up" or "-down" 8124 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 8125 wep = 0; 8126 foreach (immutable idx, immutable char ch; str[1..$]) { 8127 if (ch <= ' ' || ch == '-') break; 8128 assert(idx == wep); // for now; trick 8129 if (wep > buf.length) { wep = 0; break; } // too long 8130 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 8131 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 8132 else { wep = 0; break; } // invalid char 8133 } 8134 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 8135 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 8136 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 8137 // remove parsed part 8138 if (updown != UpDown.None) str = str[wep+1..$]; 8139 } 8140 if (updown == UpDown.None) { 8141 updown = UpDown.Down; 8142 } 8143 wasButtons = wasButtons || (bnum <= 2); 8144 //assert(updown != UpDown.None); 8145 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 8146 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 8147 if (lastButt != lastButt.max) { 8148 if ((lastButt&0xff) >= 3) return false; // wheel or motion 8149 if (mods != mods.max) { 8150 uint butbit = 0; 8151 final switch (lastButt&0x03) { 8152 case 0: butbit = ModifierState.leftButtonDown; break; 8153 case 1: butbit = ModifierState.middleButtonDown; break; 8154 case 2: butbit = ModifierState.rightButtonDown; break; 8155 } 8156 if (lastButt&Flag.Down) mods |= butbit; 8157 else if (lastButt&Flag.Up) mods &= ~butbit; 8158 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 8159 } 8160 } 8161 // remember last button 8162 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 8163 } 8164 // no button -- nothing to do 8165 if (lastButt == lastButt.max) return false; 8166 // done parsing, check if something's left 8167 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 8168 // remove action button from mask 8169 if ((lastButt&0xff) < 3) { 8170 final switch (lastButt&0x03) { 8171 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 8172 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 8173 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 8174 } 8175 } 8176 // special case: "Motion" means "ignore buttons" 8177 if ((lastButt&0xff) == 7 && !wasButtons) { 8178 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 8179 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 8180 } 8181 uint kmod = event.modifierState&kmodmask; 8182 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 8183 // check modifier state 8184 if (mods != mods.max) { 8185 if (kmod != mods) return false; 8186 } 8187 // now check type 8188 if ((lastButt&0xff) == 7) { 8189 // motion 8190 if (event.type != MouseEventType.motion) return false; 8191 } else if ((lastButt&0xff) == 3) { 8192 // wheel 8193 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 8194 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 8195 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 8196 return false; 8197 } else { 8198 // buttons 8199 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 8200 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 8201 { 8202 return false; 8203 } 8204 // button number 8205 switch (lastButt&0x03) { 8206 case 0: if (event.button != MouseButton.left) return false; break; 8207 case 1: if (event.button != MouseButton.middle) return false; break; 8208 case 2: if (event.button != MouseButton.right) return false; break; 8209 default: return false; 8210 } 8211 } 8212 return true; 8213 } 8214 } 8215 8216 version(arsd_mevent_strcmp_test) unittest { 8217 MouseEvent event; 8218 event.type = MouseEventType.buttonPressed; 8219 event.button = MouseButton.left; 8220 event.modifierState = ModifierState.ctrl; 8221 assert(event == "C-LMB"); 8222 assert(event != "C-LMBUP"); 8223 assert(event != "C-LMB-UP"); 8224 assert(event != "C-S-LMB"); 8225 assert(event == "*-LMB"); 8226 assert(event != "*-LMB-UP"); 8227 8228 event.type = MouseEventType.buttonReleased; 8229 assert(event != "C-LMB"); 8230 assert(event == "C-LMBUP"); 8231 assert(event == "C-LMB-UP"); 8232 assert(event != "C-S-LMB"); 8233 assert(event != "*-LMB"); 8234 assert(event == "*-LMB-UP"); 8235 8236 event.button = MouseButton.right; 8237 event.modifierState |= ModifierState.shift; 8238 event.type = MouseEventType.buttonPressed; 8239 assert(event != "C-LMB"); 8240 assert(event != "C-LMBUP"); 8241 assert(event != "C-LMB-UP"); 8242 assert(event != "C-S-LMB"); 8243 assert(event != "*-LMB"); 8244 assert(event != "*-LMB-UP"); 8245 8246 assert(event != "C-RMB"); 8247 assert(event != "C-RMBUP"); 8248 assert(event != "C-RMB-UP"); 8249 assert(event == "C-S-RMB"); 8250 assert(event == "*-RMB"); 8251 assert(event != "*-RMB-UP"); 8252 } 8253 8254 /// This gives a few more options to drawing lines and such 8255 struct Pen { 8256 Color color; /// the foreground color 8257 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. 8258 Style style; /// See [Style] 8259 /+ 8260 // From X.h 8261 8262 #define LineSolid 0 8263 #define LineOnOffDash 1 8264 #define LineDoubleDash 2 8265 LineDou- The full path of the line is drawn, but the 8266 bleDash even dashes are filled differently from the 8267 odd dashes (see fill-style) with CapButt 8268 style used where even and odd dashes meet. 8269 8270 8271 8272 /* capStyle */ 8273 8274 #define CapNotLast 0 8275 #define CapButt 1 8276 #define CapRound 2 8277 #define CapProjecting 3 8278 8279 /* joinStyle */ 8280 8281 #define JoinMiter 0 8282 #define JoinRound 1 8283 #define JoinBevel 2 8284 8285 /* fillStyle */ 8286 8287 #define FillSolid 0 8288 #define FillTiled 1 8289 #define FillStippled 2 8290 #define FillOpaqueStippled 3 8291 8292 8293 +/ 8294 /// Style of lines drawn 8295 enum Style { 8296 Solid, /// a solid line 8297 Dashed, /// a dashed line 8298 Dotted, /// a dotted line 8299 } 8300 } 8301 8302 8303 /++ 8304 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 8305 8306 8307 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 8308 8309 $(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.) 8310 8311 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. 8312 8313 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 8314 8315 $(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. 8316 8317 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! 8318 8319 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!) 8320 8321 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 8322 8323 --- 8324 auto image = new Image(256, 256); 8325 scope(exit) destroy(image); 8326 --- 8327 8328 As long as you don't hold on to it outside the scope. 8329 8330 I might change it to be an owned pointer at some point in the future. 8331 8332 ) 8333 8334 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 8335 you can also often get a fair amount of speedup by getting the raw data format and 8336 writing some custom code. 8337 8338 FIXME INSERT EXAMPLES HERE 8339 8340 8341 +/ 8342 final class Image { 8343 /// 8344 this(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 8345 this.width = width; 8346 this.height = height; 8347 this.enableAlpha = enableAlpha; 8348 8349 impl.createImage(width, height, forcexshm, enableAlpha); 8350 } 8351 8352 /// 8353 this(Size size, bool forcexshm=false, bool enableAlpha = false) { 8354 this(size.width, size.height, forcexshm, enableAlpha); 8355 } 8356 8357 private bool suppressDestruction; 8358 8359 version(X11) 8360 this(XImage* handle) { 8361 this.handle = handle; 8362 this.rawData = cast(ubyte*) handle.data; 8363 this.width = handle.width; 8364 this.height = handle.height; 8365 this.enableAlpha = handle.depth == 32; 8366 suppressDestruction = true; 8367 } 8368 8369 ~this() { 8370 if(suppressDestruction) return; 8371 impl.dispose(); 8372 } 8373 8374 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 8375 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 8376 pure const @system nothrow { 8377 /* 8378 To use these to draw a blue rectangle with size WxH at position X,Y... 8379 8380 // make certain that it will fit before we proceed 8381 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! 8382 8383 // gather all the values you'll need up front. These can be kept until the image changes size if you want 8384 // (though calculating them isn't really that expensive). 8385 auto nextLineAdjustment = img.adjustmentForNextLine(); 8386 auto offR = img.redByteOffset(); 8387 auto offB = img.blueByteOffset(); 8388 auto offG = img.greenByteOffset(); 8389 auto bpp = img.bytesPerPixel(); 8390 8391 auto data = img.getDataPointer(); 8392 8393 // figure out the starting byte offset 8394 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 8395 8396 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 8397 8398 // and now our drawing loop for the rectangle 8399 foreach(y; 0 .. H) { 8400 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 8401 foreach(x; 0 .. W) { 8402 // write our color 8403 data[offR] = 0; 8404 data[offG] = 0; 8405 data[offB] = 255; 8406 8407 data += bpp; // moving to the next pixel is just an addition... 8408 } 8409 startOfLine += nextLineAdjustment; 8410 } 8411 8412 8413 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 8414 8415 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 8416 can be made into a bitmask or something so we can write them as *uint... 8417 */ 8418 8419 /// 8420 int offsetForTopLeftPixel() { 8421 version(X11) { 8422 return 0; 8423 } else version(Windows) { 8424 if(enableAlpha) { 8425 return (width * 4) * (height - 1); 8426 } else { 8427 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 8428 } 8429 } else version(OSXCocoa) { 8430 return 0 ; //throw new NotYetImplementedException(); 8431 } else static assert(0, "fill in this info for other OSes"); 8432 } 8433 8434 /// 8435 int offsetForPixel(int x, int y) { 8436 version(X11) { 8437 auto offset = (y * width + x) * 4; 8438 return offset; 8439 } else version(Windows) { 8440 if(enableAlpha) { 8441 auto itemsPerLine = width * 4; 8442 // remember, bmps are upside down 8443 auto offset = itemsPerLine * (height - y - 1) + x * 4; 8444 return offset; 8445 } else { 8446 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 8447 // remember, bmps are upside down 8448 auto offset = itemsPerLine * (height - y - 1) + x * 3; 8449 return offset; 8450 } 8451 } else version(OSXCocoa) { 8452 return 0 ; //throw new NotYetImplementedException(); 8453 } else static assert(0, "fill in this info for other OSes"); 8454 } 8455 8456 /// 8457 int adjustmentForNextLine() { 8458 version(X11) { 8459 return width * 4; 8460 } else version(Windows) { 8461 // windows bmps are upside down, so the adjustment is actually negative 8462 if(enableAlpha) 8463 return - (cast(int) width * 4); 8464 else 8465 return -((cast(int) width * 3 + 3) / 4) * 4; 8466 } else version(OSXCocoa) { 8467 return 0 ; //throw new NotYetImplementedException(); 8468 } else static assert(0, "fill in this info for other OSes"); 8469 } 8470 8471 /// once you have the position of a pixel, use these to get to the proper color 8472 int redByteOffset() { 8473 version(X11) { 8474 return 2; 8475 } else version(Windows) { 8476 return 2; 8477 } else version(OSXCocoa) { 8478 return 0 ; //throw new NotYetImplementedException(); 8479 } else static assert(0, "fill in this info for other OSes"); 8480 } 8481 8482 /// 8483 int greenByteOffset() { 8484 version(X11) { 8485 return 1; 8486 } else version(Windows) { 8487 return 1; 8488 } else version(OSXCocoa) { 8489 return 0 ; //throw new NotYetImplementedException(); 8490 } else static assert(0, "fill in this info for other OSes"); 8491 } 8492 8493 /// 8494 int blueByteOffset() { 8495 version(X11) { 8496 return 0; 8497 } else version(Windows) { 8498 return 0; 8499 } else version(OSXCocoa) { 8500 return 0 ; //throw new NotYetImplementedException(); 8501 } else static assert(0, "fill in this info for other OSes"); 8502 } 8503 8504 /// Only valid if [enableAlpha] is true 8505 int alphaByteOffset() { 8506 version(X11) { 8507 return 3; 8508 } else version(Windows) { 8509 return 3; 8510 } else version(OSXCocoa) { 8511 return 3; //throw new NotYetImplementedException(); 8512 } else static assert(0, "fill in this info for other OSes"); 8513 } 8514 } 8515 8516 /// 8517 final void putPixel(int x, int y, Color c) { 8518 if(x < 0 || x >= width) 8519 return; 8520 if(y < 0 || y >= height) 8521 return; 8522 8523 impl.setPixel(x, y, c); 8524 } 8525 8526 /// 8527 final Color getPixel(int x, int y) { 8528 if(x < 0 || x >= width) 8529 return Color.transparent; 8530 if(y < 0 || y >= height) 8531 return Color.transparent; 8532 8533 version(OSXCocoa) throw new NotYetImplementedException(); else 8534 return impl.getPixel(x, y); 8535 } 8536 8537 /// 8538 final void opIndexAssign(Color c, int x, int y) { 8539 putPixel(x, y, c); 8540 } 8541 8542 /// 8543 TrueColorImage toTrueColorImage() { 8544 auto tci = new TrueColorImage(width, height); 8545 convertToRgbaBytes(tci.imageData.bytes); 8546 return tci; 8547 } 8548 8549 /// 8550 static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) { 8551 auto tci = i.getAsTrueColorImage(); 8552 auto img = new Image(tci.width, tci.height, false, enableAlpha); 8553 static if(UsingSimpledisplayX11) 8554 img.premultiply = premultiply; 8555 img.setRgbaBytes(tci.imageData.bytes); 8556 return img; 8557 } 8558 8559 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 8560 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 8561 /// if you pass null, it will allocate a new one. 8562 ubyte[] getRgbaBytes(ubyte[] where = null) { 8563 if(where is null) 8564 where = new ubyte[this.width*this.height*4]; 8565 convertToRgbaBytes(where); 8566 return where; 8567 } 8568 8569 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 8570 void setRgbaBytes(in ubyte[] from ) { 8571 assert(from.length == this.width * this.height * 4); 8572 setFromRgbaBytes(from); 8573 } 8574 8575 // FIXME: make properly cross platform by getting rgba right 8576 8577 /// warning: this is not portable across platforms because the data format can change 8578 ubyte* getDataPointer() { 8579 return impl.rawData; 8580 } 8581 8582 /// for use with getDataPointer 8583 final int bytesPerLine() const pure @safe nothrow { 8584 version(Windows) 8585 return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 8586 else version(X11) 8587 return 4 * width; 8588 else version(OSXCocoa) 8589 return 4 * width; 8590 else static assert(0); 8591 } 8592 8593 /// for use with getDataPointer 8594 final int bytesPerPixel() const pure @safe nothrow { 8595 version(Windows) 8596 return enableAlpha ? 4 : 3; 8597 else version(X11) 8598 return 4; 8599 else version(OSXCocoa) 8600 return 4; 8601 else static assert(0); 8602 } 8603 8604 /// 8605 immutable int width; 8606 8607 /// 8608 immutable int height; 8609 8610 /// 8611 immutable bool enableAlpha; 8612 //private: 8613 mixin NativeImageImplementation!() impl; 8614 } 8615 8616 /++ 8617 A convenience function to pop up a window displaying the image. 8618 If you pass a win, it will draw the image in it. Otherwise, it will 8619 create a window with the size of the image and run its event loop, closing 8620 when a key is pressed. 8621 8622 History: 8623 `BlockingMode` parameter added on December 8, 2021. Previously, it would 8624 always block until the application quit which could cause bizarre behavior 8625 inside a more complex application. Now, the default is to block until 8626 this window closes if it is the only event loop running, and otherwise, 8627 not to block at all and just pop up the display window asynchronously. 8628 +/ 8629 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) { 8630 if(win is null) { 8631 win = new SimpleWindow(image); 8632 { 8633 auto p = win.draw; 8634 p.drawImage(Point(0, 0), image); 8635 } 8636 win.eventLoopWithBlockingMode( 8637 bm, 0, 8638 (KeyEvent ev) { 8639 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 8640 } ); 8641 } else { 8642 win.image = image; 8643 } 8644 } 8645 8646 enum FontWeight : int { 8647 dontcare = 0, 8648 thin = 100, 8649 extralight = 200, 8650 light = 300, 8651 regular = 400, 8652 medium = 500, 8653 semibold = 600, 8654 bold = 700, 8655 extrabold = 800, 8656 heavy = 900 8657 } 8658 8659 /++ 8660 Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont]. 8661 8662 History: 8663 Added October 24, 2022. The methods were already on [OperatingSystemFont] before that. 8664 +/ 8665 interface MeasurableFont { 8666 /++ 8667 Returns true if it is a monospace font, meaning each of the 8668 glyphs (at least the ascii characters) have matching width 8669 and no kerning, so you can determine the display width of some 8670 strings by simply multiplying the string width by [averageWidth]. 8671 8672 (Please note that multiply doesn't $(I actually) work in general, 8673 consider characters like tab and newline, but it does sometimes.) 8674 +/ 8675 bool isMonospace(); 8676 8677 /++ 8678 The average width of glyphs in the font, traditionally equal to the 8679 width of the lowercase x. Can be used to estimate bounding boxes, 8680 especially if the font [isMonospace]. 8681 8682 Given in pixels. 8683 +/ 8684 int averageWidth(); 8685 /++ 8686 The height of the bounding box of a line. 8687 +/ 8688 int height(); 8689 /++ 8690 The maximum ascent of a glyph above the baseline. 8691 8692 Given in pixels. 8693 +/ 8694 int ascent(); 8695 /++ 8696 The maximum descent of a glyph below the baseline. For example, how low the g might go. 8697 8698 Given in pixels. 8699 +/ 8700 int descent(); 8701 /++ 8702 The display width of the given string, and if you provide a window, it will use it to 8703 make the pixel count on screen more accurate too, but this shouldn't generally be necessary. 8704 8705 Given in pixels. 8706 +/ 8707 int stringWidth(scope const(char)[] s, SimpleWindow window = null); 8708 8709 } 8710 8711 // FIXME: i need a font cache and it needs to handle disconnects. 8712 8713 /++ 8714 Represents a font loaded off the operating system or the X server. 8715 8716 8717 While the api here is unified cross platform, the fonts are not necessarily 8718 available, even across machines of the same platform, so be sure to always check 8719 for null (using [isNull]) and have a fallback plan. 8720 8721 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 8722 8723 Worst case, a null font will automatically fall back to the default font loaded 8724 for your system. 8725 +/ 8726 class OperatingSystemFont : MeasurableFont { 8727 // FIXME: when the X Connection is lost, these need to be invalidated! 8728 // that means I need to store the original stuff again to reconstruct it too. 8729 8730 version(X11) { 8731 XFontStruct* font; 8732 XFontSet fontset; 8733 8734 version(with_xft) { 8735 XftFont* xftFont; 8736 bool isXft; 8737 } 8738 } else version(Windows) { 8739 HFONT font; 8740 int width_; 8741 int height_; 8742 } else version(OSXCocoa) { 8743 NSFont font; 8744 } else static assert(0); 8745 8746 /++ 8747 Constructs the class and immediately calls [load]. 8748 +/ 8749 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8750 load(name, size, weight, italic); 8751 } 8752 8753 /++ 8754 Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object. 8755 8756 You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you. 8757 8758 History: 8759 Added January 24, 2021. 8760 +/ 8761 this() { 8762 // this space intentionally left blank 8763 } 8764 8765 /++ 8766 Constructs a copy of the given font object. 8767 8768 History: 8769 Added January 7, 2023. 8770 +/ 8771 this(OperatingSystemFont font) { 8772 if(font is null || font.loadedInfo is LoadedInfo.init) 8773 loadDefault(); 8774 else 8775 load(font.loadedInfo.tupleof); 8776 } 8777 8778 /++ 8779 Loads specifically with the Xft library - a freetype font from a fontconfig string. 8780 8781 History: 8782 Added November 13, 2020. 8783 +/ 8784 version(with_xft) 8785 bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8786 unload(); 8787 8788 if(!XftLibrary.attempted) { 8789 XftLibrary.loadDynamicLibrary(); 8790 } 8791 8792 if(!XftLibrary.loadSuccessful) 8793 return false; 8794 8795 auto display = XDisplayConnection.get; 8796 8797 char[256] nameBuffer = void; 8798 int nbp = 0; 8799 8800 void add(in char[] a) { 8801 nameBuffer[nbp .. nbp + a.length] = a[]; 8802 nbp += a.length; 8803 } 8804 add(name); 8805 8806 if(size) { 8807 add(":size="); 8808 add(toInternal!string(size)); 8809 } 8810 if(weight != FontWeight.dontcare && weight != 400) { 8811 if(weight < 400) 8812 add(":style=Light"); 8813 else 8814 add(":style=Bold"); 8815 add(":weight="); 8816 add(weightToString(weight)); 8817 } 8818 if(italic) { 8819 if(weight == FontWeight.dontcare) 8820 add(":style=Italic"); 8821 add(":slant=100"); 8822 } 8823 8824 nameBuffer[nbp] = 0; 8825 8826 this.xftFont = XftFontOpenName( 8827 display, 8828 DefaultScreen(display), 8829 nameBuffer.ptr 8830 ); 8831 8832 this.isXft = true; 8833 8834 if(xftFont !is null) { 8835 isMonospace_ = stringWidth("x") == stringWidth("M"); 8836 ascent_ = xftFont.ascent; 8837 descent_ = xftFont.descent; 8838 } 8839 8840 return !isNull(); 8841 } 8842 8843 /++ 8844 Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor. 8845 8846 8847 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. 8848 8849 If `pattern` is null, it returns all available font families. 8850 8851 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. 8852 8853 The format of the pattern is platform-specific. 8854 8855 History: 8856 Added May 1, 2021 (dub v9.5) 8857 +/ 8858 static void listFonts(string pattern, bool delegate(in char[] name) handler) { 8859 version(Windows) { 8860 auto hdc = GetDC(null); 8861 scope(exit) ReleaseDC(null, hdc); 8862 LOGFONT logfont; 8863 static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) { 8864 auto localHandler = *(cast(typeof(handler)*) p); 8865 return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0; 8866 } 8867 EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0); 8868 } else version(X11) { 8869 //import core.stdc.stdio; 8870 bool done = false; 8871 version(with_xft) { 8872 if(!XftLibrary.attempted) { 8873 XftLibrary.loadDynamicLibrary(); 8874 } 8875 8876 if(!XftLibrary.loadSuccessful) 8877 goto skipXft; 8878 8879 if(!FontConfigLibrary.attempted) 8880 FontConfigLibrary.loadDynamicLibrary(); 8881 if(!FontConfigLibrary.loadSuccessful) 8882 goto skipXft; 8883 8884 { 8885 auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null); 8886 if(got is null) 8887 goto skipXft; 8888 scope(exit) FcFontSetDestroy(got); 8889 8890 auto fontPatterns = got.fonts[0 .. got.nfont]; 8891 foreach(candidate; fontPatterns) { 8892 char* where, whereStyle; 8893 8894 char* pmg = FcNameUnparse(candidate); 8895 8896 //FcPatternGetString(candidate, "family", 0, &where); 8897 //FcPatternGetString(candidate, "style", 0, &whereStyle); 8898 //if(where && whereStyle) { 8899 if(pmg) { 8900 if(!handler(pmg.sliceCString)) 8901 return; 8902 //printf("%s || %s %s\n", pmg, where, whereStyle); 8903 } 8904 } 8905 } 8906 } 8907 8908 skipXft: 8909 8910 if(pattern is null) 8911 pattern = "*"; 8912 8913 int count; 8914 auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count); 8915 scope(exit) XFreeFontNames(coreFontsRaw); 8916 8917 auto coreFonts = coreFontsRaw[0 .. count]; 8918 8919 foreach(font; coreFonts) { 8920 char[128] tmp; 8921 tmp[0 ..5] = "core:"; 8922 auto cf = font.sliceCString; 8923 if(5 + cf.length > tmp.length) 8924 assert(0, "a font name was too long, sorry i didn't bother implementing a fallback"); 8925 tmp[5 .. 5 + cf.length] = cf; 8926 if(!handler(tmp[0 .. 5 + cf.length])) 8927 return; 8928 } 8929 } 8930 } 8931 8932 /++ 8933 Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont 8934 to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega]. 8935 8936 Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the 8937 underlying system doesn't support returning the raw bytes. 8938 8939 History: 8940 Added September 10, 2021 (dub v10.3) 8941 +/ 8942 ubyte[] getTtfBytes() { 8943 if(isNull) 8944 return null; 8945 8946 version(Windows) { 8947 auto dc = GetDC(null); 8948 auto orig = SelectObject(dc, font); 8949 8950 scope(exit) { 8951 SelectObject(dc, orig); 8952 ReleaseDC(null, dc); 8953 } 8954 8955 auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0); 8956 if(res == GDI_ERROR) 8957 return null; 8958 8959 ubyte[] buffer = new ubyte[](res); 8960 res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length); 8961 if(res == GDI_ERROR) 8962 return null; // wtf really tbh 8963 8964 return buffer; 8965 } else version(with_xft) { 8966 if(isXft && xftFont) { 8967 if(!FontConfigLibrary.attempted) 8968 FontConfigLibrary.loadDynamicLibrary(); 8969 if(!FontConfigLibrary.loadSuccessful) 8970 return null; 8971 8972 char* file; 8973 if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) { 8974 if (file !is null && file[0]) { 8975 import core.stdc.stdio; 8976 auto fp = fopen(file, "rb"); 8977 if(fp is null) 8978 return null; 8979 scope(exit) 8980 fclose(fp); 8981 fseek(fp, 0, SEEK_END); 8982 ubyte[] buffer = new ubyte[](ftell(fp)); 8983 fseek(fp, 0, SEEK_SET); 8984 8985 auto got = fread(buffer.ptr, 1, buffer.length, fp); 8986 if(got != buffer.length) 8987 return null; 8988 8989 return buffer; 8990 } 8991 } 8992 } 8993 return null; 8994 } else throw new NotYetImplementedException(); 8995 } 8996 8997 // see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352 8998 8999 private string weightToString(FontWeight weight) { 9000 with(FontWeight) 9001 final switch(weight) { 9002 case dontcare: return "*"; 9003 case thin: return "extralight"; 9004 case extralight: return "extralight"; 9005 case light: return "light"; 9006 case regular: return "regular"; 9007 case medium: return "medium"; 9008 case semibold: return "demibold"; 9009 case bold: return "bold"; 9010 case extrabold: return "demibold"; 9011 case heavy: return "black"; 9012 } 9013 } 9014 9015 /++ 9016 Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance. 9017 9018 History: 9019 Added November 13, 2020. Before then, this code was integrated in the [load] function. 9020 +/ 9021 version(X11) 9022 bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 9023 unload(); 9024 9025 string xfontstr; 9026 9027 if(name.length > 3 && name[0 .. 3] == "-*-") { 9028 // this is kinda a disgusting hack but if the user sends an exact 9029 // string I'd like to honor it... 9030 xfontstr = name; 9031 } else { 9032 string weightstr = weightToString(weight); 9033 string sizestr; 9034 if(size == 0) 9035 sizestr = "*"; 9036 else 9037 sizestr = toInternal!string(size); 9038 xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 9039 } 9040 9041 // writeln(xfontstr); 9042 9043 auto display = XDisplayConnection.get; 9044 9045 font = XLoadQueryFont(display, xfontstr.ptr); 9046 if(font is null) 9047 return false; 9048 9049 char** lol; 9050 int lol2; 9051 char* lol3; 9052 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 9053 9054 prepareFontInfo(); 9055 9056 return !isNull(); 9057 } 9058 9059 version(X11) 9060 private void prepareFontInfo() { 9061 if(font !is null) { 9062 isMonospace_ = stringWidth("l") == stringWidth("M"); 9063 ascent_ = font.max_bounds.ascent; 9064 descent_ = font.max_bounds.descent; 9065 } 9066 } 9067 9068 version(OSXCocoa) 9069 private void prepareFontInfo() { 9070 if(font !is null) { 9071 isMonospace_ = font.isFixedPitch; 9072 ascent_ = cast(int) font.ascender; 9073 descent_ = cast(int) - font.descender; 9074 } 9075 } 9076 9077 9078 /++ 9079 Loads a Windows font. You probably want to use [load] instead to be more generic. 9080 9081 History: 9082 Added November 13, 2020. Before then, this code was integrated in the [load] function. 9083 +/ 9084 version(Windows) 9085 bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) { 9086 unload(); 9087 9088 WCharzBuffer buffer = WCharzBuffer(name); 9089 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 9090 9091 prepareFontInfo(hdc); 9092 9093 return !isNull(); 9094 } 9095 9096 version(Windows) 9097 void prepareFontInfo(HDC hdc = null) { 9098 if(font is null) 9099 return; 9100 9101 TEXTMETRIC tm; 9102 auto dc = hdc ? hdc : GetDC(null); 9103 auto orig = SelectObject(dc, font); 9104 GetTextMetrics(dc, &tm); 9105 SelectObject(dc, orig); 9106 if(hdc is null) 9107 ReleaseDC(null, dc); 9108 9109 width_ = tm.tmAveCharWidth; 9110 height_ = tm.tmHeight; 9111 ascent_ = tm.tmAscent; 9112 descent_ = tm.tmDescent; 9113 // 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. 9114 isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0; 9115 } 9116 9117 9118 /++ 9119 `name` is a font name, but it can also be a more complicated string parsed in an OS-specific way. 9120 9121 On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise, 9122 it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX]. 9123 9124 On Windows, it forwards directly to [loadWin32]. 9125 9126 Params: 9127 name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences. 9128 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. 9129 weight = approximate boldness, results may vary. 9130 italic = try to get a slanted version of the given font. 9131 9132 History: 9133 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. 9134 +/ 9135 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 9136 this.loadedInfo = LoadedInfo(name, size, weight, italic); 9137 version(X11) { 9138 version(with_xft) { 9139 if(name.length > 5 && name[0 .. 5] == "core:") { 9140 goto core; 9141 } 9142 9143 if(loadXft(name, size, weight, italic)) 9144 return true; 9145 // if xft fails, fallback to core to avoid breaking 9146 // code that already depended on this. 9147 } 9148 9149 core: 9150 9151 if(name.length > 5 && name[0 .. 5] == "core:") { 9152 name = name[5 .. $]; 9153 } 9154 9155 return loadCoreX(name, size, weight, italic); 9156 } else version(Windows) { 9157 return loadWin32(name, size, weight, italic); 9158 } else version(OSXCocoa) { 9159 return loadCocoa(name, size, weight, italic); 9160 } else static assert(0); 9161 } 9162 9163 version(OSXCocoa) 9164 bool loadCocoa(string name, int size, FontWeight weight, bool italic) { 9165 unload(); 9166 9167 font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic? 9168 prepareFontInfo(); 9169 9170 return !isNull(); 9171 } 9172 9173 private struct LoadedInfo { 9174 string name; 9175 int size; 9176 FontWeight weight; 9177 bool italic; 9178 } 9179 private LoadedInfo loadedInfo; 9180 9181 /// 9182 void unload() { 9183 if(isNull()) 9184 return; 9185 9186 version(X11) { 9187 auto display = XDisplayConnection.display; 9188 9189 if(display is null) 9190 return; 9191 9192 version(with_xft) { 9193 if(isXft) { 9194 if(xftFont) 9195 XftFontClose(display, xftFont); 9196 isXft = false; 9197 xftFont = null; 9198 return; 9199 } 9200 } 9201 9202 if(font && font !is ScreenPainterImplementation.defaultfont) 9203 XFreeFont(display, font); 9204 if(fontset && fontset !is ScreenPainterImplementation.defaultfontset) 9205 XFreeFontSet(display, fontset); 9206 9207 font = null; 9208 fontset = null; 9209 } else version(Windows) { 9210 DeleteObject(font); 9211 font = null; 9212 } else version(OSXCocoa) { 9213 font.release(); 9214 font = null; 9215 } else static assert(0); 9216 } 9217 9218 private bool isMonospace_; 9219 9220 /++ 9221 History: 9222 Added January 16, 2021 9223 +/ 9224 bool isMonospace() { 9225 return isMonospace_; 9226 } 9227 9228 /++ 9229 Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character. 9230 9231 History: 9232 Added March 26, 2020 9233 Documented January 16, 2021 9234 +/ 9235 int averageWidth() { 9236 version(X11) { 9237 return stringWidth("x"); 9238 } version(OSXCocoa) { 9239 return stringWidth("x"); 9240 } else version(Windows) 9241 return width_; 9242 else assert(0); 9243 } 9244 9245 /++ 9246 Returns the width of the string as drawn on the specified window, or the default screen if the window is null. 9247 9248 History: 9249 Added January 16, 2021 9250 +/ 9251 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 9252 // FIXME: what about tab? 9253 if(isNull) 9254 return 0; 9255 9256 version(X11) { 9257 version(with_xft) 9258 if(isXft && xftFont !is null) { 9259 //return xftFont.max_advance_width; 9260 XGlyphInfo extents; 9261 XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents); 9262 // writeln(extents); 9263 return extents.xOff; 9264 } 9265 if(font is null) 9266 return 0; 9267 else if(fontset) { 9268 XRectangle rect; 9269 Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect); 9270 9271 return rect.width; 9272 } else { 9273 return XTextWidth(font, s.ptr, cast(int) s.length); 9274 } 9275 } else version(Windows) { 9276 WCharzBuffer buffer = WCharzBuffer(s); 9277 9278 return stringWidth(buffer.slice, window); 9279 } else version(OSXCocoa) { 9280 /+ 9281 int charCount = [string length]; 9282 CGGlyph glyphs[charCount]; 9283 CGRect rects[charCount]; 9284 9285 CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount); 9286 CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount); 9287 9288 int totalwidth = 0, maxheight = 0; 9289 for (int i=0; i < charCount; i++) 9290 { 9291 totalwidth += rects[i].size.width; 9292 maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight; 9293 } 9294 9295 dim = CGSizeMake(totalwidth, maxheight); 9296 +/ 9297 9298 return 16; // FIXME 9299 } 9300 else assert(0); 9301 } 9302 9303 version(Windows) 9304 /// ditto 9305 int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) { 9306 if(isNull) 9307 return 0; 9308 version(Windows) { 9309 SIZE size; 9310 9311 prepareContext(window); 9312 scope(exit) releaseContext(); 9313 9314 GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size); 9315 9316 return size.cx; 9317 } else { 9318 // std.conv can do this easily but it is slow to import and i don't think it is worth it 9319 static assert(0, "not implemented yet"); 9320 //return stringWidth(s, window); 9321 } 9322 } 9323 9324 private { 9325 int prepRefcount; 9326 9327 version(Windows) { 9328 HDC dc; 9329 HANDLE orig; 9330 HWND hwnd; 9331 } 9332 } 9333 /++ 9334 [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. 9335 9336 History: 9337 Added January 23, 2021 9338 +/ 9339 void prepareContext(SimpleWindow window = null) { 9340 prepRefcount++; 9341 if(prepRefcount == 1) { 9342 version(Windows) { 9343 hwnd = window is null ? null : window.impl.hwnd; 9344 dc = GetDC(hwnd); 9345 orig = SelectObject(dc, font); 9346 } 9347 } 9348 } 9349 /// ditto 9350 void releaseContext() { 9351 prepRefcount--; 9352 if(prepRefcount == 0) { 9353 version(Windows) { 9354 SelectObject(dc, orig); 9355 ReleaseDC(hwnd, dc); 9356 hwnd = null; 9357 dc = null; 9358 orig = null; 9359 } 9360 } 9361 } 9362 9363 /+ 9364 FIXME: I think I need advance and kerning pair 9365 9366 int advance(dchar from, dchar to) { } // use dchar.init for first item in string 9367 +/ 9368 9369 /++ 9370 Returns the height of the font. 9371 9372 History: 9373 Added March 26, 2020 9374 Documented January 16, 2021 9375 +/ 9376 int height() { 9377 version(X11) { 9378 version(with_xft) 9379 if(isXft && xftFont !is null) { 9380 return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel 9381 } 9382 if(font is null) 9383 return 0; 9384 return font.max_bounds.ascent + font.max_bounds.descent; 9385 } else version(Windows) { 9386 return height_; 9387 } else version(OSXCocoa) { 9388 if(font is null) 9389 return 0; 9390 return cast(int) (font.ascender + font.descender + 0.9 /* to round up */); // font.capHeight 9391 } 9392 else assert(0); 9393 } 9394 9395 private int ascent_; 9396 private int descent_; 9397 9398 /++ 9399 Max ascent above the baseline. 9400 9401 History: 9402 Added January 22, 2021 9403 +/ 9404 int ascent() { 9405 return ascent_; 9406 } 9407 9408 /++ 9409 Max descent below the baseline. 9410 9411 History: 9412 Added January 22, 2021 9413 +/ 9414 int descent() { 9415 return descent_; 9416 } 9417 9418 /++ 9419 Loads the default font used by [ScreenPainter] if none others are loaded. 9420 9421 Returns: 9422 This method mutates the `this` object, but then returns `this` for 9423 easy chaining like: 9424 9425 --- 9426 auto font = foo.isNull ? foo : foo.loadDefault 9427 --- 9428 9429 History: 9430 Added previously, but left unimplemented until January 24, 2021. 9431 +/ 9432 OperatingSystemFont loadDefault() { 9433 unload(); 9434 9435 loadedInfo = LoadedInfo.init; 9436 9437 version(X11) { 9438 // another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html 9439 // but meh since sdpy does its own thing, this should be ok too 9440 9441 ScreenPainterImplementation.ensureDefaultFontLoaded(); 9442 this.font = ScreenPainterImplementation.defaultfont; 9443 this.fontset = ScreenPainterImplementation.defaultfontset; 9444 9445 prepareFontInfo(); 9446 return this; 9447 } else version(Windows) { 9448 ScreenPainterImplementation.ensureDefaultFontLoaded(); 9449 this.font = ScreenPainterImplementation.defaultGuiFont; 9450 9451 prepareFontInfo(); 9452 return this; 9453 } else version(OSXCocoa) { 9454 this.font = NSFont.systemFontOfSize(15); 9455 9456 prepareFontInfo(); 9457 9458 // import std.stdio; writeln("Load default: ", this.height()); 9459 return this; 9460 } else throw new NotYetImplementedException(); 9461 } 9462 9463 /// 9464 bool isNull() { 9465 version(with_xft) 9466 if(isXft) 9467 return xftFont is null; 9468 return font is null; 9469 } 9470 9471 /* Metrics */ 9472 /+ 9473 GetABCWidth 9474 GetKerningPairs 9475 9476 if I do it right, I can size it all here, and match 9477 what happens when I draw the full string with the OS functions. 9478 9479 subclasses might do the same thing while getting the glyphs on images 9480 struct GlyphInfo { 9481 int glyph; 9482 9483 size_t stringIdxStart; 9484 size_t stringIdxEnd; 9485 9486 Rectangle boundingBox; 9487 } 9488 GlyphInfo[] getCharBoxes() { 9489 // XftTextExtentsUtf8 9490 return null; 9491 9492 } 9493 +/ 9494 9495 ~this() { 9496 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 9497 unload(); 9498 } 9499 } 9500 9501 version(Windows) 9502 private string sliceCString(const(wchar)[] w) { 9503 return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr); 9504 } 9505 9506 private inout(char)[] sliceCString(inout(char)* s) { 9507 import core.stdc.string; 9508 auto len = strlen(s); 9509 return s[0 .. len]; 9510 } 9511 9512 version(OSXCocoa) 9513 alias PaintingHandle = NSObject; 9514 else 9515 alias PaintingHandle = NativeWindowHandle; 9516 9517 /** 9518 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 9519 than constructing it directly. Then, it is reference counted so you can pass it 9520 at around and when the last ref goes out of scope, the buffered drawing activities 9521 are all carried out. 9522 9523 9524 Most functions use the outlineColor instead of taking a color themselves. 9525 ScreenPainter is reference counted and draws its buffer to the screen when its 9526 final reference goes out of scope. 9527 */ 9528 struct ScreenPainter { 9529 CapableOfBeingDrawnUpon window; 9530 this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) { 9531 this.window = window; 9532 if(window.closed) 9533 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 9534 //currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 9535 currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max); 9536 if(window.activeScreenPainter !is null) { 9537 impl = window.activeScreenPainter; 9538 if(impl.referenceCount == 0) { 9539 impl.window = window; 9540 impl.create(handle); 9541 } 9542 impl.manualInvalidations = manualInvalidations; 9543 impl.referenceCount++; 9544 // writeln("refcount ++ ", impl.referenceCount); 9545 } else { 9546 impl = new ScreenPainterImplementation; 9547 impl.window = window; 9548 impl.create(handle); 9549 impl.referenceCount = 1; 9550 impl.manualInvalidations = manualInvalidations; 9551 window.activeScreenPainter = impl; 9552 // writeln("constructed"); 9553 } 9554 9555 copyActiveOriginals(); 9556 } 9557 9558 /++ 9559 EXPERIMENTAL. subject to change. 9560 9561 When you draw a cursor, you can draw this to notify your window of where it is, 9562 for IME systems to use. 9563 +/ 9564 void notifyCursorPosition(int x, int y, int width, int height) { 9565 if(auto w = cast(SimpleWindow) window) { 9566 w.setIMEPopupLocation(x + _originX + width, y + _originY + height); 9567 } 9568 } 9569 9570 /++ 9571 If you are using manual invalidations, this informs the 9572 window system that a section needs to be redrawn. 9573 9574 If you didn't opt into manual invalidation, you don't 9575 have to call this. 9576 9577 History: 9578 Added December 30, 2021 (dub v10.5) 9579 +/ 9580 void invalidateRect(Rectangle rect) { 9581 if(impl is null) return; 9582 9583 // transform(rect) 9584 rect.left += _originX; 9585 rect.right += _originX; 9586 rect.top += _originY; 9587 rect.bottom += _originY; 9588 9589 impl.invalidateRect(rect); 9590 } 9591 9592 private Pen originalPen; 9593 private Color originalFillColor; 9594 private arsd.color.Rectangle originalClipRectangle; 9595 private OperatingSystemFont originalFont; 9596 void copyActiveOriginals() { 9597 if(impl is null) return; 9598 originalPen = impl._activePen; 9599 originalFillColor = impl._fillColor; 9600 originalClipRectangle = impl._clipRectangle; 9601 version(OSXCocoa) {} else 9602 originalFont = impl._activeFont; 9603 } 9604 9605 ~this() { 9606 if(impl is null) return; 9607 impl.referenceCount--; 9608 //writeln("refcount -- ", impl.referenceCount); 9609 if(impl.referenceCount == 0) { 9610 // writeln("destructed"); 9611 impl.dispose(); 9612 *window.activeScreenPainter = ScreenPainterImplementation.init; 9613 // writeln("paint finished"); 9614 } else { 9615 // there is still an active reference, reset stuff so the 9616 // next user doesn't get weirdness via the reference 9617 this.rasterOp = RasterOp.normal; 9618 pen = originalPen; 9619 fillColor = originalFillColor; 9620 if(originalFont) 9621 setFont(originalFont); 9622 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 9623 } 9624 } 9625 9626 this(this) { 9627 if(impl is null) return; 9628 impl.referenceCount++; 9629 //writeln("refcount ++ ", impl.referenceCount); 9630 9631 copyActiveOriginals(); 9632 } 9633 9634 private int _originX; 9635 private int _originY; 9636 @property int originX() { return _originX; } 9637 @property int originY() { return _originY; } 9638 @property int originX(int a) { 9639 _originX = a; 9640 return _originX; 9641 } 9642 @property int originY(int a) { 9643 _originY = a; 9644 return _originY; 9645 } 9646 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 9647 private void transform(ref Point p) { 9648 if(impl is null) return; 9649 p.x += _originX; 9650 p.y += _originY; 9651 } 9652 9653 // this needs to be checked BEFORE the originX/Y transformation 9654 private bool isClipped(Point p) { 9655 return !currentClipRectangle.contains(p); 9656 } 9657 private bool isClipped(Point p, int width, int height) { 9658 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 9659 } 9660 private bool isClipped(Point p, Size s) { 9661 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 9662 } 9663 private bool isClipped(Point p, Point p2) { 9664 // need to ensure the end points are actually included inside, so the +1 does that 9665 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 9666 } 9667 9668 9669 /++ 9670 Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 9671 9672 Returns: 9673 The old clip rectangle. 9674 9675 History: 9676 Return value was `void` prior to May 10, 2021. 9677 9678 +/ 9679 arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) { 9680 if(impl is null) return currentClipRectangle; 9681 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 9682 return currentClipRectangle; // no need to do anything 9683 auto old = currentClipRectangle; 9684 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 9685 transform(pt); 9686 9687 impl.setClipRectangle(pt.x, pt.y, width, height); 9688 9689 return old; 9690 } 9691 9692 /// ditto 9693 arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) { 9694 if(impl is null) return currentClipRectangle; 9695 return setClipRectangle(rect.upperLeft, rect.width, rect.height); 9696 } 9697 9698 /// 9699 void setFont(OperatingSystemFont font) { 9700 if(impl is null) return; 9701 impl.setFont(font); 9702 } 9703 9704 /// 9705 int fontHeight() { 9706 if(impl is null) return 0; 9707 return impl.fontHeight(); 9708 } 9709 9710 private Pen activePen; 9711 9712 /// 9713 @property void pen(Pen p) { 9714 if(impl is null) return; 9715 activePen = p; 9716 impl.pen(p); 9717 } 9718 9719 /// 9720 @scriptable 9721 @property void outlineColor(Color c) { 9722 if(impl is null) return; 9723 if(activePen.color == c) 9724 return; 9725 activePen.color = c; 9726 impl.pen(activePen); 9727 } 9728 9729 /// 9730 @scriptable 9731 @property void fillColor(Color c) { 9732 if(impl is null) return; 9733 impl.fillColor(c); 9734 } 9735 9736 /// 9737 @property void rasterOp(RasterOp op) { 9738 if(impl is null) return; 9739 impl.rasterOp(op); 9740 } 9741 9742 9743 void updateDisplay() { 9744 // FIXME this should do what the dtor does 9745 } 9746 9747 /// 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) 9748 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 9749 if(impl is null) return; 9750 if(isClipped(upperLeft, width, height)) return; 9751 transform(upperLeft); 9752 version(Windows) { 9753 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 9754 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 9755 RECT clip = scroll; 9756 RECT uncovered; 9757 HRGN hrgn; 9758 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 9759 throw new WindowsApiException("ScrollDC", GetLastError()); 9760 9761 } else version(X11) { 9762 // FIXME: clip stuff outside this rectangle 9763 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 9764 } else version(OSXCocoa) { 9765 throw new NotYetImplementedException(); 9766 } else static assert(0); 9767 } 9768 9769 /// 9770 void clear(Color color = Color.white()) { 9771 if(impl is null) return; 9772 fillColor = color; 9773 outlineColor = color; 9774 drawRectangle(Point(0, 0), window.width, window.height); 9775 } 9776 9777 /++ 9778 Draws a pixmap (represented by the [Sprite] class) on the drawable. 9779 9780 Params: 9781 upperLeft = point on the window where the upper left corner of the image will be drawn 9782 imageUpperLeft = point on the image to start the slice to draw 9783 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. 9784 History: 9785 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9786 +/ 9787 void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9788 if(impl is null) return; 9789 if(isClipped(upperLeft, s.width, s.height)) return; 9790 transform(upperLeft); 9791 impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height); 9792 } 9793 9794 /// 9795 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 9796 if(impl is null) return; 9797 //if(isClipped(upperLeft, w, h)) return; // FIXME 9798 transform(upperLeft); 9799 if(w == 0 || w > i.width) 9800 w = i.width; 9801 if(h == 0 || h > i.height) 9802 h = i.height; 9803 if(upperLeftOfImage.x < 0) 9804 upperLeftOfImage.x = 0; 9805 if(upperLeftOfImage.y < 0) 9806 upperLeftOfImage.y = 0; 9807 9808 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 9809 } 9810 9811 /// 9812 Size textSize(in char[] text) { 9813 if(impl is null) return Size(0, 0); 9814 return impl.textSize(text); 9815 } 9816 9817 /++ 9818 Draws a string in the window with the set font (see [setFont] to change it). 9819 9820 Params: 9821 upperLeft = the upper left point of the bounding box of the text 9822 text = the string to draw 9823 lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound. 9824 alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags 9825 +/ 9826 @scriptable 9827 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 9828 if(impl is null) return; 9829 if(lowerRight.x != 0 || lowerRight.y != 0) { 9830 if(isClipped(upperLeft, lowerRight)) return; 9831 transform(lowerRight); 9832 } else { 9833 if(isClipped(upperLeft, textSize(text))) return; 9834 } 9835 transform(upperLeft); 9836 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 9837 } 9838 9839 /++ 9840 Draws text using a custom font. 9841 9842 This is still MAJOR work in progress. 9843 9844 Creating a [DrawableFont] can be tricky and require additional dependencies. 9845 +/ 9846 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 9847 if(impl is null) return; 9848 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9849 transform(upperLeft); 9850 font.drawString(this, upperLeft, text); 9851 } 9852 9853 version(Windows) 9854 void drawText(Point upperLeft, scope const(wchar)[] text) { 9855 if(impl is null) return; 9856 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9857 transform(upperLeft); 9858 9859 if(text.length && text[$-1] == '\n') 9860 text = text[0 .. $-1]; // tailing newlines are weird on windows... 9861 9862 TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length); 9863 } 9864 9865 static struct TextDrawingContext { 9866 Point boundingBoxUpperLeft; 9867 Point boundingBoxLowerRight; 9868 9869 Point currentLocation; 9870 9871 Point lastDrewUpperLeft; 9872 Point lastDrewLowerRight; 9873 9874 // how do i do right aligned rich text? 9875 // i kinda want to do a pre-made drawing then right align 9876 // draw the whole block. 9877 // 9878 // That's exactly the diff: inline vs block stuff. 9879 9880 // I need to get coordinates of an inline section out too, 9881 // not just a bounding box, but a series of bounding boxes 9882 // should be ok. Consider what's needed to detect a click 9883 // on a link in the middle of a paragraph breaking a line. 9884 // 9885 // Generally, we should be able to get the rectangles of 9886 // any portion we draw. 9887 // 9888 // It also needs to tell what text is left if it overflows 9889 // out of the box, so we can do stuff like float images around 9890 // it. It should not attempt to draw a letter that would be 9891 // clipped. 9892 // 9893 // I might also turn off word wrap stuff. 9894 } 9895 9896 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 9897 if(impl is null) return; 9898 // FIXME 9899 } 9900 9901 /// Drawing an individual pixel is slow. Avoid it if possible. 9902 void drawPixel(Point where) { 9903 if(impl is null) return; 9904 if(isClipped(where)) return; 9905 transform(where); 9906 impl.drawPixel(where.x, where.y); 9907 } 9908 9909 9910 /// Draws a pen using the current pen / outlineColor 9911 @scriptable 9912 void drawLine(Point starting, Point ending) { 9913 if(impl is null) return; 9914 if(isClipped(starting, ending)) return; 9915 transform(starting); 9916 transform(ending); 9917 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 9918 } 9919 9920 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 9921 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 9922 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 9923 @scriptable 9924 void drawRectangle(Point upperLeft, int width, int height) { 9925 if(impl is null) return; 9926 if(isClipped(upperLeft, width, height)) return; 9927 transform(upperLeft); 9928 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 9929 } 9930 9931 /// ditto 9932 void drawRectangle(Point upperLeft, Size size) { 9933 if(impl is null) return; 9934 if(isClipped(upperLeft, size.width, size.height)) return; 9935 transform(upperLeft); 9936 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 9937 } 9938 9939 /// ditto 9940 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 9941 if(impl is null) return; 9942 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 9943 transform(upperLeft); 9944 transform(lowerRightInclusive); 9945 impl.drawRectangle(upperLeft.x, upperLeft.y, 9946 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 9947 } 9948 9949 // overload added on May 12, 2021 9950 /// ditto 9951 void drawRectangle(Rectangle rect) { 9952 drawRectangle(rect.upperLeft, rect.size); 9953 } 9954 9955 /// Arguments are the points of the bounding rectangle 9956 void drawEllipse(Point upperLeft, Point lowerRight) { 9957 if(impl is null) return; 9958 if(isClipped(upperLeft, lowerRight)) return; 9959 transform(upperLeft); 9960 transform(lowerRight); 9961 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 9962 } 9963 9964 /++ 9965 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. 9966 9967 9968 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. 9969 9970 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. 9971 9972 Bugs: 9973 They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now). 9974 9975 The arc outline on Linux sometimes goes over the target. 9976 9977 The fill on Windows sometimes stops short. 9978 9979 History: 9980 This function was broken af, totally inconsistent on platforms until September 24, 2021. 9981 9982 The interpretation of the final argument was incorrectly documented and implemented until August 2, 2024. 9983 +/ 9984 void drawArc(Point upperLeft, int width, int height, int start, int length) { 9985 if(impl is null) return; 9986 // FIXME: not actually implemented 9987 if(isClipped(upperLeft, width, height)) return; 9988 transform(upperLeft); 9989 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, length); 9990 } 9991 9992 /// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 9993 void drawCircle(Point upperLeft, int diameter) { 9994 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 9995 } 9996 9997 /++ 9998 Draws a rectangle with rounded corners. It is outlined with the current foreground pen and filled with the current background brush. 9999 10000 10001 Bugs: 10002 Not implemented on Mac; it will instead draw a non-rounded rectangle for now. 10003 10004 History: 10005 Added August 3, 2024 10006 +/ 10007 void drawRectangleRounded(Rectangle rect, int borderRadius) { 10008 drawRectangleRounded(rect.upperLeft, rect.lowerRight, borderRadius); 10009 } 10010 10011 /// ditto 10012 void drawRectangleRounded(Point upperLeft, Size size, int borderRadius) { 10013 drawRectangleRounded(upperLeft, upperLeft + Point(size.width, size.height), borderRadius); 10014 } 10015 10016 /// ditto 10017 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 10018 if(borderRadius <= 0) { 10019 drawRectangle(upperLeft, lowerRight); 10020 return; 10021 } 10022 10023 transform(upperLeft); 10024 transform(lowerRight); 10025 10026 impl.drawRectangleRounded(upperLeft, lowerRight, borderRadius); 10027 } 10028 10029 /// . 10030 void drawPolygon(Point[] vertexes) { 10031 if(impl is null) return; 10032 assert(vertexes.length); 10033 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 10034 foreach(ref vertex; vertexes) { 10035 if(vertex.x < minX) 10036 minX = vertex.x; 10037 if(vertex.y < minY) 10038 minY = vertex.y; 10039 if(vertex.x > maxX) 10040 maxX = vertex.x; 10041 if(vertex.y > maxY) 10042 maxY = vertex.y; 10043 transform(vertex); 10044 } 10045 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 10046 impl.drawPolygon(vertexes); 10047 } 10048 10049 /// ditto 10050 void drawPolygon(Point[] vertexes...) { 10051 if(impl is null) return; 10052 drawPolygon(vertexes); 10053 } 10054 10055 10056 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 10057 10058 //mixin NativeScreenPainterImplementation!() impl; 10059 10060 10061 // HACK: if I mixin the impl directly, it won't let me override the copy 10062 // constructor! The linker complains about there being multiple definitions. 10063 // I'll make the best of it and reference count it though. 10064 ScreenPainterImplementation* impl; 10065 } 10066 10067 // HACK: I need a pointer to the implementation so it's separate 10068 struct ScreenPainterImplementation { 10069 CapableOfBeingDrawnUpon window; 10070 int referenceCount; 10071 mixin NativeScreenPainterImplementation!(); 10072 } 10073 10074 // FIXME: i haven't actually tested the sprite class on MS Windows 10075 10076 /** 10077 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 10078 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 10079 10080 10081 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 10082 though I'm not sure that's ideal and the implementation might change. 10083 10084 You create one by giving a window and an image. It optimizes for that window, 10085 and copies the image into it to use as the initial picture. Creating a sprite 10086 can be quite slow (especially over a network connection) so you should do it 10087 as little as possible and just hold on to your sprite handles after making them. 10088 simpledisplay does try to do its best though, using the XSHM extension if available, 10089 but you should still write your code as if it will always be slow. 10090 10091 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 10092 a fast operation - much faster than drawing the Image itself every time. 10093 10094 `Sprite` represents a scarce resource which should be freed when you 10095 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 10096 after it has been disposed. If you are unsure about this, don't take chances, 10097 just let the garbage collector do it for you. But ideally, you can manage its 10098 lifetime more efficiently. 10099 10100 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 10101 support alpha blending in its drawing at this time. That might change in the 10102 future, but if you need alpha blending right now, use OpenGL instead. See 10103 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 10104 10105 Update: on April 23, 2021, I finally added alpha blending support. You must opt 10106 in by setting the enableAlpha = true in the constructor. 10107 */ 10108 class Sprite : CapableOfBeingDrawnUpon { 10109 10110 /// 10111 ScreenPainter draw() { 10112 return ScreenPainter(this, handle, false); 10113 } 10114 10115 /++ 10116 Copies the sprite's current state into a [TrueColorImage]. 10117 10118 Be warned: this can be a very slow operation 10119 10120 History: 10121 Actually implemented on March 14, 2021 10122 +/ 10123 TrueColorImage takeScreenshot() { 10124 return trueColorImageFromNativeHandle(handle, width, height); 10125 } 10126 10127 void delegate() paintingFinishedDg() { return null; } 10128 bool closed() { return false; } 10129 ScreenPainterImplementation* activeScreenPainter_; 10130 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 10131 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 10132 10133 version(Windows) 10134 private ubyte* rawData; 10135 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 10136 // ditto on the XPicture stuff 10137 10138 version(X11) { 10139 private static XRenderPictFormat* RGB24; 10140 private static XRenderPictFormat* ARGB32; 10141 10142 private Picture xrenderPicture; 10143 } 10144 10145 version(X11) 10146 private static void requireXRender() { 10147 if(!XRenderLibrary.loadAttempted) { 10148 XRenderLibrary.loadDynamicLibrary(); 10149 } 10150 10151 if(!XRenderLibrary.loadSuccessful) 10152 throw new Exception("XRender library load failure"); 10153 10154 auto display = XDisplayConnection.get; 10155 10156 // FIXME: if we migrate X displays, these need to be changed 10157 if(RGB24 is null) 10158 RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); 10159 if(ARGB32 is null) 10160 ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); 10161 } 10162 10163 protected this() {} 10164 10165 this(SimpleWindow win, int width, int height, bool enableAlpha = false) { 10166 this._width = width; 10167 this._height = height; 10168 this.enableAlpha = enableAlpha; 10169 10170 version(X11) { 10171 auto display = XDisplayConnection.get(); 10172 10173 if(enableAlpha) { 10174 requireXRender(); 10175 } 10176 10177 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display)); 10178 10179 if(enableAlpha) { 10180 XRenderPictureAttributes attrs; 10181 xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs); 10182 } 10183 } else version(Windows) { 10184 version(CRuntime_DigitalMars) { 10185 //if(enableAlpha) 10186 //throw new Exception("Alpha support not available, try recompiling with -m32mscoff"); 10187 } 10188 10189 BITMAPINFO infoheader; 10190 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 10191 infoheader.bmiHeader.biWidth = width; 10192 infoheader.bmiHeader.biHeight = height; 10193 infoheader.bmiHeader.biPlanes = 1; 10194 infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24; 10195 infoheader.bmiHeader.biCompression = BI_RGB; 10196 10197 // FIXME: this should prolly be a device dependent bitmap... 10198 handle = CreateDIBSection( 10199 null, 10200 &infoheader, 10201 DIB_RGB_COLORS, 10202 cast(void**) &rawData, 10203 null, 10204 0); 10205 10206 if(handle is null) 10207 throw new WindowsApiException("couldn't create pixmap", GetLastError()); 10208 } 10209 } 10210 10211 /// Makes a sprite based on the image with the initial contents from the Image 10212 this(SimpleWindow win, Image i) { 10213 this(win, i.width, i.height, i.enableAlpha); 10214 10215 version(X11) { 10216 auto display = XDisplayConnection.get(); 10217 auto gc = XCreateGC(display, this.handle, 0, null); 10218 scope(exit) XFreeGC(display, gc); 10219 if(i.usingXshm) 10220 XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 10221 else 10222 XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 10223 } else version(Windows) { 10224 auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4); 10225 auto arrLength = itemsPerLine * height; 10226 rawData[0..arrLength] = i.rawData[0..arrLength]; 10227 } else version(OSXCocoa) { 10228 // FIXME: I have no idea if this is even any good 10229 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 10230 handle = CGBitmapContextCreate(null, width, height, 8, 4*width, 10231 colorSpace, 10232 kCGImageAlphaPremultipliedLast 10233 |kCGBitmapByteOrder32Big); 10234 CGColorSpaceRelease(colorSpace); 10235 auto rawData = CGBitmapContextGetData(handle); 10236 10237 auto rdl = (width * height * 4); 10238 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 10239 } else static assert(0); 10240 } 10241 10242 /++ 10243 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 10244 10245 Params: 10246 where = point on the window where the upper left corner of the image will be drawn 10247 imageUpperLeft = point on the image to start the slice to draw 10248 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. 10249 History: 10250 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 10251 +/ 10252 void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 10253 painter.drawPixmap(this, where, imageUpperLeft, sliceSize); 10254 } 10255 10256 /// Call this when you're ready to get rid of it 10257 void dispose() { 10258 version(X11) { 10259 staticDispose(xrenderPicture, handle); 10260 xrenderPicture = None; 10261 handle = None; 10262 } else version(Windows) { 10263 staticDispose(handle); 10264 handle = null; 10265 } else version(OSXCocoa) { 10266 staticDispose(handle); 10267 handle = null; 10268 } else static assert(0); 10269 10270 } 10271 10272 version(X11) 10273 static void staticDispose(Picture xrenderPicture, Pixmap handle) { 10274 if(xrenderPicture) 10275 XRenderFreePicture(XDisplayConnection.get, xrenderPicture); 10276 if(handle) 10277 XFreePixmap(XDisplayConnection.get(), handle); 10278 } 10279 else version(Windows) 10280 static void staticDispose(HBITMAP handle) { 10281 if(handle) 10282 DeleteObject(handle); 10283 } 10284 else version(OSXCocoa) 10285 static void staticDispose(CGContextRef context) { 10286 if(context) 10287 CGContextRelease(context); 10288 } 10289 10290 ~this() { 10291 version(X11) { if(xrenderPicture || handle) 10292 cleanupQueue.queue!staticDispose(xrenderPicture, handle); 10293 } else version(Windows) { if(handle) 10294 cleanupQueue.queue!staticDispose(handle); 10295 } else version(OSXCocoa) { if(handle) 10296 cleanupQueue.queue!staticDispose(handle); 10297 } else static assert(0); 10298 } 10299 10300 /// 10301 final @property int width() { return _width; } 10302 10303 /// 10304 final @property int height() { return _height; } 10305 10306 /// 10307 static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) { 10308 return new Sprite(win, Image.fromMemoryImage(img, enableAlpha)); 10309 } 10310 10311 auto nativeHandle() { 10312 return handle; 10313 } 10314 10315 private: 10316 10317 int _width; 10318 int _height; 10319 bool enableAlpha; 10320 version(X11) 10321 Pixmap handle; 10322 else version(Windows) 10323 HBITMAP handle; 10324 else version(OSXCocoa) 10325 CGContextRef handle; 10326 else static assert(0); 10327 } 10328 10329 /++ 10330 Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient]. 10331 10332 History: 10333 Added November 20, 2021 (dub v10.4) 10334 +/ 10335 version(OSXCocoa) {} else // NotYetImplementedException 10336 abstract class Gradient : Sprite { 10337 protected this(int w, int h) { 10338 version(X11) { 10339 Sprite.requireXRender(); 10340 10341 super(); 10342 enableAlpha = true; 10343 _width = w; 10344 _height = h; 10345 } else version(Windows) { 10346 super(null, w, h, true); // on Windows i'm just making a bitmap myself 10347 } 10348 } 10349 10350 version(Windows) 10351 final void forEachPixel(scope Color delegate(int x, int y) dg) @system { 10352 auto ptr = rawData; 10353 foreach(j; 0 .. _height) 10354 foreach(i; 0 .. _width) { 10355 auto color = dg(i, _height - j - 1); // cuz of upside down bitmap 10356 *rawData = (color.a * color.b) / 255; rawData++; 10357 *rawData = (color.a * color.g) / 255; rawData++; 10358 *rawData = (color.a * color.r) / 255; rawData++; 10359 *rawData = color.a; rawData++; 10360 } 10361 } 10362 10363 version(X11) 10364 protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) { 10365 assert(stops.length > 0); 10366 assert(stops.length <= 16, "I got lazy with buffers"); 10367 10368 XFixed[16] stopsPositions = void; 10369 XRenderColor[16] colors = void; 10370 10371 foreach(idx, stop; stops) { 10372 stopsPositions[idx] = cast(int)(stop.percentage * ushort.max); 10373 auto c = stop.c; 10374 colors[idx] = XRenderColor( 10375 cast(ushort)(c.r * ushort.max / 255), 10376 cast(ushort)(c.g * ushort.max / 255), 10377 cast(ushort)(c.b * ushort.max / 255), 10378 cast(ushort)(c.a * ubyte.max) // max value here is fractional 10379 ); 10380 } 10381 10382 xrenderPicture = dg(stopsPositions, colors); 10383 } 10384 10385 /// 10386 static struct Stop { 10387 float percentage; /// between 0 and 1.0 10388 Color c; 10389 } 10390 } 10391 10392 /++ 10393 Creates a linear gradient between p1 and p2. 10394 10395 X ONLY RIGHT NOW 10396 10397 History: 10398 Added November 20, 2021 (dub v10.4) 10399 10400 Bugs: 10401 Not yet implemented on Windows. 10402 +/ 10403 version(OSXCocoa) {} else // NotYetImplementedException 10404 class LinearGradient : Gradient { 10405 /++ 10406 10407 +/ 10408 this(Point p1, Point p2, Stop[] stops...) { 10409 super(p2.x, p2.y); 10410 10411 version(X11) { 10412 XLinearGradient gradient; 10413 gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max); 10414 gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max); 10415 10416 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10417 return XRenderCreateLinearGradient( 10418 XDisplayConnection.get, 10419 &gradient, 10420 stopsPositions.ptr, 10421 colors.ptr, 10422 cast(int) stops.length); 10423 }); 10424 } else version(Windows) { 10425 // FIXME 10426 forEachPixel((int x, int y) { 10427 import core.stdc.math; 10428 10429 //sqrtf( 10430 10431 return Color.transparent; 10432 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10433 }); 10434 } 10435 } 10436 } 10437 10438 /++ 10439 A conical gradient goes from color to color around a circumference from a center point. 10440 10441 X ONLY RIGHT NOW 10442 10443 History: 10444 Added November 20, 2021 (dub v10.4) 10445 10446 Bugs: 10447 Not yet implemented on Windows. 10448 +/ 10449 version(OSXCocoa) {} else // NotYetImplementedException 10450 class ConicalGradient : Gradient { 10451 /++ 10452 10453 +/ 10454 this(Point center, float angleInDegrees, Stop[] stops...) { 10455 super(center.x * 2, center.y * 2); 10456 10457 version(X11) { 10458 XConicalGradient gradient; 10459 gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max); 10460 gradient.angle = cast(int)(angleInDegrees * ushort.max); 10461 10462 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10463 return XRenderCreateConicalGradient( 10464 XDisplayConnection.get, 10465 &gradient, 10466 stopsPositions.ptr, 10467 colors.ptr, 10468 cast(int) stops.length); 10469 }); 10470 } else version(Windows) { 10471 // FIXME 10472 forEachPixel((int x, int y) { 10473 import core.stdc.math; 10474 10475 //sqrtf( 10476 10477 return Color.transparent; 10478 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10479 }); 10480 10481 } 10482 } 10483 } 10484 10485 /++ 10486 A radial gradient goes from color to color based on distance from the center. 10487 It is like rings of color. 10488 10489 X ONLY RIGHT NOW 10490 10491 10492 More specifically, you create two circles: an inner circle and an outer circle. 10493 The gradient is only drawn in the area outside the inner circle but inside the outer 10494 circle. The closest line between those two circles forms the line for the gradient 10495 and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around. 10496 10497 History: 10498 Added November 20, 2021 (dub v10.4) 10499 10500 Bugs: 10501 Not yet implemented on Windows. 10502 +/ 10503 version(OSXCocoa) {} else // NotYetImplementedException 10504 class RadialGradient : Gradient { 10505 /++ 10506 10507 +/ 10508 this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) { 10509 super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5)); 10510 10511 version(X11) { 10512 XRadialGradient gradient; 10513 gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max)); 10514 gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max)); 10515 10516 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10517 return XRenderCreateRadialGradient( 10518 XDisplayConnection.get, 10519 &gradient, 10520 stopsPositions.ptr, 10521 colors.ptr, 10522 cast(int) stops.length); 10523 }); 10524 } else version(Windows) { 10525 // FIXME 10526 forEachPixel((int x, int y) { 10527 import core.stdc.math; 10528 10529 //sqrtf( 10530 10531 return Color.transparent; 10532 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10533 }); 10534 } 10535 } 10536 } 10537 10538 10539 10540 /+ 10541 NOT IMPLEMENTED 10542 10543 A display-stored image optimized for relatively quick drawing, like 10544 [Sprite], but this one supports alpha channel blending and does NOT 10545 support direct drawing upon it with a [ScreenPainter]. 10546 10547 You can think of it as an [arsd.game.OpenGlTexture] for usage with a 10548 plain [ScreenPainter]... sort of. 10549 10550 On X11, it requires the Xrender extension and library. This is available 10551 almost everywhere though. 10552 10553 History: 10554 Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED 10555 +/ 10556 version(none) 10557 class AlphaSprite { 10558 /++ 10559 Copies the given image into it. 10560 +/ 10561 this(MemoryImage img) { 10562 10563 if(!XRenderLibrary.loadAttempted) { 10564 XRenderLibrary.loadDynamicLibrary(); 10565 10566 // FIXME: this needs to be reconstructed when the X server changes 10567 repopulateX(); 10568 } 10569 if(!XRenderLibrary.loadSuccessful) 10570 throw new Exception("XRender library load failure"); 10571 10572 // I probably need to put the alpha mask in a separate Picture 10573 // ugh 10574 // maybe the Sprite itself can have an alpha bitmask anyway 10575 10576 10577 auto display = XDisplayConnection.get(); 10578 pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 10579 10580 10581 XRenderPictureAttributes attrs; 10582 10583 handle = XRenderCreatePicture( 10584 XDisplayConnection.get, 10585 pixmap, 10586 RGBA, 10587 0, 10588 &attrs 10589 ); 10590 10591 } 10592 10593 // maybe i'll use the create gradient functions too with static factories.. 10594 10595 void drawAt(ScreenPainter painter, Point where) { 10596 //painter.drawPixmap(this, where); 10597 10598 XRenderPictureAttributes attrs; 10599 10600 auto pic = XRenderCreatePicture( 10601 XDisplayConnection.get, 10602 painter.impl.d, 10603 RGB, 10604 0, 10605 &attrs 10606 ); 10607 10608 XRenderComposite( 10609 XDisplayConnection.get, 10610 3, // PictOpOver 10611 handle, 10612 None, 10613 pic, 10614 0, // src 10615 0, 10616 0, // mask 10617 0, 10618 10, // dest 10619 10, 10620 100, // width 10621 100 10622 ); 10623 10624 /+ 10625 XRenderFreePicture( 10626 XDisplayConnection.get, 10627 pic 10628 ); 10629 10630 XRenderFreePicture( 10631 XDisplayConnection.get, 10632 fill 10633 ); 10634 +/ 10635 // on Windows you can stretch but Xrender still can't :( 10636 } 10637 10638 static XRenderPictFormat* RGB; 10639 static XRenderPictFormat* RGBA; 10640 static void repopulateX() { 10641 auto display = XDisplayConnection.get; 10642 RGB = XRenderFindStandardFormat(display, PictStandardRGB24); 10643 RGBA = XRenderFindStandardFormat(display, PictStandardARGB32); 10644 } 10645 10646 XPixmap pixmap; 10647 Picture handle; 10648 } 10649 10650 /// 10651 interface CapableOfBeingDrawnUpon { 10652 /// 10653 ScreenPainter draw(); 10654 /// 10655 int width(); 10656 /// 10657 int height(); 10658 protected ScreenPainterImplementation* activeScreenPainter(); 10659 protected void activeScreenPainter(ScreenPainterImplementation*); 10660 bool closed(); 10661 10662 void delegate() paintingFinishedDg(); 10663 10664 /// Be warned: this can be a very slow operation 10665 TrueColorImage takeScreenshot(); 10666 } 10667 10668 /// 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]. 10669 void flushGui() { 10670 version(X11) { 10671 auto dpy = XDisplayConnection.get(); 10672 XLockDisplay(dpy); 10673 scope(exit) XUnlockDisplay(dpy); 10674 XFlush(dpy); 10675 } 10676 } 10677 10678 /++ 10679 Runs the given code in the GUI thread when its event loop 10680 is available, blocking until it completes. This allows you 10681 to create and manipulate windows from another thread without 10682 invoking undefined behavior. 10683 10684 If this is the gui thread, it runs the code immediately. 10685 10686 If no gui thread exists yet, the current thread is assumed 10687 to be it. Attempting to create windows or run the event loop 10688 in any other thread will cause an assertion failure. 10689 10690 10691 $(TIP 10692 Did you know you can use UFCS on delegate literals? 10693 10694 () { 10695 // code here 10696 }.runInGuiThread; 10697 ) 10698 10699 Returns: 10700 `true` if the function was called, `false` if it was not. 10701 The function may not be called because the gui thread had 10702 already terminated by the time you called this. 10703 10704 History: 10705 Added April 10, 2020 (v7.2.0) 10706 10707 Return value added and implementation tweaked to avoid locking 10708 at program termination on February 24, 2021 (v9.2.1). 10709 +/ 10710 bool runInGuiThread(scope void delegate() dg) @trusted { 10711 claimGuiThread(); 10712 10713 if(thisIsGuiThread) { 10714 dg(); 10715 return true; 10716 } 10717 10718 if(guiThreadTerminating) 10719 return false; 10720 10721 import core.sync.semaphore; 10722 static Semaphore sc; 10723 if(sc is null) 10724 sc = new Semaphore(); 10725 10726 static RunQueueMember* rqm; 10727 if(rqm is null) 10728 rqm = new RunQueueMember; 10729 rqm.dg = cast(typeof(rqm.dg)) dg; 10730 rqm.signal = sc; 10731 rqm.thrown = null; 10732 10733 synchronized(runInGuiThreadLock) { 10734 runInGuiThreadQueue ~= rqm; 10735 } 10736 10737 if(!SimpleWindow.eventWakeUp()) 10738 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10739 10740 rqm.signal.wait(); 10741 auto t = rqm.thrown; 10742 10743 if(t) 10744 throw t; 10745 10746 return true; 10747 } 10748 10749 // note it runs sync if this is the gui thread.... 10750 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow { 10751 claimGuiThread(); 10752 10753 try { 10754 10755 if(thisIsGuiThread) { 10756 dg(); 10757 return; 10758 } 10759 10760 if(guiThreadTerminating) 10761 return; 10762 10763 RunQueueMember* rqm = new RunQueueMember; 10764 rqm.dg = cast(typeof(rqm.dg)) dg; 10765 rqm.signal = null; 10766 rqm.thrown = null; 10767 10768 synchronized(runInGuiThreadLock) { 10769 runInGuiThreadQueue ~= rqm; 10770 } 10771 10772 if(!SimpleWindow.eventWakeUp()) 10773 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10774 } catch(Exception e) { 10775 if(handleError) 10776 handleError(e); 10777 } 10778 } 10779 10780 private void runPendingRunInGuiThreadDelegates() { 10781 more: 10782 RunQueueMember* next; 10783 synchronized(runInGuiThreadLock) { 10784 if(runInGuiThreadQueue.length) { 10785 next = runInGuiThreadQueue[0]; 10786 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 10787 } else { 10788 next = null; 10789 } 10790 } 10791 10792 if(next) { 10793 try { 10794 next.dg(); 10795 next.thrown = null; 10796 } catch(Throwable t) { 10797 next.thrown = t; 10798 } 10799 10800 if(next.signal) 10801 next.signal.notify(); 10802 10803 goto more; 10804 } 10805 } 10806 10807 private void claimGuiThread() nothrow { 10808 import core.atomic; 10809 if(cas(&guiThreadExists_, false, true)) 10810 thisIsGuiThread = true; 10811 } 10812 10813 private struct RunQueueMember { 10814 void delegate() dg; 10815 import core.sync.semaphore; 10816 Semaphore signal; 10817 Throwable thrown; 10818 } 10819 10820 private __gshared RunQueueMember*[] runInGuiThreadQueue; 10821 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE 10822 private bool thisIsGuiThread = false; 10823 private shared bool guiThreadExists_ = false; 10824 private shared bool guiThreadTerminating = false; 10825 10826 /++ 10827 Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d 10828 event loop. All windows must be exclusively created and managed by a single thread. 10829 10830 If no gui thread exists, simpledisplay.d will automatically adopt the current thread 10831 when you call one of its constructors. 10832 10833 If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this 10834 one. If so, you can run gui functions on it. If not, don't. The helper functions 10835 [runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically. 10836 10837 The reason this function is available is in case you want to message pass between a gui 10838 thread and your current thread. If no gui thread exists or if this is the gui thread, 10839 you're liable to deadlock when trying to communicate since you'd end up talking to yourself. 10840 10841 History: 10842 Added December 3, 2021 (dub v10.5) 10843 +/ 10844 public bool guiThreadExists() { 10845 return guiThreadExists_; 10846 } 10847 10848 /++ 10849 Returns `true` if this thread is either running or set to be running the 10850 simpledisplay.d gui core event loop because it owns windows. 10851 10852 It is important to keep gui-related functionality in the right thread, so you will 10853 want to `runInGuiThread` when you call them (with some specific exceptions called 10854 out in those specific functions' documentation). Notably, all windows must be 10855 created and managed only from the gui thread. 10856 10857 Will return false if simpledisplay's other functions haven't been called 10858 yet; check [guiThreadExists] in addition to this. 10859 10860 History: 10861 Added December 3, 2021 (dub v10.5) 10862 +/ 10863 public bool thisThreadRunningGui() { 10864 return thisIsGuiThread; 10865 } 10866 10867 /++ 10868 Function to help temporarily print debugging info. It will bypass any stdout/err redirection 10869 and go to the controlling tty or console (attaching to the parent and/or allocating one as 10870 needed on Windows. Please note it may overwrite output from other programs in the parent and the 10871 allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log 10872 file instead if you are in one of those situations). 10873 10874 It does not support outputting very many types; just strings and ints are likely to actually work. 10875 10876 It will perform very slowly and swallows any errors that may occur. Moreover, the specific output 10877 is unspecified meaning I can change it at any time. The only point of this function is to help 10878 in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword 10879 and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use 10880 in those contexts. 10881 10882 $(WARNING 10883 I reserve the right to change this function at any time. You can use it if it helps you 10884 but do not rely on it for anything permanent. 10885 ) 10886 10887 History: 10888 Added December 3, 2021. Not formally supported under any stable tag. 10889 +/ 10890 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted { 10891 try { 10892 version(Windows) { 10893 import core.sys.windows.wincon; 10894 if(!AttachConsole(ATTACH_PARENT_PROCESS)) 10895 AllocConsole(); 10896 const(char)* fn = "CONOUT$"; 10897 } else version(Posix) { 10898 const(char)* fn = "/dev/tty"; 10899 } else static assert(0, "Function not implemented for your system"); 10900 10901 if(fileOverride.length) 10902 fn = fileOverride.ptr; 10903 10904 import core.stdc.stdio; 10905 auto fp = fopen(fn, "wt"); 10906 if(fp is null) return; 10907 scope(exit) fclose(fp); 10908 10909 string str; 10910 foreach(item; t) { 10911 static if(is(typeof(item) : const(char)[])) 10912 str ~= item; 10913 else 10914 str ~= toInternal!string(item); 10915 str ~= " "; 10916 } 10917 str ~= "\n"; 10918 10919 fwrite(str.ptr, 1, str.length, fp); 10920 fflush(fp); 10921 } catch(Exception e) { 10922 // sorry no hope 10923 } 10924 } 10925 10926 private void guiThreadFinalize() { 10927 assert(thisIsGuiThread); 10928 10929 guiThreadTerminating = true; // don't add any more from this point on 10930 runPendingRunInGuiThreadDelegates(); 10931 } 10932 10933 /+ 10934 interface IPromise { 10935 void reportProgress(int current, int max, string message); 10936 10937 /+ // not formally in cuz of templates but still 10938 IPromise Then(); 10939 IPromise Catch(); 10940 IPromise Finally(); 10941 +/ 10942 } 10943 10944 /+ 10945 auto promise = async({ ... }); 10946 promise.Then(whatever). 10947 Then(whateverelse). 10948 Catch((exception) { }); 10949 10950 10951 A promise is run inside a fiber and it looks something like: 10952 10953 try { 10954 auto res = whatever(); 10955 auto res2 = whateverelse(res); 10956 } catch(Exception e) { 10957 { }(e); 10958 } 10959 10960 When a thing succeeds, it is passed as an arg to the next 10961 +/ 10962 class Promise(T) : IPromise { 10963 auto Then() { return null; } 10964 auto Catch() { return null; } 10965 auto Finally() { return null; } 10966 10967 // wait for it to resolve and return the value, or rethrow the error if that occurred. 10968 // cannot be called from the gui thread, but this is caught at runtime instead of compile time. 10969 T await(); 10970 } 10971 10972 interface Task { 10973 } 10974 10975 interface Resolvable(T) : Task { 10976 void run(); 10977 10978 void resolve(T); 10979 10980 Resolvable!T then(void delegate(T)); // returns a new promise 10981 Resolvable!T error(Throwable); // js catch 10982 Resolvable!T completed(); // js finally 10983 10984 } 10985 10986 /++ 10987 Runs `work` in a helper thread and sends its return value back to the main gui 10988 thread as the argument to `uponCompletion`. If `work` throws, the exception is 10989 sent to the `uponThrown` if given, or if null, rethrown from the event loop to 10990 kill the program. 10991 10992 You can call reportProgress(position, max, message) to update your parent window 10993 on your progress. 10994 10995 I should also use `shared` methods. FIXME 10996 10997 History: 10998 Added March 6, 2021 (dub version 9.3). 10999 +/ 11000 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) { 11001 uponCompletion(work(null)); 11002 } 11003 11004 +/ 11005 11006 /// Used internal to dispatch events to various classes. 11007 interface CapableOfHandlingNativeEvent { 11008 NativeEventHandler getNativeEventHandler(); 11009 11010 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 11011 11012 version(X11) { 11013 // if this is impossible, you are allowed to just throw from it 11014 // Note: if you call it from another object, set a flag cuz the manger will call you again 11015 void recreateAfterDisconnect(); 11016 // discard any *connection specific* state, but keep enough that you 11017 // can be recreated if possible. discardConnectionState() is always called immediately 11018 // before recreateAfterDisconnect(), so you can set a flag there to decide if 11019 // you need initialization order 11020 void discardConnectionState(); 11021 } 11022 } 11023 11024 version(X11) 11025 /++ 11026 State of keys on mouse events, especially motion. 11027 11028 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 11029 +/ 11030 enum ModifierState : uint { 11031 shift = 1, /// 11032 capsLock = 2, /// 11033 ctrl = 4, /// 11034 alt = 8, /// Not always available on Windows 11035 windows = 64, /// ditto 11036 numLock = 16, /// 11037 11038 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 11039 middleButtonDown = 512, /// ditto 11040 rightButtonDown = 1024, /// ditto 11041 } 11042 else version(Windows) 11043 /// ditto 11044 enum ModifierState : uint { 11045 shift = 4, /// 11046 ctrl = 8, /// 11047 11048 // i'm not sure if the next two are available 11049 alt = 256, /// not always available on Windows 11050 windows = 512, /// ditto 11051 11052 capsLock = 1024, /// 11053 numLock = 2048, /// 11054 11055 leftButtonDown = 1, /// not available on key events 11056 middleButtonDown = 16, /// ditto 11057 rightButtonDown = 2, /// ditto 11058 11059 backButtonDown = 0x20, /// not available on X 11060 forwardButtonDown = 0x40, /// ditto 11061 } 11062 else version(OSXCocoa) 11063 // FIXME FIXME NotYetImplementedException 11064 enum ModifierState : uint { 11065 shift = 1, /// 11066 capsLock = 2, /// 11067 ctrl = 4, /// 11068 alt = 8, /// Not always available on Windows 11069 windows = 64, /// ditto 11070 numLock = 16, /// 11071 11072 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 11073 middleButtonDown = 512, /// ditto 11074 rightButtonDown = 1024, /// ditto 11075 } 11076 11077 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them. 11078 enum MouseButton : int { 11079 none = 0, 11080 left = 1, /// 11081 right = 2, /// 11082 middle = 4, /// 11083 wheelUp = 8, /// 11084 wheelDown = 16, /// 11085 backButton = 32, /// often found on the thumb and used for back in browsers 11086 forwardButton = 64, /// often found on the thumb and used for forward in browsers 11087 } 11088 11089 /// Corresponds to the values found in MouseEvent.buttonLinear, being equal to `core.bitop.bsf(button) + 1` 11090 enum MouseButtonLinear : ubyte { 11091 left = 1, /// 11092 right, /// 11093 middle, /// 11094 wheelUp, /// 11095 wheelDown, /// 11096 backButton, /// often found on the thumb and used for back in browsers 11097 forwardButton, /// often found on the thumb and used for forward in browsers 11098 } 11099 11100 version(X11) { 11101 // FIXME: match ASCII whenever we can. Most of it is already there, 11102 // but there's a few exceptions and mismatches with Windows 11103 11104 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 11105 enum Key { 11106 Escape = 0xff1b, /// 11107 F1 = 0xffbe, /// 11108 F2 = 0xffbf, /// 11109 F3 = 0xffc0, /// 11110 F4 = 0xffc1, /// 11111 F5 = 0xffc2, /// 11112 F6 = 0xffc3, /// 11113 F7 = 0xffc4, /// 11114 F8 = 0xffc5, /// 11115 F9 = 0xffc6, /// 11116 F10 = 0xffc7, /// 11117 F11 = 0xffc8, /// 11118 F12 = 0xffc9, /// 11119 PrintScreen = 0xff61, /// 11120 ScrollLock = 0xff14, /// 11121 Pause = 0xff13, /// 11122 Grave = 0x60, /// The $(BACKTICK) ~ key 11123 // number keys across the top of the keyboard 11124 N1 = 0x31, /// Number key atop the keyboard 11125 N2 = 0x32, /// 11126 N3 = 0x33, /// 11127 N4 = 0x34, /// 11128 N5 = 0x35, /// 11129 N6 = 0x36, /// 11130 N7 = 0x37, /// 11131 N8 = 0x38, /// 11132 N9 = 0x39, /// 11133 N0 = 0x30, /// 11134 Dash = 0x2d, /// 11135 Equals = 0x3d, /// 11136 Backslash = 0x5c, /// The \ | key 11137 Backspace = 0xff08, /// 11138 Insert = 0xff63, /// 11139 Home = 0xff50, /// 11140 PageUp = 0xff55, /// 11141 Delete = 0xffff, /// 11142 End = 0xff57, /// 11143 PageDown = 0xff56, /// 11144 Up = 0xff52, /// 11145 Down = 0xff54, /// 11146 Left = 0xff51, /// 11147 Right = 0xff53, /// 11148 11149 Tab = 0xff09, /// 11150 Q = 0x71, /// 11151 W = 0x77, /// 11152 E = 0x65, /// 11153 R = 0x72, /// 11154 T = 0x74, /// 11155 Y = 0x79, /// 11156 U = 0x75, /// 11157 I = 0x69, /// 11158 O = 0x6f, /// 11159 P = 0x70, /// 11160 LeftBracket = 0x5b, /// the [ { key 11161 RightBracket = 0x5d, /// the ] } key 11162 CapsLock = 0xffe5, /// 11163 A = 0x61, /// 11164 S = 0x73, /// 11165 D = 0x64, /// 11166 F = 0x66, /// 11167 G = 0x67, /// 11168 H = 0x68, /// 11169 J = 0x6a, /// 11170 K = 0x6b, /// 11171 L = 0x6c, /// 11172 Semicolon = 0x3b, /// 11173 Apostrophe = 0x27, /// 11174 Enter = 0xff0d, /// 11175 Shift = 0xffe1, /// 11176 Z = 0x7a, /// 11177 X = 0x78, /// 11178 C = 0x63, /// 11179 V = 0x76, /// 11180 B = 0x62, /// 11181 N = 0x6e, /// 11182 M = 0x6d, /// 11183 Comma = 0x2c, /// 11184 Period = 0x2e, /// 11185 Slash = 0x2f, /// the / ? key 11186 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 11187 Ctrl = 0xffe3, /// 11188 Windows = 0xffeb, /// 11189 Alt = 0xffe9, /// 11190 Space = 0x20, /// 11191 Alt_r = 0xffea, /// ditto of shift_r 11192 Windows_r = 0xffec, /// 11193 Menu = 0xff67, /// 11194 Ctrl_r = 0xffe4, /// 11195 11196 NumLock = 0xff7f, /// 11197 Divide = 0xffaf, /// The / key on the number pad 11198 Multiply = 0xffaa, /// The * key on the number pad 11199 Minus = 0xffad, /// The - key on the number pad 11200 Plus = 0xffab, /// The + key on the number pad 11201 PadEnter = 0xff8d, /// Numberpad enter key 11202 Pad1 = 0xff9c, /// Numberpad keys 11203 Pad2 = 0xff99, /// 11204 Pad3 = 0xff9b, /// 11205 Pad4 = 0xff96, /// 11206 Pad5 = 0xff9d, /// 11207 Pad6 = 0xff98, /// 11208 Pad7 = 0xff95, /// 11209 Pad8 = 0xff97, /// 11210 Pad9 = 0xff9a, /// 11211 Pad0 = 0xff9e, /// 11212 PadDot = 0xff9f, /// 11213 } 11214 } else version(Windows) { 11215 // the character here is for en-us layouts and for illustration only 11216 // if you actually want to get characters, wait for character events 11217 // (the argument to your event handler is simply a dchar) 11218 // those will be converted by the OS for the right locale. 11219 11220 enum Key { 11221 Escape = 0x1b, 11222 F1 = 0x70, 11223 F2 = 0x71, 11224 F3 = 0x72, 11225 F4 = 0x73, 11226 F5 = 0x74, 11227 F6 = 0x75, 11228 F7 = 0x76, 11229 F8 = 0x77, 11230 F9 = 0x78, 11231 F10 = 0x79, 11232 F11 = 0x7a, 11233 F12 = 0x7b, 11234 PrintScreen = 0x2c, 11235 ScrollLock = 0x91, 11236 Pause = 0x13, 11237 Grave = 0xc0, 11238 // number keys across the top of the keyboard 11239 N1 = 0x31, 11240 N2 = 0x32, 11241 N3 = 0x33, 11242 N4 = 0x34, 11243 N5 = 0x35, 11244 N6 = 0x36, 11245 N7 = 0x37, 11246 N8 = 0x38, 11247 N9 = 0x39, 11248 N0 = 0x30, 11249 Dash = 0xbd, 11250 Equals = 0xbb, 11251 Backslash = 0xdc, 11252 Backspace = 0x08, 11253 Insert = 0x2d, 11254 Home = 0x24, 11255 PageUp = 0x21, 11256 Delete = 0x2e, 11257 End = 0x23, 11258 PageDown = 0x22, 11259 Up = 0x26, 11260 Down = 0x28, 11261 Left = 0x25, 11262 Right = 0x27, 11263 11264 Tab = 0x09, 11265 Q = 0x51, 11266 W = 0x57, 11267 E = 0x45, 11268 R = 0x52, 11269 T = 0x54, 11270 Y = 0x59, 11271 U = 0x55, 11272 I = 0x49, 11273 O = 0x4f, 11274 P = 0x50, 11275 LeftBracket = 0xdb, 11276 RightBracket = 0xdd, 11277 CapsLock = 0x14, 11278 A = 0x41, 11279 S = 0x53, 11280 D = 0x44, 11281 F = 0x46, 11282 G = 0x47, 11283 H = 0x48, 11284 J = 0x4a, 11285 K = 0x4b, 11286 L = 0x4c, 11287 Semicolon = 0xba, 11288 Apostrophe = 0xde, 11289 Enter = 0x0d, 11290 Shift = 0x10, 11291 Z = 0x5a, 11292 X = 0x58, 11293 C = 0x43, 11294 V = 0x56, 11295 B = 0x42, 11296 N = 0x4e, 11297 M = 0x4d, 11298 Comma = 0xbc, 11299 Period = 0xbe, 11300 Slash = 0xbf, 11301 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 11302 Ctrl = 0x11, 11303 Windows = 0x5b, 11304 Alt = -5, // FIXME 11305 Space = 0x20, 11306 Alt_r = 0xffea, // ditto of shift_r 11307 Windows_r = 0x5c, // ditto of shift_r 11308 Menu = 0x5d, 11309 Ctrl_r = 0xa3, // ditto of shift_r 11310 11311 NumLock = 0x90, 11312 Divide = 0x6f, 11313 Multiply = 0x6a, 11314 Minus = 0x6d, 11315 Plus = 0x6b, 11316 PadEnter = -8, // FIXME 11317 Pad1 = 0x61, 11318 Pad2 = 0x62, 11319 Pad3 = 0x63, 11320 Pad4 = 0x64, 11321 Pad5 = 0x65, 11322 Pad6 = 0x66, 11323 Pad7 = 0x67, 11324 Pad8 = 0x68, 11325 Pad9 = 0x69, 11326 Pad0 = 0x60, 11327 PadDot = 0x6e, 11328 } 11329 11330 // I'm keeping this around for reference purposes 11331 // ideally all these buttons will be listed for all platforms, 11332 // but now now I'm just focusing on my US keyboard 11333 version(none) 11334 enum Key { 11335 LBUTTON = 0x01, 11336 RBUTTON = 0x02, 11337 CANCEL = 0x03, 11338 MBUTTON = 0x04, 11339 //static if (_WIN32_WINNT > = 0x500) { 11340 XBUTTON1 = 0x05, 11341 XBUTTON2 = 0x06, 11342 //} 11343 BACK = 0x08, 11344 TAB = 0x09, 11345 CLEAR = 0x0C, 11346 RETURN = 0x0D, 11347 SHIFT = 0x10, 11348 CONTROL = 0x11, 11349 MENU = 0x12, 11350 PAUSE = 0x13, 11351 CAPITAL = 0x14, 11352 KANA = 0x15, 11353 HANGEUL = 0x15, 11354 HANGUL = 0x15, 11355 JUNJA = 0x17, 11356 FINAL = 0x18, 11357 HANJA = 0x19, 11358 KANJI = 0x19, 11359 ESCAPE = 0x1B, 11360 CONVERT = 0x1C, 11361 NONCONVERT = 0x1D, 11362 ACCEPT = 0x1E, 11363 MODECHANGE = 0x1F, 11364 SPACE = 0x20, 11365 PRIOR = 0x21, 11366 NEXT = 0x22, 11367 END = 0x23, 11368 HOME = 0x24, 11369 LEFT = 0x25, 11370 UP = 0x26, 11371 RIGHT = 0x27, 11372 DOWN = 0x28, 11373 SELECT = 0x29, 11374 PRINT = 0x2A, 11375 EXECUTE = 0x2B, 11376 SNAPSHOT = 0x2C, 11377 INSERT = 0x2D, 11378 DELETE = 0x2E, 11379 HELP = 0x2F, 11380 LWIN = 0x5B, 11381 RWIN = 0x5C, 11382 APPS = 0x5D, 11383 SLEEP = 0x5F, 11384 NUMPAD0 = 0x60, 11385 NUMPAD1 = 0x61, 11386 NUMPAD2 = 0x62, 11387 NUMPAD3 = 0x63, 11388 NUMPAD4 = 0x64, 11389 NUMPAD5 = 0x65, 11390 NUMPAD6 = 0x66, 11391 NUMPAD7 = 0x67, 11392 NUMPAD8 = 0x68, 11393 NUMPAD9 = 0x69, 11394 MULTIPLY = 0x6A, 11395 ADD = 0x6B, 11396 SEPARATOR = 0x6C, 11397 SUBTRACT = 0x6D, 11398 DECIMAL = 0x6E, 11399 DIVIDE = 0x6F, 11400 F1 = 0x70, 11401 F2 = 0x71, 11402 F3 = 0x72, 11403 F4 = 0x73, 11404 F5 = 0x74, 11405 F6 = 0x75, 11406 F7 = 0x76, 11407 F8 = 0x77, 11408 F9 = 0x78, 11409 F10 = 0x79, 11410 F11 = 0x7A, 11411 F12 = 0x7B, 11412 F13 = 0x7C, 11413 F14 = 0x7D, 11414 F15 = 0x7E, 11415 F16 = 0x7F, 11416 F17 = 0x80, 11417 F18 = 0x81, 11418 F19 = 0x82, 11419 F20 = 0x83, 11420 F21 = 0x84, 11421 F22 = 0x85, 11422 F23 = 0x86, 11423 F24 = 0x87, 11424 NUMLOCK = 0x90, 11425 SCROLL = 0x91, 11426 LSHIFT = 0xA0, 11427 RSHIFT = 0xA1, 11428 LCONTROL = 0xA2, 11429 RCONTROL = 0xA3, 11430 LMENU = 0xA4, 11431 RMENU = 0xA5, 11432 //static if (_WIN32_WINNT > = 0x500) { 11433 BROWSER_BACK = 0xA6, 11434 BROWSER_FORWARD = 0xA7, 11435 BROWSER_REFRESH = 0xA8, 11436 BROWSER_STOP = 0xA9, 11437 BROWSER_SEARCH = 0xAA, 11438 BROWSER_FAVORITES = 0xAB, 11439 BROWSER_HOME = 0xAC, 11440 VOLUME_MUTE = 0xAD, 11441 VOLUME_DOWN = 0xAE, 11442 VOLUME_UP = 0xAF, 11443 MEDIA_NEXT_TRACK = 0xB0, 11444 MEDIA_PREV_TRACK = 0xB1, 11445 MEDIA_STOP = 0xB2, 11446 MEDIA_PLAY_PAUSE = 0xB3, 11447 LAUNCH_MAIL = 0xB4, 11448 LAUNCH_MEDIA_SELECT = 0xB5, 11449 LAUNCH_APP1 = 0xB6, 11450 LAUNCH_APP2 = 0xB7, 11451 //} 11452 OEM_1 = 0xBA, 11453 //static if (_WIN32_WINNT > = 0x500) { 11454 OEM_PLUS = 0xBB, 11455 OEM_COMMA = 0xBC, 11456 OEM_MINUS = 0xBD, 11457 OEM_PERIOD = 0xBE, 11458 //} 11459 OEM_2 = 0xBF, 11460 OEM_3 = 0xC0, 11461 OEM_4 = 0xDB, 11462 OEM_5 = 0xDC, 11463 OEM_6 = 0xDD, 11464 OEM_7 = 0xDE, 11465 OEM_8 = 0xDF, 11466 //static if (_WIN32_WINNT > = 0x500) { 11467 OEM_102 = 0xE2, 11468 //} 11469 PROCESSKEY = 0xE5, 11470 //static if (_WIN32_WINNT > = 0x500) { 11471 PACKET = 0xE7, 11472 //} 11473 ATTN = 0xF6, 11474 CRSEL = 0xF7, 11475 EXSEL = 0xF8, 11476 EREOF = 0xF9, 11477 PLAY = 0xFA, 11478 ZOOM = 0xFB, 11479 NONAME = 0xFC, 11480 PA1 = 0xFD, 11481 OEM_CLEAR = 0xFE, 11482 } 11483 11484 } else version(OSXCocoa) { 11485 enum Key { 11486 Escape = 53, 11487 F1 = 122, 11488 F2 = 120, 11489 F3 = 99, 11490 F4 = 118, 11491 F5 = 96, 11492 F6 = 97, 11493 F7 = 98, 11494 F8 = 100, 11495 F9 = 101, 11496 F10 = 109, 11497 F11 = 103, 11498 F12 = 111, 11499 PrintScreen = 105, 11500 ScrollLock = 107, 11501 Pause = 113, 11502 Grave = 50, 11503 // number keys across the top of the keyboard 11504 N1 = 18, 11505 N2 = 19, 11506 N3 = 20, 11507 N4 = 21, 11508 N5 = 23, 11509 N6 = 22, 11510 N7 = 26, 11511 N8 = 28, 11512 N9 = 25, 11513 N0 = 29, 11514 Dash = 27, 11515 Equals = 24, 11516 Backslash = 42, 11517 Backspace = 51, 11518 Insert = 114, 11519 Home = 115, 11520 PageUp = 116, 11521 Delete = 117, 11522 End = 119, 11523 PageDown = 121, 11524 Up = 126, 11525 Down = 125, 11526 Left = 123, 11527 Right = 124, 11528 11529 Tab = 48, 11530 Q = 12, 11531 W = 13, 11532 E = 14, 11533 R = 15, 11534 T = 17, 11535 Y = 16, 11536 U = 32, 11537 I = 34, 11538 O = 31, 11539 P = 35, 11540 LeftBracket = 33, 11541 RightBracket = 30, 11542 CapsLock = 57, 11543 A = 0, 11544 S = 1, 11545 D = 2, 11546 F = 3, 11547 G = 5, 11548 H = 4, 11549 J = 38, 11550 K = 40, 11551 L = 37, 11552 Semicolon = 41, 11553 Apostrophe = 39, 11554 Enter = 36, 11555 Shift = 56, 11556 Z = 6, 11557 X = 7, 11558 C = 8, 11559 V = 9, 11560 B = 11, 11561 N = 45, 11562 M = 46, 11563 Comma = 43, 11564 Period = 47, 11565 Slash = 44, 11566 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 11567 Ctrl = 59, 11568 Windows = 55, 11569 Alt = 58, 11570 Space = 49, 11571 Alt_r = -3, // ditto of shift_r 11572 Windows_r = -2, 11573 Menu = 110, 11574 Ctrl_r = -1, 11575 11576 NumLock = 1, 11577 Divide = 75, 11578 Multiply = 67, 11579 Minus = 78, 11580 Plus = 69, 11581 PadEnter = 76, 11582 Pad1 = 83, 11583 Pad2 = 84, 11584 Pad3 = 85, 11585 Pad4 = 86, 11586 Pad5 = 87, 11587 Pad6 = 88, 11588 Pad7 = 89, 11589 Pad8 = 91, 11590 Pad9 = 92, 11591 Pad0 = 82, 11592 PadDot = 65, 11593 } 11594 11595 } 11596 11597 /* Additional utilities */ 11598 11599 11600 Color fromHsl(real h, real s, real l) { 11601 return arsd.color.fromHsl([h,s,l]); 11602 } 11603 11604 11605 11606 /* ********** What follows is the system-specific implementations *********/ 11607 version(Windows) { 11608 11609 11610 // helpers for making HICONs from MemoryImages 11611 class WindowsIcon { 11612 struct Win32Icon { 11613 align(1): 11614 uint biSize; 11615 int biWidth; 11616 int biHeight; 11617 ushort biPlanes; 11618 ushort biBitCount; 11619 uint biCompression; 11620 uint biSizeImage; 11621 int biXPelsPerMeter; 11622 int biYPelsPerMeter; 11623 uint biClrUsed; 11624 uint biClrImportant; 11625 // RGBQUAD[colorCount] biColors; 11626 /* Pixels: 11627 Uint8 pixels[] 11628 */ 11629 /* Mask: 11630 Uint8 mask[] 11631 */ 11632 } 11633 11634 ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 11635 11636 assert(mi.width <= 256, "image too wide"); 11637 assert(mi.height <= 256, "image too tall"); 11638 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 11639 assert(mi.height % 4 == 0, "image not multiple of 4 height"); 11640 11641 int icon_plen = mi.width * mi.height * 4; 11642 int icon_mlen = mi.width * mi.height / 8; 11643 11644 int colorCount = 0; 11645 icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 11646 11647 ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen); 11648 Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr; 11649 11650 auto data = memory[Win32Icon.sizeof .. $]; 11651 11652 width = mi.width; 11653 height = mi.height; 11654 11655 auto trueColorImage = mi.getAsTrueColorImage(); 11656 11657 icon_win32.biSize = 40; 11658 icon_win32.biWidth = mi.width; 11659 icon_win32.biHeight = mi.height*2; 11660 icon_win32.biPlanes = 1; 11661 icon_win32.biBitCount = 32; 11662 icon_win32.biSizeImage = icon_plen + icon_mlen; 11663 11664 int offset = 0; 11665 int andOff = icon_plen * 8; // the and offset is in bits 11666 11667 // leaving the and mask as the default 0 so the rgba alpha blend 11668 // does its thing instead 11669 for(int y = height - 1; y >= 0; y--) { 11670 int off2 = y * width * 4; 11671 foreach(x; 0 .. width) { 11672 data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0]; 11673 data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1]; 11674 data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2]; 11675 data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3]; 11676 11677 offset += 4; 11678 off2 += 4; 11679 } 11680 } 11681 11682 return memory; 11683 } 11684 11685 this(MemoryImage mi) { 11686 int icon_len, width, height; 11687 11688 auto icon_win32 = fromMemoryImage(mi, icon_len, width, height); 11689 11690 /* 11691 PNG* png = readPnpngData); 11692 PNGHeader pngh = getHeader(png); 11693 void* icon_win32; 11694 if(pngh.depth == 4) { 11695 auto i = new Win32Icon!(16); 11696 i.fromPNG(png, pngh, icon_len, width, height); 11697 icon_win32 = i; 11698 } 11699 else if(pngh.depth == 8) { 11700 auto i = new Win32Icon!(256); 11701 i.fromPNG(png, pngh, icon_len, width, height); 11702 icon_win32 = i; 11703 } else assert(0); 11704 */ 11705 11706 hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0); 11707 11708 if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError()); 11709 } 11710 11711 ~this() { 11712 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 11713 DestroyIcon(hIcon); 11714 } 11715 11716 HICON hIcon; 11717 } 11718 11719 11720 11721 11722 11723 11724 alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler; 11725 alias HWND NativeWindowHandle; 11726 11727 extern(Windows) 11728 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 11729 try { 11730 if(SimpleWindow.handleNativeGlobalEvent !is null) { 11731 // it returns zero if the message is handled, so we won't do anything more there 11732 // do I like that though? 11733 int mustReturn; 11734 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn); 11735 if(mustReturn) 11736 return ret; 11737 } 11738 11739 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 11740 if(window.getNativeEventHandler !is null) { 11741 int mustReturn; 11742 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn); 11743 if(mustReturn) 11744 return ret; 11745 } 11746 if(auto w = cast(SimpleWindow) (*window)) 11747 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 11748 else 11749 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11750 } else { 11751 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11752 } 11753 } catch (Exception e) { 11754 try { 11755 sdpy_abort(e); 11756 return 0; 11757 } catch(Exception e) { assert(0); } 11758 } 11759 } 11760 11761 void sdpy_abort(Throwable e) nothrow { 11762 try 11763 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 11764 catch(Exception e) 11765 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 11766 ExitProcess(1); 11767 } 11768 11769 mixin template NativeScreenPainterImplementation() { 11770 HDC hdc; 11771 HWND hwnd; 11772 //HDC windowHdc; 11773 HBITMAP oldBmp; 11774 11775 void create(PaintingHandle window) { 11776 hwnd = window; 11777 11778 if(auto sw = cast(SimpleWindow) this.window) { 11779 // drawing on a window, double buffer 11780 auto windowHdc = GetDC(hwnd); 11781 11782 auto buffer = sw.impl.buffer; 11783 if(buffer is null) { 11784 hdc = windowHdc; 11785 windowDc = true; 11786 } else { 11787 hdc = CreateCompatibleDC(windowHdc); 11788 11789 ReleaseDC(hwnd, windowHdc); 11790 11791 oldBmp = SelectObject(hdc, buffer); 11792 } 11793 } else { 11794 // drawing on something else, draw directly 11795 hdc = CreateCompatibleDC(null); 11796 SelectObject(hdc, window); 11797 } 11798 11799 // X doesn't draw a text background, so neither should we 11800 SetBkMode(hdc, TRANSPARENT); 11801 11802 ensureDefaultFontLoaded(); 11803 11804 if(defaultGuiFont) { 11805 SelectObject(hdc, defaultGuiFont); 11806 // DeleteObject(defaultGuiFont); 11807 } 11808 } 11809 11810 static HFONT defaultGuiFont; 11811 static void ensureDefaultFontLoaded() { 11812 static bool triedDefaultGuiFont = false; 11813 if(!triedDefaultGuiFont) { 11814 NONCLIENTMETRICS params; 11815 params.cbSize = params.sizeof; 11816 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 11817 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 11818 } 11819 triedDefaultGuiFont = true; 11820 } 11821 } 11822 11823 private OperatingSystemFont _activeFont; 11824 11825 void setFont(OperatingSystemFont font) { 11826 _activeFont = font; 11827 if(font && font.font) { 11828 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 11829 // error... how to handle tho? 11830 } else { 11831 11832 } 11833 } 11834 else if(defaultGuiFont) 11835 SelectObject(hdc, defaultGuiFont); 11836 } 11837 11838 arsd.color.Rectangle _clipRectangle; 11839 11840 void setClipRectangle(int x, int y, int width, int height) { 11841 auto old = _clipRectangle; 11842 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 11843 if(old == _clipRectangle) 11844 return; 11845 11846 if(width == 0 || height == 0) { 11847 SelectClipRgn(hdc, null); 11848 } else { 11849 auto region = CreateRectRgn(x, y, x + width, y + height); 11850 SelectClipRgn(hdc, region); 11851 DeleteObject(region); 11852 } 11853 } 11854 11855 11856 // just because we can on Windows... 11857 //void create(Image image); 11858 11859 void invalidateRect(Rectangle invalidRect) { 11860 RECT rect; 11861 rect.left = invalidRect.left; 11862 rect.right = invalidRect.right; 11863 rect.top = invalidRect.top; 11864 rect.bottom = invalidRect.bottom; 11865 InvalidateRect(hwnd, &rect, false); 11866 } 11867 bool manualInvalidations; 11868 11869 void dispose() { 11870 // FIXME: this.window.width/height is probably wrong 11871 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 11872 // ReleaseDC(hwnd, windowHdc); 11873 11874 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 11875 if(cast(SimpleWindow) this.window) { 11876 if(!manualInvalidations) 11877 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 11878 } 11879 11880 if(originalPen !is null) 11881 SelectObject(hdc, originalPen); 11882 if(currentPen !is null) 11883 DeleteObject(currentPen); 11884 if(originalBrush !is null) 11885 SelectObject(hdc, originalBrush); 11886 if(currentBrush !is null) 11887 DeleteObject(currentBrush); 11888 11889 SelectObject(hdc, oldBmp); 11890 11891 if(windowDc) 11892 ReleaseDC(hwnd, hdc); 11893 else 11894 DeleteDC(hdc); 11895 11896 if(window.paintingFinishedDg !is null) 11897 window.paintingFinishedDg()(); 11898 } 11899 11900 bool windowDc; 11901 HPEN originalPen; 11902 HPEN currentPen; 11903 11904 Pen _activePen; 11905 11906 Color _outlineColor; 11907 11908 @property void pen(Pen p) { 11909 _activePen = p; 11910 _outlineColor = p.color; 11911 11912 HPEN pen; 11913 if(p.color.a == 0) { 11914 pen = GetStockObject(NULL_PEN); 11915 } else { 11916 int style = PS_SOLID; 11917 final switch(p.style) { 11918 case Pen.Style.Solid: 11919 style = PS_SOLID; 11920 break; 11921 case Pen.Style.Dashed: 11922 style = PS_DASH; 11923 break; 11924 case Pen.Style.Dotted: 11925 style = PS_DOT; 11926 break; 11927 } 11928 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 11929 } 11930 auto orig = SelectObject(hdc, pen); 11931 if(originalPen is null) 11932 originalPen = orig; 11933 11934 if(currentPen !is null) 11935 DeleteObject(currentPen); 11936 11937 currentPen = pen; 11938 11939 // the outline is like a foreground since it's done that way on X 11940 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 11941 11942 } 11943 11944 @property void rasterOp(RasterOp op) { 11945 int mode; 11946 final switch(op) { 11947 case RasterOp.normal: 11948 mode = R2_COPYPEN; 11949 break; 11950 case RasterOp.xor: 11951 mode = R2_XORPEN; 11952 break; 11953 } 11954 SetROP2(hdc, mode); 11955 } 11956 11957 HBRUSH originalBrush; 11958 HBRUSH currentBrush; 11959 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 11960 @property void fillColor(Color c) { 11961 if(c == _fillColor) 11962 return; 11963 _fillColor = c; 11964 HBRUSH brush; 11965 if(c.a == 0) { 11966 brush = GetStockObject(HOLLOW_BRUSH); 11967 } else { 11968 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 11969 } 11970 auto orig = SelectObject(hdc, brush); 11971 if(originalBrush is null) 11972 originalBrush = orig; 11973 11974 if(currentBrush !is null) 11975 DeleteObject(currentBrush); 11976 11977 currentBrush = brush; 11978 11979 // background color is NOT set because X doesn't draw text backgrounds 11980 // SetBkColor(hdc, RGB(255, 255, 255)); 11981 } 11982 11983 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 11984 BITMAP bm; 11985 11986 HDC hdcMem = CreateCompatibleDC(hdc); 11987 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 11988 11989 GetObject(i.handle, bm.sizeof, &bm); 11990 11991 // or should I AlphaBlend!??!?! 11992 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 11993 11994 SelectObject(hdcMem, hbmOld); 11995 DeleteDC(hdcMem); 11996 } 11997 11998 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 11999 BITMAP bm; 12000 12001 HDC hdcMem = CreateCompatibleDC(hdc); 12002 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 12003 12004 GetObject(s.handle, bm.sizeof, &bm); 12005 12006 version(CRuntime_DigitalMars) goto noalpha; 12007 12008 // or should I AlphaBlend!??!?! note it is supposed to be premultiplied http://www.fengyuan.com/article/alphablend.html 12009 if(s.enableAlpha) { 12010 auto dw = w ? w : bm.bmWidth; 12011 auto dh = h ? h : bm.bmHeight; 12012 BLENDFUNCTION bf; 12013 bf.BlendOp = AC_SRC_OVER; 12014 bf.SourceConstantAlpha = 255; 12015 bf.AlphaFormat = AC_SRC_ALPHA; 12016 AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf); 12017 } else { 12018 noalpha: 12019 BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY); 12020 } 12021 12022 SelectObject(hdcMem, hbmOld); 12023 DeleteDC(hdcMem); 12024 } 12025 12026 Size textSize(scope const(char)[] text) { 12027 bool dummyX; 12028 if(text.length == 0) { 12029 text = " "; 12030 dummyX = true; 12031 } 12032 RECT rect; 12033 WCharzBuffer buffer = WCharzBuffer(text); 12034 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX); 12035 return Size(dummyX ? 0 : rect.right, rect.bottom); 12036 } 12037 12038 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 12039 if(text.length && text[$-1] == '\n') 12040 text = text[0 .. $-1]; // tailing newlines are weird on windows... 12041 if(text.length && text[$-1] == '\r') 12042 text = text[0 .. $-1]; 12043 12044 WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines); 12045 if(x2 == 0 && y2 == 0) { 12046 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 12047 } else { 12048 RECT rect; 12049 rect.left = x; 12050 rect.top = y; 12051 rect.right = x2; 12052 rect.bottom = y2; 12053 12054 uint mode = DT_LEFT; 12055 if(alignment & TextAlignment.Right) 12056 mode = DT_RIGHT; 12057 else if(alignment & TextAlignment.Center) 12058 mode = DT_CENTER; 12059 12060 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 12061 if(alignment & TextAlignment.VerticalCenter) 12062 mode |= DT_VCENTER | DT_SINGLELINE; 12063 12064 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX); 12065 } 12066 12067 /* 12068 uint mode; 12069 12070 if(alignment & TextAlignment.Center) 12071 mode = TA_CENTER; 12072 12073 SetTextAlign(hdc, mode); 12074 */ 12075 } 12076 12077 int fontHeight() { 12078 TEXTMETRIC metric; 12079 if(GetTextMetricsW(hdc, &metric)) { 12080 return metric.tmHeight; 12081 } 12082 12083 return 16; // idk just guessing here, maybe we should throw 12084 } 12085 12086 void drawPixel(int x, int y) { 12087 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 12088 } 12089 12090 // The basic shapes, outlined 12091 12092 void drawLine(int x1, int y1, int x2, int y2) { 12093 MoveToEx(hdc, x1, y1, null); 12094 LineTo(hdc, x2, y2); 12095 } 12096 12097 void drawRectangle(int x, int y, int width, int height) { 12098 // FIXME: with a wider pen this might not draw quite right. im not sure. 12099 gdi.Rectangle(hdc, x, y, x + width, y + height); 12100 } 12101 12102 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 12103 RoundRect( 12104 hdc, 12105 upperLeft.x, upperLeft.y, 12106 lowerRight.x, lowerRight.y, 12107 borderRadius, borderRadius 12108 ); 12109 } 12110 12111 /// Arguments are the points of the bounding rectangle 12112 void drawEllipse(int x1, int y1, int x2, int y2) { 12113 Ellipse(hdc, x1, y1, x2, y2); 12114 } 12115 12116 void drawArc(int x1, int y1, int width, int height, int start, int length) { 12117 //if(length > 360*64) 12118 //length = 360*64; 12119 12120 if((start == 0 && length == 360*64)) { 12121 drawEllipse(x1, y1, x1 + width, y1 + height); 12122 } else { 12123 import core.stdc.math; 12124 12125 bool clockwise = false; 12126 if(length < 0) { 12127 clockwise = true; 12128 length = -length; 12129 } 12130 12131 double startAngle = cast(double) start / 64.0 / 180.0 * 3.14159265358979323; 12132 double endAngle = cast(double) (start + length) / 64.0 / 180.0 * 3.14159265358979323; 12133 12134 auto c1 = cast(int) (cos(startAngle) * width / 2.0 + double(x1) + double(width) / 2.0); 12135 auto c2 = cast(int) (-sin(startAngle) * height / 2.0 + double(y1) + double(height) / 2.0); 12136 auto c3 = cast(int) (cos(endAngle) * width / 2.0 + double(x1) + double(width) / 2.0); 12137 auto c4 = cast(int) (-sin(endAngle) * height / 2.0 + double(y1) + double(height) / 2.0); 12138 12139 if(clockwise) { 12140 auto t1 = c1; 12141 auto t2 = c2; 12142 c1 = c3; 12143 c2 = c4; 12144 c3 = t1; 12145 c4 = t2; 12146 } 12147 12148 //if(_activePen.color.a) 12149 //Arc(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4); 12150 //if(_fillColor.a) 12151 12152 Pie(hdc, x1, y1, x1 + width + 0, y1 + height + 0, c1, c2, c3, c4); 12153 } 12154 } 12155 12156 void drawPolygon(Point[] vertexes) { 12157 POINT[] points; 12158 points.length = vertexes.length; 12159 12160 foreach(i, p; vertexes) { 12161 points[i].x = p.x; 12162 points[i].y = p.y; 12163 } 12164 12165 Polygon(hdc, points.ptr, cast(int) points.length); 12166 } 12167 } 12168 12169 12170 // Mix this into the SimpleWindow class 12171 mixin template NativeSimpleWindowImplementation() { 12172 int curHidden = 0; // counter 12173 __gshared static bool[string] knownWinClasses; 12174 static bool altPressed = false; 12175 12176 HANDLE oldCursor; 12177 12178 void hideCursor () { 12179 if(curHidden == 0) 12180 oldCursor = SetCursor(null); 12181 ++curHidden; 12182 } 12183 12184 void showCursor () { 12185 --curHidden; 12186 if(curHidden == 0) { 12187 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 12188 } 12189 } 12190 12191 12192 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 12193 12194 void setMinSize (int minwidth, int minheight) { 12195 minWidth = minwidth; 12196 minHeight = minheight; 12197 } 12198 void setMaxSize (int maxwidth, int maxheight) { 12199 maxWidth = maxwidth; 12200 maxHeight = maxheight; 12201 } 12202 12203 // FIXME i'm not sure that Windows has this functionality 12204 // though it is nonessential anyway. 12205 void setResizeGranularity (int granx, int grany) {} 12206 12207 ScreenPainter getPainter(bool manualInvalidations) { 12208 return ScreenPainter(this, hwnd, manualInvalidations); 12209 } 12210 12211 HBITMAP buffer; 12212 12213 void setTitle(string title) { 12214 WCharzBuffer bfr = WCharzBuffer(title); 12215 SetWindowTextW(hwnd, bfr.ptr); 12216 } 12217 12218 string getTitle() { 12219 auto len = GetWindowTextLengthW(hwnd); 12220 if (!len) 12221 return null; 12222 wchar[256] tmpBuffer; 12223 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 12224 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 12225 auto str = buffer[0 .. len2]; 12226 return makeUtf8StringFromWindowsString(str); 12227 } 12228 12229 void move(int x, int y) { 12230 RECT rect; 12231 GetWindowRect(hwnd, &rect); 12232 // move it while maintaining the same size... 12233 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 12234 } 12235 12236 void resize(int w, int h) { 12237 RECT rect; 12238 GetWindowRect(hwnd, &rect); 12239 12240 RECT client; 12241 GetClientRect(hwnd, &client); 12242 12243 rect.right = rect.right - client.right + w; 12244 rect.bottom = rect.bottom - client.bottom + h; 12245 12246 // same position, new size for the client rectangle 12247 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 12248 12249 updateOpenglViewportIfNeeded(w, h); 12250 } 12251 12252 void moveResize (int x, int y, int w, int h) { 12253 // what's given is the client rectangle, we need to adjust 12254 12255 RECT rect; 12256 rect.left = x; 12257 rect.top = y; 12258 rect.right = w + x; 12259 rect.bottom = h + y; 12260 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 12261 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12262 12263 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 12264 updateOpenglViewportIfNeeded(w, h); 12265 if (windowResized !is null) windowResized(w, h); 12266 } 12267 12268 version(without_opengl) {} else { 12269 HGLRC ghRC; 12270 HDC ghDC; 12271 } 12272 12273 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 12274 string cnamec; 12275 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 12276 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 12277 cnamec = "DSimpleWindow"; 12278 } else { 12279 cnamec = sdpyWindowClass; 12280 } 12281 12282 WCharzBuffer cn = WCharzBuffer(cnamec); 12283 12284 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 12285 12286 if(cnamec !in knownWinClasses) { 12287 WNDCLASSEX wc; 12288 12289 // FIXME: I might be able to use cbWndExtra to hold the pointer back 12290 // to the object. Maybe. 12291 wc.cbSize = wc.sizeof; 12292 wc.cbClsExtra = 0; 12293 wc.cbWndExtra = 0; 12294 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 12295 wc.hCursor = LoadCursorW(null, IDC_ARROW); 12296 wc.hIcon = LoadIcon(hInstance, null); 12297 wc.hInstance = hInstance; 12298 wc.lpfnWndProc = &WndProc; 12299 wc.lpszClassName = cn.ptr; 12300 wc.hIconSm = null; 12301 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 12302 if(!RegisterClassExW(&wc)) 12303 throw new WindowsApiException("RegisterClassExW", GetLastError()); 12304 knownWinClasses[cnamec] = true; 12305 } 12306 12307 int style; 12308 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 12309 12310 // FIXME: windowType and customizationFlags 12311 final switch(windowType) { 12312 case WindowTypes.normal: 12313 if(resizability == Resizability.fixedSize) { 12314 style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION; 12315 } else { 12316 style = WS_OVERLAPPEDWINDOW; 12317 } 12318 break; 12319 case WindowTypes.undecorated: 12320 style = WS_POPUP | WS_SYSMENU; 12321 break; 12322 case WindowTypes.eventOnly: 12323 _hidden = true; 12324 break; 12325 case WindowTypes.dropdownMenu: 12326 case WindowTypes.popupMenu: 12327 case WindowTypes.notification: 12328 style = WS_POPUP; 12329 flags |= WS_EX_NOACTIVATE; 12330 break; 12331 case WindowTypes.dialog: 12332 style = WS_OVERLAPPEDWINDOW; 12333 break; 12334 case WindowTypes.nestedChild: 12335 style = WS_CHILD; 12336 break; 12337 case WindowTypes.minimallyWrapped: 12338 assert(0, "construct minimally wrapped through the other ctor overlad"); 12339 } 12340 12341 if ((customizationFlags & WindowFlags.extraComposite) != 0) 12342 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 12343 12344 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 12345 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 12346 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 12347 12348 if(!hwnd) 12349 throw new WindowsApiException("CreateWindowEx", GetLastError()); 12350 12351 if ((customizationFlags & WindowFlags.extraComposite) != 0) 12352 setOpacity(255); 12353 12354 SimpleWindow.nativeMapping[hwnd] = this; 12355 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 12356 12357 if(windowType == WindowTypes.eventOnly) 12358 return; 12359 12360 HDC hdc = GetDC(hwnd); 12361 12362 if(!hdc) 12363 throw new WindowsApiException("GetDC", GetLastError()); 12364 12365 version(without_opengl) {} 12366 else { 12367 if(opengl == OpenGlOptions.yes) { 12368 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 12369 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 12370 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 12371 ghDC = hdc; 12372 PIXELFORMATDESCRIPTOR pfd; 12373 12374 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 12375 pfd.nVersion = 1; 12376 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 12377 pfd.dwLayerMask = PFD_MAIN_PLANE; 12378 pfd.iPixelType = PFD_TYPE_RGBA; 12379 pfd.cColorBits = 24; 12380 pfd.cDepthBits = 24; 12381 pfd.cAccumBits = 0; 12382 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 12383 12384 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 12385 12386 if (pixelformat == 0) 12387 throw new WindowsApiException("ChoosePixelFormat", GetLastError()); 12388 12389 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 12390 throw new WindowsApiException("SetPixelFormat", GetLastError()); 12391 12392 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 12393 // windoze is idiotic: we have to have OpenGL context to get function addresses 12394 // so we will create fake context to get that stupid address 12395 auto tmpcc = wglCreateContext(ghDC); 12396 if (tmpcc !is null) { 12397 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 12398 wglMakeCurrent(ghDC, tmpcc); 12399 wglInitOtherFunctions(); 12400 } 12401 } 12402 12403 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 12404 int[9] contextAttribs = [ 12405 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 12406 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 12407 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 12408 // for modern context, set "forward compatibility" flag too 12409 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 12410 0/*None*/, 12411 ]; 12412 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 12413 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 12414 // activate fallback mode 12415 // 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; 12416 ghRC = wglCreateContext(ghDC); 12417 } 12418 if (ghRC is null) 12419 throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError()); 12420 } else { 12421 // try to do at least something 12422 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 12423 sdpyOpenGLContextVersion = 0; 12424 ghRC = wglCreateContext(ghDC); 12425 } 12426 if (ghRC is null) 12427 throw new WindowsApiException("wglCreateContext", GetLastError()); 12428 } 12429 } 12430 } 12431 12432 if(opengl == OpenGlOptions.no) { 12433 buffer = CreateCompatibleBitmap(hdc, width, height); 12434 12435 auto hdcBmp = CreateCompatibleDC(hdc); 12436 // make sure it's filled with a blank slate 12437 auto oldBmp = SelectObject(hdcBmp, buffer); 12438 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 12439 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 12440 gdi.Rectangle(hdcBmp, 0, 0, width, height); 12441 SelectObject(hdcBmp, oldBmp); 12442 SelectObject(hdcBmp, oldBrush); 12443 SelectObject(hdcBmp, oldPen); 12444 DeleteDC(hdcBmp); 12445 12446 bmpWidth = width; 12447 bmpHeight = height; 12448 12449 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 12450 } 12451 12452 // We want the window's client area to match the image size 12453 RECT rcClient, rcWindow; 12454 POINT ptDiff; 12455 GetClientRect(hwnd, &rcClient); 12456 GetWindowRect(hwnd, &rcWindow); 12457 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 12458 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 12459 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 12460 12461 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 12462 ShowWindow(hwnd, SW_SHOWNORMAL); 12463 } else { 12464 _hidden = true; 12465 } 12466 this._visibleForTheFirstTimeCalled = false; // hack! 12467 } 12468 12469 12470 void dispose() { 12471 if(buffer) 12472 DeleteObject(buffer); 12473 } 12474 12475 void closeWindow() { 12476 if(ghRC) { 12477 wglDeleteContext(ghRC); 12478 ghRC = null; 12479 } 12480 DestroyWindow(hwnd); 12481 } 12482 12483 bool setOpacity(ubyte alpha) { 12484 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 12485 } 12486 12487 HANDLE currentCursor; 12488 12489 // returns zero if it recognized the event 12490 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 12491 MouseEvent mouse; 12492 12493 void mouseEvent(bool isScreen, ulong mods) { 12494 auto x = LOWORD(lParam); 12495 auto y = HIWORD(lParam); 12496 if(isScreen) { 12497 POINT p; 12498 p.x = x; 12499 p.y = y; 12500 ScreenToClient(hwnd, &p); 12501 x = cast(ushort) p.x; 12502 y = cast(ushort) p.y; 12503 } 12504 12505 if(wind.resizability == Resizability.automaticallyScaleIfPossible) { 12506 x = cast(ushort)( x * wind._virtualWidth / wind._width ); 12507 y = cast(ushort)( y * wind._virtualHeight / wind._height ); 12508 } 12509 12510 mouse.x = x + offsetX; 12511 mouse.y = y + offsetY; 12512 12513 wind.mdx(mouse); 12514 mouse.modifierState = cast(int) mods; 12515 mouse.window = wind; 12516 12517 if(wind.handleMouseEvent) 12518 wind.handleMouseEvent(mouse); 12519 } 12520 12521 switch(msg) { 12522 case WM_GETMINMAXINFO: 12523 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 12524 12525 if(wind.minWidth > 0) { 12526 RECT rect; 12527 rect.left = 100; 12528 rect.top = 100; 12529 rect.right = wind.minWidth + 100; 12530 rect.bottom = wind.minHeight + 100; 12531 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 12532 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12533 12534 mmi.ptMinTrackSize.x = rect.right - rect.left; 12535 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 12536 } 12537 12538 if(wind.maxWidth < int.max) { 12539 RECT rect; 12540 rect.left = 100; 12541 rect.top = 100; 12542 rect.right = wind.maxWidth + 100; 12543 rect.bottom = wind.maxHeight + 100; 12544 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 12545 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 12546 12547 mmi.ptMaxTrackSize.x = rect.right - rect.left; 12548 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 12549 } 12550 break; 12551 case WM_CHAR: 12552 wchar c = cast(wchar) wParam; 12553 if(wind.handleCharEvent) 12554 wind.handleCharEvent(cast(dchar) c); 12555 break; 12556 case WM_SETFOCUS: 12557 case WM_KILLFOCUS: 12558 wind._focused = (msg == WM_SETFOCUS); 12559 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 12560 if(wind.onFocusChange) 12561 wind.onFocusChange(msg == WM_SETFOCUS); 12562 break; 12563 12564 case WM_SYSKEYDOWN: 12565 goto case; 12566 case WM_SYSKEYUP: 12567 if(lParam & (1 << 29)) { 12568 goto case; 12569 } else { 12570 // no window has keyboard focus 12571 goto default; 12572 } 12573 case WM_KEYDOWN: 12574 case WM_KEYUP: 12575 KeyEvent ev; 12576 ev.key = cast(Key) wParam; 12577 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 12578 if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way 12579 12580 ev.hardwareCode = (lParam & 0xff0000) >> 16; 12581 12582 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 12583 ev.modifierState |= ModifierState.shift; 12584 //k8: this doesn't work; thanks for nothing, windows 12585 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 12586 ev.modifierState |= ModifierState.alt;*/ 12587 // this never seems to actually be set 12588 // if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12589 12590 if (wParam == 0x12) { 12591 altPressed = (msg == WM_SYSKEYDOWN); 12592 } 12593 12594 if(msg == WM_KEYDOWN || msg == WM_KEYUP) { 12595 altPressed = false; 12596 } 12597 // sdpyPrintDebugString(altPressed ? "alt down" : " up "); 12598 12599 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12600 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 12601 ev.modifierState |= ModifierState.ctrl; 12602 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 12603 ev.modifierState |= ModifierState.windows; 12604 if(GetKeyState(Key.NumLock)) 12605 ev.modifierState |= ModifierState.numLock; 12606 if(GetKeyState(Key.CapsLock)) 12607 ev.modifierState |= ModifierState.capsLock; 12608 12609 /+ 12610 // we always want to send the character too, so let's convert it 12611 ubyte[256] state; 12612 wchar[16] buffer; 12613 GetKeyboardState(state.ptr); 12614 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 12615 12616 foreach(dchar d; buffer) { 12617 ev.character = d; 12618 break; 12619 } 12620 +/ 12621 12622 ev.window = wind; 12623 if(wind.handleKeyEvent) 12624 wind.handleKeyEvent(ev); 12625 break; 12626 case 0x020a /*WM_MOUSEWHEEL*/: 12627 // send click 12628 mouse.type = cast(MouseEventType) 1; 12629 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12630 mouseEvent(true, LOWORD(wParam)); 12631 12632 // also send release 12633 mouse.type = cast(MouseEventType) 2; 12634 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12635 mouseEvent(true, LOWORD(wParam)); 12636 break; 12637 case WM_MOUSEMOVE: 12638 mouse.type = cast(MouseEventType) 0; 12639 mouseEvent(false, wParam); 12640 break; 12641 case WM_LBUTTONDOWN: 12642 case WM_LBUTTONDBLCLK: 12643 mouse.type = cast(MouseEventType) 1; 12644 mouse.button = MouseButton.left; 12645 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 12646 mouseEvent(false, wParam); 12647 break; 12648 case WM_LBUTTONUP: 12649 mouse.type = cast(MouseEventType) 2; 12650 mouse.button = MouseButton.left; 12651 mouseEvent(false, wParam); 12652 break; 12653 case WM_RBUTTONDOWN: 12654 case WM_RBUTTONDBLCLK: 12655 mouse.type = cast(MouseEventType) 1; 12656 mouse.button = MouseButton.right; 12657 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 12658 mouseEvent(false, wParam); 12659 break; 12660 case WM_RBUTTONUP: 12661 mouse.type = cast(MouseEventType) 2; 12662 mouse.button = MouseButton.right; 12663 mouseEvent(false, wParam); 12664 break; 12665 case WM_MBUTTONDOWN: 12666 case WM_MBUTTONDBLCLK: 12667 mouse.type = cast(MouseEventType) 1; 12668 mouse.button = MouseButton.middle; 12669 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 12670 mouseEvent(false, wParam); 12671 break; 12672 case WM_MBUTTONUP: 12673 mouse.type = cast(MouseEventType) 2; 12674 mouse.button = MouseButton.middle; 12675 mouseEvent(false, wParam); 12676 break; 12677 case WM_XBUTTONDOWN: 12678 case WM_XBUTTONDBLCLK: 12679 mouse.type = cast(MouseEventType) 1; 12680 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12681 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 12682 mouseEvent(false, wParam); 12683 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 12684 case WM_XBUTTONUP: 12685 mouse.type = cast(MouseEventType) 2; 12686 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12687 mouseEvent(false, wParam); 12688 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 12689 12690 default: return 1; 12691 } 12692 return 0; 12693 } 12694 12695 HWND hwnd; 12696 private int oldWidth; 12697 private int oldHeight; 12698 private bool inSizeMove; 12699 12700 /++ 12701 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. 12702 12703 History: 12704 Added November 23, 2021 12705 12706 Not fully stable, may be moved out of the impl struct. 12707 12708 Default value changed to `true` on February 15, 2021 12709 +/ 12710 bool doLiveResizing = true; 12711 12712 package int bmpWidth; 12713 package int bmpHeight; 12714 12715 // the extern(Windows) wndproc should just forward to this 12716 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 12717 try { 12718 assert(hwnd is this.hwnd); 12719 12720 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 12721 switch(msg) { 12722 case WM_MENUCHAR: // menu active but key not associated with a thing. 12723 // you would ideally use this for like a search function but sdpy not that ideally designed. alas. 12724 // The main things we can do are select, execute, close, or ignore 12725 // the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to 12726 // the user. This can be a bit annoying for sdpy things so instead im overriding and setting it 12727 // to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy 12728 // that's the lesser bad choice rn. Can always override by returning true in triggerEvents.... 12729 12730 // returns the value in the *high order word* of the return value 12731 // hence the << 16 12732 return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user 12733 case WM_SETCURSOR: 12734 if(cast(HWND) wParam !is hwnd) 12735 return 0; // further processing elsewhere 12736 12737 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 12738 SetCursor(this.curHidden > 0 ? null : currentCursor); 12739 return 1; 12740 } else { 12741 return DefWindowProc(hwnd, msg, wParam, lParam); 12742 } 12743 //break; 12744 12745 case WM_CLOSE: 12746 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 12747 break; 12748 case WM_DESTROY: 12749 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 12750 SimpleWindow.nativeMapping.remove(hwnd); 12751 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 12752 12753 bool anyImportant = false; 12754 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 12755 if(w.beingOpenKeepsAppOpen) { 12756 anyImportant = true; 12757 break; 12758 } 12759 if(!anyImportant) { 12760 PostQuitMessage(0); 12761 } 12762 break; 12763 case 0x02E0 /*WM_DPICHANGED*/: 12764 this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs 12765 12766 RECT* prcNewWindow = cast(RECT*)lParam; 12767 // docs say this is the recommended position and we should honor it 12768 SetWindowPos(hwnd, 12769 null, 12770 prcNewWindow.left, 12771 prcNewWindow.top, 12772 prcNewWindow.right - prcNewWindow.left, 12773 prcNewWindow.bottom - prcNewWindow.top, 12774 SWP_NOZORDER | SWP_NOACTIVATE); 12775 12776 // doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp 12777 // im not sure it is completely correct 12778 // but without it the tabs and such do look weird as things change. 12779 if(SystemParametersInfoForDpi) { 12780 LOGFONT lfText; 12781 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_); 12782 HFONT hFontNew = CreateFontIndirect(&lfText); 12783 if (hFontNew) 12784 { 12785 //DeleteObject(hFontOld); 12786 static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) { 12787 SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0)); 12788 return TRUE; 12789 } 12790 EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew); 12791 } 12792 } 12793 12794 if(this.onDpiChanged) 12795 this.onDpiChanged(); 12796 break; 12797 case WM_ENTERIDLE: 12798 // when a menu is up, it stops normal event processing (modal message loop) 12799 // but this at least gives us a chance to SOMETIMES catch up 12800 // FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it. 12801 SimpleWindow.processAllCustomEvents; 12802 SimpleWindow.processAllCustomEvents; 12803 SleepEx(0, true); 12804 break; 12805 case WM_SIZE: 12806 if(wParam == 1 /* SIZE_MINIMIZED */) 12807 break; 12808 _width = LOWORD(lParam); 12809 _height = HIWORD(lParam); 12810 12811 // I want to avoid tearing in the windows (my code is inefficient 12812 // so this is a hack around that) so while sizing, we don't trigger, 12813 // but we do want to trigger on events like mazimize. 12814 if(!inSizeMove || doLiveResizing) 12815 goto size_changed; 12816 break; 12817 /+ 12818 case WM_SIZING: 12819 writeln("size"); 12820 break; 12821 +/ 12822 // I don't like the tearing I get when redrawing on WM_SIZE 12823 // (I know there's other ways to fix that but I don't like that behavior anyway) 12824 // so instead it is going to redraw only at the end of a size. 12825 case 0x0231: /* WM_ENTERSIZEMOVE */ 12826 inSizeMove = true; 12827 break; 12828 case 0x0232: /* WM_EXITSIZEMOVE */ 12829 inSizeMove = false; 12830 12831 size_changed: 12832 12833 // nothing relevant changed, don't bother redrawing 12834 if(oldWidth == _width && oldHeight == _height) { 12835 if(msg == 0x0232) 12836 goto finalize_resize; 12837 break; 12838 } 12839 12840 // note: OpenGL windows don't use a backing bmp, so no need to change them 12841 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 12842 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 12843 // gotta get the double buffer bmp to match the window 12844 // FIXME: could this be more efficient? it never relinquishes a large bitmap 12845 12846 // if it is auto-scaled, we keep the backing bitmap the same size all the time 12847 if(resizability != Resizability.automaticallyScaleIfPossible) 12848 if(_width > bmpWidth || _height > bmpHeight) { 12849 auto hdc = GetDC(hwnd); 12850 auto oldBuffer = buffer; 12851 buffer = CreateCompatibleBitmap(hdc, _width, _height); 12852 12853 auto hdcBmp = CreateCompatibleDC(hdc); 12854 auto oldBmp = SelectObject(hdcBmp, buffer); 12855 12856 auto hdcOldBmp = CreateCompatibleDC(hdc); 12857 auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer); 12858 12859 /+ 12860 RECT r; 12861 r.left = 0; 12862 r.top = 0; 12863 r.right = width; 12864 r.bottom = height; 12865 auto c = Color.green; 12866 auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 12867 FillRect(hdcBmp, &r, brush); 12868 DeleteObject(brush); 12869 +/ 12870 12871 BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY); 12872 12873 bmpWidth = _width; 12874 bmpHeight = _height; 12875 12876 SelectObject(hdcOldBmp, oldOldBmp); 12877 DeleteDC(hdcOldBmp); 12878 12879 SelectObject(hdcBmp, oldBmp); 12880 DeleteDC(hdcBmp); 12881 12882 ReleaseDC(hwnd, hdc); 12883 12884 DeleteObject(oldBuffer); 12885 } 12886 } 12887 12888 updateOpenglViewportIfNeeded(_width, _height); 12889 12890 if(resizability != Resizability.automaticallyScaleIfPossible) 12891 if(windowResized !is null) 12892 windowResized(_width, _height); 12893 12894 /+ 12895 if(inSizeMove) { 12896 // SimpleWindow.processAllCustomEvents(); 12897 // SimpleWindow.processAllCustomEvents(); 12898 12899 //RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 12900 //sdpyPrintDebugString("redraw b"); 12901 } else { 12902 +/ { 12903 finalize_resize: 12904 // when it is all done, make sure everything is freshly drawn or there might be 12905 // weird bugs left. 12906 SimpleWindow.processAllCustomEvents(); 12907 SimpleWindow.processAllCustomEvents(); 12908 12909 RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 12910 // sdpyPrintDebugString("redraw"); 12911 } 12912 12913 oldWidth = this._width; 12914 oldHeight = this._height; 12915 break; 12916 case WM_ERASEBKGND: 12917 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 12918 if (!this._visibleForTheFirstTimeCalled) { 12919 this._visibleForTheFirstTimeCalled = true; 12920 if (this.visibleForTheFirstTime !is null) { 12921 this.visibleForTheFirstTime(); 12922 } 12923 } 12924 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 12925 version(without_opengl) {} else { 12926 if (openglMode == OpenGlOptions.yes) return 1; 12927 } 12928 // call windows default handler, so it can paint standard controls 12929 goto default; 12930 case WM_CTLCOLORBTN: 12931 case WM_CTLCOLORSTATIC: 12932 SetBkMode(cast(HDC) wParam, TRANSPARENT); 12933 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 12934 GetSysColorBrush(COLOR_3DFACE); 12935 //break; 12936 case WM_SHOWWINDOW: 12937 this._visible = (wParam != 0); 12938 if (!this._visibleForTheFirstTimeCalled && this._visible) { 12939 this._visibleForTheFirstTimeCalled = true; 12940 if (this.visibleForTheFirstTime !is null) { 12941 this.visibleForTheFirstTime(); 12942 } 12943 } 12944 if (this.visibilityChanged !is null) this.visibilityChanged(this._visible); 12945 break; 12946 case WM_PAINT: { 12947 if (!this._visibleForTheFirstTimeCalled) { 12948 this._visibleForTheFirstTimeCalled = true; 12949 if (this.visibleForTheFirstTime !is null) { 12950 this.visibleForTheFirstTime(); 12951 } 12952 } 12953 12954 BITMAP bm; 12955 PAINTSTRUCT ps; 12956 12957 HDC hdc = BeginPaint(hwnd, &ps); 12958 12959 if(openglMode == OpenGlOptions.no) { 12960 12961 HDC hdcMem = CreateCompatibleDC(hdc); 12962 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 12963 12964 GetObject(buffer, bm.sizeof, &bm); 12965 12966 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 12967 if(resizability == Resizability.automaticallyScaleIfPossible) 12968 StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 12969 else 12970 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 12971 //BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY); 12972 12973 SelectObject(hdcMem, hbmOld); 12974 DeleteDC(hdcMem); 12975 EndPaint(hwnd, &ps); 12976 } else { 12977 EndPaint(hwnd, &ps); 12978 version(without_opengl) {} else 12979 redrawOpenGlSceneSoon(); 12980 } 12981 } break; 12982 default: 12983 return DefWindowProc(hwnd, msg, wParam, lParam); 12984 } 12985 return 0; 12986 12987 } 12988 catch(Throwable t) { 12989 sdpyPrintDebugString(t.toString); 12990 return 0; 12991 } 12992 } 12993 } 12994 12995 mixin template NativeImageImplementation() { 12996 HBITMAP handle; 12997 ubyte* rawData; 12998 12999 final: 13000 13001 Color getPixel(int x, int y) @system { 13002 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 13003 // remember, bmps are upside down 13004 auto offset = itemsPerLine * (height - y - 1) + x * 3; 13005 13006 Color c; 13007 if(enableAlpha) 13008 c.a = rawData[offset + 3]; 13009 else 13010 c.a = 255; 13011 c.b = rawData[offset + 0]; 13012 c.g = rawData[offset + 1]; 13013 c.r = rawData[offset + 2]; 13014 c.unPremultiply(); 13015 return c; 13016 } 13017 13018 void setPixel(int x, int y, Color c) @system { 13019 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 13020 // remember, bmps are upside down 13021 auto offset = itemsPerLine * (height - y - 1) + x * 3; 13022 13023 if(enableAlpha) 13024 c.premultiply(); 13025 13026 rawData[offset + 0] = c.b; 13027 rawData[offset + 1] = c.g; 13028 rawData[offset + 2] = c.r; 13029 if(enableAlpha) 13030 rawData[offset + 3] = c.a; 13031 } 13032 13033 void convertToRgbaBytes(ubyte[] where) @system { 13034 assert(where.length == this.width * this.height * 4); 13035 13036 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 13037 int idx = 0; 13038 int offset = itemsPerLine * (height - 1); 13039 // remember, bmps are upside down 13040 for(int y = height - 1; y >= 0; y--) { 13041 auto offsetStart = offset; 13042 for(int x = 0; x < width; x++) { 13043 where[idx + 0] = rawData[offset + 2]; // r 13044 where[idx + 1] = rawData[offset + 1]; // g 13045 where[idx + 2] = rawData[offset + 0]; // b 13046 if(enableAlpha) { 13047 where[idx + 3] = rawData[offset + 3]; // a 13048 unPremultiplyRgba(where[idx .. idx + 4]); 13049 offset++; 13050 } else 13051 where[idx + 3] = 255; // a 13052 idx += 4; 13053 offset += 3; 13054 } 13055 13056 offset = offsetStart - itemsPerLine; 13057 } 13058 } 13059 13060 void setFromRgbaBytes(in ubyte[] what) @system { 13061 assert(what.length == this.width * this.height * 4); 13062 13063 auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 13064 int idx = 0; 13065 int offset = itemsPerLine * (height - 1); 13066 // remember, bmps are upside down 13067 for(int y = height - 1; y >= 0; y--) { 13068 auto offsetStart = offset; 13069 for(int x = 0; x < width; x++) { 13070 if(enableAlpha) { 13071 auto a = what[idx + 3]; 13072 13073 rawData[offset + 2] = (a * what[idx + 0]) / 255; // r 13074 rawData[offset + 1] = (a * what[idx + 1]) / 255; // g 13075 rawData[offset + 0] = (a * what[idx + 2]) / 255; // b 13076 rawData[offset + 3] = a; // a 13077 //premultiplyBgra(rawData[offset .. offset + 4]); 13078 offset++; 13079 } else { 13080 rawData[offset + 2] = what[idx + 0]; // r 13081 rawData[offset + 1] = what[idx + 1]; // g 13082 rawData[offset + 0] = what[idx + 2]; // b 13083 } 13084 idx += 4; 13085 offset += 3; 13086 } 13087 13088 offset = offsetStart - itemsPerLine; 13089 } 13090 } 13091 13092 13093 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 13094 BITMAPINFO infoheader; 13095 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 13096 infoheader.bmiHeader.biWidth = width; 13097 infoheader.bmiHeader.biHeight = height; 13098 infoheader.bmiHeader.biPlanes = 1; 13099 infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24; 13100 infoheader.bmiHeader.biCompression = BI_RGB; 13101 13102 handle = CreateDIBSection( 13103 null, 13104 &infoheader, 13105 DIB_RGB_COLORS, 13106 cast(void**) &rawData, 13107 null, 13108 0); 13109 if(handle is null) 13110 throw new WindowsApiException("create image failed", GetLastError()); 13111 13112 } 13113 13114 void dispose() { 13115 DeleteObject(handle); 13116 } 13117 } 13118 13119 enum KEY_ESCAPE = 27; 13120 } 13121 version(X11) { 13122 /// This is the default font used. You might change this before doing anything else with 13123 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 13124 /// for cross-platform compatibility. 13125 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 13126 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 13127 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 13128 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 13129 13130 alias int delegate(XEvent) NativeEventHandler; 13131 alias Window NativeWindowHandle; 13132 13133 enum KEY_ESCAPE = 9; 13134 13135 mixin template NativeScreenPainterImplementation() { 13136 Display* display; 13137 Drawable d; 13138 Drawable destiny; 13139 13140 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 13141 GC gc; 13142 13143 __gshared bool fontAttempted; 13144 13145 __gshared XFontStruct* defaultfont; 13146 __gshared XFontSet defaultfontset; 13147 13148 XFontStruct* font; 13149 XFontSet fontset; 13150 13151 void create(PaintingHandle window) { 13152 this.display = XDisplayConnection.get(); 13153 13154 Drawable buffer = None; 13155 if(auto sw = cast(SimpleWindow) this.window) { 13156 buffer = sw.impl.buffer; 13157 this.destiny = cast(Drawable) window; 13158 } else { 13159 buffer = cast(Drawable) window; 13160 this.destiny = None; 13161 } 13162 13163 this.d = cast(Drawable) buffer; 13164 13165 auto dgc = DefaultGC(display, DefaultScreen(display)); 13166 13167 this.gc = XCreateGC(display, d, 0, null); 13168 13169 XCopyGC(display, dgc, 0xffffffff, this.gc); 13170 13171 ensureDefaultFontLoaded(); 13172 13173 font = defaultfont; 13174 fontset = defaultfontset; 13175 13176 if(font) { 13177 XSetFont(display, gc, font.fid); 13178 } 13179 } 13180 13181 static void ensureDefaultFontLoaded() { 13182 if(!fontAttempted) { 13183 auto display = XDisplayConnection.get; 13184 auto font = XLoadQueryFont(display, xfontstr.ptr); 13185 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 13186 if(font is null) { 13187 xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"; 13188 font = XLoadQueryFont(display, xfontstr.ptr); 13189 } 13190 13191 char** lol; 13192 int lol2; 13193 char* lol3; 13194 auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 13195 13196 fontAttempted = true; 13197 13198 defaultfont = font; 13199 defaultfontset = fontset; 13200 } 13201 } 13202 13203 arsd.color.Rectangle _clipRectangle; 13204 void setClipRectangle(int x, int y, int width, int height) { 13205 auto old = _clipRectangle; 13206 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 13207 if(old == _clipRectangle) 13208 return; 13209 13210 if(width == 0 || height == 0) { 13211 XSetClipMask(display, gc, None); 13212 13213 if(xrenderPicturePainter) { 13214 13215 XRectangle[1] rects; 13216 rects[0] = XRectangle(short.min, short.min, short.max, short.max); 13217 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 13218 } 13219 13220 version(with_xft) { 13221 if(xftFont is null || xftDraw is null) 13222 return; 13223 XftDrawSetClip(xftDraw, null); 13224 } 13225 } else { 13226 XRectangle[1] rects; 13227 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 13228 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 13229 13230 if(xrenderPicturePainter) 13231 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 13232 13233 version(with_xft) { 13234 if(xftFont is null || xftDraw is null) 13235 return; 13236 XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1); 13237 } 13238 } 13239 } 13240 13241 version(with_xft) { 13242 XftFont* xftFont; 13243 XftDraw* xftDraw; 13244 13245 XftColor xftColor; 13246 13247 void updateXftColor() { 13248 if(xftFont is null) 13249 return; 13250 13251 // not bothering with XftColorFree since p sure i don't need it on 24 bit displays.... 13252 XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255); 13253 13254 XftColorAllocValue( 13255 display, 13256 DefaultVisual(display, DefaultScreen(display)), 13257 DefaultColormap(display, 0), 13258 &colorIn, 13259 &xftColor 13260 ); 13261 } 13262 } 13263 13264 private OperatingSystemFont _activeFont; 13265 void setFont(OperatingSystemFont font) { 13266 _activeFont = font; 13267 version(with_xft) { 13268 if(font && font.isXft && font.xftFont) 13269 this.xftFont = font.xftFont; 13270 else 13271 this.xftFont = null; 13272 13273 if(this.xftFont) { 13274 if(xftDraw is null) { 13275 xftDraw = XftDrawCreate( 13276 display, 13277 d, 13278 DefaultVisual(display, DefaultScreen(display)), 13279 DefaultColormap(display, 0) 13280 ); 13281 13282 updateXftColor(); 13283 } 13284 13285 return; 13286 } 13287 } 13288 13289 if(font && font.font) { 13290 this.font = font.font; 13291 this.fontset = font.fontset; 13292 XSetFont(display, gc, font.font.fid); 13293 } else { 13294 this.font = defaultfont; 13295 this.fontset = defaultfontset; 13296 } 13297 13298 } 13299 13300 private Picture xrenderPicturePainter; 13301 13302 bool manualInvalidations; 13303 void invalidateRect(Rectangle invalidRect) { 13304 // FIXME if manualInvalidations 13305 } 13306 13307 void dispose() { 13308 this.rasterOp = RasterOp.normal; 13309 13310 if(xrenderPicturePainter) { 13311 XRenderFreePicture(display, xrenderPicturePainter); 13312 xrenderPicturePainter = None; 13313 } 13314 13315 // FIXME: this.window.width/height is probably wrong 13316 13317 // src x,y then dest x, y 13318 if(destiny != None) { 13319 // FIXME: if manual invalidations we can actually only copy some of the area. 13320 // if(manualInvalidations) 13321 XSetClipMask(display, gc, None); 13322 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 13323 } 13324 13325 XFreeGC(display, gc); 13326 13327 version(with_xft) 13328 if(xftDraw) { 13329 XftDrawDestroy(xftDraw); 13330 xftDraw = null; 13331 } 13332 13333 /+ 13334 // this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource. 13335 if(font && font !is defaultfont) { 13336 XFreeFont(display, font); 13337 font = null; 13338 } 13339 if(fontset && fontset !is defaultfontset) { 13340 XFreeFontSet(display, fontset); 13341 fontset = null; 13342 } 13343 +/ 13344 XFlush(display); 13345 13346 if(window.paintingFinishedDg !is null) 13347 window.paintingFinishedDg()(); 13348 } 13349 13350 bool backgroundIsNotTransparent = true; 13351 bool foregroundIsNotTransparent = true; 13352 13353 bool _penInitialized = false; 13354 Pen _activePen; 13355 13356 Color _outlineColor; 13357 Color _fillColor; 13358 13359 @property void pen(Pen p) { 13360 if(_penInitialized && p == _activePen) { 13361 return; 13362 } 13363 _penInitialized = true; 13364 _activePen = p; 13365 _outlineColor = p.color; 13366 13367 int style; 13368 13369 byte dashLength; 13370 13371 final switch(p.style) { 13372 case Pen.Style.Solid: 13373 style = 0 /*LineSolid*/; 13374 break; 13375 case Pen.Style.Dashed: 13376 style = 1 /*LineOnOffDash*/; 13377 dashLength = 4; 13378 break; 13379 case Pen.Style.Dotted: 13380 style = 1 /*LineOnOffDash*/; 13381 dashLength = 1; 13382 break; 13383 } 13384 13385 XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0); 13386 if(dashLength) 13387 XSetDashes(display, gc, 0, &dashLength, 1); 13388 13389 if(p.color.a == 0) { 13390 foregroundIsNotTransparent = false; 13391 return; 13392 } 13393 13394 foregroundIsNotTransparent = true; 13395 13396 XSetForeground(display, gc, colorToX(p.color, display)); 13397 13398 version(with_xft) 13399 updateXftColor(); 13400 } 13401 13402 RasterOp _currentRasterOp; 13403 bool _currentRasterOpInitialized = false; 13404 @property void rasterOp(RasterOp op) { 13405 if(_currentRasterOpInitialized && _currentRasterOp == op) 13406 return; 13407 _currentRasterOp = op; 13408 _currentRasterOpInitialized = true; 13409 int mode; 13410 final switch(op) { 13411 case RasterOp.normal: 13412 mode = GXcopy; 13413 break; 13414 case RasterOp.xor: 13415 mode = GXxor; 13416 break; 13417 } 13418 XSetFunction(display, gc, mode); 13419 } 13420 13421 13422 bool _fillColorInitialized = false; 13423 13424 @property void fillColor(Color c) { 13425 if(_fillColorInitialized && _fillColor == c) 13426 return; // already good, no need to waste time calling it 13427 _fillColor = c; 13428 _fillColorInitialized = true; 13429 if(c.a == 0) { 13430 backgroundIsNotTransparent = false; 13431 return; 13432 } 13433 13434 backgroundIsNotTransparent = true; 13435 13436 XSetBackground(display, gc, colorToX(c, display)); 13437 13438 } 13439 13440 void swapColors() { 13441 auto tmp = _fillColor; 13442 fillColor = _outlineColor; 13443 auto newPen = _activePen; 13444 newPen.color = tmp; 13445 pen(newPen); 13446 } 13447 13448 uint colorToX(Color c, Display* display) { 13449 auto visual = DefaultVisual(display, DefaultScreen(display)); 13450 import core.bitop; 13451 uint color = 0; 13452 { 13453 auto startBit = bsf(visual.red_mask); 13454 auto lastBit = bsr(visual.red_mask); 13455 auto r = cast(uint) c.r; 13456 r >>= 7 - (lastBit - startBit); 13457 r <<= startBit; 13458 color |= r; 13459 } 13460 { 13461 auto startBit = bsf(visual.green_mask); 13462 auto lastBit = bsr(visual.green_mask); 13463 auto g = cast(uint) c.g; 13464 g >>= 7 - (lastBit - startBit); 13465 g <<= startBit; 13466 color |= g; 13467 } 13468 { 13469 auto startBit = bsf(visual.blue_mask); 13470 auto lastBit = bsr(visual.blue_mask); 13471 auto b = cast(uint) c.b; 13472 b >>= 7 - (lastBit - startBit); 13473 b <<= startBit; 13474 color |= b; 13475 } 13476 13477 13478 13479 return color; 13480 } 13481 13482 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 13483 // source x, source y 13484 if(ix >= i.width) return; 13485 if(iy >= i.height) return; 13486 if(ix + w > i.width) w = i.width - ix; 13487 if(iy + h > i.height) h = i.height - iy; 13488 if(i.usingXshm) 13489 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 13490 else 13491 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 13492 } 13493 13494 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 13495 if(s.enableAlpha) { 13496 // the Sprite must be created first, meaning if we're here, XRender is already loaded 13497 if(this.xrenderPicturePainter == None) { 13498 XRenderPictureAttributes attrs; 13499 // FIXME: I can prolly reuse this as long as the pixmap itself is valid. 13500 xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs); 13501 13502 // need to initialize the clip 13503 XRectangle[1] rects; 13504 rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height); 13505 13506 if(_clipRectangle != Rectangle.init) 13507 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 13508 } 13509 13510 XRenderComposite( 13511 display, 13512 3, // PicOpOver 13513 s.xrenderPicture, 13514 None, 13515 this.xrenderPicturePainter, 13516 ix, 13517 iy, 13518 0, 13519 0, 13520 x, 13521 y, 13522 w ? w : s.width, 13523 h ? h : s.height 13524 ); 13525 } else { 13526 XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y); 13527 } 13528 } 13529 13530 int fontHeight() { 13531 version(with_xft) 13532 if(xftFont !is null) 13533 return xftFont.height; 13534 if(font) 13535 return font.max_bounds.ascent + font.max_bounds.descent; 13536 return 12; // pretty common default... 13537 } 13538 13539 int textWidth(in char[] line) { 13540 version(with_xft) 13541 if(xftFont) { 13542 if(line.length == 0) 13543 return 0; 13544 XGlyphInfo extents; 13545 XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents); 13546 return extents.width; 13547 } 13548 13549 if(fontset) { 13550 if(line.length == 0) 13551 return 0; 13552 XRectangle rect; 13553 Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect); 13554 13555 return rect.width; 13556 } 13557 13558 if(font) 13559 // FIXME: unicode 13560 return XTextWidth( font, line.ptr, cast(int) line.length); 13561 else 13562 return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 13563 } 13564 13565 Size textSize(in char[] text) { 13566 auto maxWidth = 0; 13567 auto lineHeight = fontHeight; 13568 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 13569 foreach(line; text.split('\n')) { 13570 int textWidth = this.textWidth(line); 13571 if(textWidth > maxWidth) 13572 maxWidth = textWidth; 13573 h += lineHeight + 4; 13574 } 13575 return Size(maxWidth, h); 13576 } 13577 13578 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 13579 const(char)[] text; 13580 version(with_xft) 13581 if(xftFont) { 13582 text = originalText; 13583 goto loaded; 13584 } 13585 13586 if(fontset) 13587 text = originalText; 13588 else { 13589 text.reserve(originalText.length); 13590 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 13591 // then strip the rest so there isn't garbage 13592 foreach(dchar ch; originalText) 13593 if(ch < 256) 13594 text ~= cast(ubyte) ch; 13595 else 13596 text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space 13597 } 13598 loaded: 13599 if(text.length == 0) 13600 return; 13601 13602 // FIXME: should we clip it to the bounding box? 13603 int textHeight = fontHeight; 13604 13605 auto lines = text.split('\n'); 13606 13607 const lineHeight = textHeight; 13608 textHeight *= lines.length; 13609 13610 int cy = y; 13611 13612 if(alignment & TextAlignment.VerticalBottom) { 13613 if(y2 <= 0) 13614 return; 13615 auto h = y2 - y; 13616 if(h > textHeight) { 13617 cy += h - textHeight; 13618 cy -= lineHeight / 2; 13619 } 13620 } else if(alignment & TextAlignment.VerticalCenter) { 13621 if(y2 <= 0) 13622 return; 13623 auto h = y2 - y; 13624 if(textHeight < h) { 13625 cy += (h - textHeight) / 2; 13626 //cy -= lineHeight / 4; 13627 } 13628 } 13629 13630 foreach(line; text.split('\n')) { 13631 int textWidth = this.textWidth(line); 13632 13633 int px = x, py = cy; 13634 13635 if(alignment & TextAlignment.Center) { 13636 if(x2 <= 0) 13637 return; 13638 auto w = x2 - x; 13639 if(w > textWidth) 13640 px += (w - textWidth) / 2; 13641 } else if(alignment & TextAlignment.Right) { 13642 if(x2 <= 0) 13643 return; 13644 auto pos = x2 - textWidth; 13645 if(pos > x) 13646 px = pos; 13647 } 13648 13649 version(with_xft) 13650 if(xftFont) { 13651 XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length); 13652 13653 goto carry_on; 13654 } 13655 13656 if(fontset) 13657 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13658 else 13659 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13660 carry_on: 13661 cy += lineHeight + 4; 13662 } 13663 } 13664 13665 void drawPixel(int x, int y) { 13666 XDrawPoint(display, d, gc, x, y); 13667 } 13668 13669 // The basic shapes, outlined 13670 13671 void drawLine(int x1, int y1, int x2, int y2) { 13672 if(foregroundIsNotTransparent) 13673 XDrawLine(display, d, gc, x1, y1, x2, y2); 13674 } 13675 13676 void drawRectangle(int x, int y, int width, int height) { 13677 if(backgroundIsNotTransparent) { 13678 swapColors(); 13679 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 13680 swapColors(); 13681 } 13682 // 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 13683 if(foregroundIsNotTransparent) 13684 XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2); 13685 } 13686 13687 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 13688 int[4] radii = borderRadius; 13689 auto r = Rectangle(upperLeft, lowerRight); 13690 13691 if(backgroundIsNotTransparent) { 13692 swapColors(); 13693 // FIXME these overlap and thus draw the pixels multiple times 13694 XFillRectangle(display, d, gc, r.left, r.top + borderRadius/2, r.width, r.height - borderRadius); 13695 XFillRectangle(display, d, gc, r.left + borderRadius/2, r.top, r.width - borderRadius, r.height); 13696 swapColors(); 13697 } 13698 13699 drawLine(r.left + borderRadius / 2, r.top, r.right - borderRadius / 2, r.top); 13700 drawLine(r.left + borderRadius / 2, r.bottom-1, r.right - borderRadius / 2, r.bottom-1); 13701 drawLine(r.left, r.top + borderRadius / 2, r.left, r.bottom - borderRadius / 2); 13702 drawLine(r.right - 1, r.top + borderRadius / 2, r.right - 1, r.bottom - borderRadius / 2); 13703 13704 //drawRectangle(r.left + borderRadius/2, r.top, r.width - borderRadius, r.height); 13705 13706 drawArc(r.upperLeft.x, r.upperLeft.y, radii[0], radii[0], 90*64, 90*64); 13707 drawArc(r.upperRight.x - radii[1], r.upperRight.y, radii[1] - 1, radii[1], 0*64, 90*64); 13708 drawArc(r.lowerLeft.x, r.lowerLeft.y - radii[2], radii[2], radii[2] - 1, 180*64, 90*64); 13709 drawArc(r.lowerRight.x - radii[3], r.lowerRight.y - radii[3], radii[3] - 1, radii[3] - 1, 270*64, 90*64); 13710 } 13711 13712 13713 /// Arguments are the points of the bounding rectangle 13714 void drawEllipse(int x1, int y1, int x2, int y2) { 13715 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 13716 } 13717 13718 // NOTE: start and finish are in units of degrees * 64 13719 void drawArc(int x1, int y1, int width, int height, int start, int length) { 13720 if(backgroundIsNotTransparent) { 13721 swapColors(); 13722 XFillArc(display, d, gc, x1, y1, width, height, start, length); 13723 swapColors(); 13724 } 13725 if(foregroundIsNotTransparent) { 13726 XDrawArc(display, d, gc, x1, y1, width, height, start, length); 13727 13728 // Windows draws the straight lines on the edges too so FIXME sort of 13729 } 13730 } 13731 13732 void drawPolygon(Point[] vertexes) { 13733 XPoint[16] pointsBuffer; 13734 XPoint[] points; 13735 if(vertexes.length <= pointsBuffer.length) 13736 points = pointsBuffer[0 .. vertexes.length]; 13737 else 13738 points.length = vertexes.length; 13739 13740 foreach(i, p; vertexes) { 13741 points[i].x = cast(short) p.x; 13742 points[i].y = cast(short) p.y; 13743 } 13744 13745 if(backgroundIsNotTransparent) { 13746 swapColors(); 13747 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 13748 swapColors(); 13749 } 13750 if(foregroundIsNotTransparent) { 13751 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 13752 } 13753 } 13754 } 13755 13756 /* XRender { */ 13757 13758 struct XRenderColor { 13759 ushort red; 13760 ushort green; 13761 ushort blue; 13762 ushort alpha; 13763 } 13764 13765 alias Picture = XID; 13766 alias PictFormat = XID; 13767 13768 struct XGlyphInfo { 13769 ushort width; 13770 ushort height; 13771 short x; 13772 short y; 13773 short xOff; 13774 short yOff; 13775 } 13776 13777 struct XRenderDirectFormat { 13778 short red; 13779 short redMask; 13780 short green; 13781 short greenMask; 13782 short blue; 13783 short blueMask; 13784 short alpha; 13785 short alphaMask; 13786 } 13787 13788 struct XRenderPictFormat { 13789 PictFormat id; 13790 int type; 13791 int depth; 13792 XRenderDirectFormat direct; 13793 Colormap colormap; 13794 } 13795 13796 enum PictFormatID = (1 << 0); 13797 enum PictFormatType = (1 << 1); 13798 enum PictFormatDepth = (1 << 2); 13799 enum PictFormatRed = (1 << 3); 13800 enum PictFormatRedMask =(1 << 4); 13801 enum PictFormatGreen = (1 << 5); 13802 enum PictFormatGreenMask=(1 << 6); 13803 enum PictFormatBlue = (1 << 7); 13804 enum PictFormatBlueMask =(1 << 8); 13805 enum PictFormatAlpha = (1 << 9); 13806 enum PictFormatAlphaMask=(1 << 10); 13807 enum PictFormatColormap =(1 << 11); 13808 13809 struct XRenderPictureAttributes { 13810 int repeat; 13811 Picture alpha_map; 13812 int alpha_x_origin; 13813 int alpha_y_origin; 13814 int clip_x_origin; 13815 int clip_y_origin; 13816 Pixmap clip_mask; 13817 Bool graphics_exposures; 13818 int subwindow_mode; 13819 int poly_edge; 13820 int poly_mode; 13821 Atom dither; 13822 Bool component_alpha; 13823 } 13824 13825 alias int XFixed; 13826 13827 struct XPointFixed { 13828 XFixed x, y; 13829 } 13830 13831 struct XCircle { 13832 XFixed x; 13833 XFixed y; 13834 XFixed radius; 13835 } 13836 13837 struct XTransform { 13838 XFixed[3][3] matrix; 13839 } 13840 13841 struct XFilters { 13842 int nfilter; 13843 char **filter; 13844 int nalias; 13845 short *alias_; 13846 } 13847 13848 struct XIndexValue { 13849 c_ulong pixel; 13850 ushort red, green, blue, alpha; 13851 } 13852 13853 struct XAnimCursor { 13854 Cursor cursor; 13855 c_ulong delay; 13856 } 13857 13858 struct XLinearGradient { 13859 XPointFixed p1; 13860 XPointFixed p2; 13861 } 13862 13863 struct XRadialGradient { 13864 XCircle inner; 13865 XCircle outer; 13866 } 13867 13868 struct XConicalGradient { 13869 XPointFixed center; 13870 XFixed angle; /* in degrees */ 13871 } 13872 13873 enum PictStandardARGB32 = 0; 13874 enum PictStandardRGB24 = 1; 13875 enum PictStandardA8 = 2; 13876 enum PictStandardA4 = 3; 13877 enum PictStandardA1 = 4; 13878 enum PictStandardNUM = 5; 13879 13880 interface XRender { 13881 extern(C) @nogc: 13882 13883 Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep); 13884 13885 Status XRenderQueryVersion (Display *dpy, 13886 int *major_versionp, 13887 int *minor_versionp); 13888 13889 Status XRenderQueryFormats (Display *dpy); 13890 13891 int XRenderQuerySubpixelOrder (Display *dpy, int screen); 13892 13893 Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel); 13894 13895 XRenderPictFormat * 13896 XRenderFindVisualFormat (Display *dpy, const Visual *visual); 13897 13898 XRenderPictFormat * 13899 XRenderFindFormat (Display *dpy, 13900 c_ulong mask, 13901 const XRenderPictFormat *templ, 13902 int count); 13903 XRenderPictFormat * 13904 XRenderFindStandardFormat (Display *dpy, 13905 int format); 13906 13907 XIndexValue * 13908 XRenderQueryPictIndexValues(Display *dpy, 13909 const XRenderPictFormat *format, 13910 int *num); 13911 13912 Picture XRenderCreatePicture( 13913 Display *dpy, 13914 Drawable drawable, 13915 const XRenderPictFormat *format, 13916 c_ulong valuemask, 13917 const XRenderPictureAttributes *attributes); 13918 13919 void XRenderChangePicture (Display *dpy, 13920 Picture picture, 13921 c_ulong valuemask, 13922 const XRenderPictureAttributes *attributes); 13923 13924 void 13925 XRenderSetPictureClipRectangles (Display *dpy, 13926 Picture picture, 13927 int xOrigin, 13928 int yOrigin, 13929 const XRectangle *rects, 13930 int n); 13931 13932 void 13933 XRenderSetPictureClipRegion (Display *dpy, 13934 Picture picture, 13935 Region r); 13936 13937 void 13938 XRenderSetPictureTransform (Display *dpy, 13939 Picture picture, 13940 XTransform *transform); 13941 13942 void 13943 XRenderFreePicture (Display *dpy, 13944 Picture picture); 13945 13946 void 13947 XRenderComposite (Display *dpy, 13948 int op, 13949 Picture src, 13950 Picture mask, 13951 Picture dst, 13952 int src_x, 13953 int src_y, 13954 int mask_x, 13955 int mask_y, 13956 int dst_x, 13957 int dst_y, 13958 uint width, 13959 uint height); 13960 13961 13962 Picture XRenderCreateSolidFill (Display *dpy, 13963 const XRenderColor *color); 13964 13965 Picture XRenderCreateLinearGradient (Display *dpy, 13966 const XLinearGradient *gradient, 13967 const XFixed *stops, 13968 const XRenderColor *colors, 13969 int nstops); 13970 13971 Picture XRenderCreateRadialGradient (Display *dpy, 13972 const XRadialGradient *gradient, 13973 const XFixed *stops, 13974 const XRenderColor *colors, 13975 int nstops); 13976 13977 Picture XRenderCreateConicalGradient (Display *dpy, 13978 const XConicalGradient *gradient, 13979 const XFixed *stops, 13980 const XRenderColor *colors, 13981 int nstops); 13982 13983 13984 13985 Cursor 13986 XRenderCreateCursor (Display *dpy, 13987 Picture source, 13988 uint x, 13989 uint y); 13990 13991 XFilters * 13992 XRenderQueryFilters (Display *dpy, Drawable drawable); 13993 13994 void 13995 XRenderSetPictureFilter (Display *dpy, 13996 Picture picture, 13997 const char *filter, 13998 XFixed *params, 13999 int nparams); 14000 14001 Cursor 14002 XRenderCreateAnimCursor (Display *dpy, 14003 int ncursor, 14004 XAnimCursor *cursors); 14005 } 14006 14007 __gshared bool XRenderLibrarySuccessfullyLoaded = true; 14008 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary; 14009 14010 /* XRender } */ 14011 14012 /* Xrandr { */ 14013 14014 struct XRRMonitorInfo { 14015 Atom name; 14016 Bool primary; 14017 Bool automatic; 14018 int noutput; 14019 int x; 14020 int y; 14021 int width; 14022 int height; 14023 int mwidth; 14024 int mheight; 14025 /*RROutput*/ void *outputs; 14026 } 14027 14028 struct XRRScreenChangeNotifyEvent { 14029 int type; /* event base */ 14030 c_ulong serial; /* # of last request processed by server */ 14031 Bool send_event; /* true if this came from a SendEvent request */ 14032 Display *display; /* Display the event was read from */ 14033 Window window; /* window which selected for this event */ 14034 Window root; /* Root window for changed screen */ 14035 Time timestamp; /* when the screen change occurred */ 14036 Time config_timestamp; /* when the last configuration change */ 14037 ushort/*SizeID*/ size_index; 14038 ushort/*SubpixelOrder*/ subpixel_order; 14039 ushort/*Rotation*/ rotation; 14040 int width; 14041 int height; 14042 int mwidth; 14043 int mheight; 14044 } 14045 14046 enum RRScreenChangeNotify = 0; 14047 14048 enum RRScreenChangeNotifyMask = 1; 14049 14050 __gshared int xrrEventBase = -1; 14051 14052 14053 interface XRandr { 14054 extern(C) @nogc: 14055 Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return); 14056 Status XRRQueryVersion (Display *dpy, int *major_version_return, int *minor_version_return); 14057 14058 XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors); 14059 void XRRFreeMonitors(XRRMonitorInfo *monitors); 14060 14061 void XRRSelectInput(Display *dpy, Window window, int mask); 14062 } 14063 14064 __gshared bool XRandrLibrarySuccessfullyLoaded = true; 14065 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary; 14066 /* Xrandr } */ 14067 14068 /* Xft { */ 14069 14070 // actually freetype 14071 alias void FT_Face; 14072 14073 // actually fontconfig 14074 private alias FcBool = int; 14075 alias void FcCharSet; 14076 alias void FcPattern; 14077 alias void FcResult; 14078 enum FcEndian { FcEndianBig, FcEndianLittle } 14079 struct FcFontSet { 14080 int nfont; 14081 int sfont; 14082 FcPattern** fonts; 14083 } 14084 14085 // actually XRegion 14086 struct BOX { 14087 short x1, x2, y1, y2; 14088 } 14089 struct _XRegion { 14090 c_long size; 14091 c_long numRects; 14092 BOX* rects; 14093 BOX extents; 14094 } 14095 14096 alias Region = _XRegion*; 14097 14098 // ok actually Xft 14099 14100 struct XftFontInfo; 14101 14102 struct XftFont { 14103 int ascent; 14104 int descent; 14105 int height; 14106 int max_advance_width; 14107 FcCharSet* charset; 14108 FcPattern* pattern; 14109 } 14110 14111 struct XftDraw; 14112 14113 struct XftColor { 14114 c_ulong pixel; 14115 XRenderColor color; 14116 } 14117 14118 struct XftCharSpec { 14119 dchar ucs4; 14120 short x; 14121 short y; 14122 } 14123 14124 struct XftCharFontSpec { 14125 XftFont *font; 14126 dchar ucs4; 14127 short x; 14128 short y; 14129 } 14130 14131 struct XftGlyphSpec { 14132 uint glyph; 14133 short x; 14134 short y; 14135 } 14136 14137 struct XftGlyphFontSpec { 14138 XftFont *font; 14139 uint glyph; 14140 short x; 14141 short y; 14142 } 14143 14144 interface Xft { 14145 extern(C) @nogc pure: 14146 14147 Bool XftColorAllocName (Display *dpy, 14148 const Visual *visual, 14149 Colormap cmap, 14150 const char *name, 14151 XftColor *result); 14152 14153 Bool XftColorAllocValue (Display *dpy, 14154 Visual *visual, 14155 Colormap cmap, 14156 const XRenderColor *color, 14157 XftColor *result); 14158 14159 void XftColorFree (Display *dpy, 14160 Visual *visual, 14161 Colormap cmap, 14162 XftColor *color); 14163 14164 Bool XftDefaultHasRender (Display *dpy); 14165 14166 Bool XftDefaultSet (Display *dpy, FcPattern *defaults); 14167 14168 void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern); 14169 14170 XftDraw * XftDrawCreate (Display *dpy, 14171 Drawable drawable, 14172 Visual *visual, 14173 Colormap colormap); 14174 14175 XftDraw * XftDrawCreateBitmap (Display *dpy, 14176 Pixmap bitmap); 14177 14178 XftDraw * XftDrawCreateAlpha (Display *dpy, 14179 Pixmap pixmap, 14180 int depth); 14181 14182 void XftDrawChange (XftDraw *draw, 14183 Drawable drawable); 14184 14185 Display * XftDrawDisplay (XftDraw *draw); 14186 14187 Drawable XftDrawDrawable (XftDraw *draw); 14188 14189 Colormap XftDrawColormap (XftDraw *draw); 14190 14191 Visual * XftDrawVisual (XftDraw *draw); 14192 14193 void XftDrawDestroy (XftDraw *draw); 14194 14195 Picture XftDrawPicture (XftDraw *draw); 14196 14197 Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color); 14198 14199 void XftDrawGlyphs (XftDraw *draw, 14200 const XftColor *color, 14201 XftFont *pub, 14202 int x, 14203 int y, 14204 const uint *glyphs, 14205 int nglyphs); 14206 14207 void XftDrawString8 (XftDraw *draw, 14208 const XftColor *color, 14209 XftFont *pub, 14210 int x, 14211 int y, 14212 const char *string, 14213 int len); 14214 14215 void XftDrawString16 (XftDraw *draw, 14216 const XftColor *color, 14217 XftFont *pub, 14218 int x, 14219 int y, 14220 const wchar *string, 14221 int len); 14222 14223 void XftDrawString32 (XftDraw *draw, 14224 const XftColor *color, 14225 XftFont *pub, 14226 int x, 14227 int y, 14228 const dchar *string, 14229 int len); 14230 14231 void XftDrawStringUtf8 (XftDraw *draw, 14232 const XftColor *color, 14233 XftFont *pub, 14234 int x, 14235 int y, 14236 const char *string, 14237 int len); 14238 void XftDrawStringUtf16 (XftDraw *draw, 14239 const XftColor *color, 14240 XftFont *pub, 14241 int x, 14242 int y, 14243 const char *string, 14244 FcEndian endian, 14245 int len); 14246 14247 void XftDrawCharSpec (XftDraw *draw, 14248 const XftColor *color, 14249 XftFont *pub, 14250 const XftCharSpec *chars, 14251 int len); 14252 14253 void XftDrawCharFontSpec (XftDraw *draw, 14254 const XftColor *color, 14255 const XftCharFontSpec *chars, 14256 int len); 14257 14258 void XftDrawGlyphSpec (XftDraw *draw, 14259 const XftColor *color, 14260 XftFont *pub, 14261 const XftGlyphSpec *glyphs, 14262 int len); 14263 14264 void XftDrawGlyphFontSpec (XftDraw *draw, 14265 const XftColor *color, 14266 const XftGlyphFontSpec *glyphs, 14267 int len); 14268 14269 void XftDrawRect (XftDraw *draw, 14270 const XftColor *color, 14271 int x, 14272 int y, 14273 uint width, 14274 uint height); 14275 14276 Bool XftDrawSetClip (XftDraw *draw, 14277 Region r); 14278 14279 14280 Bool XftDrawSetClipRectangles (XftDraw *draw, 14281 int xOrigin, 14282 int yOrigin, 14283 const XRectangle *rects, 14284 int n); 14285 14286 void XftDrawSetSubwindowMode (XftDraw *draw, 14287 int mode); 14288 14289 void XftGlyphExtents (Display *dpy, 14290 XftFont *pub, 14291 const uint *glyphs, 14292 int nglyphs, 14293 XGlyphInfo *extents); 14294 14295 void XftTextExtents8 (Display *dpy, 14296 XftFont *pub, 14297 const char *string, 14298 int len, 14299 XGlyphInfo *extents); 14300 14301 void XftTextExtents16 (Display *dpy, 14302 XftFont *pub, 14303 const wchar *string, 14304 int len, 14305 XGlyphInfo *extents); 14306 14307 void XftTextExtents32 (Display *dpy, 14308 XftFont *pub, 14309 const dchar *string, 14310 int len, 14311 XGlyphInfo *extents); 14312 14313 void XftTextExtentsUtf8 (Display *dpy, 14314 XftFont *pub, 14315 const char *string, 14316 int len, 14317 XGlyphInfo *extents); 14318 14319 void XftTextExtentsUtf16 (Display *dpy, 14320 XftFont *pub, 14321 const char *string, 14322 FcEndian endian, 14323 int len, 14324 XGlyphInfo *extents); 14325 14326 FcPattern * XftFontMatch (Display *dpy, 14327 int screen, 14328 const FcPattern *pattern, 14329 FcResult *result); 14330 14331 XftFont * XftFontOpen (Display *dpy, int screen, ...); 14332 14333 XftFont * XftFontOpenName (Display *dpy, int screen, const char *name); 14334 14335 XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd); 14336 14337 FT_Face XftLockFace (XftFont *pub); 14338 14339 void XftUnlockFace (XftFont *pub); 14340 14341 XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern); 14342 14343 void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi); 14344 14345 dchar XftFontInfoHash (const XftFontInfo *fi); 14346 14347 FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b); 14348 14349 XftFont * XftFontOpenInfo (Display *dpy, 14350 FcPattern *pattern, 14351 XftFontInfo *fi); 14352 14353 XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern); 14354 14355 XftFont * XftFontCopy (Display *dpy, XftFont *pub); 14356 14357 void XftFontClose (Display *dpy, XftFont *pub); 14358 14359 FcBool XftInitFtLibrary(); 14360 void XftFontLoadGlyphs (Display *dpy, 14361 XftFont *pub, 14362 FcBool need_bitmaps, 14363 const uint *glyphs, 14364 int nglyph); 14365 14366 void XftFontUnloadGlyphs (Display *dpy, 14367 XftFont *pub, 14368 const uint *glyphs, 14369 int nglyph); 14370 14371 FcBool XftFontCheckGlyph (Display *dpy, 14372 XftFont *pub, 14373 FcBool need_bitmaps, 14374 uint glyph, 14375 uint *missing, 14376 int *nmissing); 14377 14378 FcBool XftCharExists (Display *dpy, 14379 XftFont *pub, 14380 dchar ucs4); 14381 14382 uint XftCharIndex (Display *dpy, 14383 XftFont *pub, 14384 dchar ucs4); 14385 FcBool XftInit (const char *config); 14386 14387 int XftGetVersion (); 14388 14389 FcFontSet * XftListFonts (Display *dpy, 14390 int screen, 14391 ...); 14392 14393 FcPattern *XftNameParse (const char *name); 14394 14395 void XftGlyphRender (Display *dpy, 14396 int op, 14397 Picture src, 14398 XftFont *pub, 14399 Picture dst, 14400 int srcx, 14401 int srcy, 14402 int x, 14403 int y, 14404 const uint *glyphs, 14405 int nglyphs); 14406 14407 void XftGlyphSpecRender (Display *dpy, 14408 int op, 14409 Picture src, 14410 XftFont *pub, 14411 Picture dst, 14412 int srcx, 14413 int srcy, 14414 const XftGlyphSpec *glyphs, 14415 int nglyphs); 14416 14417 void XftCharSpecRender (Display *dpy, 14418 int op, 14419 Picture src, 14420 XftFont *pub, 14421 Picture dst, 14422 int srcx, 14423 int srcy, 14424 const XftCharSpec *chars, 14425 int len); 14426 void XftGlyphFontSpecRender (Display *dpy, 14427 int op, 14428 Picture src, 14429 Picture dst, 14430 int srcx, 14431 int srcy, 14432 const XftGlyphFontSpec *glyphs, 14433 int nglyphs); 14434 14435 void XftCharFontSpecRender (Display *dpy, 14436 int op, 14437 Picture src, 14438 Picture dst, 14439 int srcx, 14440 int srcy, 14441 const XftCharFontSpec *chars, 14442 int len); 14443 14444 void XftTextRender8 (Display *dpy, 14445 int op, 14446 Picture src, 14447 XftFont *pub, 14448 Picture dst, 14449 int srcx, 14450 int srcy, 14451 int x, 14452 int y, 14453 const char *string, 14454 int len); 14455 void XftTextRender16 (Display *dpy, 14456 int op, 14457 Picture src, 14458 XftFont *pub, 14459 Picture dst, 14460 int srcx, 14461 int srcy, 14462 int x, 14463 int y, 14464 const wchar *string, 14465 int len); 14466 14467 void XftTextRender16BE (Display *dpy, 14468 int op, 14469 Picture src, 14470 XftFont *pub, 14471 Picture dst, 14472 int srcx, 14473 int srcy, 14474 int x, 14475 int y, 14476 const char *string, 14477 int len); 14478 14479 void XftTextRender16LE (Display *dpy, 14480 int op, 14481 Picture src, 14482 XftFont *pub, 14483 Picture dst, 14484 int srcx, 14485 int srcy, 14486 int x, 14487 int y, 14488 const char *string, 14489 int len); 14490 14491 void XftTextRender32 (Display *dpy, 14492 int op, 14493 Picture src, 14494 XftFont *pub, 14495 Picture dst, 14496 int srcx, 14497 int srcy, 14498 int x, 14499 int y, 14500 const dchar *string, 14501 int len); 14502 14503 void XftTextRender32BE (Display *dpy, 14504 int op, 14505 Picture src, 14506 XftFont *pub, 14507 Picture dst, 14508 int srcx, 14509 int srcy, 14510 int x, 14511 int y, 14512 const char *string, 14513 int len); 14514 14515 void XftTextRender32LE (Display *dpy, 14516 int op, 14517 Picture src, 14518 XftFont *pub, 14519 Picture dst, 14520 int srcx, 14521 int srcy, 14522 int x, 14523 int y, 14524 const char *string, 14525 int len); 14526 14527 void XftTextRenderUtf8 (Display *dpy, 14528 int op, 14529 Picture src, 14530 XftFont *pub, 14531 Picture dst, 14532 int srcx, 14533 int srcy, 14534 int x, 14535 int y, 14536 const char *string, 14537 int len); 14538 14539 void XftTextRenderUtf16 (Display *dpy, 14540 int op, 14541 Picture src, 14542 XftFont *pub, 14543 Picture dst, 14544 int srcx, 14545 int srcy, 14546 int x, 14547 int y, 14548 const char *string, 14549 FcEndian endian, 14550 int len); 14551 FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete); 14552 14553 } 14554 14555 interface FontConfig { 14556 extern(C) @nogc pure: 14557 int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s); 14558 void FcFontSetDestroy(FcFontSet*); 14559 char* FcNameUnparse(const FcPattern *); 14560 } 14561 14562 mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary; 14563 mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary; 14564 14565 14566 /* Xft } */ 14567 14568 class XDisconnectException : Exception { 14569 bool userRequested; 14570 this(bool userRequested = true) { 14571 this.userRequested = userRequested; 14572 super("X disconnected"); 14573 } 14574 } 14575 14576 /++ 14577 Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this. 14578 14579 Please note that it returns 14580 +/ 14581 XErrorEvent[] trapXErrors(scope void delegate() dg) { 14582 14583 static XErrorEvent[] errorBuffer; 14584 14585 static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow { 14586 errorBuffer ~= *evt; 14587 return 0; 14588 } 14589 14590 auto savedErrorHandler = XSetErrorHandler(&handler); 14591 14592 try { 14593 dg(); 14594 } finally { 14595 XSync(XDisplayConnection.get, 0/*False*/); 14596 XSetErrorHandler(savedErrorHandler); 14597 } 14598 14599 auto bfr = errorBuffer; 14600 errorBuffer = null; 14601 14602 return bfr; 14603 } 14604 14605 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`. 14606 class XDisplayConnection { 14607 private __gshared Display* display; 14608 private __gshared XIM xim; 14609 private __gshared char* displayName; 14610 14611 private __gshared int connectionSequence_; 14612 private __gshared bool isLocal_; 14613 14614 /// use this for lazy caching when reconnection 14615 static int connectionSequenceNumber() { return connectionSequence_; } 14616 14617 /++ 14618 Guesses if the connection appears to be local. 14619 14620 History: 14621 Added June 3, 2021 14622 +/ 14623 static @property bool isLocal() nothrow @trusted @nogc { 14624 return isLocal_; 14625 } 14626 14627 /// Attempts recreation of state, may require application assistance 14628 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 14629 /// then call this, and if successful, reenter the loop. 14630 static void discardAndRecreate(string newDisplayString = null) { 14631 if(insideXEventLoop) 14632 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 14633 14634 // 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 14635 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 14636 14637 foreach(handle; chnenhm) { 14638 handle.discardConnectionState(); 14639 } 14640 14641 discardState(); 14642 14643 if(newDisplayString !is null) 14644 setDisplayName(newDisplayString); 14645 14646 auto display = get(); 14647 14648 foreach(handle; chnenhm) { 14649 handle.recreateAfterDisconnect(); 14650 } 14651 } 14652 14653 private __gshared EventMask rootEventMask; 14654 14655 /++ 14656 Requests the specified input from the root window on the connection, in addition to any other request. 14657 14658 14659 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. 14660 14661 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 14662 +/ 14663 static void addRootInput(EventMask mask) { 14664 auto old = rootEventMask; 14665 rootEventMask |= mask; 14666 get(); // to ensure display connected 14667 if(display !is null && rootEventMask != old) 14668 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 14669 } 14670 14671 static void discardState() { 14672 freeImages(); 14673 14674 foreach(atomPtr; interredAtoms) 14675 *atomPtr = 0; 14676 interredAtoms = null; 14677 interredAtoms.assumeSafeAppend(); 14678 14679 ScreenPainterImplementation.fontAttempted = false; 14680 ScreenPainterImplementation.defaultfont = null; 14681 ScreenPainterImplementation.defaultfontset = null; 14682 14683 Image.impl.xshmQueryCompleted = false; 14684 Image.impl._xshmAvailable = false; 14685 14686 SimpleWindow.nativeMapping = null; 14687 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 14688 // GlobalHotkeyManager 14689 14690 display = null; 14691 xim = null; 14692 } 14693 14694 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 14695 private static void createXIM () { 14696 import core.stdc.locale : setlocale, LC_ALL; 14697 import core.stdc.stdio : stderr, fprintf; 14698 import core.stdc.stdlib : free; 14699 import core.stdc.string : strdup; 14700 14701 static immutable string[3] mtry = [ "", "@im=local", "@im=" ]; 14702 14703 auto olocale = strdup(setlocale(LC_ALL, null)); 14704 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 14705 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 14706 14707 //fprintf(stderr, "opening IM...\n"); 14708 foreach (string s; mtry) { 14709 XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 14710 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 14711 } 14712 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 14713 } 14714 14715 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 14716 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 14717 static struct ImgList { 14718 size_t img; // class; hide it from GC 14719 ImgList* next; 14720 } 14721 14722 static __gshared ImgList* imglist = null; 14723 static __gshared bool imglistLocked = false; // true: don't register and unregister images 14724 14725 static void registerImage (Image img) { 14726 if (!imglistLocked && img !is null) { 14727 import core.stdc.stdlib : malloc; 14728 auto it = cast(ImgList*)malloc(ImgList.sizeof); 14729 assert(it !is null); // do proper checks 14730 it.img = cast(size_t)cast(void*)img; 14731 it.next = imglist; 14732 imglist = it; 14733 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 14734 } 14735 } 14736 14737 static void unregisterImage (Image img) { 14738 if (!imglistLocked && img !is null) { 14739 import core.stdc.stdlib : free; 14740 ImgList* prev = null; 14741 ImgList* cur = imglist; 14742 while (cur !is null) { 14743 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 14744 prev = cur; 14745 cur = cur.next; 14746 } 14747 if (cur !is null) { 14748 if (prev is null) imglist = cur.next; else prev.next = cur.next; 14749 free(cur); 14750 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 14751 } else { 14752 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 14753 } 14754 } 14755 } 14756 14757 static void freeImages () { // needed for discardAndRecreate 14758 imglistLocked = true; 14759 scope(exit) imglistLocked = false; 14760 ImgList* cur = imglist; 14761 ImgList* next = null; 14762 while (cur !is null) { 14763 import core.stdc.stdlib : free; 14764 next = cur.next; 14765 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 14766 (cast(Image)cast(void*)cur.img).dispose(); 14767 free(cur); 14768 cur = next; 14769 } 14770 imglist = null; 14771 } 14772 14773 /// can be used to override normal handling of display name 14774 /// from environment and/or command line 14775 static setDisplayName(string newDisplayName) { 14776 displayName = cast(char*) (newDisplayName ~ '\0'); 14777 } 14778 14779 /// resets to the default display string 14780 static resetDisplayName() { 14781 displayName = null; 14782 } 14783 14784 /// 14785 static Display* get() { 14786 if(display is null) { 14787 if(!librariesSuccessfullyLoaded) 14788 throw new Exception("Unable to load X11 client libraries"); 14789 display = XOpenDisplay(displayName); 14790 14791 isLocal_ = false; 14792 14793 connectionSequence_++; 14794 if(display is null) 14795 throw new Exception("Unable to open X display"); 14796 14797 auto str = display.display_name; 14798 // this is a bit of a hack but like if it looks like a unix socket we assume it is local 14799 // and otherwise it probably isn't 14800 if(str is null || (str[0] != ':' && str[0] != '/')) 14801 isLocal_ = false; 14802 else 14803 isLocal_ = true; 14804 14805 debug(sdpy_x_errors) { 14806 XSetErrorHandler(&adrlogger); 14807 XSynchronize(display, true); 14808 14809 extern(C) int wtf() { 14810 if(errorHappened) { 14811 asm { int 3; } 14812 errorHappened = false; 14813 } 14814 return 0; 14815 } 14816 XSetAfterFunction(display, &wtf); 14817 } 14818 14819 14820 XSetIOErrorHandler(&x11ioerrCB); 14821 Bool sup; 14822 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 14823 createXIM(); 14824 version(with_eventloop) { 14825 import arsd.eventloop; 14826 addFileEventListeners(display.fd, &eventListener, null, null); 14827 } 14828 } 14829 14830 return display; 14831 } 14832 14833 extern(C) 14834 static int x11ioerrCB(Display* dpy) { 14835 throw new XDisconnectException(false); 14836 } 14837 14838 version(with_eventloop) { 14839 import arsd.eventloop; 14840 static void eventListener(OsFileHandle fd) { 14841 //this.mtLock(); 14842 //scope(exit) this.mtUnlock(); 14843 while(XPending(display)) 14844 doXNextEvent(display); 14845 } 14846 } 14847 14848 // close connection on program exit -- we need this to properly free all images 14849 static ~this () { 14850 // the gui thread must clean up after itself or else Xlib might deadlock 14851 // using this flag on any thread destruction is the easiest way i know of 14852 // (shared static this is run by the LAST thread to exit, which may not be 14853 // the gui thread, and normal static this run by ALL threads, so we gotta check.) 14854 if(thisIsGuiThread) 14855 close(); 14856 } 14857 14858 /// 14859 static void close() { 14860 if(display is null) 14861 return; 14862 14863 version(with_eventloop) { 14864 import arsd.eventloop; 14865 removeFileEventListeners(display.fd); 14866 } 14867 14868 // now remove all registered images to prevent shared memory leaks 14869 freeImages(); 14870 14871 // tbh I don't know why it is doing this but like if this happens to run 14872 // from the other thread there's frequent hanging inside here. 14873 if(thisIsGuiThread) 14874 XCloseDisplay(display); 14875 display = null; 14876 } 14877 } 14878 14879 mixin template NativeImageImplementation() { 14880 XImage* handle; 14881 ubyte* rawData; 14882 14883 XShmSegmentInfo shminfo; 14884 bool premultiply = true; 14885 14886 __gshared bool xshmQueryCompleted; 14887 __gshared bool _xshmAvailable; 14888 public static @property bool xshmAvailable() { 14889 if(!xshmQueryCompleted) { 14890 int i1, i2, i3; 14891 xshmQueryCompleted = true; 14892 14893 if(!XDisplayConnection.isLocal) 14894 _xshmAvailable = false; 14895 else 14896 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 14897 } 14898 return _xshmAvailable; 14899 } 14900 14901 bool usingXshm; 14902 final: 14903 14904 private __gshared bool xshmfailed; 14905 14906 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 14907 auto display = XDisplayConnection.get(); 14908 assert(display !is null); 14909 auto screen = DefaultScreen(display); 14910 14911 // it will only use shared memory for somewhat largish images, 14912 // since otherwise we risk wasting shared memory handles on a lot of little ones 14913 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 14914 14915 14916 // it is possible for the query extension to return true, the DISPLAY check to pass, yet 14917 // the actual use still fails. For example, if the program is in a container and permission denied 14918 // on shared memory, or if it is a local thing forwarded to a remote server, etc. 14919 // 14920 // If it does fail, we need to detect it now, abort the xshm and fall back to core protocol. 14921 14922 14923 // synchronize so preexisting buffers are clear 14924 XSync(display, false); 14925 xshmfailed = false; 14926 14927 auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler); 14928 14929 14930 usingXshm = true; 14931 handle = XShmCreateImage( 14932 display, 14933 DefaultVisual(display, screen), 14934 enableAlpha ? 32: 24, 14935 ImageFormat.ZPixmap, 14936 null, 14937 &shminfo, 14938 width, height); 14939 if(handle is null) 14940 goto abortXshm1; 14941 14942 if(handle.bytes_per_line != 4 * width) 14943 goto abortXshm2; 14944 14945 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 14946 if(shminfo.shmid < 0) 14947 goto abortXshm3; 14948 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 14949 if(rawData == cast(ubyte*) -1) 14950 goto abortXshm4; 14951 shminfo.readOnly = 0; 14952 XShmAttach(display, &shminfo); 14953 14954 // and now to the final error check to ensure it actually worked. 14955 XSync(display, false); 14956 if(xshmfailed) 14957 goto abortXshm5; 14958 14959 XSetErrorHandler(oldErrorHandler); 14960 14961 XDisplayConnection.registerImage(this); 14962 // if I don't flush here there's a chance the dtor will run before the 14963 // ctor and lead to a bad value X error. While this hurts the efficiency 14964 // it is local anyway so prolly better to keep it simple 14965 XFlush(display); 14966 14967 return; 14968 14969 abortXshm5: 14970 shmdt(shminfo.shmaddr); 14971 rawData = null; 14972 14973 abortXshm4: 14974 shmctl(shminfo.shmid, IPC_RMID, null); 14975 14976 abortXshm3: 14977 // nothing needed, the shmget failed so there's nothing to free 14978 14979 abortXshm2: 14980 XDestroyImage(handle); 14981 handle = null; 14982 14983 abortXshm1: 14984 XSetErrorHandler(oldErrorHandler); 14985 usingXshm = false; 14986 handle = null; 14987 14988 shminfo = typeof(shminfo).init; 14989 14990 _xshmAvailable = false; // don't try again in the future 14991 14992 // writeln("fallingback"); 14993 14994 goto fallback; 14995 14996 } else { 14997 fallback: 14998 14999 if (forcexshm) throw new Exception("can't create XShm Image"); 15000 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 15001 import core.stdc.stdlib : malloc; 15002 rawData = cast(ubyte*) malloc(width * height * 4); 15003 15004 handle = XCreateImage( 15005 display, 15006 DefaultVisual(display, screen), 15007 enableAlpha ? 32 : 24, // bpp 15008 ImageFormat.ZPixmap, 15009 0, // offset 15010 rawData, 15011 width, height, 15012 enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line 15013 } 15014 } 15015 15016 void dispose() { 15017 // note: this calls free(rawData) for us 15018 if(handle) { 15019 if (usingXshm) { 15020 XDisplayConnection.unregisterImage(this); 15021 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 15022 } 15023 XDestroyImage(handle); 15024 if(usingXshm) { 15025 shmdt(shminfo.shmaddr); 15026 shmctl(shminfo.shmid, IPC_RMID, null); 15027 } 15028 handle = null; 15029 } 15030 } 15031 15032 Color getPixel(int x, int y) @system { 15033 auto offset = (y * width + x) * 4; 15034 Color c; 15035 c.a = enableAlpha ? rawData[offset + 3] : 255; 15036 c.b = rawData[offset + 0]; 15037 c.g = rawData[offset + 1]; 15038 c.r = rawData[offset + 2]; 15039 if(enableAlpha && premultiply) 15040 c.unPremultiply; 15041 return c; 15042 } 15043 15044 void setPixel(int x, int y, Color c) @system { 15045 if(enableAlpha && premultiply) 15046 c.premultiply(); 15047 auto offset = (y * width + x) * 4; 15048 rawData[offset + 0] = c.b; 15049 rawData[offset + 1] = c.g; 15050 rawData[offset + 2] = c.r; 15051 if(enableAlpha) 15052 rawData[offset + 3] = c.a; 15053 } 15054 15055 void convertToRgbaBytes(ubyte[] where) @system { 15056 assert(where.length == this.width * this.height * 4); 15057 15058 // if rawData had a length.... 15059 //assert(rawData.length == where.length); 15060 for(int idx = 0; idx < where.length; idx += 4) { 15061 where[idx + 0] = rawData[idx + 2]; // r 15062 where[idx + 1] = rawData[idx + 1]; // g 15063 where[idx + 2] = rawData[idx + 0]; // b 15064 where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a 15065 15066 if(enableAlpha && premultiply) 15067 unPremultiplyRgba(where[idx .. idx + 4]); 15068 } 15069 } 15070 15071 void setFromRgbaBytes(in ubyte[] where) @system { 15072 assert(where.length == this.width * this.height * 4); 15073 15074 // if rawData had a length.... 15075 //assert(rawData.length == where.length); 15076 for(int idx = 0; idx < where.length; idx += 4) { 15077 rawData[idx + 2] = where[idx + 0]; // r 15078 rawData[idx + 1] = where[idx + 1]; // g 15079 rawData[idx + 0] = where[idx + 2]; // b 15080 if(enableAlpha) { 15081 rawData[idx + 3] = where[idx + 3]; // a 15082 if(premultiply) 15083 premultiplyBgra(rawData[idx .. idx + 4]); 15084 } 15085 } 15086 } 15087 15088 } 15089 15090 mixin template NativeSimpleWindowImplementation() { 15091 GC gc; 15092 Window window; 15093 Display* display; 15094 15095 Pixmap buffer; 15096 int bufferw, bufferh; // size of the buffer; can be bigger than window 15097 XIC xic; // input context 15098 int curHidden = 0; // counter 15099 Cursor blankCurPtr = 0; 15100 int cursorSequenceNumber = 0; 15101 int warpEventCount = 0; // number of mouse movement events to eat 15102 15103 __gshared X11SetSelectionHandler[Atom] setSelectionHandlers; // FIXME: make sure this is not accessed from other threads. it might be ok to make it TLS 15104 X11GetSelectionHandler[Atom] getSelectionHandlers; 15105 15106 version(without_opengl) {} else 15107 GLXContext glc; 15108 15109 private void fixFixedSize(bool forced=false) (int width, int height) { 15110 if (forced || this.resizability == Resizability.fixedSize) { 15111 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 15112 XSizeHints sh; 15113 static if (!forced) { 15114 c_long spr; 15115 XGetWMNormalHints(display, window, &sh, &spr); 15116 sh.flags |= PMaxSize | PMinSize; 15117 } else { 15118 sh.flags = PMaxSize | PMinSize; 15119 } 15120 sh.min_width = width; 15121 sh.min_height = height; 15122 sh.max_width = width; 15123 sh.max_height = height; 15124 XSetWMNormalHints(display, window, &sh); 15125 //XFlush(display); 15126 } 15127 } 15128 15129 ScreenPainter getPainter(bool manualInvalidations) { 15130 return ScreenPainter(this, window, manualInvalidations); 15131 } 15132 15133 void move(int x, int y) { 15134 XMoveWindow(display, window, x, y); 15135 } 15136 15137 void resize(int w, int h) { 15138 if (w < 1) w = 1; 15139 if (h < 1) h = 1; 15140 XResizeWindow(display, window, w, h); 15141 15142 // calling this now to avoid waiting for the server to 15143 // acknowledge the resize; draws without returning to the 15144 // event loop will thus actually work. the server's event 15145 // btw might overrule this and resize it again 15146 recordX11Resize(display, this, w, h); 15147 15148 updateOpenglViewportIfNeeded(w, h); 15149 } 15150 15151 void moveResize (int x, int y, int w, int h) { 15152 if (w < 1) w = 1; 15153 if (h < 1) h = 1; 15154 XMoveResizeWindow(display, window, x, y, w, h); 15155 updateOpenglViewportIfNeeded(w, h); 15156 } 15157 15158 void hideCursor () { 15159 if (curHidden++ == 0) { 15160 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 15161 static const(char)[1] cmbmp = 0; 15162 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 15163 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 15164 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 15165 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 15166 XFreePixmap(display, pm); 15167 } 15168 XDefineCursor(display, window, blankCurPtr); 15169 } 15170 } 15171 15172 void showCursor () { 15173 if (--curHidden == 0) XUndefineCursor(display, window); 15174 } 15175 15176 void warpMouse (int x, int y) { 15177 // here i will send dummy "ignore next mouse motion" event, 15178 // 'cause `XWarpPointer()` sends synthesised mouse motion, 15179 // and we don't need to report it to the user (as warping is 15180 // used when the user needs movement deltas). 15181 //XClientMessageEvent xclient; 15182 XEvent e; 15183 e.xclient.type = EventType.ClientMessage; 15184 e.xclient.window = window; 15185 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 15186 e.xclient.format = 32; 15187 e.xclient.data.l[0] = 0; 15188 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 15189 //{ 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]); } 15190 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 15191 // now warp pointer... 15192 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 15193 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 15194 // ...and flush 15195 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 15196 XFlush(display); 15197 } 15198 15199 void sendDummyEvent () { 15200 // here i will send dummy event to ping event queue 15201 XEvent e; 15202 e.xclient.type = EventType.ClientMessage; 15203 e.xclient.window = window; 15204 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 15205 e.xclient.format = 32; 15206 e.xclient.data.l[0] = 0; 15207 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 15208 XFlush(display); 15209 } 15210 15211 void setTitle(string title) { 15212 if (title.ptr is null) title = ""; 15213 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 15214 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 15215 XTextProperty windowName; 15216 windowName.value = title.ptr; 15217 windowName.encoding = XA_UTF8; //XA_STRING; 15218 windowName.format = 8; 15219 windowName.nitems = cast(uint)title.length; 15220 XSetWMName(display, window, &windowName); 15221 char[1024] namebuf = 0; 15222 auto maxlen = namebuf.length-1; 15223 if (maxlen > title.length) maxlen = title.length; 15224 namebuf[0..maxlen] = title[0..maxlen]; 15225 XStoreName(display, window, namebuf.ptr); 15226 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 15227 flushGui(); // without this OpenGL windows has a LONG delay before changing title 15228 } 15229 15230 string[] getTitles() { 15231 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 15232 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 15233 XTextProperty textProp; 15234 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 15235 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 15236 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 15237 } else 15238 return []; 15239 } else 15240 return null; 15241 } 15242 15243 string getTitle() { 15244 auto titles = getTitles(); 15245 return titles.length ? titles[0] : null; 15246 } 15247 15248 void setMinSize (int minwidth, int minheight) { 15249 import core.stdc.config : c_long; 15250 if (minwidth < 1) minwidth = 1; 15251 if (minheight < 1) minheight = 1; 15252 XSizeHints sh; 15253 c_long spr; 15254 XGetWMNormalHints(display, window, &sh, &spr); 15255 sh.min_width = minwidth; 15256 sh.min_height = minheight; 15257 sh.flags |= PMinSize; 15258 XSetWMNormalHints(display, window, &sh); 15259 flushGui(); 15260 } 15261 15262 void setMaxSize (int maxwidth, int maxheight) { 15263 import core.stdc.config : c_long; 15264 if (maxwidth < 1) maxwidth = 1; 15265 if (maxheight < 1) maxheight = 1; 15266 XSizeHints sh; 15267 c_long spr; 15268 XGetWMNormalHints(display, window, &sh, &spr); 15269 sh.max_width = maxwidth; 15270 sh.max_height = maxheight; 15271 sh.flags |= PMaxSize; 15272 XSetWMNormalHints(display, window, &sh); 15273 flushGui(); 15274 } 15275 15276 void setResizeGranularity (int granx, int grany) { 15277 import core.stdc.config : c_long; 15278 if (granx < 1) granx = 1; 15279 if (grany < 1) grany = 1; 15280 XSizeHints sh; 15281 c_long spr; 15282 XGetWMNormalHints(display, window, &sh, &spr); 15283 sh.width_inc = granx; 15284 sh.height_inc = grany; 15285 sh.flags |= PResizeInc; 15286 XSetWMNormalHints(display, window, &sh); 15287 flushGui(); 15288 } 15289 15290 void setOpacity (uint opacity) { 15291 arch_ulong o = opacity; 15292 if (opacity == uint.max) 15293 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 15294 else 15295 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 15296 XA_CARDINAL, 32, PropModeReplace, &o, 1); 15297 } 15298 15299 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) @trusted { 15300 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 15301 display = XDisplayConnection.get(); 15302 auto screen = DefaultScreen(display); 15303 15304 bool overrideRedirect = false; 15305 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild) 15306 overrideRedirect = true; 15307 15308 version(without_opengl) {} 15309 else { 15310 if(opengl == OpenGlOptions.yes) { 15311 GLXFBConfig fbconf = null; 15312 XVisualInfo* vi = null; 15313 bool useLegacy = false; 15314 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 15315 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 15316 int[23] visualAttribs = [ 15317 GLX_X_RENDERABLE , 1/*True*/, 15318 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 15319 GLX_RENDER_TYPE , GLX_RGBA_BIT, 15320 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 15321 GLX_RED_SIZE , 8, 15322 GLX_GREEN_SIZE , 8, 15323 GLX_BLUE_SIZE , 8, 15324 GLX_ALPHA_SIZE , 8, 15325 GLX_DEPTH_SIZE , 24, 15326 GLX_STENCIL_SIZE , 8, 15327 GLX_DOUBLEBUFFER , 1/*True*/, 15328 0/*None*/, 15329 ]; 15330 int fbcount; 15331 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 15332 if (fbcount == 0) { 15333 useLegacy = true; // try to do at least something 15334 } else { 15335 // pick the FB config/visual with the most samples per pixel 15336 int bestidx = -1, bestns = -1; 15337 foreach (int fbi; 0..fbcount) { 15338 int sb, samples; 15339 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 15340 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 15341 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 15342 } 15343 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 15344 fbconf = fbc[bestidx]; 15345 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 15346 XFree(fbc); 15347 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 15348 } 15349 } 15350 if (vi is null || useLegacy) { 15351 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 15352 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 15353 useLegacy = true; 15354 } 15355 if (vi is null) throw new Exception("no open gl visual found"); 15356 15357 XSetWindowAttributes swa; 15358 auto root = RootWindow(display, screen); 15359 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 15360 15361 swa.override_redirect = overrideRedirect; 15362 15363 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 15364 0, 0, width, height, 15365 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa); 15366 15367 // now try to use `glXCreateContextAttribsARB()` if it's here 15368 if (!useLegacy) { 15369 // request fairly advanced context, even with stencil buffer! 15370 int[9] contextAttribs = [ 15371 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 15372 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 15373 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 15374 // for modern context, set "forward compatibility" flag too 15375 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 15376 0/*None*/, 15377 ]; 15378 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 15379 if (glc is null && sdpyOpenGLContextAllowFallback) { 15380 sdpyOpenGLContextVersion = 0; 15381 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 15382 } 15383 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 15384 } else { 15385 // fallback to old GLX call 15386 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 15387 sdpyOpenGLContextVersion = 0; 15388 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 15389 } 15390 } 15391 // sync to ensure any errors generated are processed 15392 XSync(display, 0/*False*/); 15393 //{ import core.stdc.stdio; printf("ogl is here\n"); } 15394 if(glc is null) 15395 throw new Exception("glc"); 15396 } 15397 } 15398 15399 if(opengl == OpenGlOptions.no) { 15400 15401 XSetWindowAttributes swa; 15402 swa.background_pixel = WhitePixel(display, screen); 15403 swa.border_pixel = BlackPixel(display, screen); 15404 swa.override_redirect = overrideRedirect; 15405 auto root = RootWindow(display, screen); 15406 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 15407 15408 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 15409 0, 0, width, height, 15410 // I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit. 15411 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa); 15412 15413 15414 15415 /* 15416 window = XCreateSimpleWindow( 15417 display, 15418 parent is null ? RootWindow(display, screen) : parent.impl.window, 15419 0, 0, // x, y 15420 width, height, 15421 1, // border width 15422 BlackPixel(display, screen), // border 15423 WhitePixel(display, screen)); // background 15424 */ 15425 15426 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 15427 bufferw = width; 15428 bufferh = height; 15429 15430 gc = DefaultGC(display, screen); 15431 15432 // clear out the buffer to get us started... 15433 XSetForeground(display, gc, WhitePixel(display, screen)); 15434 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 15435 XSetForeground(display, gc, BlackPixel(display, screen)); 15436 } 15437 15438 // input context 15439 //TODO: create this only for top-level windows, and reuse that? 15440 populateXic(); 15441 15442 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 15443 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 15444 // window class 15445 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 15446 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 15447 XClassHint klass; 15448 XWMHints wh; 15449 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 15450 wh.input = true; 15451 wh.flags |= InputHint; 15452 } 15453 XSizeHints size; 15454 klass.res_name = sdpyWindowClassStr; 15455 klass.res_class = sdpyWindowClassStr; 15456 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 15457 } 15458 15459 setTitle(title); 15460 SimpleWindow.nativeMapping[window] = this; 15461 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 15462 15463 // This gives our window a close button 15464 if (windowType != WindowTypes.eventOnly) { 15465 Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)]; 15466 int useAtoms; 15467 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 15468 useAtoms = 2; 15469 } else { 15470 useAtoms = 1; 15471 } 15472 assert(useAtoms <= atoms.length); 15473 XSetWMProtocols(display, window, atoms.ptr, useAtoms); 15474 } 15475 15476 // FIXME: windowType and customizationFlags 15477 Atom[8] wsatoms; // here, due to goto 15478 int wmsacount = 0; // here, due to goto 15479 15480 try 15481 final switch(windowType) { 15482 case WindowTypes.normal: 15483 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 15484 break; 15485 case WindowTypes.undecorated: 15486 motifHideDecorations(); 15487 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 15488 break; 15489 case WindowTypes.eventOnly: 15490 _hidden = true; 15491 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 15492 goto hiddenWindow; 15493 //break; 15494 case WindowTypes.nestedChild: 15495 // handled in XCreateWindow calls 15496 break; 15497 15498 case WindowTypes.dropdownMenu: 15499 motifHideDecorations(); 15500 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 15501 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15502 break; 15503 case WindowTypes.popupMenu: 15504 motifHideDecorations(); 15505 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 15506 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15507 break; 15508 case WindowTypes.notification: 15509 motifHideDecorations(); 15510 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 15511 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 15512 break; 15513 case WindowTypes.minimallyWrapped: 15514 assert(0, "don't create a minimallyWrapped thing explicitly!"); 15515 15516 case WindowTypes.dialog: 15517 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display)); 15518 break; 15519 /+ 15520 case WindowTypes.menu: 15521 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 15522 motifHideDecorations(); 15523 break; 15524 case WindowTypes.desktop: 15525 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 15526 break; 15527 case WindowTypes.dock: 15528 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 15529 break; 15530 case WindowTypes.toolbar: 15531 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 15532 break; 15533 case WindowTypes.menu: 15534 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 15535 break; 15536 case WindowTypes.utility: 15537 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 15538 break; 15539 case WindowTypes.splash: 15540 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 15541 break; 15542 case WindowTypes.tooltip: 15543 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display); 15544 break; 15545 case WindowTypes.notification: 15546 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 15547 break; 15548 case WindowTypes.combo: 15549 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display); 15550 break; 15551 case WindowTypes.dnd: 15552 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display); 15553 break; 15554 +/ 15555 } 15556 catch(Exception e) { 15557 // XInternAtom failed, prolly a WM 15558 // that doesn't support these things 15559 } 15560 15561 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 15562 // the two following flags may be ignored by WM 15563 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 15564 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 15565 15566 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 15567 15568 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 15569 15570 // What would be ideal here is if they only were 15571 // selected if there was actually an event handler 15572 // for them... 15573 15574 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 15575 15576 hiddenWindow: 15577 15578 // set the pid property for lookup later by window managers 15579 // a standard convenience 15580 import core.sys.posix.unistd; 15581 arch_ulong pid = getpid(); 15582 15583 XChangeProperty( 15584 display, 15585 impl.window, 15586 GetAtom!("_NET_WM_PID", true)(display), 15587 XA_CARDINAL, 15588 32 /* bits */, 15589 0 /*PropModeReplace*/, 15590 &pid, 15591 1); 15592 15593 if(isTransient && parent) { // customizationFlags & WindowFlags.transient) { 15594 if(parent is null) assert(0); 15595 // sdpyPrintDebugString("transient"); 15596 XChangeProperty( 15597 display, 15598 impl.window, 15599 GetAtom!("WM_TRANSIENT_FOR", true)(display), 15600 XA_WINDOW, 15601 32 /* bits */, 15602 0 /*PropModeReplace*/, 15603 &parent.impl.window, 15604 1); 15605 15606 } 15607 15608 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 15609 XMapWindow(display, window); 15610 } else { 15611 _hidden = true; 15612 } 15613 } 15614 15615 void populateXic() { 15616 if (XDisplayConnection.xim !is null) { 15617 xic = XCreateIC(XDisplayConnection.xim, 15618 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 15619 /*XNClientWindow*/"clientWindow".ptr, window, 15620 /*XNFocusWindow*/"focusWindow".ptr, window, 15621 null); 15622 if (xic is null) { 15623 import core.stdc.stdio : stderr, fprintf; 15624 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 15625 } 15626 } 15627 } 15628 15629 void selectDefaultInput(bool forceIncludeMouseMotion) { 15630 auto mask = EventMask.ExposureMask | 15631 EventMask.KeyPressMask | 15632 EventMask.KeyReleaseMask | 15633 EventMask.PropertyChangeMask | 15634 EventMask.FocusChangeMask | 15635 EventMask.StructureNotifyMask | 15636 EventMask.SubstructureNotifyMask | 15637 EventMask.VisibilityChangeMask 15638 | EventMask.ButtonPressMask 15639 | EventMask.ButtonReleaseMask 15640 ; 15641 15642 // xshm is our shortcut for local connections 15643 if(XDisplayConnection.isLocal || forceIncludeMouseMotion) 15644 mask |= EventMask.PointerMotionMask; 15645 else 15646 mask |= EventMask.ButtonMotionMask; 15647 15648 XSelectInput(display, window, mask); 15649 } 15650 15651 15652 void setNetWMWindowType(Atom type) { 15653 Atom[2] atoms; 15654 15655 atoms[0] = type; 15656 // generic fallback 15657 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 15658 15659 XChangeProperty( 15660 display, 15661 impl.window, 15662 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 15663 XA_ATOM, 15664 32 /* bits */, 15665 0 /*PropModeReplace*/, 15666 atoms.ptr, 15667 cast(int) atoms.length); 15668 } 15669 15670 void motifHideDecorations(bool hide = true) { 15671 MwmHints hints; 15672 hints.flags = MWM_HINTS_DECORATIONS; 15673 hints.decorations = hide ? 0 : 1; 15674 15675 XChangeProperty( 15676 display, 15677 impl.window, 15678 GetAtom!"_MOTIF_WM_HINTS"(display), 15679 GetAtom!"_MOTIF_WM_HINTS"(display), 15680 32 /* bits */, 15681 0 /*PropModeReplace*/, 15682 &hints, 15683 hints.sizeof / 4); 15684 } 15685 15686 /*k8: unused 15687 void createOpenGlContext() { 15688 15689 } 15690 */ 15691 15692 void closeWindow() { 15693 // I can't close this or a child window closing will 15694 // break events for everyone. So I'm just leaking it right 15695 // now and that is probably perfectly fine... 15696 version(none) 15697 if (customEventFDRead != -1) { 15698 import core.sys.posix.unistd : close; 15699 auto same = customEventFDRead == customEventFDWrite; 15700 15701 close(customEventFDRead); 15702 if(!same) 15703 close(customEventFDWrite); 15704 customEventFDRead = -1; 15705 customEventFDWrite = -1; 15706 } 15707 15708 version(without_opengl) {} else 15709 if(glc !is null) { 15710 glXDestroyContext(display, glc); 15711 glc = null; 15712 } 15713 15714 if(buffer) 15715 XFreePixmap(display, buffer); 15716 bufferw = bufferh = 0; 15717 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 15718 XDestroyWindow(display, window); 15719 XFlush(display); 15720 } 15721 15722 void dispose() { 15723 } 15724 15725 bool destroyed = false; 15726 } 15727 15728 bool insideXEventLoop; 15729 } 15730 15731 version(X11) { 15732 15733 int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this. 15734 15735 private class ResizeEvent { 15736 int width, height; 15737 } 15738 15739 void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) { 15740 if(win.windowType == WindowTypes.minimallyWrapped) 15741 return; 15742 15743 if(win.pendingResizeEvent is null) { 15744 win.pendingResizeEvent = new ResizeEvent(); 15745 win.addEventListener((ResizeEvent re) { 15746 recordX11Resize(XDisplayConnection.get, win, re.width, re.height); 15747 }); 15748 } 15749 win.pendingResizeEvent.width = width; 15750 win.pendingResizeEvent.height = height; 15751 if(!win.eventQueued!ResizeEvent) { 15752 win.postEvent(win.pendingResizeEvent); 15753 } 15754 } 15755 15756 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 15757 if(win.windowType == WindowTypes.minimallyWrapped) 15758 return; 15759 if(win.closed) 15760 return; 15761 15762 if(width != win.width || height != win.height) { 15763 15764 // writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window); 15765 win._width = width; 15766 win._height = height; 15767 15768 if(win.openglMode == OpenGlOptions.no) { 15769 // FIXME: could this be more efficient? 15770 15771 if (win.bufferw < width || win.bufferh < height) { 15772 //{ 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); } 15773 // grow the internal buffer to match the window... 15774 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 15775 { 15776 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15777 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15778 scope(exit) XFreeGC(win.display, xgc); 15779 XSetClipMask(win.display, xgc, None); 15780 XSetForeground(win.display, xgc, 0); 15781 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 15782 } 15783 XCopyArea(display, 15784 cast(Drawable) win.buffer, 15785 cast(Drawable) newPixmap, 15786 win.gc, 0, 0, 15787 win.bufferw < width ? win.bufferw : win.width, 15788 win.bufferh < height ? win.bufferh : win.height, 15789 0, 0); 15790 15791 XFreePixmap(display, win.buffer); 15792 win.buffer = newPixmap; 15793 win.bufferw = width; 15794 win.bufferh = height; 15795 } 15796 15797 // clear unused parts of the buffer 15798 if (win.bufferw > width || win.bufferh > height) { 15799 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15800 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15801 scope(exit) XFreeGC(win.display, xgc); 15802 XSetClipMask(win.display, xgc, None); 15803 XSetForeground(win.display, xgc, 0); 15804 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 15805 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 15806 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 15807 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 15808 } 15809 15810 } 15811 15812 win.updateOpenglViewportIfNeeded(width, height); 15813 15814 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 15815 15816 if(win.resizability != Resizability.automaticallyScaleIfPossible) 15817 if(win.windowResized !is null) { 15818 XUnlockDisplay(display); 15819 scope(exit) XLockDisplay(display); 15820 win.windowResized(width, height); 15821 } 15822 } 15823 } 15824 15825 15826 /// Platform-specific, you might use it when doing a custom event loop. 15827 bool doXNextEvent(Display* display) { 15828 bool done; 15829 XEvent e; 15830 XNextEvent(display, &e); 15831 version(sddddd) { 15832 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15833 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 15834 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 15835 } 15836 } 15837 15838 // filter out compose events 15839 if (XFilterEvent(&e, None)) { 15840 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 15841 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 15842 return false; 15843 } 15844 // process keyboard mapping changes 15845 if (e.type == EventType.KeymapNotify) { 15846 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 15847 XRefreshKeyboardMapping(&e.xmapping); 15848 return false; 15849 } 15850 15851 version(with_eventloop) 15852 import arsd.eventloop; 15853 15854 if(SimpleWindow.handleNativeGlobalEvent !is null) { 15855 // see windows impl's comments 15856 XUnlockDisplay(display); 15857 scope(exit) XLockDisplay(display); 15858 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 15859 if(ret == 0) 15860 return done; 15861 } 15862 15863 15864 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15865 if(win.getNativeEventHandler !is null) { 15866 XUnlockDisplay(display); 15867 scope(exit) XLockDisplay(display); 15868 auto ret = win.getNativeEventHandler()(e); 15869 if(ret == 0) 15870 return done; 15871 } 15872 } 15873 15874 if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) { 15875 if(auto win = e.xany.window in SimpleWindow.nativeMapping) { 15876 // we get this because of the RRScreenChangeNotifyMask 15877 15878 // this isn't actually an ideal way to do it since it wastes time 15879 // but meh it is simple and it works. 15880 win.actualDpiLoadAttempted = false; 15881 SimpleWindow.xRandrInfoLoadAttemped = false; 15882 win.updateActualDpi(); // trigger a reload 15883 } 15884 } 15885 15886 switch(e.type) { 15887 case EventType.SelectionClear: 15888 // writeln("SelectionClear"); 15889 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) { 15890 // FIXME so it is supposed to finish any in progress transfers... but idk... 15891 // writeln("SelectionClear"); 15892 } 15893 SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection); 15894 mightShortCircuitClipboard = false; 15895 break; 15896 case EventType.SelectionRequest: 15897 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 15898 if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) { 15899 // printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target)); 15900 XUnlockDisplay(display); 15901 scope(exit) XLockDisplay(display); 15902 (*ssh).handleRequest(e); 15903 } 15904 break; 15905 case EventType.PropertyNotify: 15906 // import core.stdc.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state); 15907 15908 foreach(ssh; SimpleWindow.impl.setSelectionHandlers) { 15909 if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete) 15910 ssh.sendMoreIncr(&e.xproperty); 15911 } 15912 15913 15914 if(auto win = e.xproperty.window in SimpleWindow.nativeMapping) 15915 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15916 if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) { 15917 Atom target; 15918 int format; 15919 arch_ulong bytesafter, length; 15920 void* value; 15921 15922 ubyte[] s; 15923 Atom targetToKeep; 15924 15925 XGetWindowProperty( 15926 e.xproperty.display, 15927 e.xproperty.window, 15928 e.xproperty.atom, 15929 0, 15930 100000 /* length */, 15931 true, /* erase it to signal we got it and want more */ 15932 0 /*AnyPropertyType*/, 15933 &target, &format, &length, &bytesafter, &value); 15934 15935 if(!targetToKeep) 15936 targetToKeep = target; 15937 15938 auto id = (cast(ubyte*) value)[0 .. length]; 15939 15940 handler.handleIncrData(targetToKeep, id); 15941 if(length == 0) { 15942 win.getSelectionHandlers.remove(e.xproperty.atom); 15943 } 15944 15945 XFree(value); 15946 } 15947 } 15948 break; 15949 case EventType.SelectionNotify: 15950 // import std.stdio; writefln("SelectionNotify %06x %06x", e.xselection.requestor, e.xproperty.atom); 15951 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 15952 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15953 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 15954 XUnlockDisplay(display); 15955 scope(exit) XLockDisplay(display); 15956 handler.handleData(None, null); 15957 win.getSelectionHandlers.remove(e.xproperty.atom); 15958 } else { 15959 Atom target; 15960 int format; 15961 arch_ulong bytesafter, length; 15962 void* value; 15963 XGetWindowProperty( 15964 e.xselection.display, 15965 e.xselection.requestor, 15966 e.xselection.property, 15967 0, 15968 100000 /* length */, 15969 //false, /* don't erase it */ 15970 true, /* do erase it lol */ 15971 0 /*AnyPropertyType*/, 15972 &target, &format, &length, &bytesafter, &value); 15973 15974 // FIXME: I don't have to copy it now since it is in char[] instead of string 15975 15976 { 15977 XUnlockDisplay(display); 15978 scope(exit) XLockDisplay(display); 15979 15980 if(target == XA_ATOM) { 15981 // initial request, see what they are able to work with and request the best one 15982 // we can handle, if available 15983 15984 Atom[] answer = (cast(Atom*) value)[0 .. length]; 15985 Atom best = handler.findBestFormat(answer); 15986 15987 /+ 15988 writeln("got ", answer); 15989 foreach(a; answer) 15990 writeln(XGetAtomName(display, a).stringz); 15991 writeln("best ", best); 15992 +/ 15993 15994 if(best != None) { 15995 // actually request the best format 15996 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 15997 } 15998 } else if(target == GetAtom!"INCR"(display)) { 15999 // incremental 16000 16001 handler.prepareIncremental(e.xselection.requestor, e.xselection.property); 16002 16003 // signal the sending program that we see 16004 // the incr and are ready to receive more. 16005 XDeleteProperty( 16006 e.xselection.display, 16007 e.xselection.requestor, 16008 e.xselection.property); 16009 } else { 16010 // unsupported type... maybe, forward, then we done with it 16011 if(target != None) { 16012 handler.handleData(target, cast(ubyte[]) value[0 .. length]); 16013 win.getSelectionHandlers.remove(e.xproperty.atom); 16014 } 16015 } 16016 } 16017 XFree(value); 16018 /* 16019 XDeleteProperty( 16020 e.xselection.display, 16021 e.xselection.requestor, 16022 e.xselection.property); 16023 */ 16024 } 16025 } 16026 break; 16027 case EventType.ConfigureNotify: 16028 auto event = e.xconfigure; 16029 if(auto win = event.window in SimpleWindow.nativeMapping) { 16030 if(win.windowType == WindowTypes.minimallyWrapped) 16031 break; 16032 //version(sdddd) { writeln(" w=", event.width, "; h=", event.height); } 16033 16034 /+ 16035 The ICCCM says window managers must send a synthetic event when the window 16036 is moved but NOT when it is resized. In the resize case, an event is sent 16037 with position (0, 0) which can be wrong and break the dpi calculations. 16038 16039 So we only consider the synthetic events from the WM and otherwise 16040 need to wait for some other event to get the position which... sucks. 16041 16042 I'd rather not have windows changing their layout on mouse motion after 16043 switching monitors... might be forced to but for now just ignoring it. 16044 16045 Easiest way to switch monitors without sending a size position is by 16046 maximize or fullscreen in a setup like mine, but on most setups those 16047 work on the monitor it is already living on, so it should be ok most the 16048 time. 16049 +/ 16050 if(event.send_event) { 16051 win.screenPositionKnown = true; 16052 win.screenPositionX = event.x; 16053 win.screenPositionY = event.y; 16054 win.updateActualDpi(); 16055 } 16056 16057 win.updateIMEPopupLocation(); 16058 recordX11ResizeAsync(display, *win, event.width, event.height); 16059 } 16060 break; 16061 case EventType.Expose: 16062 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 16063 if(win.windowType == WindowTypes.minimallyWrapped) 16064 break; 16065 // if it is closing from a popup menu, it can get 16066 // an Expose event right by the end and trigger a 16067 // BadDrawable error ... we'll just check 16068 // closed to handle that. 16069 if((*win).closed) break; 16070 if((*win).openglMode == OpenGlOptions.no) { 16071 bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh 16072 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 16073 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); 16074 } else { 16075 // need to redraw the scene somehow 16076 if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all 16077 XUnlockDisplay(display); 16078 scope(exit) XLockDisplay(display); 16079 version(without_opengl) {} else 16080 win.redrawOpenGlSceneSoon(); 16081 } 16082 } 16083 } 16084 break; 16085 case EventType.FocusIn: 16086 case EventType.FocusOut: 16087 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 16088 16089 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 16090 /+ 16091 16092 void info(string detail) { 16093 string s; 16094 // import std.conv; 16095 // import std.datetime; 16096 s ~= to!string(Clock.currTime); 16097 s ~= " "; 16098 s ~= e.type == EventType.FocusIn ? "in " : "out"; 16099 s ~= " "; 16100 s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main "; 16101 s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed "; 16102 s ~= detail; 16103 s ~= " "; 16104 16105 sdpyPrintDebugString(s); 16106 16107 } 16108 16109 switch(e.xfocus.detail) { 16110 case NotifyDetail.NotifyAncestor: info("Ancestor"); break; 16111 case NotifyDetail.NotifyVirtual: info("Virtual"); break; 16112 case NotifyDetail.NotifyInferior: info("Inferior"); break; 16113 case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break; 16114 case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break; 16115 case NotifyDetail.NotifyPointer: info("pointer"); break; 16116 case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break; 16117 case NotifyDetail.NotifyDetailNone: info("none"); break; 16118 default: 16119 16120 } 16121 +/ 16122 16123 16124 if(e.xfocus.detail == NotifyDetail.NotifyPointer) 16125 break; // just ignore these they seem irrelevant 16126 16127 auto old = win._focused; 16128 win._focused = e.type == EventType.FocusIn; 16129 16130 // yes, we are losing the focus, but to our own child. that's actually kinda keeping it. 16131 if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior) 16132 win._focused = true; 16133 16134 if(win.demandingAttention) 16135 demandAttention(*win, false); 16136 16137 win.updateIMEFocused(); 16138 16139 if(old != win._focused && win.onFocusChange) { 16140 XUnlockDisplay(display); 16141 scope(exit) XLockDisplay(display); 16142 win.onFocusChange(win._focused); 16143 } 16144 } 16145 break; 16146 case EventType.VisibilityNotify: 16147 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 16148 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 16149 if (win.visibilityChanged !is null) { 16150 XUnlockDisplay(display); 16151 scope(exit) XLockDisplay(display); 16152 win.visibilityChanged(false); 16153 } 16154 } else { 16155 if (win.visibilityChanged !is null) { 16156 XUnlockDisplay(display); 16157 scope(exit) XLockDisplay(display); 16158 win.visibilityChanged(true); 16159 } 16160 } 16161 } 16162 break; 16163 case EventType.ClientMessage: 16164 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 16165 // "ignore next mouse motion" event, increment ignore counter for teh window 16166 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16167 ++(*win).warpEventCount; 16168 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 16169 } else { 16170 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 16171 } 16172 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 16173 // user clicked the close button on the window manager 16174 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16175 XUnlockDisplay(display); 16176 scope(exit) XLockDisplay(display); 16177 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 16178 } 16179 16180 } else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) { 16181 // writeln("HAPPENED"); 16182 // user clicked the close button on the window manager 16183 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16184 XUnlockDisplay(display); 16185 scope(exit) XLockDisplay(display); 16186 16187 auto setTo = *win; 16188 16189 if(win.setRequestedInputFocus !is null) { 16190 auto s = win.setRequestedInputFocus(); 16191 if(s !is null) { 16192 setTo = s; 16193 } 16194 } 16195 16196 assert(setTo !is null); 16197 16198 // FIXME: so this is actually supposed to focus to a relevant child window if appropriate 16199 16200 XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]); 16201 } 16202 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 16203 foreach(nai; NotificationAreaIcon.activeIcons) 16204 nai.newManager(); 16205 } else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 16206 16207 bool xDragWindow = true; 16208 if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) { 16209 //XDefineCursor(display, xDragWindow.impl.window, 16210 //writeln("XdndStatus ", e.xclient.data.l); 16211 } 16212 if(auto dh = win.dropHandler) { 16213 16214 static Atom[3] xFormatsBuffer; 16215 static Atom[] xFormats; 16216 16217 void resetXFormats() { 16218 xFormatsBuffer[] = 0; 16219 xFormats = xFormatsBuffer[]; 16220 } 16221 16222 if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) { 16223 // on Windows it is supposed to return the effect you actually do FIXME 16224 16225 auto sourceWindow = e.xclient.data.l[0]; 16226 16227 xFormatsBuffer[0] = e.xclient.data.l[2]; 16228 xFormatsBuffer[1] = e.xclient.data.l[3]; 16229 xFormatsBuffer[2] = e.xclient.data.l[4]; 16230 16231 if(e.xclient.data.l[1] & 1) { 16232 // can just grab it all but like we don't necessarily need them... 16233 xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM); 16234 } else { 16235 int len; 16236 foreach(fmt; xFormatsBuffer) 16237 if(fmt) len++; 16238 xFormats = xFormatsBuffer[0 .. len]; 16239 } 16240 16241 auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats); 16242 16243 dh.dragEnter(&pkg); 16244 } else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) { 16245 16246 auto pack = e.xclient.data.l[2]; 16247 16248 auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords 16249 16250 16251 XClientMessageEvent xclient; 16252 16253 xclient.type = EventType.ClientMessage; 16254 xclient.window = e.xclient.data.l[0]; 16255 xclient.message_type = GetAtom!"XdndStatus"(display); 16256 xclient.format = 32; 16257 xclient.data.l[0] = win.impl.window; 16258 xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept 16259 auto r = result.consistentWithin; 16260 xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top); 16261 xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height); 16262 xclient.data.l[4] = dndActionAtom(e.xany.display, result.action); 16263 16264 XSendEvent( 16265 display, 16266 e.xclient.data.l[0], 16267 false, 16268 EventMask.NoEventMask, 16269 cast(XEvent*) &xclient 16270 ); 16271 16272 16273 } else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) { 16274 //writeln("XdndLeave"); 16275 // drop cancelled. 16276 // data.l[0] is the source window 16277 dh.dragLeave(); 16278 16279 resetXFormats(); 16280 } else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) { 16281 // drop happening, should fetch data, then send finished 16282 // writeln("XdndDrop"); 16283 16284 auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats); 16285 16286 dh.drop(&pkg); 16287 16288 resetXFormats(); 16289 } else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) { 16290 // writeln("XdndFinished"); 16291 16292 dh.finish(); 16293 } 16294 16295 } 16296 } 16297 break; 16298 case EventType.MapNotify: 16299 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 16300 (*win)._visible = true; 16301 if (!(*win)._visibleForTheFirstTimeCalled) { 16302 (*win)._visibleForTheFirstTimeCalled = true; 16303 if ((*win).visibleForTheFirstTime !is null) { 16304 XUnlockDisplay(display); 16305 scope(exit) XLockDisplay(display); 16306 (*win).visibleForTheFirstTime(); 16307 } 16308 } 16309 if ((*win).visibilityChanged !is null) { 16310 XUnlockDisplay(display); 16311 scope(exit) XLockDisplay(display); 16312 (*win).visibilityChanged(true); 16313 } 16314 } 16315 break; 16316 case EventType.UnmapNotify: 16317 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 16318 win._visible = false; 16319 if (win.visibilityChanged !is null) { 16320 XUnlockDisplay(display); 16321 scope(exit) XLockDisplay(display); 16322 win.visibilityChanged(false); 16323 } 16324 } 16325 break; 16326 case EventType.DestroyNotify: 16327 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 16328 if(win.destroyed) 16329 break; // might get a notification both for itself and from its parent 16330 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 16331 win._closed = true; // just in case 16332 win.destroyed = true; 16333 if (win.xic !is null) { 16334 XDestroyIC(win.xic); 16335 win.xic = null; // just in case 16336 } 16337 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 16338 bool anyImportant = false; 16339 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 16340 if(w.beingOpenKeepsAppOpen) { 16341 anyImportant = true; 16342 break; 16343 } 16344 if(!anyImportant) { 16345 EventLoop.quitApplication(); 16346 done = true; 16347 } 16348 } 16349 auto window = e.xdestroywindow.window; 16350 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 16351 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 16352 16353 version(with_eventloop) { 16354 if(done) exit(); 16355 } 16356 break; 16357 16358 case EventType.MotionNotify: 16359 MouseEvent mouse; 16360 auto event = e.xmotion; 16361 16362 mouse.type = MouseEventType.motion; 16363 mouse.x = event.x; 16364 mouse.y = event.y; 16365 mouse.modifierState = event.state; 16366 16367 mouse.timestamp = event.time; 16368 16369 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 16370 mouse.window = *win; 16371 if (win.warpEventCount > 0) { 16372 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 16373 --(*win).warpEventCount; 16374 (*win).mdx(mouse); // so deltas will be correctly updated 16375 } else { 16376 win.warpEventCount = 0; // just in case 16377 (*win).mdx(mouse); 16378 if((*win).handleMouseEvent) { 16379 XUnlockDisplay(display); 16380 scope(exit) XLockDisplay(display); 16381 (*win).handleMouseEvent(mouse); 16382 } 16383 } 16384 } 16385 16386 version(with_eventloop) 16387 send(mouse); 16388 break; 16389 case EventType.ButtonPress: 16390 case EventType.ButtonRelease: 16391 MouseEvent mouse; 16392 auto event = e.xbutton; 16393 16394 mouse.timestamp = event.time; 16395 16396 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 16397 mouse.x = event.x; 16398 mouse.y = event.y; 16399 16400 static Time lastMouseDownTime = 0; 16401 static int lastMouseDownButton = -1; 16402 16403 mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 16404 if(e.type == EventType.ButtonPress) { 16405 lastMouseDownTime = event.time; 16406 lastMouseDownButton = event.button; 16407 } 16408 16409 switch(event.button) { 16410 case 1: mouse.button = MouseButton.left; break; // left 16411 case 2: mouse.button = MouseButton.middle; break; // middle 16412 case 3: mouse.button = MouseButton.right; break; // right 16413 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 16414 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 16415 case 6: break; // idk 16416 case 7: break; // idk 16417 case 8: mouse.button = MouseButton.backButton; break; 16418 case 9: mouse.button = MouseButton.forwardButton; break; 16419 default: 16420 } 16421 16422 // FIXME: double check this 16423 mouse.modifierState = event.state; 16424 16425 //mouse.modifierState = event.detail; 16426 16427 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 16428 mouse.window = *win; 16429 (*win).mdx(mouse); 16430 if((*win).handleMouseEvent) { 16431 XUnlockDisplay(display); 16432 scope(exit) XLockDisplay(display); 16433 (*win).handleMouseEvent(mouse); 16434 } 16435 } 16436 version(with_eventloop) 16437 send(mouse); 16438 break; 16439 16440 case EventType.KeyPress: 16441 case EventType.KeyRelease: 16442 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 16443 KeyEvent ke; 16444 ke.pressed = e.type == EventType.KeyPress; 16445 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 16446 16447 auto sym = XKeycodeToKeysym( 16448 XDisplayConnection.get(), 16449 e.xkey.keycode, 16450 0); 16451 16452 ke.key = cast(Key) sym;//e.xkey.keycode; 16453 16454 ke.modifierState = e.xkey.state; 16455 16456 // writefln("%x", sym); 16457 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 16458 int charbuflen = 0; // return value of XwcLookupString 16459 if (ke.pressed) { 16460 auto win = e.xkey.window in SimpleWindow.nativeMapping; 16461 if (win !is null && win.xic !is null) { 16462 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 16463 Status status; 16464 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 16465 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 16466 } else { 16467 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 16468 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 16469 char[16] buffer; 16470 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 16471 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 16472 } 16473 } 16474 16475 // if there's no char, subst one 16476 if (charbuflen == 0) { 16477 switch (sym) { 16478 case 0xff09: charbuf[charbuflen++] = '\t'; break; 16479 case 0xff8d: // keypad enter 16480 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 16481 default : // ignore 16482 } 16483 } 16484 16485 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 16486 ke.window = *win; 16487 16488 16489 if(win.inputProxy) 16490 win = &win.inputProxy; 16491 16492 // char events are separate since they are on Windows too 16493 // also, xcompose can generate long char sequences 16494 // don't send char events if Meta and/or Hyper is pressed 16495 // TODO: ctrl+char should only send control chars; not yet 16496 if ((e.xkey.state&ModifierState.ctrl) != 0) { 16497 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 16498 } 16499 16500 dchar[32] charsComingBuffer; 16501 int charsComingPosition; 16502 dchar[] charsComing = charsComingBuffer[]; 16503 16504 if (ke.pressed && charbuflen > 0) { 16505 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 16506 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 16507 if(charsComingPosition >= charsComing.length) 16508 charsComing.length = charsComingPosition + 8; 16509 16510 charsComing[charsComingPosition++] = ch; 16511 } 16512 16513 charsComing = charsComing[0 .. charsComingPosition]; 16514 } else { 16515 charsComing = null; 16516 } 16517 16518 ke.charsPossible = charsComing; 16519 16520 if (win.handleKeyEvent) { 16521 XUnlockDisplay(display); 16522 scope(exit) XLockDisplay(display); 16523 win.handleKeyEvent(ke); 16524 } 16525 16526 // Super and alt modifier keys never actually send the chars, they are assumed to be special. 16527 if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) { 16528 XUnlockDisplay(display); 16529 scope(exit) XLockDisplay(display); 16530 foreach(ch; charsComing) 16531 win.handleCharEvent(ch); 16532 } 16533 } 16534 16535 version(with_eventloop) 16536 send(ke); 16537 break; 16538 default: 16539 } 16540 16541 return done; 16542 } 16543 } 16544 16545 /* *************************************** */ 16546 /* Done with simpledisplay stuff */ 16547 /* *************************************** */ 16548 16549 // Necessary C library bindings follow 16550 version(Windows) {} else 16551 version(X11) { 16552 16553 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 16554 16555 // X11 bindings needed here 16556 /* 16557 A little of this is from the bindings project on 16558 D Source and some of it is copy/paste from the C 16559 header. 16560 16561 The DSource listing consistently used D's long 16562 where C used long. That's wrong - C long is 32 bit, so 16563 it should be int in D. I changed that here. 16564 16565 Note: 16566 This isn't complete, just took what I needed for myself. 16567 */ 16568 16569 import core.stdc.stddef : wchar_t; 16570 16571 interface XLib { 16572 extern(C) nothrow @nogc { 16573 char* XResourceManagerString(Display*); 16574 void XrmInitialize(); 16575 XrmDatabase XrmGetStringDatabase(char* data); 16576 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 16577 16578 Cursor XCreateFontCursor(Display*, uint shape); 16579 int XDefineCursor(Display* display, Window w, Cursor cursor); 16580 int XUndefineCursor(Display* display, Window w); 16581 16582 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 16583 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 16584 int XFreeCursor(Display* display, Cursor cursor); 16585 16586 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 16587 16588 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 16589 16590 XVaNestedList XVaCreateNestedList(int unused, ...); 16591 16592 char *XKeysymToString(KeySym keysym); 16593 KeySym XKeycodeToKeysym( 16594 Display* /* display */, 16595 KeyCode /* keycode */, 16596 int /* index */ 16597 ); 16598 16599 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 16600 16601 int XFree(void*); 16602 int XDeleteProperty(Display *display, Window w, Atom property); 16603 16604 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements); 16605 16606 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 16607 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 16608 *actual_type_return, int *actual_format_return, arch_ulong 16609 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 16610 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 16611 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 16612 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 16613 16614 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 16615 16616 Window XGetSelectionOwner(Display *display, Atom selection); 16617 16618 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 16619 16620 char** XListFonts(Display*, const char*, int, int*); 16621 void XFreeFontNames(char**); 16622 16623 Display* XOpenDisplay(const char*); 16624 int XCloseDisplay(Display*); 16625 16626 int function() XSynchronize(Display*, bool); 16627 int function() XSetAfterFunction(Display*, int function() proc); 16628 16629 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 16630 16631 Bool XSupportsLocale(); 16632 char* XSetLocaleModifiers(const(char)* modifier_list); 16633 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16634 Status XCloseOM(XOM om); 16635 16636 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16637 Status XCloseIM(XIM im); 16638 16639 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16640 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16641 Display* XDisplayOfIM(XIM im); 16642 char* XLocaleOfIM(XIM im); 16643 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 16644 void XDestroyIC(XIC ic); 16645 void XSetICFocus(XIC ic); 16646 void XUnsetICFocus(XIC ic); 16647 //wchar_t* XwcResetIC(XIC ic); 16648 char* XmbResetIC(XIC ic); 16649 char* Xutf8ResetIC(XIC ic); 16650 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16651 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16652 XIM XIMOfIC(XIC ic); 16653 16654 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 16655 16656 16657 XFontStruct *XLoadQueryFont(Display *display, scope const char *name); 16658 int XFreeFont(Display *display, XFontStruct *font_struct); 16659 int XSetFont(Display* display, GC gc, Font font); 16660 int XTextWidth(XFontStruct*, scope const char*, int); 16661 16662 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 16663 int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n); 16664 16665 Window XCreateSimpleWindow( 16666 Display* /* display */, 16667 Window /* parent */, 16668 int /* x */, 16669 int /* y */, 16670 uint /* width */, 16671 uint /* height */, 16672 uint /* border_width */, 16673 uint /* border */, 16674 uint /* background */ 16675 ); 16676 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); 16677 16678 int XReparentWindow(Display*, Window, Window, int, int); 16679 int XClearWindow(Display*, Window); 16680 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 16681 int XMoveWindow(Display*, Window, int, int); 16682 int XResizeWindow(Display *display, Window w, uint width, uint height); 16683 16684 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 16685 16686 Status XMatchVisualInfo(Display *display, int screen, int depth, int class_, XVisualInfo *vinfo_return); 16687 16688 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 16689 16690 XImage *XCreateImage( 16691 Display* /* display */, 16692 Visual* /* visual */, 16693 uint /* depth */, 16694 int /* format */, 16695 int /* offset */, 16696 ubyte* /* data */, 16697 uint /* width */, 16698 uint /* height */, 16699 int /* bitmap_pad */, 16700 int /* bytes_per_line */ 16701 ); 16702 16703 Status XInitImage (XImage* image); 16704 16705 Atom XInternAtom( 16706 Display* /* display */, 16707 const char* /* atom_name */, 16708 Bool /* only_if_exists */ 16709 ); 16710 16711 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 16712 char* XGetAtomName(Display*, Atom); 16713 Status XGetAtomNames(Display*, Atom*, int count, char**); 16714 16715 int XPutImage( 16716 Display* /* display */, 16717 Drawable /* d */, 16718 GC /* gc */, 16719 XImage* /* image */, 16720 int /* src_x */, 16721 int /* src_y */, 16722 int /* dest_x */, 16723 int /* dest_y */, 16724 uint /* width */, 16725 uint /* height */ 16726 ); 16727 16728 XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format); 16729 16730 16731 int XDestroyWindow( 16732 Display* /* display */, 16733 Window /* w */ 16734 ); 16735 16736 int XDestroyImage(XImage*); 16737 16738 int XSelectInput( 16739 Display* /* display */, 16740 Window /* w */, 16741 EventMask /* event_mask */ 16742 ); 16743 16744 int XMapWindow( 16745 Display* /* display */, 16746 Window /* w */ 16747 ); 16748 16749 Status XIconifyWindow(Display*, Window, int); 16750 int XMapRaised(Display*, Window); 16751 int XMapSubwindows(Display*, Window); 16752 16753 int XNextEvent( 16754 Display* /* display */, 16755 XEvent* /* event_return */ 16756 ); 16757 16758 int XMaskEvent(Display*, arch_long, XEvent*); 16759 16760 Bool XFilterEvent(XEvent *event, Window window); 16761 int XRefreshKeyboardMapping(XMappingEvent *event_map); 16762 16763 Status XSetWMProtocols( 16764 Display* /* display */, 16765 Window /* w */, 16766 Atom* /* protocols */, 16767 int /* count */ 16768 ); 16769 16770 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 16771 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 16772 16773 16774 Status XInitThreads(); 16775 void XLockDisplay (Display* display); 16776 void XUnlockDisplay (Display* display); 16777 16778 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 16779 16780 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 16781 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 16782 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 16783 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 16784 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 16785 16786 16787 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 16788 int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int); 16789 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 16790 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 16791 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16792 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 16793 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16794 int XDrawPoint(Display*, Drawable, GC, int, int); 16795 int XSetForeground(Display*, GC, uint); 16796 int XSetBackground(Display*, GC, uint); 16797 16798 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 16799 void XFreeFontSet(Display*, XFontSet); 16800 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int); 16801 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 16802 16803 int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 16804 16805 16806 //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); 16807 16808 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 16809 int XSetFunction(Display*, GC, int); 16810 16811 GC XCreateGC(Display*, Drawable, uint, void*); 16812 int XCopyGC(Display*, GC, uint, GC); 16813 int XFreeGC(Display*, GC); 16814 16815 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 16816 bool XCheckMaskEvent(Display*, int, XEvent*); 16817 16818 int XPending(Display*); 16819 int XEventsQueued(Display* display, int mode); 16820 16821 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 16822 int XFreePixmap(Display*, Pixmap); 16823 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 16824 int XFlush(Display*); 16825 int XBell(Display*, int); 16826 int XSync(Display*, bool); 16827 16828 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 16829 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 16830 16831 int XGrabKeyboard(Display*, Window, Bool, int, int, Time); 16832 int XUngrabKeyboard(Display*, Time); 16833 16834 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 16835 16836 KeySym XStringToKeysym(const char *string); 16837 16838 Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return); 16839 16840 Window XDefaultRootWindow(Display*); 16841 16842 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); 16843 16844 int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 16845 16846 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 16847 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 16848 16849 Status XAllocColor(Display*, Colormap, XColor*); 16850 16851 int XWithdrawWindow(Display*, Window, int); 16852 int XUnmapWindow(Display*, Window); 16853 int XLowerWindow(Display*, Window); 16854 int XRaiseWindow(Display*, Window); 16855 16856 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); 16857 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); 16858 16859 int XGetInputFocus(Display*, Window*, int*); 16860 int XSetInputFocus(Display*, Window, int, Time); 16861 16862 XErrorHandler XSetErrorHandler(XErrorHandler); 16863 16864 int XGetErrorText(Display*, int, char*, int); 16865 16866 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 16867 16868 16869 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); 16870 int XUngrabPointer(Display *display, Time time); 16871 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 16872 16873 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 16874 16875 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 16876 int XSetClipMask(Display*, GC, Pixmap); 16877 int XSetClipOrigin(Display*, GC, int, int); 16878 16879 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 16880 16881 void XSetWMName(Display*, Window, XTextProperty*); 16882 Status XGetWMName(Display*, Window, XTextProperty*); 16883 int XStoreName(Display* display, Window w, const(char)* window_name); 16884 16885 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 16886 16887 } 16888 } 16889 16890 interface Xext { 16891 extern(C) nothrow @nogc { 16892 Status XShmAttach(Display*, XShmSegmentInfo*); 16893 Status XShmDetach(Display*, XShmSegmentInfo*); 16894 Status XShmPutImage( 16895 Display* /* dpy */, 16896 Drawable /* d */, 16897 GC /* gc */, 16898 XImage* /* image */, 16899 int /* src_x */, 16900 int /* src_y */, 16901 int /* dst_x */, 16902 int /* dst_y */, 16903 uint /* src_width */, 16904 uint /* src_height */, 16905 Bool /* send_event */ 16906 ); 16907 16908 Status XShmQueryExtension(Display*); 16909 16910 XImage *XShmCreateImage( 16911 Display* /* dpy */, 16912 Visual* /* visual */, 16913 uint /* depth */, 16914 int /* format */, 16915 char* /* data */, 16916 XShmSegmentInfo* /* shminfo */, 16917 uint /* width */, 16918 uint /* height */ 16919 ); 16920 16921 Pixmap XShmCreatePixmap( 16922 Display* /* dpy */, 16923 Drawable /* d */, 16924 char* /* data */, 16925 XShmSegmentInfo* /* shminfo */, 16926 uint /* width */, 16927 uint /* height */, 16928 uint /* depth */ 16929 ); 16930 16931 } 16932 } 16933 16934 // this requires -lXpm 16935 //int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 16936 16937 16938 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib; 16939 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext; 16940 shared static this() { 16941 xlib.loadDynamicLibrary(); 16942 xext.loadDynamicLibrary(); 16943 } 16944 16945 16946 extern(C) nothrow @nogc { 16947 16948 alias XrmDatabase = void*; 16949 struct XrmValue { 16950 uint size; 16951 void* addr; 16952 } 16953 16954 struct XVisualInfo { 16955 Visual* visual; 16956 VisualID visualid; 16957 int screen; 16958 uint depth; 16959 int c_class; 16960 c_ulong red_mask; 16961 c_ulong green_mask; 16962 c_ulong blue_mask; 16963 int colormap_size; 16964 int bits_per_rgb; 16965 } 16966 16967 enum VisualNoMask= 0x0; 16968 enum VisualIDMask= 0x1; 16969 enum VisualScreenMask=0x2; 16970 enum VisualDepthMask= 0x4; 16971 enum VisualClassMask= 0x8; 16972 enum VisualRedMaskMask=0x10; 16973 enum VisualGreenMaskMask=0x20; 16974 enum VisualBlueMaskMask=0x40; 16975 enum VisualColormapSizeMask=0x80; 16976 enum VisualBitsPerRGBMask=0x100; 16977 enum VisualAllMask= 0x1FF; 16978 16979 enum AnyKey = 0; 16980 enum AnyModifier = 1 << 15; 16981 16982 // XIM and other crap 16983 struct _XOM {} 16984 struct _XIM {} 16985 struct _XIC {} 16986 alias XOM = _XOM*; 16987 alias XIM = _XIM*; 16988 alias XIC = _XIC*; 16989 16990 alias XVaNestedList = void*; 16991 16992 alias XIMStyle = arch_ulong; 16993 enum : arch_ulong { 16994 XIMPreeditArea = 0x0001, 16995 XIMPreeditCallbacks = 0x0002, 16996 XIMPreeditPosition = 0x0004, 16997 XIMPreeditNothing = 0x0008, 16998 XIMPreeditNone = 0x0010, 16999 XIMStatusArea = 0x0100, 17000 XIMStatusCallbacks = 0x0200, 17001 XIMStatusNothing = 0x0400, 17002 XIMStatusNone = 0x0800, 17003 } 17004 17005 17006 /* X Shared Memory Extension functions */ 17007 //pragma(lib, "Xshm"); 17008 alias arch_ulong ShmSeg; 17009 struct XShmSegmentInfo { 17010 ShmSeg shmseg; 17011 int shmid; 17012 ubyte* shmaddr; 17013 Bool readOnly; 17014 } 17015 17016 // and the necessary OS functions 17017 int shmget(int, size_t, int); 17018 void* shmat(int, scope const void*, int); 17019 int shmdt(scope const void*); 17020 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 17021 17022 enum IPC_PRIVATE = 0; 17023 enum IPC_CREAT = 512; 17024 enum IPC_RMID = 0; 17025 17026 /* MIT-SHM end */ 17027 17028 17029 enum MappingType:int { 17030 MappingModifier =0, 17031 MappingKeyboard =1, 17032 MappingPointer =2 17033 } 17034 17035 /* ImageFormat -- PutImage, GetImage */ 17036 enum ImageFormat:int { 17037 XYBitmap =0, /* depth 1, XYFormat */ 17038 XYPixmap =1, /* depth == drawable depth */ 17039 ZPixmap =2 /* depth == drawable depth */ 17040 } 17041 17042 enum ModifierName:int { 17043 ShiftMapIndex =0, 17044 LockMapIndex =1, 17045 ControlMapIndex =2, 17046 Mod1MapIndex =3, 17047 Mod2MapIndex =4, 17048 Mod3MapIndex =5, 17049 Mod4MapIndex =6, 17050 Mod5MapIndex =7 17051 } 17052 17053 enum ButtonMask:int { 17054 Button1Mask =1<<8, 17055 Button2Mask =1<<9, 17056 Button3Mask =1<<10, 17057 Button4Mask =1<<11, 17058 Button5Mask =1<<12, 17059 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 17060 } 17061 17062 enum KeyOrButtonMask:uint { 17063 ShiftMask =1<<0, 17064 LockMask =1<<1, 17065 ControlMask =1<<2, 17066 Mod1Mask =1<<3, 17067 Mod2Mask =1<<4, 17068 Mod3Mask =1<<5, 17069 Mod4Mask =1<<6, 17070 Mod5Mask =1<<7, 17071 Button1Mask =1<<8, 17072 Button2Mask =1<<9, 17073 Button3Mask =1<<10, 17074 Button4Mask =1<<11, 17075 Button5Mask =1<<12, 17076 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 17077 } 17078 17079 enum ButtonName:int { 17080 Button1 =1, 17081 Button2 =2, 17082 Button3 =3, 17083 Button4 =4, 17084 Button5 =5 17085 } 17086 17087 /* Notify modes */ 17088 enum NotifyModes:int 17089 { 17090 NotifyNormal =0, 17091 NotifyGrab =1, 17092 NotifyUngrab =2, 17093 NotifyWhileGrabbed =3 17094 } 17095 enum NotifyHint = 1; /* for MotionNotify events */ 17096 17097 /* Notify detail */ 17098 enum NotifyDetail:int 17099 { 17100 NotifyAncestor =0, 17101 NotifyVirtual =1, 17102 NotifyInferior =2, 17103 NotifyNonlinear =3, 17104 NotifyNonlinearVirtual =4, 17105 NotifyPointer =5, 17106 NotifyPointerRoot =6, 17107 NotifyDetailNone =7 17108 } 17109 17110 /* Visibility notify */ 17111 17112 enum VisibilityNotify:int 17113 { 17114 VisibilityUnobscured =0, 17115 VisibilityPartiallyObscured =1, 17116 VisibilityFullyObscured =2 17117 } 17118 17119 17120 enum WindowStackingMethod:int 17121 { 17122 Above =0, 17123 Below =1, 17124 TopIf =2, 17125 BottomIf =3, 17126 Opposite =4 17127 } 17128 17129 /* Circulation request */ 17130 enum CirculationRequest:int 17131 { 17132 PlaceOnTop =0, 17133 PlaceOnBottom =1 17134 } 17135 17136 enum PropertyNotification:int 17137 { 17138 PropertyNewValue =0, 17139 PropertyDelete =1 17140 } 17141 17142 enum ColorMapNotification:int 17143 { 17144 ColormapUninstalled =0, 17145 ColormapInstalled =1 17146 } 17147 17148 17149 struct _XPrivate {} 17150 struct _XrmHashBucketRec {} 17151 17152 alias void* XPointer; 17153 alias void* XExtData; 17154 17155 version( X86_64 ) { 17156 alias ulong XID; 17157 alias ulong arch_ulong; 17158 alias long arch_long; 17159 } else version (AArch64) { 17160 alias ulong XID; 17161 alias ulong arch_ulong; 17162 alias long arch_long; 17163 } else { 17164 alias uint XID; 17165 alias uint arch_ulong; 17166 alias int arch_long; 17167 } 17168 17169 alias XID Window; 17170 alias XID Drawable; 17171 alias XID Pixmap; 17172 17173 alias arch_ulong Atom; 17174 alias int Bool; 17175 alias Display XDisplay; 17176 17177 alias int ByteOrder; 17178 alias arch_ulong Time; 17179 alias void ScreenFormat; 17180 17181 struct XImage { 17182 int width, height; /* size of image */ 17183 int xoffset; /* number of pixels offset in X direction */ 17184 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 17185 void *data; /* pointer to image data */ 17186 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 17187 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 17188 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 17189 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 17190 int depth; /* depth of image */ 17191 int bytes_per_line; /* accelarator to next line */ 17192 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 17193 arch_ulong red_mask; /* bits in z arrangment */ 17194 arch_ulong green_mask; 17195 arch_ulong blue_mask; 17196 XPointer obdata; /* hook for the object routines to hang on */ 17197 static struct F { /* image manipulation routines */ 17198 XImage* function( 17199 XDisplay* /* display */, 17200 Visual* /* visual */, 17201 uint /* depth */, 17202 int /* format */, 17203 int /* offset */, 17204 ubyte* /* data */, 17205 uint /* width */, 17206 uint /* height */, 17207 int /* bitmap_pad */, 17208 int /* bytes_per_line */) create_image; 17209 int function(XImage *) destroy_image; 17210 arch_ulong function(XImage *, int, int) get_pixel; 17211 int function(XImage *, int, int, arch_ulong) put_pixel; 17212 XImage* function(XImage *, int, int, uint, uint) sub_image; 17213 int function(XImage *, arch_long) add_pixel; 17214 } 17215 F f; 17216 } 17217 version(X86_64) static assert(XImage.sizeof == 136); 17218 else version(X86) static assert(XImage.sizeof == 88); 17219 17220 struct XCharStruct { 17221 short lbearing; /* origin to left edge of raster */ 17222 short rbearing; /* origin to right edge of raster */ 17223 short width; /* advance to next char's origin */ 17224 short ascent; /* baseline to top edge of raster */ 17225 short descent; /* baseline to bottom edge of raster */ 17226 ushort attributes; /* per char flags (not predefined) */ 17227 } 17228 17229 /* 17230 * To allow arbitrary information with fonts, there are additional properties 17231 * returned. 17232 */ 17233 struct XFontProp { 17234 Atom name; 17235 arch_ulong card32; 17236 } 17237 17238 alias Atom Font; 17239 17240 struct XFontStruct { 17241 XExtData *ext_data; /* Hook for extension to hang data */ 17242 Font fid; /* Font ID for this font */ 17243 uint direction; /* Direction the font is painted */ 17244 uint min_char_or_byte2; /* First character */ 17245 uint max_char_or_byte2; /* Last character */ 17246 uint min_byte1; /* First row that exists (for two-byte fonts) */ 17247 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 17248 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 17249 uint default_char; /* Char to print for undefined character */ 17250 int n_properties; /* How many properties there are */ 17251 XFontProp *properties; /* Pointer to array of additional properties*/ 17252 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 17253 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 17254 XCharStruct *per_char; /* first_char to last_char information */ 17255 int ascent; /* Max extent above baseline for spacing */ 17256 int descent; /* Max descent below baseline for spacing */ 17257 } 17258 17259 17260 /* 17261 * Definitions of specific events. 17262 */ 17263 struct XKeyEvent 17264 { 17265 int type; /* of event */ 17266 arch_ulong serial; /* # of last request processed by server */ 17267 Bool send_event; /* true if this came from a SendEvent request */ 17268 Display *display; /* Display the event was read from */ 17269 Window window; /* "event" window it is reported relative to */ 17270 Window root; /* root window that the event occurred on */ 17271 Window subwindow; /* child window */ 17272 Time time; /* milliseconds */ 17273 int x, y; /* pointer x, y coordinates in event window */ 17274 int x_root, y_root; /* coordinates relative to root */ 17275 KeyOrButtonMask state; /* key or button mask */ 17276 uint keycode; /* detail */ 17277 Bool same_screen; /* same screen flag */ 17278 } 17279 version(X86_64) static assert(XKeyEvent.sizeof == 96); 17280 alias XKeyEvent XKeyPressedEvent; 17281 alias XKeyEvent XKeyReleasedEvent; 17282 17283 struct XButtonEvent 17284 { 17285 int type; /* of event */ 17286 arch_ulong serial; /* # of last request processed by server */ 17287 Bool send_event; /* true if this came from a SendEvent request */ 17288 Display *display; /* Display the event was read from */ 17289 Window window; /* "event" window it is reported relative to */ 17290 Window root; /* root window that the event occurred on */ 17291 Window subwindow; /* child window */ 17292 Time time; /* milliseconds */ 17293 int x, y; /* pointer x, y coordinates in event window */ 17294 int x_root, y_root; /* coordinates relative to root */ 17295 KeyOrButtonMask state; /* key or button mask */ 17296 uint button; /* detail */ 17297 Bool same_screen; /* same screen flag */ 17298 } 17299 alias XButtonEvent XButtonPressedEvent; 17300 alias XButtonEvent XButtonReleasedEvent; 17301 17302 struct XMotionEvent{ 17303 int type; /* of event */ 17304 arch_ulong serial; /* # of last request processed by server */ 17305 Bool send_event; /* true if this came from a SendEvent request */ 17306 Display *display; /* Display the event was read from */ 17307 Window window; /* "event" window reported relative to */ 17308 Window root; /* root window that the event occurred on */ 17309 Window subwindow; /* child window */ 17310 Time time; /* milliseconds */ 17311 int x, y; /* pointer x, y coordinates in event window */ 17312 int x_root, y_root; /* coordinates relative to root */ 17313 KeyOrButtonMask state; /* key or button mask */ 17314 byte is_hint; /* detail */ 17315 Bool same_screen; /* same screen flag */ 17316 } 17317 alias XMotionEvent XPointerMovedEvent; 17318 17319 struct XCrossingEvent{ 17320 int type; /* of event */ 17321 arch_ulong serial; /* # of last request processed by server */ 17322 Bool send_event; /* true if this came from a SendEvent request */ 17323 Display *display; /* Display the event was read from */ 17324 Window window; /* "event" window reported relative to */ 17325 Window root; /* root window that the event occurred on */ 17326 Window subwindow; /* child window */ 17327 Time time; /* milliseconds */ 17328 int x, y; /* pointer x, y coordinates in event window */ 17329 int x_root, y_root; /* coordinates relative to root */ 17330 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 17331 NotifyDetail detail; 17332 /* 17333 * NotifyAncestor, NotifyVirtual, NotifyInferior, 17334 * NotifyNonlinear,NotifyNonlinearVirtual 17335 */ 17336 Bool same_screen; /* same screen flag */ 17337 Bool focus; /* Boolean focus */ 17338 KeyOrButtonMask state; /* key or button mask */ 17339 } 17340 alias XCrossingEvent XEnterWindowEvent; 17341 alias XCrossingEvent XLeaveWindowEvent; 17342 17343 struct XFocusChangeEvent{ 17344 int type; /* FocusIn or FocusOut */ 17345 arch_ulong serial; /* # of last request processed by server */ 17346 Bool send_event; /* true if this came from a SendEvent request */ 17347 Display *display; /* Display the event was read from */ 17348 Window window; /* window of event */ 17349 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 17350 NotifyGrab, NotifyUngrab */ 17351 NotifyDetail detail; 17352 /* 17353 * NotifyAncestor, NotifyVirtual, NotifyInferior, 17354 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 17355 * NotifyPointerRoot, NotifyDetailNone 17356 */ 17357 } 17358 alias XFocusChangeEvent XFocusInEvent; 17359 alias XFocusChangeEvent XFocusOutEvent; 17360 17361 enum CWBackPixmap = (1L<<0); 17362 enum CWBackPixel = (1L<<1); 17363 enum CWBorderPixmap = (1L<<2); 17364 enum CWBorderPixel = (1L<<3); 17365 enum CWBitGravity = (1L<<4); 17366 enum CWWinGravity = (1L<<5); 17367 enum CWBackingStore = (1L<<6); 17368 enum CWBackingPlanes = (1L<<7); 17369 enum CWBackingPixel = (1L<<8); 17370 enum CWOverrideRedirect = (1L<<9); 17371 enum CWSaveUnder = (1L<<10); 17372 enum CWEventMask = (1L<<11); 17373 enum CWDontPropagate = (1L<<12); 17374 enum CWColormap = (1L<<13); 17375 enum CWCursor = (1L<<14); 17376 17377 struct XWindowAttributes { 17378 int x, y; /* location of window */ 17379 int width, height; /* width and height of window */ 17380 int border_width; /* border width of window */ 17381 int depth; /* depth of window */ 17382 Visual *visual; /* the associated visual structure */ 17383 Window root; /* root of screen containing window */ 17384 int class_; /* InputOutput, InputOnly*/ 17385 int bit_gravity; /* one of the bit gravity values */ 17386 int win_gravity; /* one of the window gravity values */ 17387 int backing_store; /* NotUseful, WhenMapped, Always */ 17388 arch_ulong backing_planes; /* planes to be preserved if possible */ 17389 arch_ulong backing_pixel; /* value to be used when restoring planes */ 17390 Bool save_under; /* boolean, should bits under be saved? */ 17391 Colormap colormap; /* color map to be associated with window */ 17392 Bool map_installed; /* boolean, is color map currently installed*/ 17393 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 17394 arch_long all_event_masks; /* set of events all people have interest in*/ 17395 arch_long your_event_mask; /* my event mask */ 17396 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 17397 Bool override_redirect; /* boolean value for override-redirect */ 17398 Screen *screen; /* back pointer to correct screen */ 17399 } 17400 17401 enum IsUnmapped = 0; 17402 enum IsUnviewable = 1; 17403 enum IsViewable = 2; 17404 17405 struct XSetWindowAttributes { 17406 Pixmap background_pixmap;/* background, None, or ParentRelative */ 17407 arch_ulong background_pixel;/* background pixel */ 17408 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 17409 arch_ulong border_pixel;/* border pixel value */ 17410 int bit_gravity; /* one of bit gravity values */ 17411 int win_gravity; /* one of the window gravity values */ 17412 int backing_store; /* NotUseful, WhenMapped, Always */ 17413 arch_ulong backing_planes;/* planes to be preserved if possible */ 17414 arch_ulong backing_pixel;/* value to use in restoring planes */ 17415 Bool save_under; /* should bits under be saved? (popups) */ 17416 arch_long event_mask; /* set of events that should be saved */ 17417 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 17418 Bool override_redirect; /* boolean value for override_redirect */ 17419 Colormap colormap; /* color map to be associated with window */ 17420 Cursor cursor; /* cursor to be displayed (or None) */ 17421 } 17422 17423 17424 alias int Status; 17425 17426 17427 enum EventMask:int 17428 { 17429 NoEventMask =0, 17430 KeyPressMask =1<<0, 17431 KeyReleaseMask =1<<1, 17432 ButtonPressMask =1<<2, 17433 ButtonReleaseMask =1<<3, 17434 EnterWindowMask =1<<4, 17435 LeaveWindowMask =1<<5, 17436 PointerMotionMask =1<<6, 17437 PointerMotionHintMask =1<<7, 17438 Button1MotionMask =1<<8, 17439 Button2MotionMask =1<<9, 17440 Button3MotionMask =1<<10, 17441 Button4MotionMask =1<<11, 17442 Button5MotionMask =1<<12, 17443 ButtonMotionMask =1<<13, 17444 KeymapStateMask =1<<14, 17445 ExposureMask =1<<15, 17446 VisibilityChangeMask =1<<16, 17447 StructureNotifyMask =1<<17, 17448 ResizeRedirectMask =1<<18, 17449 SubstructureNotifyMask =1<<19, 17450 SubstructureRedirectMask=1<<20, 17451 FocusChangeMask =1<<21, 17452 PropertyChangeMask =1<<22, 17453 ColormapChangeMask =1<<23, 17454 OwnerGrabButtonMask =1<<24 17455 } 17456 17457 struct MwmHints { 17458 c_ulong flags; 17459 c_ulong functions; 17460 c_ulong decorations; 17461 c_long input_mode; 17462 c_ulong status; 17463 } 17464 17465 enum { 17466 MWM_HINTS_FUNCTIONS = (1L << 0), 17467 MWM_HINTS_DECORATIONS = (1L << 1), 17468 17469 MWM_FUNC_ALL = (1L << 0), 17470 MWM_FUNC_RESIZE = (1L << 1), 17471 MWM_FUNC_MOVE = (1L << 2), 17472 MWM_FUNC_MINIMIZE = (1L << 3), 17473 MWM_FUNC_MAXIMIZE = (1L << 4), 17474 MWM_FUNC_CLOSE = (1L << 5), 17475 17476 MWM_DECOR_ALL = (1L << 0), 17477 MWM_DECOR_BORDER = (1L << 1), 17478 MWM_DECOR_RESIZEH = (1L << 2), 17479 MWM_DECOR_TITLE = (1L << 3), 17480 MWM_DECOR_MENU = (1L << 4), 17481 MWM_DECOR_MINIMIZE = (1L << 5), 17482 MWM_DECOR_MAXIMIZE = (1L << 6), 17483 } 17484 17485 import core.stdc.config : c_long, c_ulong; 17486 17487 /* Size hints mask bits */ 17488 17489 enum USPosition = (1L << 0) /* user specified x, y */; 17490 enum USSize = (1L << 1) /* user specified width, height */; 17491 enum PPosition = (1L << 2) /* program specified position */; 17492 enum PSize = (1L << 3) /* program specified size */; 17493 enum PMinSize = (1L << 4) /* program specified minimum size */; 17494 enum PMaxSize = (1L << 5) /* program specified maximum size */; 17495 enum PResizeInc = (1L << 6) /* program specified resize increments */; 17496 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 17497 enum PBaseSize = (1L << 8); 17498 enum PWinGravity = (1L << 9); 17499 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 17500 struct XSizeHints { 17501 arch_long flags; /* marks which fields in this structure are defined */ 17502 int x, y; /* Obsolete */ 17503 int width, height; /* Obsolete */ 17504 int min_width, min_height; 17505 int max_width, max_height; 17506 int width_inc, height_inc; 17507 struct Aspect { 17508 int x; /* numerator */ 17509 int y; /* denominator */ 17510 } 17511 17512 Aspect min_aspect; 17513 Aspect max_aspect; 17514 int base_width, base_height; 17515 int win_gravity; 17516 /* this structure may be extended in the future */ 17517 } 17518 17519 17520 17521 enum EventType:int 17522 { 17523 KeyPress =2, 17524 KeyRelease =3, 17525 ButtonPress =4, 17526 ButtonRelease =5, 17527 MotionNotify =6, 17528 EnterNotify =7, 17529 LeaveNotify =8, 17530 FocusIn =9, 17531 FocusOut =10, 17532 KeymapNotify =11, 17533 Expose =12, 17534 GraphicsExpose =13, 17535 NoExpose =14, 17536 VisibilityNotify =15, 17537 CreateNotify =16, 17538 DestroyNotify =17, 17539 UnmapNotify =18, 17540 MapNotify =19, 17541 MapRequest =20, 17542 ReparentNotify =21, 17543 ConfigureNotify =22, 17544 ConfigureRequest =23, 17545 GravityNotify =24, 17546 ResizeRequest =25, 17547 CirculateNotify =26, 17548 CirculateRequest =27, 17549 PropertyNotify =28, 17550 SelectionClear =29, 17551 SelectionRequest =30, 17552 SelectionNotify =31, 17553 ColormapNotify =32, 17554 ClientMessage =33, 17555 MappingNotify =34, 17556 LASTEvent =35 /* must be bigger than any event # */ 17557 } 17558 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 17559 struct XKeymapEvent 17560 { 17561 int type; 17562 arch_ulong serial; /* # of last request processed by server */ 17563 Bool send_event; /* true if this came from a SendEvent request */ 17564 Display *display; /* Display the event was read from */ 17565 Window window; 17566 byte[32] key_vector; 17567 } 17568 17569 struct XExposeEvent 17570 { 17571 int type; 17572 arch_ulong serial; /* # of last request processed by server */ 17573 Bool send_event; /* true if this came from a SendEvent request */ 17574 Display *display; /* Display the event was read from */ 17575 Window window; 17576 int x, y; 17577 int width, height; 17578 int count; /* if non-zero, at least this many more */ 17579 } 17580 17581 struct XGraphicsExposeEvent{ 17582 int type; 17583 arch_ulong serial; /* # of last request processed by server */ 17584 Bool send_event; /* true if this came from a SendEvent request */ 17585 Display *display; /* Display the event was read from */ 17586 Drawable drawable; 17587 int x, y; 17588 int width, height; 17589 int count; /* if non-zero, at least this many more */ 17590 int major_code; /* core is CopyArea or CopyPlane */ 17591 int minor_code; /* not defined in the core */ 17592 } 17593 17594 struct XNoExposeEvent{ 17595 int type; 17596 arch_ulong serial; /* # of last request processed by server */ 17597 Bool send_event; /* true if this came from a SendEvent request */ 17598 Display *display; /* Display the event was read from */ 17599 Drawable drawable; 17600 int major_code; /* core is CopyArea or CopyPlane */ 17601 int minor_code; /* not defined in the core */ 17602 } 17603 17604 struct XVisibilityEvent{ 17605 int type; 17606 arch_ulong serial; /* # of last request processed by server */ 17607 Bool send_event; /* true if this came from a SendEvent request */ 17608 Display *display; /* Display the event was read from */ 17609 Window window; 17610 VisibilityNotify state; /* Visibility state */ 17611 } 17612 17613 struct XCreateWindowEvent{ 17614 int type; 17615 arch_ulong serial; /* # of last request processed by server */ 17616 Bool send_event; /* true if this came from a SendEvent request */ 17617 Display *display; /* Display the event was read from */ 17618 Window parent; /* parent of the window */ 17619 Window window; /* window id of window created */ 17620 int x, y; /* window location */ 17621 int width, height; /* size of window */ 17622 int border_width; /* border width */ 17623 Bool override_redirect; /* creation should be overridden */ 17624 } 17625 17626 struct XDestroyWindowEvent 17627 { 17628 int type; 17629 arch_ulong serial; /* # of last request processed by server */ 17630 Bool send_event; /* true if this came from a SendEvent request */ 17631 Display *display; /* Display the event was read from */ 17632 Window event; 17633 Window window; 17634 } 17635 17636 struct XUnmapEvent 17637 { 17638 int type; 17639 arch_ulong serial; /* # of last request processed by server */ 17640 Bool send_event; /* true if this came from a SendEvent request */ 17641 Display *display; /* Display the event was read from */ 17642 Window event; 17643 Window window; 17644 Bool from_configure; 17645 } 17646 17647 struct XMapEvent 17648 { 17649 int type; 17650 arch_ulong serial; /* # of last request processed by server */ 17651 Bool send_event; /* true if this came from a SendEvent request */ 17652 Display *display; /* Display the event was read from */ 17653 Window event; 17654 Window window; 17655 Bool override_redirect; /* Boolean, is override set... */ 17656 } 17657 17658 struct XMapRequestEvent 17659 { 17660 int type; 17661 arch_ulong serial; /* # of last request processed by server */ 17662 Bool send_event; /* true if this came from a SendEvent request */ 17663 Display *display; /* Display the event was read from */ 17664 Window parent; 17665 Window window; 17666 } 17667 17668 struct XReparentEvent 17669 { 17670 int type; 17671 arch_ulong serial; /* # of last request processed by server */ 17672 Bool send_event; /* true if this came from a SendEvent request */ 17673 Display *display; /* Display the event was read from */ 17674 Window event; 17675 Window window; 17676 Window parent; 17677 int x, y; 17678 Bool override_redirect; 17679 } 17680 17681 struct XConfigureEvent 17682 { 17683 int type; 17684 arch_ulong serial; /* # of last request processed by server */ 17685 Bool send_event; /* true if this came from a SendEvent request */ 17686 Display *display; /* Display the event was read from */ 17687 Window event; 17688 Window window; 17689 int x, y; 17690 int width, height; 17691 int border_width; 17692 Window above; 17693 Bool override_redirect; 17694 } 17695 17696 struct XGravityEvent 17697 { 17698 int type; 17699 arch_ulong serial; /* # of last request processed by server */ 17700 Bool send_event; /* true if this came from a SendEvent request */ 17701 Display *display; /* Display the event was read from */ 17702 Window event; 17703 Window window; 17704 int x, y; 17705 } 17706 17707 struct XResizeRequestEvent 17708 { 17709 int type; 17710 arch_ulong serial; /* # of last request processed by server */ 17711 Bool send_event; /* true if this came from a SendEvent request */ 17712 Display *display; /* Display the event was read from */ 17713 Window window; 17714 int width, height; 17715 } 17716 17717 struct XConfigureRequestEvent 17718 { 17719 int type; 17720 arch_ulong serial; /* # of last request processed by server */ 17721 Bool send_event; /* true if this came from a SendEvent request */ 17722 Display *display; /* Display the event was read from */ 17723 Window parent; 17724 Window window; 17725 int x, y; 17726 int width, height; 17727 int border_width; 17728 Window above; 17729 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 17730 arch_ulong value_mask; 17731 } 17732 17733 struct XCirculateEvent 17734 { 17735 int type; 17736 arch_ulong serial; /* # of last request processed by server */ 17737 Bool send_event; /* true if this came from a SendEvent request */ 17738 Display *display; /* Display the event was read from */ 17739 Window event; 17740 Window window; 17741 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 17742 } 17743 17744 struct XCirculateRequestEvent 17745 { 17746 int type; 17747 arch_ulong serial; /* # of last request processed by server */ 17748 Bool send_event; /* true if this came from a SendEvent request */ 17749 Display *display; /* Display the event was read from */ 17750 Window parent; 17751 Window window; 17752 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 17753 } 17754 17755 struct XPropertyEvent 17756 { 17757 int type; 17758 arch_ulong serial; /* # of last request processed by server */ 17759 Bool send_event; /* true if this came from a SendEvent request */ 17760 Display *display; /* Display the event was read from */ 17761 Window window; 17762 Atom atom; 17763 Time time; 17764 PropertyNotification state; /* NewValue, Deleted */ 17765 } 17766 17767 struct XSelectionClearEvent 17768 { 17769 int type; 17770 arch_ulong serial; /* # of last request processed by server */ 17771 Bool send_event; /* true if this came from a SendEvent request */ 17772 Display *display; /* Display the event was read from */ 17773 Window window; 17774 Atom selection; 17775 Time time; 17776 } 17777 17778 struct XSelectionRequestEvent 17779 { 17780 int type; 17781 arch_ulong serial; /* # of last request processed by server */ 17782 Bool send_event; /* true if this came from a SendEvent request */ 17783 Display *display; /* Display the event was read from */ 17784 Window owner; 17785 Window requestor; 17786 Atom selection; 17787 Atom target; 17788 Atom property; 17789 Time time; 17790 } 17791 17792 struct XSelectionEvent 17793 { 17794 int type; 17795 arch_ulong serial; /* # of last request processed by server */ 17796 Bool send_event; /* true if this came from a SendEvent request */ 17797 Display *display; /* Display the event was read from */ 17798 Window requestor; 17799 Atom selection; 17800 Atom target; 17801 Atom property; /* ATOM or None */ 17802 Time time; 17803 } 17804 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 17805 17806 struct XColormapEvent 17807 { 17808 int type; 17809 arch_ulong serial; /* # of last request processed by server */ 17810 Bool send_event; /* true if this came from a SendEvent request */ 17811 Display *display; /* Display the event was read from */ 17812 Window window; 17813 Colormap colormap; /* COLORMAP or None */ 17814 Bool new_; /* C++ */ 17815 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 17816 } 17817 version(X86_64) static assert(XColormapEvent.sizeof == 56); 17818 17819 struct XClientMessageEvent 17820 { 17821 int type; 17822 arch_ulong serial; /* # of last request processed by server */ 17823 Bool send_event; /* true if this came from a SendEvent request */ 17824 Display *display; /* Display the event was read from */ 17825 Window window; 17826 Atom message_type; 17827 int format; 17828 union Data{ 17829 byte[20] b; 17830 short[10] s; 17831 arch_ulong[5] l; 17832 } 17833 Data data; 17834 17835 } 17836 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 17837 17838 struct XMappingEvent 17839 { 17840 int type; 17841 arch_ulong serial; /* # of last request processed by server */ 17842 Bool send_event; /* true if this came from a SendEvent request */ 17843 Display *display; /* Display the event was read from */ 17844 Window window; /* unused */ 17845 MappingType request; /* one of MappingModifier, MappingKeyboard, 17846 MappingPointer */ 17847 int first_keycode; /* first keycode */ 17848 int count; /* defines range of change w. first_keycode*/ 17849 } 17850 17851 struct XErrorEvent 17852 { 17853 int type; 17854 Display *display; /* Display the event was read from */ 17855 XID resourceid; /* resource id */ 17856 arch_ulong serial; /* serial number of failed request */ 17857 ubyte error_code; /* error code of failed request */ 17858 ubyte request_code; /* Major op-code of failed request */ 17859 ubyte minor_code; /* Minor op-code of failed request */ 17860 } 17861 17862 struct XAnyEvent 17863 { 17864 int type; 17865 arch_ulong serial; /* # of last request processed by server */ 17866 Bool send_event; /* true if this came from a SendEvent request */ 17867 Display *display;/* Display the event was read from */ 17868 Window window; /* window on which event was requested in event mask */ 17869 } 17870 17871 union XEvent{ 17872 int type; /* must not be changed; first element */ 17873 XAnyEvent xany; 17874 XKeyEvent xkey; 17875 XButtonEvent xbutton; 17876 XMotionEvent xmotion; 17877 XCrossingEvent xcrossing; 17878 XFocusChangeEvent xfocus; 17879 XExposeEvent xexpose; 17880 XGraphicsExposeEvent xgraphicsexpose; 17881 XNoExposeEvent xnoexpose; 17882 XVisibilityEvent xvisibility; 17883 XCreateWindowEvent xcreatewindow; 17884 XDestroyWindowEvent xdestroywindow; 17885 XUnmapEvent xunmap; 17886 XMapEvent xmap; 17887 XMapRequestEvent xmaprequest; 17888 XReparentEvent xreparent; 17889 XConfigureEvent xconfigure; 17890 XGravityEvent xgravity; 17891 XResizeRequestEvent xresizerequest; 17892 XConfigureRequestEvent xconfigurerequest; 17893 XCirculateEvent xcirculate; 17894 XCirculateRequestEvent xcirculaterequest; 17895 XPropertyEvent xproperty; 17896 XSelectionClearEvent xselectionclear; 17897 XSelectionRequestEvent xselectionrequest; 17898 XSelectionEvent xselection; 17899 XColormapEvent xcolormap; 17900 XClientMessageEvent xclient; 17901 XMappingEvent xmapping; 17902 XErrorEvent xerror; 17903 XKeymapEvent xkeymap; 17904 arch_ulong[24] pad; 17905 } 17906 17907 17908 struct Display { 17909 XExtData *ext_data; /* hook for extension to hang data */ 17910 _XPrivate *private1; 17911 int fd; /* Network socket. */ 17912 int private2; 17913 int proto_major_version;/* major version of server's X protocol */ 17914 int proto_minor_version;/* minor version of servers X protocol */ 17915 char *vendor; /* vendor of the server hardware */ 17916 XID private3; 17917 XID private4; 17918 XID private5; 17919 int private6; 17920 XID function(Display*)resource_alloc;/* allocator function */ 17921 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 17922 int bitmap_unit; /* padding and data requirements */ 17923 int bitmap_pad; /* padding requirements on bitmaps */ 17924 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 17925 int nformats; /* number of pixmap formats in list */ 17926 ScreenFormat *pixmap_format; /* pixmap format list */ 17927 int private8; 17928 int release; /* release of the server */ 17929 _XPrivate *private9; 17930 _XPrivate *private10; 17931 int qlen; /* Length of input event queue */ 17932 arch_ulong last_request_read; /* seq number of last event read */ 17933 arch_ulong request; /* sequence number of last request. */ 17934 XPointer private11; 17935 XPointer private12; 17936 XPointer private13; 17937 XPointer private14; 17938 uint max_request_size; /* maximum number 32 bit words in request*/ 17939 _XrmHashBucketRec *db; 17940 int function (Display*)private15; 17941 char *display_name; /* "host:display" string used on this connect*/ 17942 int default_screen; /* default screen for operations */ 17943 int nscreens; /* number of screens on this server*/ 17944 Screen *screens; /* pointer to list of screens */ 17945 arch_ulong motion_buffer; /* size of motion buffer */ 17946 arch_ulong private16; 17947 int min_keycode; /* minimum defined keycode */ 17948 int max_keycode; /* maximum defined keycode */ 17949 XPointer private17; 17950 XPointer private18; 17951 int private19; 17952 byte *xdefaults; /* contents of defaults from server */ 17953 /* there is more to this structure, but it is private to Xlib */ 17954 } 17955 17956 // I got these numbers from a C program as a sanity test 17957 version(X86_64) { 17958 static assert(Display.sizeof == 296); 17959 static assert(XPointer.sizeof == 8); 17960 static assert(XErrorEvent.sizeof == 40); 17961 static assert(XAnyEvent.sizeof == 40); 17962 static assert(XMappingEvent.sizeof == 56); 17963 static assert(XEvent.sizeof == 192); 17964 } else version (AArch64) { 17965 // omit check for aarch64 17966 } else { 17967 static assert(Display.sizeof == 176); 17968 static assert(XPointer.sizeof == 4); 17969 static assert(XEvent.sizeof == 96); 17970 } 17971 17972 struct Depth 17973 { 17974 int depth; /* this depth (Z) of the depth */ 17975 int nvisuals; /* number of Visual types at this depth */ 17976 Visual *visuals; /* list of visuals possible at this depth */ 17977 } 17978 17979 alias void* GC; 17980 alias c_ulong VisualID; 17981 alias XID Colormap; 17982 alias XID Cursor; 17983 alias XID KeySym; 17984 alias uint KeyCode; 17985 enum None = 0; 17986 } 17987 17988 version(without_opengl) {} 17989 else { 17990 extern(C) nothrow @nogc { 17991 17992 17993 static if(!SdpyIsUsingIVGLBinds) { 17994 enum GLX_USE_GL= 1; /* support GLX rendering */ 17995 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 17996 enum GLX_LEVEL= 3; /* level in plane stacking */ 17997 enum GLX_RGBA= 4; /* true if RGBA mode */ 17998 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 17999 enum GLX_STEREO= 6; /* stereo buffering supported */ 18000 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 18001 enum GLX_RED_SIZE= 8; /* number of red component bits */ 18002 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 18003 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 18004 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 18005 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 18006 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 18007 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 18008 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 18009 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 18010 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 18011 18012 18013 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 18014 18015 18016 18017 enum GL_TRUE = 1; 18018 enum GL_FALSE = 0; 18019 } 18020 18021 alias XID GLXContextID; 18022 alias XID GLXPixmap; 18023 alias XID GLXDrawable; 18024 alias XID GLXPbuffer; 18025 alias XID GLXWindow; 18026 alias XID GLXFBConfigID; 18027 alias void* GLXContext; 18028 18029 } 18030 } 18031 18032 enum AllocNone = 0; 18033 18034 extern(C) { 18035 /* WARNING, this type not in Xlib spec */ 18036 extern(C) alias XIOErrorHandler = int function (Display* display); 18037 } 18038 18039 extern(C) nothrow 18040 alias XErrorHandler = int function(Display*, XErrorEvent*); 18041 18042 extern(C) nothrow @nogc { 18043 struct Screen{ 18044 XExtData *ext_data; /* hook for extension to hang data */ 18045 Display *display; /* back pointer to display structure */ 18046 Window root; /* Root window id. */ 18047 int width, height; /* width and height of screen */ 18048 int mwidth, mheight; /* width and height of in millimeters */ 18049 int ndepths; /* number of depths possible */ 18050 Depth *depths; /* list of allowable depths on the screen */ 18051 int root_depth; /* bits per pixel */ 18052 Visual *root_visual; /* root visual */ 18053 GC default_gc; /* GC for the root root visual */ 18054 Colormap cmap; /* default color map */ 18055 uint white_pixel; 18056 uint black_pixel; /* White and Black pixel values */ 18057 int max_maps, min_maps; /* max and min color maps */ 18058 int backing_store; /* Never, WhenMapped, Always */ 18059 bool save_unders; 18060 int root_input_mask; /* initial root input mask */ 18061 } 18062 18063 struct Visual 18064 { 18065 XExtData *ext_data; /* hook for extension to hang data */ 18066 VisualID visualid; /* visual id of this visual */ 18067 int class_; /* class of screen (monochrome, etc.) */ 18068 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 18069 int bits_per_rgb; /* log base 2 of distinct color values */ 18070 int map_entries; /* color map entries */ 18071 } 18072 18073 alias Display* _XPrivDisplay; 18074 18075 extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) @system { 18076 assert(dpy !is null); 18077 return &dpy.screens[scr]; 18078 } 18079 18080 extern(D) Window RootWindow(Display *dpy,int scr) { 18081 return ScreenOfDisplay(dpy,scr).root; 18082 } 18083 18084 struct XWMHints { 18085 arch_long flags; 18086 Bool input; 18087 int initial_state; 18088 Pixmap icon_pixmap; 18089 Window icon_window; 18090 int icon_x, icon_y; 18091 Pixmap icon_mask; 18092 XID window_group; 18093 } 18094 18095 struct XClassHint { 18096 char* res_name; 18097 char* res_class; 18098 } 18099 18100 extern(D) int DefaultScreen(Display *dpy) { 18101 return dpy.default_screen; 18102 } 18103 18104 extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 18105 extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 18106 extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 18107 extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 18108 extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 18109 extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 18110 18111 extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; } 18112 18113 enum int AnyPropertyType = 0; 18114 enum int Success = 0; 18115 18116 enum int RevertToNone = None; 18117 enum int PointerRoot = 1; 18118 enum Time CurrentTime = 0; 18119 enum int RevertToPointerRoot = PointerRoot; 18120 enum int RevertToParent = 2; 18121 18122 extern(D) int DefaultDepthOfDisplay(Display* dpy) { 18123 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 18124 } 18125 18126 extern(D) Visual* DefaultVisual(Display *dpy,int scr) { 18127 return ScreenOfDisplay(dpy,scr).root_visual; 18128 } 18129 18130 extern(D) GC DefaultGC(Display *dpy,int scr) { 18131 return ScreenOfDisplay(dpy,scr).default_gc; 18132 } 18133 18134 extern(D) uint BlackPixel(Display *dpy,int scr) { 18135 return ScreenOfDisplay(dpy,scr).black_pixel; 18136 } 18137 18138 extern(D) uint WhitePixel(Display *dpy,int scr) { 18139 return ScreenOfDisplay(dpy,scr).white_pixel; 18140 } 18141 18142 alias void* XFontSet; // i think 18143 struct XmbTextItem { 18144 char* chars; 18145 int nchars; 18146 int delta; 18147 XFontSet font_set; 18148 } 18149 18150 struct XTextItem { 18151 char* chars; 18152 int nchars; 18153 int delta; 18154 Font font; 18155 } 18156 18157 enum { 18158 GXclear = 0x0, /* 0 */ 18159 GXand = 0x1, /* src AND dst */ 18160 GXandReverse = 0x2, /* src AND NOT dst */ 18161 GXcopy = 0x3, /* src */ 18162 GXandInverted = 0x4, /* NOT src AND dst */ 18163 GXnoop = 0x5, /* dst */ 18164 GXxor = 0x6, /* src XOR dst */ 18165 GXor = 0x7, /* src OR dst */ 18166 GXnor = 0x8, /* NOT src AND NOT dst */ 18167 GXequiv = 0x9, /* NOT src XOR dst */ 18168 GXinvert = 0xa, /* NOT dst */ 18169 GXorReverse = 0xb, /* src OR NOT dst */ 18170 GXcopyInverted = 0xc, /* NOT src */ 18171 GXorInverted = 0xd, /* NOT src OR dst */ 18172 GXnand = 0xe, /* NOT src OR NOT dst */ 18173 GXset = 0xf, /* 1 */ 18174 } 18175 enum QueueMode : int { 18176 QueuedAlready, 18177 QueuedAfterReading, 18178 QueuedAfterFlush 18179 } 18180 18181 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 18182 18183 struct XPoint { 18184 short x; 18185 short y; 18186 } 18187 18188 enum CoordMode:int { 18189 CoordModeOrigin = 0, 18190 CoordModePrevious = 1 18191 } 18192 18193 enum PolygonShape:int { 18194 Complex = 0, 18195 Nonconvex = 1, 18196 Convex = 2 18197 } 18198 18199 struct XTextProperty { 18200 const(char)* value; /* same as Property routines */ 18201 Atom encoding; /* prop type */ 18202 int format; /* prop data format: 8, 16, or 32 */ 18203 arch_ulong nitems; /* number of data items in value */ 18204 } 18205 18206 version( X86_64 ) { 18207 static assert(XTextProperty.sizeof == 32); 18208 } 18209 18210 18211 struct XGCValues { 18212 int function_; /* logical operation */ 18213 arch_ulong plane_mask;/* plane mask */ 18214 arch_ulong foreground;/* foreground pixel */ 18215 arch_ulong background;/* background pixel */ 18216 int line_width; /* line width */ 18217 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 18218 int cap_style; /* CapNotLast, CapButt, 18219 CapRound, CapProjecting */ 18220 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 18221 int fill_style; /* FillSolid, FillTiled, 18222 FillStippled, FillOpaeueStippled */ 18223 int fill_rule; /* EvenOddRule, WindingRule */ 18224 int arc_mode; /* ArcChord, ArcPieSlice */ 18225 Pixmap tile; /* tile pixmap for tiling operations */ 18226 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 18227 int ts_x_origin; /* offset for tile or stipple operations */ 18228 int ts_y_origin; 18229 Font font; /* default text font for text operations */ 18230 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 18231 Bool graphics_exposures;/* boolean, should exposures be generated */ 18232 int clip_x_origin; /* origin for clipping */ 18233 int clip_y_origin; 18234 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 18235 int dash_offset; /* patterned/dashed line information */ 18236 char dashes; 18237 } 18238 18239 struct XColor { 18240 arch_ulong pixel; 18241 ushort red, green, blue; 18242 byte flags; 18243 byte pad; 18244 } 18245 18246 struct XRectangle { 18247 short x; 18248 short y; 18249 ushort width; 18250 ushort height; 18251 } 18252 18253 enum ClipByChildren = 0; 18254 enum IncludeInferiors = 1; 18255 18256 enum Atom XA_PRIMARY = 1; 18257 enum Atom XA_SECONDARY = 2; 18258 enum Atom XA_STRING = 31; 18259 enum Atom XA_CARDINAL = 6; 18260 enum Atom XA_WM_NAME = 39; 18261 enum Atom XA_ATOM = 4; 18262 enum Atom XA_WINDOW = 33; 18263 enum Atom XA_WM_HINTS = 35; 18264 enum int PropModeAppend = 2; 18265 enum int PropModeReplace = 0; 18266 enum int PropModePrepend = 1; 18267 18268 enum int CopyFromParent = 0; 18269 enum int InputOutput = 1; 18270 18271 // XWMHints 18272 enum InputHint = 1 << 0; 18273 enum StateHint = 1 << 1; 18274 enum IconPixmapHint = (1L << 2); 18275 enum IconWindowHint = (1L << 3); 18276 enum IconPositionHint = (1L << 4); 18277 enum IconMaskHint = (1L << 5); 18278 enum WindowGroupHint = (1L << 6); 18279 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 18280 enum XUrgencyHint = (1L << 8); 18281 18282 // GC Components 18283 enum GCFunction = (1L<<0); 18284 enum GCPlaneMask = (1L<<1); 18285 enum GCForeground = (1L<<2); 18286 enum GCBackground = (1L<<3); 18287 enum GCLineWidth = (1L<<4); 18288 enum GCLineStyle = (1L<<5); 18289 enum GCCapStyle = (1L<<6); 18290 enum GCJoinStyle = (1L<<7); 18291 enum GCFillStyle = (1L<<8); 18292 enum GCFillRule = (1L<<9); 18293 enum GCTile = (1L<<10); 18294 enum GCStipple = (1L<<11); 18295 enum GCTileStipXOrigin = (1L<<12); 18296 enum GCTileStipYOrigin = (1L<<13); 18297 enum GCFont = (1L<<14); 18298 enum GCSubwindowMode = (1L<<15); 18299 enum GCGraphicsExposures= (1L<<16); 18300 enum GCClipXOrigin = (1L<<17); 18301 enum GCClipYOrigin = (1L<<18); 18302 enum GCClipMask = (1L<<19); 18303 enum GCDashOffset = (1L<<20); 18304 enum GCDashList = (1L<<21); 18305 enum GCArcMode = (1L<<22); 18306 enum GCLastBit = 22; 18307 18308 18309 enum int WithdrawnState = 0; 18310 enum int NormalState = 1; 18311 enum int IconicState = 3; 18312 18313 } 18314 } else version (OSXCocoa) { 18315 18316 /+ 18317 DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do. 18318 +/ 18319 18320 private __gshared AppDelegate globalAppDelegate; 18321 18322 extern(Objective-C) 18323 class AppDelegate : NSObject, NSApplicationDelegate { 18324 override static AppDelegate alloc() @selector("alloc"); 18325 18326 18327 void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") { 18328 SimpleWindow.processAllCustomEvents(); 18329 } 18330 18331 override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") { 18332 immutable style = NSWindowStyleMask.resizable | 18333 NSWindowStyleMask.closable | 18334 NSWindowStyleMask.miniaturizable | 18335 NSWindowStyleMask.titled; 18336 18337 NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow); 18338 18339 { 18340 auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow); 18341 auto menu = NSMenu.alloc.init(MacString("Test2").borrow); 18342 mainMenu.setSubmenu(menu, item); 18343 18344 auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow); 18345 newItem.target = NSApp; 18346 auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow); 18347 newItem2.target = NSApp; 18348 } 18349 18350 { 18351 auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow); 18352 auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used 18353 mainMenu.setSubmenu(menu, item); 18354 18355 auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow); 18356 menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow); 18357 } 18358 18359 18360 NSApp.menu = mainMenu; 18361 18362 18363 // auto controller = ViewController.alloc.init; 18364 18365 // auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true); 18366 18367 /+ 18368 this.window = window; 18369 this.controller = controller; 18370 +/ 18371 } 18372 18373 override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") { 18374 NSApplication.shared_.activateIgnoringOtherApps(true); 18375 } 18376 override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") { 18377 return true; 18378 } 18379 } 18380 18381 extern(Objective-C) 18382 class SDWindowDelegate : NSObject, NSWindowDelegate { 18383 override static SDWindowDelegate alloc() @selector("alloc"); 18384 override SDWindowDelegate init() @selector("init"); 18385 18386 SimpleWindow simpleWindow; 18387 18388 override void windowWillClose(NSNotification notification) @selector("windowWillClose:") { 18389 auto window = cast(void*) notification.object; 18390 18391 // FIXME: do i need to release it? 18392 SimpleWindow.nativeMapping.remove(window); 18393 } 18394 18395 override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") { 18396 if(simpleWindow.windowResized) { 18397 // FIXME: automaticallyScaleIfPossible behaviors 18398 18399 simpleWindow._width = cast(int) frameSize.width; 18400 simpleWindow._height = cast(int) frameSize.height; 18401 18402 simpleWindow.view.setFrameSize(frameSize); 18403 18404 /+ 18405 auto size = simpleWindow.view.frame.size; 18406 writeln(cast(int) size.width, "x", cast(int) size.height); 18407 +/ 18408 18409 simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height); 18410 18411 simpleWindow.windowResized(simpleWindow._width, simpleWindow._height); 18412 18413 // simpleWindow.view.setNeedsDisplay(true); 18414 } 18415 18416 return frameSize; 18417 } 18418 18419 /+ 18420 override void windowDidResize(NSNotification notification) @selector("windowDidResize:") { 18421 if(simpleWindow.windowResized) { 18422 auto window = simpleWindow.window; 18423 auto rect = window.contentRectForFrameRect(window.frame); 18424 import std.stdio; writeln(window.frame.size); 18425 simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height); 18426 } 18427 } 18428 +/ 18429 } 18430 18431 extern(Objective-C) 18432 class SDGraphicsView : NSView { 18433 SimpleWindow simpleWindow; 18434 18435 override static SDGraphicsView alloc() @selector("alloc"); 18436 override SDGraphicsView init() @selector("init") { 18437 super.init(); 18438 return this; 18439 } 18440 18441 override void drawRect(NSRect rect) @selector("drawRect:") { 18442 auto curCtx = NSGraphicsContext.currentContext.graphicsPort; 18443 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 18444 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext)); 18445 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 18446 CGImageRelease(cgImage); 18447 } 18448 18449 extern(D) 18450 private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) { 18451 MouseEvent me; 18452 me.type = type; 18453 18454 auto pos = event.locationInWindow; 18455 18456 me.x = cast(int) pos.x; 18457 me.y = cast(int) (simpleWindow.height - pos.y); 18458 18459 me.dx = 0; // FIXME 18460 me.dy = 0; // FIXME 18461 18462 me.button = button; 18463 me.modifierState = cast(uint) event.modifierFlags; 18464 me.window = simpleWindow; 18465 18466 me.doubleClick = false; 18467 18468 if(simpleWindow && simpleWindow.handleMouseEvent) 18469 simpleWindow.handleMouseEvent(me); 18470 } 18471 18472 override void mouseDown(NSEvent event) @selector("mouseDown:") { 18473 // writeln(event.pressedMouseButtons); 18474 18475 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 18476 } 18477 override void mouseDragged(NSEvent event) @selector("mouseDragged:") { 18478 mouseHelper(event, MouseEventType.motion, MouseButton.left); 18479 } 18480 override void mouseUp(NSEvent event) @selector("mouseUp:") { 18481 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left); 18482 } 18483 override void mouseMoved(NSEvent event) @selector("mouseMoved:") { 18484 mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly 18485 } 18486 /+ 18487 // FIXME 18488 override void mouseEntered(NSEvent event) @selector("mouseEntered:") { 18489 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 18490 } 18491 override void mouseExited(NSEvent event) @selector("mouseExited:") { 18492 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 18493 } 18494 +/ 18495 18496 override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") { 18497 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right); 18498 } 18499 override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") { 18500 mouseHelper(event, MouseEventType.motion, MouseButton.right); 18501 } 18502 override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") { 18503 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right); 18504 } 18505 18506 override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") { 18507 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle); 18508 } 18509 override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") { 18510 mouseHelper(event, MouseEventType.motion, MouseButton.middle); 18511 } 18512 override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") { 18513 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle); 18514 } 18515 18516 override void scrollWheel(NSEvent event) @selector("scrollWheel:") { 18517 // import std.stdio; writeln(event.deltaY); 18518 } 18519 18520 override void keyDown(NSEvent event) @selector("keyDown:") { 18521 // the event may have multiple characters, and we send them all at once. 18522 if (simpleWindow.handleCharEvent) { 18523 auto chars = DeifiedNSString(event.characters); 18524 foreach (dchar dc; chars.str) 18525 simpleWindow.handleCharEvent(dc); 18526 } 18527 18528 keyHelper(event, true); 18529 } 18530 18531 override void keyUp(NSEvent event) @selector("keyUp:") { 18532 keyHelper(event, false); 18533 } 18534 18535 extern(D) 18536 private void keyHelper(NSEvent event, bool pressed) { 18537 if(simpleWindow.handleKeyEvent) { 18538 KeyEvent ev; 18539 ev.key = cast(Key) event.keyCode;// (event.specialKey ? event.specialKey : event.keyCode); 18540 ev.pressed = pressed; 18541 ev.hardwareCode = cast(ubyte) event.keyCode; 18542 ev.modifierState = cast(uint) event.modifierFlags; 18543 ev.window = simpleWindow; 18544 18545 simpleWindow.handleKeyEvent(ev); 18546 } 18547 } 18548 18549 override bool isFlipped() @selector("isFlipped") { 18550 return true; 18551 } 18552 override bool acceptsFirstResponder() @selector("acceptsFirstResponder") { 18553 return true; 18554 } 18555 18556 void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") { 18557 if(simpleWindow && simpleWindow.handlePulse) 18558 simpleWindow.handlePulse(); 18559 /+ 18560 setNeedsDisplay = true; 18561 +/ 18562 } 18563 } 18564 18565 private: 18566 alias const(void)* CFStringRef; 18567 alias const(void)* CFAllocatorRef; 18568 alias const(void)* CFTypeRef; 18569 alias const(void)* CGColorSpaceRef; 18570 alias const(void)* CGImageRef; 18571 alias ulong CGBitmapInfo; 18572 alias NSGraphicsContext CGContextRef; 18573 18574 alias NSPoint CGPoint; 18575 alias NSSize CGSize; 18576 alias NSRect CGRect; 18577 18578 struct CGAffineTransform { 18579 double a, b, c, d, tx, ty; 18580 } 18581 18582 enum NSApplicationActivationPolicyRegular = 0; 18583 enum NSBackingStoreBuffered = 2; 18584 enum kCFStringEncodingUTF8 = 0x08000100; 18585 18586 enum : size_t { 18587 NSBorderlessWindowMask = 0, 18588 NSTitledWindowMask = 1 << 0, 18589 NSClosableWindowMask = 1 << 1, 18590 NSMiniaturizableWindowMask = 1 << 2, 18591 NSResizableWindowMask = 1 << 3, 18592 NSTexturedBackgroundWindowMask = 1 << 8 18593 } 18594 18595 enum : ulong { 18596 kCGImageAlphaNone, 18597 kCGImageAlphaPremultipliedLast, 18598 kCGImageAlphaPremultipliedFirst, 18599 kCGImageAlphaLast, 18600 kCGImageAlphaFirst, 18601 kCGImageAlphaNoneSkipLast, 18602 kCGImageAlphaNoneSkipFirst 18603 } 18604 enum : ulong { 18605 kCGBitmapAlphaInfoMask = 0x1F, 18606 kCGBitmapFloatComponents = (1 << 8), 18607 kCGBitmapByteOrderMask = 0x7000, 18608 kCGBitmapByteOrderDefault = (0 << 12), 18609 kCGBitmapByteOrder16Little = (1 << 12), 18610 kCGBitmapByteOrder32Little = (2 << 12), 18611 kCGBitmapByteOrder16Big = (3 << 12), 18612 kCGBitmapByteOrder32Big = (4 << 12) 18613 } 18614 enum CGPathDrawingMode { 18615 kCGPathFill, 18616 kCGPathEOFill, 18617 kCGPathStroke, 18618 kCGPathFillStroke, 18619 kCGPathEOFillStroke 18620 } 18621 enum objc_AssociationPolicy : size_t { 18622 OBJC_ASSOCIATION_ASSIGN = 0, 18623 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 18624 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 18625 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 18626 OBJC_ASSOCIATION_COPY = 0x303 //01403 18627 } 18628 18629 extern(C) { 18630 CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo); 18631 void CGContextRelease(CGContextRef c); 18632 ubyte* CGBitmapContextGetData(CGContextRef c); 18633 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 18634 size_t CGBitmapContextGetWidth(CGContextRef c); 18635 size_t CGBitmapContextGetHeight(CGContextRef c); 18636 18637 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 18638 void CGColorSpaceRelease(CGColorSpaceRef cs); 18639 18640 void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha); 18641 void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha); 18642 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 18643 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length); 18644 void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count); 18645 void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat *lengths, size_t count); 18646 18647 void CGContextBeginPath(CGContextRef c); 18648 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 18649 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 18650 void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise); 18651 void CGContextAddRect(CGContextRef c, CGRect rect); 18652 void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count); 18653 void CGContextSaveGState(CGContextRef c); 18654 void CGContextRestoreGState(CGContextRef c); 18655 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding); 18656 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 18657 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 18658 18659 void CGImageRelease(CGImageRef image); 18660 } 18661 } else static assert(0, "Unsupported operating system"); 18662 18663 18664 version(OSXCocoa) { 18665 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 18666 // 18667 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 18668 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 18669 // 18670 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 18671 // Probably won't even fully compile right now 18672 18673 private enum double PI = 3.14159265358979323; 18674 18675 alias NSWindow NativeWindowHandle; 18676 alias void delegate(NSid) NativeEventHandler; 18677 18678 enum KEY_ESCAPE = 27; 18679 18680 mixin template NativeImageImplementation() { 18681 CGContextRef context; 18682 ubyte* rawData; 18683 18684 final: 18685 18686 void convertToRgbaBytes(ubyte[] where) @system { 18687 assert(where.length == this.width * this.height * 4); 18688 18689 // if rawData had a length.... 18690 //assert(rawData.length == where.length); 18691 for(long idx = 0; idx < where.length; idx += 4) { 18692 auto alpha = rawData[idx + 3]; 18693 if(alpha == 255) { 18694 where[idx + 0] = rawData[idx + 0]; // r 18695 where[idx + 1] = rawData[idx + 1]; // g 18696 where[idx + 2] = rawData[idx + 2]; // b 18697 where[idx + 3] = rawData[idx + 3]; // a 18698 } else { 18699 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 18700 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 18701 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 18702 where[idx + 3] = rawData[idx + 3]; // a 18703 18704 } 18705 } 18706 } 18707 18708 void setFromRgbaBytes(in ubyte[] where) @system { 18709 // FIXME: this is probably wrong 18710 assert(where.length == this.width * this.height * 4); 18711 18712 // if rawData had a length.... 18713 //assert(rawData.length == where.length); 18714 for(long idx = 0; idx < where.length; idx += 4) { 18715 auto alpha = where[idx + 3]; 18716 if(alpha == 255) { 18717 rawData[idx + 0] = where[idx + 0]; // r 18718 rawData[idx + 1] = where[idx + 1]; // g 18719 rawData[idx + 2] = where[idx + 2]; // b 18720 rawData[idx + 3] = where[idx + 3]; // a 18721 } else if(alpha == 0) { 18722 rawData[idx + 0] = 0; 18723 rawData[idx + 1] = 0; 18724 rawData[idx + 2] = 0; 18725 rawData[idx + 3] = 0; 18726 } else { 18727 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 18728 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 18729 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 18730 rawData[idx + 3] = where[idx + 3]; // a 18731 } 18732 } 18733 } 18734 18735 18736 void createImage(int width, int height, bool forcexshm=false, bool ignored = false) { 18737 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 18738 context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 18739 CGColorSpaceRelease(colorSpace); 18740 rawData = CGBitmapContextGetData(context); 18741 } 18742 void dispose() { 18743 CGContextRelease(context); 18744 } 18745 18746 void setPixel(int x, int y, Color c) @system { 18747 auto offset = (y * width + x) * 4; 18748 if (c.a == 255) { 18749 rawData[offset + 0] = c.r; 18750 rawData[offset + 1] = c.g; 18751 rawData[offset + 2] = c.b; 18752 rawData[offset + 3] = c.a; 18753 } else { 18754 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 18755 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 18756 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 18757 rawData[offset + 3] = c.a; 18758 } 18759 } 18760 } 18761 18762 mixin template NativeScreenPainterImplementation() { 18763 CGContextRef context; 18764 ubyte[4] _outlineComponents; 18765 NSView view; 18766 18767 Pen _activePen; 18768 Color _fillColor; 18769 Rectangle _clipRectangle; 18770 OperatingSystemFont _font; 18771 18772 OperatingSystemFont getFont() { 18773 if(_font is null) { 18774 static OperatingSystemFont _defaultFont; 18775 if(_defaultFont is null) { 18776 _defaultFont = new OperatingSystemFont(); 18777 _defaultFont.loadDefault(); 18778 } 18779 _font = _defaultFont; 18780 } 18781 18782 return _font; 18783 } 18784 18785 void create(PaintingHandle window) { 18786 // this.destiny = window; 18787 if(auto sw = cast(SimpleWindow) this.window) { 18788 context = sw.drawingContext; 18789 view = sw.view; 18790 } else { 18791 throw new NotYetImplementedException(); 18792 } 18793 } 18794 18795 void dispose() { 18796 view.setNeedsDisplay(true); 18797 } 18798 18799 bool manualInvalidations; 18800 void invalidateRect(Rectangle invalidRect) { } 18801 18802 // NotYetImplementedException 18803 void rasterOp(RasterOp op) { 18804 } 18805 void setClipRectangle(int, int, int, int) { 18806 } 18807 Size textSize(in char[] txt) { 18808 auto font = getFont(); 18809 return Size(font.stringWidth(txt), font.height()); 18810 } 18811 18812 void setFont(OperatingSystemFont font) { 18813 _font = font; 18814 //font.font.setInContext(context); 18815 } 18816 int fontHeight() { 18817 auto font = getFont(); 18818 return font.height; 18819 } 18820 18821 // end 18822 18823 void pen(Pen pen) { 18824 _activePen = pen; 18825 auto color = pen.color; // FIXME 18826 double alphaComponent = color.a/255.0f; 18827 CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 18828 18829 double[2] patternBuffer; 18830 double[] pattern; 18831 final switch(pen.style) { 18832 case Pen.Style.Solid: 18833 pattern = null; 18834 break; 18835 case Pen.Style.Dashed: 18836 patternBuffer[0] = 4; 18837 patternBuffer[1] = 1; 18838 pattern = patternBuffer[]; 18839 break; 18840 case Pen.Style.Dotted: 18841 patternBuffer[0] = 1; 18842 patternBuffer[1] = 1; 18843 pattern = patternBuffer[]; 18844 break; 18845 } 18846 18847 CGContextSetLineDash(context, 0, pattern.ptr, pattern.length); 18848 18849 if (color.a != 255) { 18850 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 18851 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 18852 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 18853 _outlineComponents[3] = color.a; 18854 } else { 18855 _outlineComponents[0] = color.r; 18856 _outlineComponents[1] = color.g; 18857 _outlineComponents[2] = color.b; 18858 _outlineComponents[3] = color.a; 18859 } 18860 } 18861 18862 @property void fillColor(Color color) { 18863 CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 18864 } 18865 18866 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 18867 // NotYetImplementedException for upper left/width/height 18868 auto cgImage = CGBitmapContextCreateImage(image.context); 18869 auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context)); 18870 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 18871 CGImageRelease(cgImage); 18872 } 18873 18874 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 18875 // FIXME: is this efficient? 18876 auto cgImage = CGBitmapContextCreateImage(s.handle); 18877 auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle)); 18878 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 18879 CGImageRelease(cgImage); 18880 } 18881 18882 18883 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 18884 // FIXME: alignment 18885 if (_outlineComponents[3] != 0) { 18886 CGContextSaveGState(context); 18887 auto invAlpha = 1.0f/_outlineComponents[3]; 18888 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 18889 _outlineComponents[1]*invAlpha, 18890 _outlineComponents[2]*invAlpha, 18891 _outlineComponents[3]/255.0f); 18892 18893 18894 18895 // FIXME: should we clip it to the bounding box? 18896 int textHeight = fontHeight; 18897 18898 auto lines = text.split('\n'); 18899 18900 const lineHeight = textHeight; 18901 textHeight *= lines.length; 18902 18903 int cy = y; 18904 18905 if(alignment & TextAlignment.VerticalBottom) { 18906 if(y2 <= 0) 18907 return; 18908 auto h = y2 - y; 18909 if(h > textHeight) { 18910 cy += h - textHeight; 18911 cy -= lineHeight / 2; 18912 } 18913 } else if(alignment & TextAlignment.VerticalCenter) { 18914 if(y2 <= 0) 18915 return; 18916 auto h = y2 - y; 18917 if(textHeight < h) { 18918 cy += (h - textHeight) / 2; 18919 //cy -= lineHeight / 4; 18920 } 18921 } 18922 18923 foreach(line; text.split('\n')) { 18924 int textWidth = this.textSize(line).width; 18925 18926 int px = x, py = cy; 18927 18928 if(alignment & TextAlignment.Center) { 18929 if(x2 <= 0) 18930 return; 18931 auto w = x2 - x; 18932 if(w > textWidth) 18933 px += (w - textWidth) / 2; 18934 } else if(alignment & TextAlignment.Right) { 18935 if(x2 <= 0) 18936 return; 18937 auto pos = x2 - textWidth; 18938 if(pos > x) 18939 px = pos; 18940 } 18941 18942 CGContextShowTextAtPoint(context, px, py + getFont.ascent /* this is cuz this picks baseline but i want bounding box */, line.ptr, line.length); 18943 18944 carry_on: 18945 cy += lineHeight + 4; 18946 } 18947 18948 // auto cfstr = cast(NSid)createCFString(text); 18949 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 18950 // NSPoint(x, y), null); 18951 // CFRelease(cfstr); 18952 CGContextRestoreGState(context); 18953 } 18954 } 18955 18956 void drawPixel(int x, int y) { 18957 auto rawData = CGBitmapContextGetData(context); 18958 auto width = CGBitmapContextGetWidth(context); 18959 auto height = CGBitmapContextGetHeight(context); 18960 auto offset = ((height - y - 1) * width + x) * 4; 18961 rawData[offset .. offset+4] = _outlineComponents; 18962 } 18963 18964 void drawLine(int x1, int y1, int x2, int y2) { 18965 CGPoint[2] linePoints; 18966 linePoints[0] = CGPoint(x1, y1); 18967 linePoints[1] = CGPoint(x2, y2); 18968 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 18969 } 18970 18971 void drawRectangleRounded(Point upperLeft, Point lowerRight, int borderRadius) { 18972 drawRectangle(upperLeft.x, upperLeft.y, lowerRight.x - upperLeft.x, lowerRight.y - upperLeft.y); // FIXME not rounded 18973 } 18974 18975 void drawRectangle(int x, int y, int width, int height) { 18976 CGContextBeginPath(context); 18977 auto rect = CGRect(CGPoint(x, y), CGSize(width, height)); 18978 CGContextAddRect(context, rect); 18979 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18980 } 18981 18982 void drawEllipse(int x1, int y1, int x2, int y2) { 18983 CGContextBeginPath(context); 18984 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 18985 CGContextAddEllipseInRect(context, rect); 18986 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18987 } 18988 18989 void drawArc(int x1, int y1, int width, int height, int start, int length) { 18990 // @@@BUG@@@ Does not support elliptic arc (width != height). 18991 CGContextBeginPath(context); 18992 int clockwise = 0; 18993 if(length < 0) { 18994 clockwise = 1; 18995 length = -length; 18996 } 18997 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 18998 start*PI/(180*64), (start+length)*PI/(180*64), clockwise); 18999 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 19000 } 19001 19002 void drawPolygon(Point[] intPoints) { 19003 CGContextBeginPath(context); 19004 CGPoint[16] pointsBuffer; 19005 CGPoint[] points; 19006 if(intPoints.length <= pointsBuffer.length) 19007 points = pointsBuffer[0 .. intPoints.length]; 19008 else 19009 points = new CGPoint[](intPoints.length); 19010 19011 foreach(idx, pt; intPoints) 19012 points[idx] = CGPoint(pt.x, pt.y); 19013 19014 CGContextAddLines(context, points.ptr, points.length); 19015 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 19016 } 19017 } 19018 19019 private bool appInitialized = false; 19020 void initializeApp() { 19021 if(appInitialized) 19022 return; 19023 synchronized { 19024 if(appInitialized) 19025 return; 19026 19027 auto app = NSApp(); // ensure the is initialized 19028 19029 auto dg = AppDelegate.alloc; 19030 globalAppDelegate = dg; 19031 NSApp.delegate_ = dg; 19032 19033 NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular); 19034 19035 appInitialized = true; 19036 } 19037 } 19038 19039 mixin template NativeSimpleWindowImplementation() { 19040 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 19041 initializeApp(); 19042 19043 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 19044 19045 auto window = NSWindow.alloc.initWithContentRect( 19046 contentRect, 19047 NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled, 19048 NSBackingStoreType.buffered, 19049 true 19050 ); 19051 19052 SimpleWindow.nativeMapping[cast(void*) window] = this; 19053 19054 window.title = MacString(title).borrow; 19055 19056 auto dg = SDWindowDelegate.alloc.init; 19057 dg.simpleWindow = this; 19058 window.delegate_ = dg; 19059 19060 auto view = SDGraphicsView.alloc.init; 19061 assert(view !is null); 19062 window.contentView = view; 19063 this.view = view; 19064 view.simpleWindow = this; 19065 19066 window.center(); 19067 19068 window.makeKeyAndOrderFront(null); 19069 19070 // no need to make a bitmap on mac since everything is double buffered already 19071 19072 // create area to draw on. 19073 createNewDrawingContext(width, height); 19074 19075 window.setBackgroundColor(NSColor.whiteColor); 19076 } 19077 19078 void createNewDrawingContext(int width, int height) { 19079 // FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay 19080 if(this.drawingContext) 19081 CGContextRelease(this.drawingContext); 19082 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 19083 this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 19084 CGColorSpaceRelease(colorSpace); 19085 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 19086 auto matrix = CGContextGetTextMatrix(drawingContext); 19087 matrix.c = -matrix.c; 19088 matrix.d = -matrix.d; 19089 CGContextSetTextMatrix(drawingContext, matrix); 19090 19091 } 19092 19093 void dispose() { 19094 closeWindow(); 19095 // window.release(); // closing the window does this automatically i think 19096 } 19097 void closeWindow() { 19098 if(timer) 19099 timer.invalidate(); 19100 window.close(); 19101 } 19102 19103 ScreenPainter getPainter(bool manualInvalidations) { 19104 return ScreenPainter(this, this.window, manualInvalidations); 19105 } 19106 19107 NSWindow window; 19108 NSTimer timer; 19109 NSView view; 19110 CGContextRef drawingContext; 19111 } 19112 } 19113 19114 version(without_opengl) {} else 19115 extern(System) nothrow @nogc { 19116 //enum uint GL_VERSION = 0x1F02; 19117 //const(char)* glGetString (/*GLenum*/uint); 19118 version(X11) { 19119 static if (!SdpyIsUsingIVGLBinds) { 19120 19121 enum GLX_X_RENDERABLE = 0x8012; 19122 enum GLX_DRAWABLE_TYPE = 0x8010; 19123 enum GLX_RENDER_TYPE = 0x8011; 19124 enum GLX_X_VISUAL_TYPE = 0x22; 19125 enum GLX_TRUE_COLOR = 0x8002; 19126 enum GLX_WINDOW_BIT = 0x00000001; 19127 enum GLX_RGBA_BIT = 0x00000001; 19128 enum GLX_COLOR_INDEX_BIT = 0x00000002; 19129 enum GLX_SAMPLE_BUFFERS = 0x186a0; 19130 enum GLX_SAMPLES = 0x186a1; 19131 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 19132 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 19133 } 19134 19135 // GLX_EXT_swap_control 19136 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 19137 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 19138 19139 //k8: ugly code to prevent warnings when sdpy is compiled into .a 19140 extern(System) { 19141 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 19142 } 19143 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 19144 19145 // this made public so we don't have to get it again and again 19146 public bool glXCreateContextAttribsARB_present () @system { 19147 if (glXCreateContextAttribsARBFn is cast(void*)1) { 19148 // get it 19149 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 19150 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 19151 } 19152 return (glXCreateContextAttribsARBFn !is null); 19153 } 19154 19155 // this made public so we don't have to get it again and again 19156 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) @system { 19157 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 19158 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 19159 } 19160 19161 // extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers 19162 extern(C) private __gshared int function(int) glXSwapIntervalMESA; 19163 19164 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 19165 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 19166 if (_glx_swapInterval_fn is null) { 19167 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 19168 if (_glx_swapInterval_fn is null) { 19169 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 19170 return; 19171 } 19172 version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); } 19173 } 19174 19175 if(glXSwapIntervalMESA is null) { 19176 // it seems to require both to actually take effect on many computers 19177 // idk why 19178 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA"); 19179 if(glXSwapIntervalMESA is null) 19180 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1; 19181 } 19182 19183 if(cast(void*) glXSwapIntervalMESA > cast(void*) 1) 19184 glXSwapIntervalMESA(wait ? 1 : 0); 19185 19186 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 19187 } 19188 } else version(Windows) { 19189 static if (!SdpyIsUsingIVGLBinds) { 19190 enum GL_TRUE = 1; 19191 enum GL_FALSE = 0; 19192 19193 public void* glbindGetProcAddress (const(char)* name) { 19194 void* res = wglGetProcAddress(name); 19195 if (res is null) { 19196 /+ 19197 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 19198 import core.sys.windows.windef, core.sys.windows.winbase; 19199 __gshared HINSTANCE dll = null; 19200 if (dll is null) { 19201 dll = LoadLibraryA("opengl32.dll"); 19202 if (dll is null) return null; // <32, but idc 19203 } 19204 res = GetProcAddress(dll, name); 19205 +/ 19206 res = GetProcAddress(gl.libHandle, name); 19207 } 19208 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 19209 return res; 19210 } 19211 } 19212 19213 19214 private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT; 19215 void wglSetVSync(bool wait) { 19216 if(wglSwapIntervalEXT is null) { 19217 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT"); 19218 if(wglSwapIntervalEXT is null) 19219 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1; 19220 } 19221 if(cast(void*) wglSwapIntervalEXT is cast(void*) 1) 19222 return; 19223 19224 wglSwapIntervalEXT(wait ? 1 : 0); 19225 } 19226 19227 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 19228 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 19229 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 19230 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 19231 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 19232 19233 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 19234 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 19235 19236 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 19237 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 19238 19239 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 19240 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 19241 19242 void wglInitOtherFunctions () { 19243 if (wglCreateContextAttribsARB is null) { 19244 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 19245 } 19246 } 19247 } 19248 19249 static if (!SdpyIsUsingIVGLBinds) { 19250 19251 interface GL { 19252 extern(System) @nogc nothrow: 19253 19254 void glGetIntegerv(int, void*); 19255 void glMatrixMode(int); 19256 void glPushMatrix(); 19257 void glLoadIdentity(); 19258 void glOrtho(double, double, double, double, double, double); 19259 void glFrustum(double, double, double, double, double, double); 19260 19261 void glPopMatrix(); 19262 void glEnable(int); 19263 void glDisable(int); 19264 void glClear(int); 19265 void glBegin(int); 19266 void glVertex2f(float, float); 19267 void glVertex3f(float, float, float); 19268 void glEnd(); 19269 void glColor3b(byte, byte, byte); 19270 void glColor3ub(ubyte, ubyte, ubyte); 19271 void glColor4b(byte, byte, byte, byte); 19272 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 19273 void glColor3i(int, int, int); 19274 void glColor3ui(uint, uint, uint); 19275 void glColor4i(int, int, int, int); 19276 void glColor4ui(uint, uint, uint, uint); 19277 void glColor3f(float, float, float); 19278 void glColor4f(float, float, float, float); 19279 void glTranslatef(float, float, float); 19280 void glScalef(float, float, float); 19281 version(X11) { 19282 void glSecondaryColor3b(byte, byte, byte); 19283 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 19284 void glSecondaryColor3i(int, int, int); 19285 void glSecondaryColor3ui(uint, uint, uint); 19286 void glSecondaryColor3f(float, float, float); 19287 } 19288 19289 void glDrawElements(int, int, int, void*); 19290 19291 void glRotatef(float, float, float, float); 19292 19293 uint glGetError(); 19294 19295 void glDeleteTextures(int, uint*); 19296 19297 19298 void glRasterPos2i(int, int); 19299 void glDrawPixels(int, int, uint, uint, void*); 19300 void glClearColor(float, float, float, float); 19301 19302 19303 void glPixelStorei(uint, int); 19304 19305 void glGenTextures(uint, uint*); 19306 void glBindTexture(int, int); 19307 void glTexParameteri(uint, uint, int); 19308 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 19309 void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*); 19310 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 19311 /*GLsizei*/int width, /*GLsizei*/int height, 19312 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 19313 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 19314 19315 void glLineWidth(int); 19316 19317 19318 void glTexCoord2f(float, float); 19319 void glVertex2i(int, int); 19320 void glBlendFunc (int, int); 19321 void glDepthFunc (int); 19322 void glViewport(int, int, int, int); 19323 19324 void glClearDepth(double); 19325 19326 void glReadBuffer(uint); 19327 void glReadPixels(int, int, int, int, int, int, void*); 19328 19329 void glScissor(GLint x, GLint y, GLsizei width, GLsizei height); 19330 19331 void glFlush(); 19332 void glFinish(); 19333 19334 version(Windows) { 19335 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 19336 HGLRC wglCreateContext(HDC); 19337 HGLRC wglCreateLayerContext(HDC, int); 19338 BOOL wglDeleteContext(HGLRC); 19339 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 19340 HGLRC wglGetCurrentContext(); 19341 HDC wglGetCurrentDC(); 19342 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 19343 PROC wglGetProcAddress(LPCSTR); 19344 BOOL wglMakeCurrent(HDC, HGLRC); 19345 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 19346 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 19347 BOOL wglShareLists(HGLRC, HGLRC); 19348 BOOL wglSwapLayerBuffers(HDC, UINT); 19349 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 19350 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 19351 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 19352 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 19353 } 19354 19355 } 19356 19357 interface GL3 { 19358 extern(System) @nogc nothrow: 19359 19360 void glGenVertexArrays(GLsizei, GLuint*); 19361 void glBindVertexArray(GLuint); 19362 void glDeleteVertexArrays(GLsizei, const(GLuint)*); 19363 void glGenerateMipmap(GLenum); 19364 void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 19365 void glStencilMask(GLuint); 19366 void glStencilFunc(GLenum, GLint, GLuint); 19367 void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 19368 void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 19369 GLuint glCreateProgram(); 19370 GLuint glCreateShader(GLenum); 19371 void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 19372 void glCompileShader(GLuint); 19373 void glGetShaderiv(GLuint, GLenum, GLint*); 19374 void glAttachShader(GLuint, GLuint); 19375 void glBindAttribLocation(GLuint, GLuint, const(GLchar)*); 19376 void glLinkProgram(GLuint); 19377 void glGetProgramiv(GLuint, GLenum, GLint*); 19378 void glDeleteProgram(GLuint); 19379 void glDeleteShader(GLuint); 19380 GLint glGetUniformLocation(GLuint, const(GLchar)*); 19381 void glGenBuffers(GLsizei, GLuint*); 19382 19383 void glUniform1f(GLint location, GLfloat v0); 19384 void glUniform2f(GLint location, GLfloat v0, GLfloat v1); 19385 void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); 19386 void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); 19387 void glUniform1i(GLint location, GLint v0); 19388 void glUniform2i(GLint location, GLint v0, GLint v1); 19389 void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2); 19390 void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); 19391 void glUniform1ui(GLint location, GLuint v0); 19392 void glUniform2ui(GLint location, GLuint v0, GLuint v1); 19393 void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2); 19394 void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); 19395 void glUniform1fv(GLint location, GLsizei count, const GLfloat *value); 19396 void glUniform2fv(GLint location, GLsizei count, const GLfloat *value); 19397 void glUniform3fv(GLint location, GLsizei count, const GLfloat *value); 19398 void glUniform4fv(GLint location, GLsizei count, const GLfloat *value); 19399 void glUniform1iv(GLint location, GLsizei count, const GLint *value); 19400 void glUniform2iv(GLint location, GLsizei count, const GLint *value); 19401 void glUniform3iv(GLint location, GLsizei count, const GLint *value); 19402 void glUniform4iv(GLint location, GLsizei count, const GLint *value); 19403 void glUniform1uiv(GLint location, GLsizei count, const GLuint *value); 19404 void glUniform2uiv(GLint location, GLsizei count, const GLuint *value); 19405 void glUniform3uiv(GLint location, GLsizei count, const GLuint *value); 19406 void glUniform4uiv(GLint location, GLsizei count, const GLuint *value); 19407 void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19408 void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19409 void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19410 void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19411 void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19412 void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19413 void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19414 void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19415 void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 19416 19417 void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); 19418 void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); 19419 void glDrawArrays(GLenum, GLint, GLsizei); 19420 void glStencilOp(GLenum, GLenum, GLenum); 19421 void glUseProgram(GLuint); 19422 void glCullFace(GLenum); 19423 void glFrontFace(GLenum); 19424 void glActiveTexture(GLenum); 19425 void glBindBuffer(GLenum, GLuint); 19426 void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum); 19427 void glEnableVertexAttribArray(GLuint); 19428 void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 19429 void glUniform1i(GLint, GLint); 19430 void glUniform2fv(GLint, GLsizei, const(GLfloat)*); 19431 void glDisableVertexAttribArray(GLuint); 19432 void glDeleteBuffers(GLsizei, const(GLuint)*); 19433 void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); 19434 void glLogicOp (GLenum opcode); 19435 void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 19436 void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers); 19437 void glGenFramebuffers (GLsizei n, GLuint* framebuffers); 19438 GLenum glCheckFramebufferStatus (GLenum target); 19439 void glBindFramebuffer (GLenum target, GLuint framebuffer); 19440 } 19441 19442 interface GL4 { 19443 extern(System) @nogc nothrow: 19444 19445 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 19446 /*GLsizei*/int width, /*GLsizei*/int height, 19447 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 19448 } 19449 19450 interface GLU { 19451 extern(System) @nogc nothrow: 19452 19453 void gluLookAt(double, double, double, double, double, double, double, double, double); 19454 void gluPerspective(double, double, double, double); 19455 19456 char* gluErrorString(uint); 19457 } 19458 19459 19460 enum GL_RED = 0x1903; 19461 enum GL_ALPHA = 0x1906; 19462 19463 enum uint GL_FRONT = 0x0404; 19464 19465 enum uint GL_BLEND = 0x0be2; 19466 enum uint GL_LEQUAL = 0x0203; 19467 19468 19469 enum uint GL_RGB = 0x1907; 19470 enum uint GL_BGRA = 0x80e1; 19471 enum uint GL_RGBA = 0x1908; 19472 enum uint GL_RGBA8 = 0x8058; 19473 enum uint GL_TEXTURE_2D = 0x0DE1; 19474 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 19475 enum uint GL_NEAREST = 0x2600; 19476 enum uint GL_LINEAR = 0x2601; 19477 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 19478 enum uint GL_TEXTURE_WRAP_S = 0x2802; 19479 enum uint GL_TEXTURE_WRAP_T = 0x2803; 19480 enum uint GL_REPEAT = 0x2901; 19481 enum uint GL_CLAMP = 0x2900; 19482 enum uint GL_CLAMP_TO_EDGE = 0x812F; 19483 enum uint GL_CLAMP_TO_BORDER = 0x812D; 19484 enum uint GL_DECAL = 0x2101; 19485 enum uint GL_MODULATE = 0x2100; 19486 enum uint GL_TEXTURE_ENV = 0x2300; 19487 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 19488 enum uint GL_REPLACE = 0x1E01; 19489 enum uint GL_LIGHTING = 0x0B50; 19490 enum uint GL_DITHER = 0x0BD0; 19491 19492 enum uint GL_NO_ERROR = 0; 19493 19494 19495 19496 enum int GL_VIEWPORT = 0x0BA2; 19497 enum int GL_MODELVIEW = 0x1700; 19498 enum int GL_TEXTURE = 0x1702; 19499 enum int GL_PROJECTION = 0x1701; 19500 enum int GL_DEPTH_TEST = 0x0B71; 19501 19502 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 19503 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 19504 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 19505 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 19506 19507 enum int GL_POINTS = 0x0000; 19508 enum int GL_LINES = 0x0001; 19509 enum int GL_LINE_LOOP = 0x0002; 19510 enum int GL_LINE_STRIP = 0x0003; 19511 enum int GL_TRIANGLES = 0x0004; 19512 enum int GL_TRIANGLE_STRIP = 5; 19513 enum int GL_TRIANGLE_FAN = 6; 19514 enum int GL_QUADS = 7; 19515 enum int GL_QUAD_STRIP = 8; 19516 enum int GL_POLYGON = 9; 19517 19518 alias GLvoid = void; 19519 alias GLboolean = ubyte; 19520 alias GLint = int; 19521 alias GLuint = uint; 19522 alias GLenum = uint; 19523 alias GLchar = char; 19524 alias GLsizei = int; 19525 alias GLfloat = float; 19526 alias GLintptr = size_t; 19527 alias GLsizeiptr = ptrdiff_t; 19528 19529 19530 enum uint GL_INVALID_ENUM = 0x0500; 19531 19532 enum uint GL_ZERO = 0; 19533 enum uint GL_ONE = 1; 19534 19535 enum uint GL_BYTE = 0x1400; 19536 enum uint GL_UNSIGNED_BYTE = 0x1401; 19537 enum uint GL_SHORT = 0x1402; 19538 enum uint GL_UNSIGNED_SHORT = 0x1403; 19539 enum uint GL_INT = 0x1404; 19540 enum uint GL_UNSIGNED_INT = 0x1405; 19541 enum uint GL_FLOAT = 0x1406; 19542 enum uint GL_2_BYTES = 0x1407; 19543 enum uint GL_3_BYTES = 0x1408; 19544 enum uint GL_4_BYTES = 0x1409; 19545 enum uint GL_DOUBLE = 0x140A; 19546 19547 enum uint GL_STREAM_DRAW = 0x88E0; 19548 19549 enum uint GL_CCW = 0x0901; 19550 19551 enum uint GL_STENCIL_TEST = 0x0B90; 19552 enum uint GL_SCISSOR_TEST = 0x0C11; 19553 19554 enum uint GL_EQUAL = 0x0202; 19555 enum uint GL_NOTEQUAL = 0x0205; 19556 19557 enum uint GL_ALWAYS = 0x0207; 19558 enum uint GL_KEEP = 0x1E00; 19559 19560 enum uint GL_INCR = 0x1E02; 19561 19562 enum uint GL_INCR_WRAP = 0x8507; 19563 enum uint GL_DECR_WRAP = 0x8508; 19564 19565 enum uint GL_CULL_FACE = 0x0B44; 19566 enum uint GL_BACK = 0x0405; 19567 19568 enum uint GL_FRAGMENT_SHADER = 0x8B30; 19569 enum uint GL_VERTEX_SHADER = 0x8B31; 19570 19571 enum uint GL_COMPILE_STATUS = 0x8B81; 19572 enum uint GL_LINK_STATUS = 0x8B82; 19573 19574 enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893; 19575 19576 enum uint GL_STATIC_DRAW = 0x88E4; 19577 19578 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 19579 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 19580 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 19581 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 19582 19583 enum uint GL_GENERATE_MIPMAP = 0x8191; 19584 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 19585 19586 enum uint GL_TEXTURE0 = 0x84C0U; 19587 enum uint GL_TEXTURE1 = 0x84C1U; 19588 19589 enum uint GL_ARRAY_BUFFER = 0x8892; 19590 19591 enum uint GL_SRC_COLOR = 0x0300; 19592 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 19593 enum uint GL_SRC_ALPHA = 0x0302; 19594 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 19595 enum uint GL_DST_ALPHA = 0x0304; 19596 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 19597 enum uint GL_DST_COLOR = 0x0306; 19598 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 19599 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 19600 19601 enum uint GL_INVERT = 0x150AU; 19602 19603 enum uint GL_DEPTH_STENCIL = 0x84F9U; 19604 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 19605 19606 enum uint GL_FRAMEBUFFER = 0x8D40U; 19607 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 19608 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 19609 19610 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 19611 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 19612 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 19613 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 19614 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 19615 19616 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 19617 enum uint GL_CLEAR = 0x1500U; 19618 enum uint GL_COPY = 0x1503U; 19619 enum uint GL_XOR = 0x1506U; 19620 19621 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 19622 19623 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 19624 19625 } 19626 } 19627 19628 /++ 19629 History: 19630 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. 19631 +/ 19632 __gshared bool gluSuccessfullyLoaded = true; 19633 19634 version(without_opengl) {} else { 19635 static if(!SdpyIsUsingIVGLBinds) { 19636 version(Windows) { 19637 mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl; 19638 mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu; 19639 } else { 19640 mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl; 19641 mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu; 19642 } 19643 mixin DynamicLoadSupplementalOpenGL!(GL3) gl3; 19644 19645 19646 shared static this() { 19647 gl.loadDynamicLibrary(); 19648 19649 // FIXME: this is NOT actually required and should NOT fail if it is not loaded 19650 // unless those functions are actually used 19651 // go to mark b openGlLibrariesSuccessfullyLoaded = false; 19652 glu.loadDynamicLibrary(); 19653 } 19654 } 19655 } 19656 19657 /++ 19658 Convenience method for converting D arrays to opengl buffer data 19659 19660 I would LOVE to overload it with the original glBufferData, but D won't 19661 let me since glBufferData is a function pointer :( 19662 19663 Added: August 25, 2020 (version 8.5) 19664 +/ 19665 version(without_opengl) {} else 19666 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) { 19667 glBufferData(target, data.length, data.ptr, usage); 19668 } 19669 19670 /++ 19671 History: 19672 Added September 1, 2024 19673 +/ 19674 version(without_opengl) {} else 19675 void glBufferSubDataSlice(GLenum target, size_t offset, const(void[]) data, GLenum usage) { 19676 glBufferSubData(target, offset, data.length, data.ptr); 19677 } 19678 19679 /++ 19680 Convenience class for using opengl shaders. 19681 19682 Ensure that you've loaded opengl 3+ and set your active 19683 context before trying to use this. 19684 19685 Added: August 25, 2020 (version 8.5) 19686 +/ 19687 version(without_opengl) {} else 19688 final class OpenGlShader { 19689 private int shaderProgram_; 19690 private @property void shaderProgram(int a) { 19691 shaderProgram_ = a; 19692 } 19693 /// Get the program ID for use in OpenGL functions. 19694 public @property int shaderProgram() { 19695 return shaderProgram_; 19696 } 19697 19698 /++ 19699 19700 +/ 19701 static struct Source { 19702 uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc. 19703 string code; /// 19704 } 19705 19706 /++ 19707 Helper method to just compile some shader code and check for errors 19708 while you do glCreateShader, etc. on the outside yourself. 19709 19710 This just does `glShaderSource` and `glCompileShader` for the given code. 19711 19712 If you the OpenGlShader class constructor, you never need to call this yourself. 19713 +/ 19714 static void compile(int sid, Source code) { 19715 const(char)*[1] buffer; 19716 int[1] lengthBuffer; 19717 19718 buffer[0] = code.code.ptr; 19719 lengthBuffer[0] = cast(int) code.code.length; 19720 19721 glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr); 19722 glCompileShader(sid); 19723 19724 int success; 19725 glGetShaderiv(sid, GL_COMPILE_STATUS, &success); 19726 if(!success) { 19727 char[512] info; 19728 int len; 19729 glGetShaderInfoLog(sid, info.length, &len, info.ptr); 19730 19731 throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]); 19732 } 19733 } 19734 19735 /++ 19736 Calls `glLinkProgram` and throws if error a occurs. 19737 19738 If you the OpenGlShader class constructor, you never need to call this yourself. 19739 +/ 19740 static void link(int shaderProgram) { 19741 glLinkProgram(shaderProgram); 19742 int success; 19743 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 19744 if(!success) { 19745 char[512] info; 19746 int len; 19747 glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr); 19748 19749 throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]); 19750 } 19751 } 19752 19753 /++ 19754 Constructs the shader object by calling `glCreateProgram`, then 19755 compiling each given [Source], and finally, linking them together. 19756 19757 Throws: on compile or link failure. 19758 +/ 19759 this(Source[] codes...) { 19760 shaderProgram = glCreateProgram(); 19761 19762 int[16] shadersBufferStack; 19763 19764 int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 19765 shadersBufferStack[0 .. codes.length] : 19766 new int[](codes.length); 19767 19768 foreach(idx, code; codes) { 19769 shadersBuffer[idx] = glCreateShader(code.type); 19770 19771 compile(shadersBuffer[idx], code); 19772 19773 glAttachShader(shaderProgram, shadersBuffer[idx]); 19774 } 19775 19776 link(shaderProgram); 19777 19778 foreach(s; shadersBuffer) 19779 glDeleteShader(s); 19780 } 19781 19782 /// Calls `glUseProgram(this.shaderProgram)` 19783 void use() { 19784 glUseProgram(this.shaderProgram); 19785 } 19786 19787 /// Deletes the program. 19788 void delete_() { 19789 glDeleteProgram(shaderProgram); 19790 shaderProgram = 0; 19791 } 19792 19793 /++ 19794 [OpenGlShader.uniforms].name gives you one of these. 19795 19796 You can get the id out of it or just assign 19797 +/ 19798 static struct Uniform { 19799 /// the id passed to glUniform* 19800 int id; 19801 19802 /// Assigns the 4 floats. You will probably have to call this via the .opAssign name 19803 void opAssign(float x, float y, float z, float w) { 19804 if(id != -1) 19805 glUniform4f(id, x, y, z, w); 19806 } 19807 19808 void opAssign(float x) { 19809 if(id != -1) 19810 glUniform1f(id, x); 19811 } 19812 19813 void opAssign(float x, float y) { 19814 if(id != -1) 19815 glUniform2f(id, x, y); 19816 } 19817 19818 void opAssign(T)(T t) { 19819 t.glUniform(id); 19820 } 19821 } 19822 19823 static struct UniformsHelper { 19824 OpenGlShader _shader; 19825 19826 @property Uniform opDispatch(string name)() { 19827 auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); 19828 // FIXME: decide what to do here; the exception is liable to be swallowed by the event syste 19829 //if(i == -1) 19830 //throw new Exception("Could not find uniform " ~ name); 19831 return Uniform(i); 19832 } 19833 19834 @property void opDispatch(string name, T)(T t) { 19835 Uniform f = this.opDispatch!name; 19836 t.glUniform(f); 19837 } 19838 } 19839 19840 /++ 19841 Gives access to the uniforms through dot access. 19842 `OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo"); 19843 +/ 19844 @property UniformsHelper uniforms() { return UniformsHelper(this); } 19845 } 19846 19847 version(without_opengl) {} else { 19848 /++ 19849 A static container of experimental types and value constructors for opengl 3+ shaders. 19850 19851 19852 You can declare variables like: 19853 19854 ``` 19855 OGL.vec3f something; 19856 ``` 19857 19858 But generally it would be used with [OpenGlShader]'s uniform helpers like 19859 19860 ``` 19861 shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific 19862 ``` 19863 19864 This is still extremely experimental, not very useful at this point, and thus subject to change at random. 19865 19866 19867 History: 19868 Added December 7, 2021. Not yet stable. 19869 +/ 19870 final class OGL { 19871 static: 19872 19873 private template typeFromSpecifier(string specifier) { 19874 static if(specifier == "f") 19875 alias typeFromSpecifier = GLfloat; 19876 else static if(specifier == "i") 19877 alias typeFromSpecifier = GLint; 19878 else static if(specifier == "ui") 19879 alias typeFromSpecifier = GLuint; 19880 else static assert(0, "I don't know this ogl type suffix " ~ specifier); 19881 } 19882 19883 private template CommonType(T...) { 19884 static if(T.length == 1) 19885 alias CommonType = T[0]; 19886 else static if(is(typeof(true ? T[0].init : T[1].init) C)) 19887 alias CommonType = CommonType!(C, T[2 .. $]); 19888 } 19889 19890 private template typesToSpecifier(T...) { 19891 static if(is(CommonType!T == float)) 19892 enum typesToSpecifier = "f"; 19893 else static if(is(CommonType!T == int)) 19894 enum typesToSpecifier = "i"; 19895 else static if(is(CommonType!T == uint)) 19896 enum typesToSpecifier = "ui"; 19897 else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof); 19898 } 19899 19900 private template genNames(size_t dim, size_t dim2 = 0) { 19901 string helper() { 19902 string s; 19903 if(dim2) { 19904 static if(__VERSION__ < 2102) 19905 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = void;"; // stupid compiler bug 19906 else 19907 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix = 0;"; 19908 } else { 19909 if(dim > 0) s ~= "type x = 0;"; 19910 if(dim > 1) s ~= "type y = 0;"; 19911 if(dim > 2) s ~= "type z = 0;"; 19912 if(dim > 3) s ~= "type w = 0;"; 19913 } 19914 19915 s ~= "this(typeof(this.tupleof) args) { this.tupleof = args; }"; 19916 if(dim2) 19917 s ~= "this(type["~(dim*dim2).stringof~"] t) { (cast(typeof(t)) this.matrix)[] = t[]; }"; 19918 19919 return s; 19920 } 19921 19922 enum genNames = helper(); 19923 } 19924 19925 // there's vec, arrays of vec, mat, and arrays of mat 19926 template opDispatch(string name) 19927 if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat")) 19928 { 19929 static if(name[4] == 'x') { 19930 enum dimX = cast(int) (name[3] - '0'); 19931 static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]); 19932 19933 enum dimY = cast(int) (name[5] - '0'); 19934 static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]); 19935 19936 enum isArray = name[$ - 1] == 'v'; 19937 enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $]; 19938 alias type = typeFromSpecifier!typeSpecifier; 19939 } else { 19940 enum dim = cast(int) (name[3] - '0'); 19941 static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]); 19942 enum isArray = name[$ - 1] == 'v'; 19943 enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $]; 19944 alias type = typeFromSpecifier!typeSpecifier; 19945 } 19946 19947 align(1) 19948 struct opDispatch { 19949 align(1): 19950 static if(name[4] == 'x') 19951 mixin(genNames!(dimX, dimY)); 19952 else 19953 mixin(genNames!dim); 19954 19955 private void glUniform(OpenGlShader.Uniform assignTo) { 19956 glUniform(assignTo.id); 19957 } 19958 private void glUniform(int assignTo) { 19959 static if(name[4] == 'x') { 19960 static if(name[3] == name[5]) { 19961 // import std.stdio; writeln(name, " ", this.matrix, dimX, " ", dimY); 19962 mixin("glUniformMatrix" ~ name[5 .. $] ~ "v")(assignTo, 1, true, &this.matrix[0][0]); 19963 } else { 19964 mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, 1, false, this.matrix.ptr); 19965 } 19966 } else 19967 mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof); 19968 } 19969 } 19970 } 19971 19972 auto vec(T...)(T members) { 19973 return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members); 19974 } 19975 } 19976 19977 void checkGlError() { 19978 auto error = glGetError(); 19979 int[] errors; 19980 string[] errorStrings; 19981 while(error != GL_NO_ERROR) { 19982 errors ~= error; 19983 switch(error) { 19984 case 0x0500: errorStrings ~= "GL_INVALID_ENUM"; break; 19985 case 0x0501: errorStrings ~= "GL_INVALID_VALUE"; break; 19986 case 0x0502: errorStrings ~= "GL_INVALID_OPERATION"; break; 19987 case 0x0503: errorStrings ~= "GL_STACK_OVERFLOW"; break; 19988 case 0x0504: errorStrings ~= "GL_STACK_UNDERFLOW"; break; 19989 case 0x0505: errorStrings ~= "GL_OUT_OF_MEMORY"; break; 19990 default: errorStrings ~= "idk"; 19991 } 19992 error = glGetError(); 19993 } 19994 if(errors.length) 19995 throw ArsdException!"glGetError"(errors, errorStrings); 19996 } 19997 19998 /++ 19999 A matrix for simple uses that easily integrates with [OpenGlShader]. 20000 20001 Might not be useful to you since it only as some simple functions and 20002 probably isn't that fast. 20003 20004 Note it uses an inline static array for its storage, so copying it 20005 may be expensive. 20006 +/ 20007 struct BasicMatrix(int columns, int rows, T = float) { 20008 static import core.stdc.math; 20009 static if(is(T == float)) { 20010 alias cos = core.stdc.math.cosf; 20011 alias sin = core.stdc.math.sinf; 20012 } else { 20013 alias cos = core.stdc.math.cos; 20014 alias sin = core.stdc.math.sin; 20015 } 20016 20017 T[columns * rows] data = 0.0; 20018 20019 /++ 20020 20021 +/ 20022 this(T[columns * rows] data) { 20023 this.data = data; 20024 } 20025 20026 /++ 20027 Basic operations that operate *in place*. 20028 +/ 20029 static if(columns == 4 && rows == 4) 20030 void translate(T x, T y, T z) { 20031 BasicMatrix m = [ 20032 1, 0, 0, x, 20033 0, 1, 0, y, 20034 0, 0, 1, z, 20035 0, 0, 0, 1 20036 ]; 20037 20038 this *= m; 20039 } 20040 20041 /// ditto 20042 static if(columns == 4 && rows == 4) 20043 void scale(T x, T y, T z) { 20044 BasicMatrix m = [ 20045 x, 0, 0, 0, 20046 0, y, 0, 0, 20047 0, 0, z, 0, 20048 0, 0, 0, 1 20049 ]; 20050 20051 this *= m; 20052 } 20053 20054 /// ditto 20055 static if(columns == 4 && rows == 4) 20056 void rotateX(T theta) { 20057 BasicMatrix m = [ 20058 1, 0, 0, 0, 20059 0, cos(theta), -sin(theta), 0, 20060 0, sin(theta), cos(theta), 0, 20061 0, 0, 0, 1 20062 ]; 20063 20064 this *= m; 20065 } 20066 20067 /// ditto 20068 static if(columns == 4 && rows == 4) 20069 void rotateY(T theta) { 20070 BasicMatrix m = [ 20071 cos(theta), 0, sin(theta), 0, 20072 0, 1, 0, 0, 20073 -sin(theta), 0, cos(theta), 0, 20074 0, 0, 0, 1 20075 ]; 20076 20077 this *= m; 20078 } 20079 20080 /// ditto 20081 static if(columns == 4 && rows == 4) 20082 void rotateZ(T theta) { 20083 BasicMatrix m = [ 20084 cos(theta), -sin(theta), 0, 0, 20085 sin(theta), cos(theta), 0, 0, 20086 0, 0, 1, 0, 20087 0, 0, 0, 1 20088 ]; 20089 20090 this *= m; 20091 } 20092 20093 /++ 20094 20095 +/ 20096 static if(columns == rows) 20097 static BasicMatrix identity() { 20098 BasicMatrix m; 20099 foreach(i; 0 .. columns) 20100 m.data[0 + i + i * columns] = 1.0; 20101 return m; 20102 } 20103 20104 static if(columns == rows) 20105 void loadIdentity() { 20106 this = identity(); 20107 } 20108 20109 static if(columns == 4 && rows == 4) 20110 static BasicMatrix ortho(T l, T r, T b, T t, T n, T f) { 20111 return BasicMatrix([ 20112 2/(r-l), 0, 0, -(r+l)/(r-l), 20113 0, 2/(t-b), 0, -(t+b)/(t-b), 20114 0, 0, -2/(f-n), -(f+n)/(f-n), 20115 0, 0, 0, 1 20116 ]); 20117 } 20118 20119 static if(columns == 4 && rows == 4) 20120 void loadOrtho(T l, T r, T b, T t, T n, T f) { 20121 this = ortho(l, r, b, t, n, f); 20122 } 20123 20124 void opOpAssign(string op : "+")(const BasicMatrix rhs) { 20125 this.data[] += rhs.data; 20126 } 20127 void opOpAssign(string op : "-")(const BasicMatrix rhs) { 20128 this.data[] -= rhs.data; 20129 } 20130 void opOpAssign(string op : "*")(const T rhs) { 20131 this.data[] *= rhs; 20132 } 20133 void opOpAssign(string op : "/")(const T rhs) { 20134 this.data[] /= rhs; 20135 } 20136 void opOpAssign(string op : "*", BM : BasicMatrix!(rhsColumns, rhsRows, rhsT), int rhsColumns, int rhsRows, rhsT)(const BM rhs) { 20137 static assert(columns == rhsRows); 20138 auto multiplySize = columns; 20139 20140 auto tmp = this.data; // copy cuz it is a value type 20141 20142 int idx = 0; 20143 foreach(r; 0 .. rows) 20144 foreach(c; 0 .. columns) { 20145 T sum = 0.0; 20146 20147 foreach(i; 0 .. multiplySize) 20148 sum += this.data[r * columns + i] * rhs.data[i * rhsColumns + c]; 20149 20150 tmp[idx++] = sum; 20151 } 20152 20153 this.data = tmp; 20154 } 20155 } 20156 20157 unittest { 20158 auto m = BasicMatrix!(2, 2)([ 20159 1, 2, 20160 3, 4 20161 ]); 20162 20163 auto m2 = BasicMatrix!(2, 2)([ 20164 5, 6, 20165 7, 8 20166 ]); 20167 20168 //import std.conv; 20169 m *= m2; 20170 assert(m.data == [ 20171 19, 22, 20172 43, 50 20173 ]);//, to!string(m.data)); 20174 } 20175 20176 20177 20178 class GlObjectBase { 20179 protected uint _vao; 20180 protected uint _elementsCount; 20181 20182 protected uint element_buffer; 20183 20184 void gen() { 20185 glGenVertexArrays(1, &_vao); 20186 } 20187 20188 void bind() { 20189 glBindVertexArray(_vao); 20190 } 20191 20192 void dispose() { 20193 glDeleteVertexArrays(1, &_vao); 20194 } 20195 20196 void draw() { 20197 bind(); 20198 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer); 20199 glDrawElements(GL_TRIANGLES, _elementsCount, GL_UNSIGNED_INT, null); 20200 } 20201 } 20202 20203 /++ 20204 20205 +/ 20206 class GlObject(T) : GlObjectBase { 20207 protected uint VBO; 20208 20209 this(T[] arr, uint[] indices) { 20210 gen(); 20211 bind(); 20212 20213 glGenBuffers(1, &VBO); 20214 glGenBuffers(1, &element_buffer); 20215 20216 glBindBuffer(GL_ARRAY_BUFFER, VBO); 20217 glBufferDataSlice(GL_ARRAY_BUFFER, arr, GL_STATIC_DRAW); 20218 20219 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer); 20220 glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); 20221 _elementsCount = cast(int) indices.length; 20222 20223 foreach(int idx, memberName; __traits(allMembers, T)) { 20224 static if(memberName != "__ctor") { 20225 static if(is(typeof(__traits(getMember, T, memberName)) == float[N], size_t N)) { 20226 glVertexAttribPointer(idx, N, GL_FLOAT, GL_FALSE, T.sizeof, cast(void*) __traits(getMember, T, memberName).offsetof); 20227 glEnableVertexAttribArray(idx); 20228 } else static assert(0); } 20229 } 20230 } 20231 20232 static string generateShaderDefinitions() { 20233 string code; 20234 20235 foreach(idx, memberName; __traits(allMembers, T)) { 20236 // never use stringof ladies and gents it has a LU thing at the end of it 20237 static if(memberName != "__ctor") 20238 code ~= "layout (location = " ~ idx.stringof[0..$-2] ~ ") in " ~ typeToGl!(typeof(__traits(getMember, T, memberName))) ~ " " ~ memberName ~ ";\n"; 20239 } 20240 20241 return code; 20242 } 20243 } 20244 20245 private string typeToGl(T)() { 20246 static if(is(T == float[4])) 20247 return "vec4"; 20248 else static if(is(T == float[3])) 20249 return "vec3"; 20250 else static if(is(T == float[2])) 20251 return "vec2"; 20252 else static assert(0, T.stringof); 20253 } 20254 20255 20256 } 20257 20258 version(linux) { 20259 version(with_eventloop) {} else { 20260 private int epollFd = -1; 20261 void prepareEventLoop() { 20262 if(epollFd != -1) 20263 return; // already initialized, no need to do it again 20264 import ep = core.sys.linux.epoll; 20265 20266 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 20267 if(epollFd == -1) 20268 throw new Exception("epoll create failure"); 20269 } 20270 } 20271 } else version(Posix) { 20272 void prepareEventLoop() {} 20273 } 20274 20275 version(X11) { 20276 import core.stdc.locale : LC_ALL; // rdmd fix 20277 __gshared bool sdx_isUTF8Locale; 20278 20279 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 20280 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 20281 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 20282 // anal magic is here. I (Ketmar) hope you like it. 20283 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 20284 // always return correct unicode symbols. The detection is here 'cause user can change locale 20285 // later. 20286 20287 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 20288 shared static this () @system { 20289 if(!librariesSuccessfullyLoaded) 20290 return; 20291 20292 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 20293 20294 // this doesn't hurt; it may add some locking, but the speed is still 20295 // allows doing 60 FPS videogames; also, ignore the result, as most 20296 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 20297 // never seen this failing). 20298 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 20299 20300 setlocale(LC_ALL, ""); 20301 // check if out locale is UTF-8 20302 auto lct = setlocale(LC_CTYPE, null); 20303 if (lct is null) { 20304 sdx_isUTF8Locale = false; 20305 } else { 20306 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 20307 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 20308 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 20309 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 20310 { 20311 sdx_isUTF8Locale = true; 20312 break; 20313 } 20314 } 20315 } 20316 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 20317 } 20318 } 20319 20320 class ExperimentalTextComponent2 { 20321 /+ 20322 Stage 1: get it working monospace 20323 Stage 2: use proportional font 20324 Stage 3: allow changes in inline style 20325 Stage 4: allow new fonts and sizes in the middle 20326 Stage 5: optimize gap buffer 20327 Stage 6: optimize layout 20328 Stage 7: word wrap 20329 Stage 8: justification 20330 Stage 9: editing, selection, etc. 20331 20332 Operations: 20333 insert text 20334 overstrike text 20335 select 20336 cut 20337 modify 20338 +/ 20339 20340 /++ 20341 It asks for a window so it can translate abstract font sizes to actual on-screen values depending on the window's current dpi, scaling settings, etc. 20342 +/ 20343 this(SimpleWindow window) { 20344 this.window = window; 20345 } 20346 20347 private SimpleWindow window; 20348 20349 20350 /++ 20351 When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces 20352 representing the internal parts. The first pass is focused on the x parameter, then the 20353 renderer is responsible for going back to the parts in the current line and calling 20354 adjustDownForAscent to change the y params. 20355 +/ 20356 static interface ComponentRenderHelper { 20357 20358 /+ 20359 When you do an edit, possibly stuff on the same line previously need to move (to adjust 20360 the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs 20361 to move (adjust y to make room for new line) until you get back to the same position, 20362 then you can stop - if one thing is unchanged, nothing after it is changed too. 20363 20364 Word wrap might change this as if can rewrap tons of stuff, but the same idea applies, 20365 once you reach something that is unchanged, you can stop. 20366 +/ 20367 20368 void adjustDownForAscent(int amount); // at the end of the line it needs to do these 20369 20370 int ascent() const; 20371 int descent() const; 20372 20373 int advance() const; 20374 20375 bool endsWithExplititLineBreak() const; 20376 } 20377 20378 static interface RenderResult { 20379 /++ 20380 This is responsible for using what space is left (your object is responsible for keeping its own state after getting it updated from [repositionForNextLine]) and not going over if at all possible. If you can word wrap, you should when space is out. Otherwise, you can keep going if it means overflow hidden or scroll. 20381 +/ 20382 void popFront(); 20383 @property bool empty() const; 20384 @property ComponentRenderHelper front() const; 20385 20386 void repositionForNextLine(Point baseline, int availableWidth); 20387 } 20388 20389 static interface ComponentInFlow { 20390 void draw(ScreenPainter painter); 20391 //RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different" 20392 20393 bool startsWithExplicitLineBreak() const; 20394 } 20395 20396 static class TextFlowComponent : ComponentInFlow { 20397 bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true 20398 20399 Color foreground; 20400 Color background; 20401 20402 OperatingSystemFont font; // should NEVER be null 20403 20404 ubyte attributes; // underline, strike through, display on new block 20405 20406 version(Windows) 20407 const(wchar)[] content; 20408 else 20409 const(char)[] content; // this should NEVER have a newline, except at the end 20410 20411 RenderedComponent[] rendered; // entirely controlled by [rerender] 20412 20413 // could prolly put some spacing around it too like margin / padding 20414 20415 this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) 20416 in { assert(font !is null); 20417 assert(!font.isNull); } 20418 do 20419 { 20420 this.foreground = f; 20421 this.background = b; 20422 this.font = font; 20423 20424 this.attributes = attr; 20425 version(Windows) { 20426 auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines; 20427 auto sz = sizeOfConvertedWstring(c, conversionFlags); 20428 auto buffer = new wchar[](sz); 20429 this.content = makeWindowsString(c, buffer, conversionFlags); 20430 } else { 20431 this.content = c.dup; 20432 } 20433 } 20434 20435 void draw(ScreenPainter painter) { 20436 painter.setFont(this.font); 20437 painter.outlineColor = this.foreground; 20438 painter.fillColor = Color.transparent; 20439 foreach(rendered; this.rendered) { 20440 // the component works in term of baseline, 20441 // but the painter works in term of upper left bounding box 20442 // so need to translate that 20443 20444 if(this.background.a) { 20445 painter.fillColor = this.background; 20446 painter.outlineColor = this.background; 20447 20448 painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height)); 20449 20450 painter.outlineColor = this.foreground; 20451 painter.fillColor = Color.transparent; 20452 } 20453 20454 painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice); 20455 20456 // FIXME: strike through, underline, highlight selection, etc. 20457 } 20458 } 20459 } 20460 20461 // I could split the parts into words on render 20462 // for easier word-wrap, each one being an unbreakable "inline-block" 20463 private TextFlowComponent[] parts; 20464 private int needsRerenderFrom; 20465 20466 void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) { 20467 // FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop. 20468 parts ~= new TextFlowComponent(f, b, font, attr, c); 20469 } 20470 20471 static struct RenderedComponent { 20472 int startX; 20473 int startY; 20474 short width; 20475 // height is always from the containing part's font. This saves some space and means recalculations need not continue past the current line, unless a new part is added with a different font! 20476 // for individual chars in here you've gotta process on demand 20477 version(Windows) 20478 const(wchar)[] slice; 20479 else 20480 const(char)[] slice; 20481 } 20482 20483 20484 void rerender(Rectangle boundingBox) { 20485 Point baseline = boundingBox.upperLeft; 20486 20487 this.boundingBox.left = boundingBox.left; 20488 this.boundingBox.top = boundingBox.top; 20489 20490 auto remainingParts = parts; 20491 20492 int largestX; 20493 20494 20495 foreach(part; parts) 20496 part.font.prepareContext(window); 20497 scope(exit) 20498 foreach(part; parts) 20499 part.font.releaseContext(); 20500 20501 calculateNextLine: 20502 20503 int nextLineHeight = 0; 20504 int nextBiggestDescent = 0; 20505 20506 foreach(part; remainingParts) { 20507 auto height = part.font.ascent; 20508 if(height > nextLineHeight) 20509 nextLineHeight = height; 20510 if(part.font.descent > nextBiggestDescent) 20511 nextBiggestDescent = part.font.descent; 20512 if(part.content.length && part.content[$-1] == '\n') 20513 break; 20514 } 20515 20516 baseline.y += nextLineHeight; 20517 auto lineStart = baseline; 20518 20519 while(remainingParts.length) { 20520 remainingParts[0].rendered = null; 20521 20522 bool eol; 20523 if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n') 20524 eol = true; 20525 20526 // FIXME: word wrap 20527 auto font = remainingParts[0].font; 20528 auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)]; 20529 auto width = font.stringWidth(slice, window); 20530 remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice); 20531 20532 remainingParts = remainingParts[1 .. $]; 20533 baseline.x += width; 20534 20535 if(eol) { 20536 baseline.y += nextBiggestDescent; 20537 if(baseline.x > largestX) 20538 largestX = baseline.x; 20539 baseline.x = lineStart.x; 20540 goto calculateNextLine; 20541 } 20542 } 20543 20544 if(baseline.x > largestX) 20545 largestX = baseline.x; 20546 20547 this.boundingBox.right = largestX; 20548 this.boundingBox.bottom = baseline.y; 20549 } 20550 20551 // you must call rerender first! 20552 void draw(ScreenPainter painter) { 20553 foreach(part; parts) { 20554 part.draw(painter); 20555 } 20556 } 20557 20558 struct IdentifyResult { 20559 TextFlowComponent part; 20560 int charIndexInPart; 20561 int totalCharIndex = -1; // if this is -1, it just means the end 20562 20563 Rectangle boundingBox; 20564 } 20565 20566 IdentifyResult identify(Point pt, bool exact = false) { 20567 if(parts.length == 0) 20568 return IdentifyResult(null, 0); 20569 20570 if(pt.y < boundingBox.top) { 20571 if(exact) 20572 return IdentifyResult(null, 1); 20573 return IdentifyResult(parts[0], 0); 20574 } 20575 if(pt.y > boundingBox.bottom) { 20576 if(exact) 20577 return IdentifyResult(null, 2); 20578 return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length); 20579 } 20580 20581 int tci = 0; 20582 20583 // I should probably like binary search this or something... 20584 foreach(ref part; parts) { 20585 foreach(rendered; part.rendered) { 20586 auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent); 20587 if(rect.contains(pt)) { 20588 auto x = pt.x - rendered.startX; 20589 auto estimatedIdx = x / part.font.averageWidth; 20590 20591 if(estimatedIdx < 0) 20592 estimatedIdx = 0; 20593 20594 if(estimatedIdx > rendered.slice.length) 20595 estimatedIdx = cast(int) rendered.slice.length; 20596 20597 int idx; 20598 int x1, x2; 20599 if(part.font.isMonospace) { 20600 auto w = part.font.averageWidth; 20601 if(!exact && x > (estimatedIdx + 1) * w) 20602 return IdentifyResult(null, 4); 20603 idx = estimatedIdx; 20604 x1 = idx * w; 20605 x2 = (idx + 1) * w; 20606 } else { 20607 idx = estimatedIdx; 20608 20609 part.font.prepareContext(window); 20610 scope(exit) part.font.releaseContext(); 20611 20612 // int iterations; 20613 20614 while(true) { 20615 // iterations++; 20616 x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0; 20617 x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies. 20618 20619 x1 += rendered.startX; 20620 x2 += rendered.startX; 20621 20622 if(pt.x < x1) { 20623 if(idx == 0) { 20624 if(exact) 20625 return IdentifyResult(null, 6); 20626 else 20627 break; 20628 } 20629 idx--; 20630 } else if(pt.x > x2) { 20631 idx++; 20632 if(idx > rendered.slice.length) { 20633 if(exact) 20634 return IdentifyResult(null, 5); 20635 else 20636 break; 20637 } 20638 } else if(pt.x >= x1 && pt.x <= x2) { 20639 if(idx) 20640 idx--; // point it at the original index 20641 break; // we fit 20642 } 20643 } 20644 20645 // writeln(iterations) 20646 } 20647 20648 20649 return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8? 20650 } 20651 } 20652 tci += cast(int) part.content.length; // FIXME: utf-8? 20653 } 20654 return IdentifyResult(null, 3); 20655 } 20656 20657 Rectangle boundingBox; // only set after [rerender] 20658 20659 // text will be positioned around the exclusion zone 20660 static struct ExclusionZone { 20661 20662 } 20663 20664 ExclusionZone[] exclusionZones; 20665 } 20666 20667 20668 // Don't use this yet. When I'm happy with it, I will move it to the 20669 // regular module namespace. 20670 mixin template ExperimentalTextComponent() { 20671 20672 static: 20673 20674 alias Rectangle = arsd.color.Rectangle; 20675 20676 struct ForegroundColor { 20677 Color color; 20678 alias color this; 20679 20680 this(Color c) { 20681 color = c; 20682 } 20683 20684 this(int r, int g, int b, int a = 255) { 20685 color = Color(r, g, b, a); 20686 } 20687 20688 static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) { 20689 return ForegroundColor(mixin("Color." ~ s)); 20690 } 20691 } 20692 20693 struct BackgroundColor { 20694 Color color; 20695 alias color this; 20696 20697 this(Color c) { 20698 color = c; 20699 } 20700 20701 this(int r, int g, int b, int a = 255) { 20702 color = Color(r, g, b, a); 20703 } 20704 20705 static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) { 20706 return BackgroundColor(mixin("Color." ~ s)); 20707 } 20708 } 20709 20710 static class InlineElement { 20711 string text; 20712 20713 BlockElement containingBlock; 20714 20715 Color color = Color.black; 20716 Color backgroundColor = Color.transparent; 20717 ushort styles; 20718 20719 string font; 20720 int fontSize; 20721 20722 int lineHeight; 20723 20724 void* identifier; 20725 20726 Rectangle boundingBox; 20727 int[] letterXs; // FIXME: maybe i should do bounding boxes for every character 20728 20729 bool isMergeCompatible(InlineElement other) { 20730 return 20731 containingBlock is other.containingBlock && 20732 color == other.color && 20733 backgroundColor == other.backgroundColor && 20734 styles == other.styles && 20735 font == other.font && 20736 fontSize == other.fontSize && 20737 lineHeight == other.lineHeight && 20738 true; 20739 } 20740 20741 int xOfIndex(size_t index) { 20742 if(index < letterXs.length) 20743 return letterXs[index]; 20744 else 20745 return boundingBox.right; 20746 } 20747 20748 InlineElement clone() { 20749 auto ie = new InlineElement(); 20750 ie.tupleof = this.tupleof; 20751 return ie; 20752 } 20753 20754 InlineElement getPreviousInlineElement() { 20755 InlineElement prev = null; 20756 foreach(ie; this.containingBlock.parts) { 20757 if(ie is this) 20758 break; 20759 prev = ie; 20760 } 20761 if(prev is null) { 20762 BlockElement pb; 20763 BlockElement cb = this.containingBlock; 20764 moar: 20765 foreach(ie; this.containingBlock.containingLayout.blocks) { 20766 if(ie is cb) 20767 break; 20768 pb = ie; 20769 } 20770 if(pb is null) 20771 return null; 20772 if(pb.parts.length == 0) { 20773 cb = pb; 20774 goto moar; 20775 } 20776 20777 prev = pb.parts[$-1]; 20778 20779 } 20780 return prev; 20781 } 20782 20783 InlineElement getNextInlineElement() { 20784 InlineElement next = null; 20785 foreach(idx, ie; this.containingBlock.parts) { 20786 if(ie is this) { 20787 if(idx + 1 < this.containingBlock.parts.length) 20788 next = this.containingBlock.parts[idx + 1]; 20789 break; 20790 } 20791 } 20792 if(next is null) { 20793 BlockElement n; 20794 foreach(idx, ie; this.containingBlock.containingLayout.blocks) { 20795 if(ie is this.containingBlock) { 20796 if(idx + 1 < this.containingBlock.containingLayout.blocks.length) 20797 n = this.containingBlock.containingLayout.blocks[idx + 1]; 20798 break; 20799 } 20800 } 20801 if(n is null) 20802 return null; 20803 20804 if(n.parts.length) 20805 next = n.parts[0]; 20806 else {} // FIXME 20807 20808 } 20809 return next; 20810 } 20811 20812 } 20813 20814 // Block elements are used entirely for positioning inline elements, 20815 // which are the things that are actually drawn. 20816 class BlockElement { 20817 InlineElement[] parts; 20818 uint alignment; 20819 20820 int whiteSpace; // pre, pre-wrap, wrap 20821 20822 TextLayout containingLayout; 20823 20824 // inputs 20825 Point where; 20826 Size minimumSize; 20827 Size maximumSize; 20828 Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box. 20829 void* identifier; 20830 20831 Rectangle margin; 20832 Rectangle padding; 20833 20834 // outputs 20835 Rectangle[] boundingBoxes; 20836 } 20837 20838 struct TextIdentifyResult { 20839 InlineElement element; 20840 int offset; 20841 20842 private TextIdentifyResult fixupNewline() { 20843 if(element !is null && offset < element.text.length && element.text[offset] == '\n') { 20844 offset--; 20845 } else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') { 20846 offset--; 20847 } 20848 return this; 20849 } 20850 } 20851 20852 class TextLayout { 20853 BlockElement[] blocks; 20854 Rectangle boundingBox_; 20855 Rectangle boundingBox() { return boundingBox_; } 20856 void boundingBox(Rectangle r) { 20857 if(r != boundingBox_) { 20858 boundingBox_ = r; 20859 layoutInvalidated = true; 20860 } 20861 } 20862 20863 Rectangle contentBoundingBox() { 20864 Rectangle r; 20865 foreach(block; blocks) 20866 foreach(ie; block.parts) { 20867 if(ie.boundingBox.right > r.right) 20868 r.right = ie.boundingBox.right; 20869 if(ie.boundingBox.bottom > r.bottom) 20870 r.bottom = ie.boundingBox.bottom; 20871 } 20872 return r; 20873 } 20874 20875 BlockElement[] getBlocks() { 20876 return blocks; 20877 } 20878 20879 InlineElement[] getTexts() { 20880 InlineElement[] elements; 20881 foreach(block; blocks) 20882 elements ~= block.parts; 20883 return elements; 20884 } 20885 20886 string getPlainText() { 20887 string text; 20888 foreach(block; blocks) 20889 foreach(part; block.parts) 20890 text ~= part.text; 20891 return text; 20892 } 20893 20894 string getHtml() { 20895 return null; // FIXME 20896 } 20897 20898 this(Rectangle boundingBox) { 20899 this.boundingBox = boundingBox; 20900 } 20901 20902 BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) { 20903 auto be = new BlockElement(); 20904 be.containingLayout = this; 20905 if(after is null) 20906 blocks ~= be; 20907 else { 20908 foreach(idx, b; blocks) { 20909 if(b is after.containingBlock) { 20910 blocks = blocks[0 .. idx + 1] ~ be ~ blocks[idx + 1 .. $]; 20911 break; 20912 } 20913 } 20914 } 20915 return be; 20916 } 20917 20918 void clear() { 20919 blocks = null; 20920 selectionStart = selectionEnd = caret = Caret.init; 20921 } 20922 20923 void addText(Args...)(Args args) { 20924 if(blocks.length == 0) 20925 addBlock(); 20926 20927 InlineElement ie = new InlineElement(); 20928 foreach(idx, arg; args) { 20929 static if(is(typeof(arg) == ForegroundColor)) 20930 ie.color = arg; 20931 else static if(is(typeof(arg) == TextFormat)) { 20932 if(arg & 0x8000) // ~TextFormat.something turns it off 20933 ie.styles &= arg; 20934 else 20935 ie.styles |= arg; 20936 } else static if(is(typeof(arg) == string)) { 20937 static if(idx == 0 && args.length > 1) 20938 static assert(0, "Put styles before the string."); 20939 size_t lastLineIndex; 20940 foreach(cidx, char a; arg) { 20941 if(a == '\n') { 20942 ie.text = arg[lastLineIndex .. cidx + 1]; 20943 lastLineIndex = cidx + 1; 20944 ie.containingBlock = blocks[$-1]; 20945 blocks[$-1].parts ~= ie.clone; 20946 ie.text = null; 20947 } else { 20948 20949 } 20950 } 20951 20952 ie.text = arg[lastLineIndex .. $]; 20953 ie.containingBlock = blocks[$-1]; 20954 blocks[$-1].parts ~= ie.clone; 20955 caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length); 20956 } 20957 } 20958 20959 invalidateLayout(); 20960 } 20961 20962 void tryMerge(InlineElement into, InlineElement what) { 20963 if(!into.isMergeCompatible(what)) { 20964 return; // cannot merge, different configs 20965 } 20966 20967 // cool, can merge, bring text together... 20968 into.text ~= what.text; 20969 20970 // and remove what 20971 for(size_t a = 0; a < what.containingBlock.parts.length; a++) { 20972 if(what.containingBlock.parts[a] is what) { 20973 for(size_t i = a; i < what.containingBlock.parts.length - 1; i++) 20974 what.containingBlock.parts[i] = what.containingBlock.parts[i + 1]; 20975 what.containingBlock.parts = what.containingBlock.parts[0 .. $-1]; 20976 20977 } 20978 } 20979 20980 // FIXME: ensure no other carets have a reference to it 20981 } 20982 20983 /// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click. 20984 TextIdentifyResult identify(int x, int y, bool exact = false) { 20985 TextIdentifyResult inexactMatch; 20986 foreach(block; blocks) { 20987 foreach(part; block.parts) { 20988 if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { 20989 20990 // FIXME binary search 20991 int tidx; 20992 int lastX; 20993 foreach_reverse(idxo, lx; part.letterXs) { 20994 int idx = cast(int) idxo; 20995 if(lx <= x) { 20996 if(lastX && lastX - x < x - lx) 20997 tidx = idx + 1; 20998 else 20999 tidx = idx; 21000 break; 21001 } 21002 lastX = lx; 21003 } 21004 21005 return TextIdentifyResult(part, tidx).fixupNewline; 21006 } else if(!exact) { 21007 // we're not in the box, but are we on the same line? 21008 if(y >= part.boundingBox.top && y < part.boundingBox.bottom) 21009 inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length); 21010 } 21011 } 21012 } 21013 21014 if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) 21015 return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline; 21016 21017 return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; 21018 } 21019 21020 void moveCaretToPixelCoordinates(int x, int y) { 21021 auto result = identify(x, y); 21022 caret.inlineElement = result.element; 21023 caret.offset = result.offset; 21024 } 21025 21026 void selectToPixelCoordinates(int x, int y) { 21027 auto result = identify(x, y); 21028 21029 if(y < caretLastDrawnY1) { 21030 // on a previous line, carat is selectionEnd 21031 selectionEnd = caret; 21032 21033 selectionStart = Caret(this, result.element, result.offset); 21034 } else if(y > caretLastDrawnY2) { 21035 // on a later line 21036 selectionStart = caret; 21037 21038 selectionEnd = Caret(this, result.element, result.offset); 21039 } else { 21040 // on the same line... 21041 if(x <= caretLastDrawnX) { 21042 selectionEnd = caret; 21043 selectionStart = Caret(this, result.element, result.offset); 21044 } else { 21045 selectionStart = caret; 21046 selectionEnd = Caret(this, result.element, result.offset); 21047 } 21048 21049 } 21050 } 21051 21052 21053 /// Call this if the inputs change. It will reflow everything 21054 void redoLayout(ScreenPainter painter) { 21055 //painter.setClipRectangle(boundingBox); 21056 auto pos = Point(boundingBox.left, boundingBox.top); 21057 21058 int lastHeight; 21059 void nl() { 21060 pos.x = boundingBox.left; 21061 pos.y += lastHeight; 21062 } 21063 foreach(block; blocks) { 21064 nl(); 21065 foreach(part; block.parts) { 21066 part.letterXs = null; 21067 21068 auto size = painter.textSize(part.text); 21069 version(Windows) 21070 if(part.text.length && part.text[$-1] == '\n') 21071 size.height /= 2; // windows counts the new line at the end, but we don't want that 21072 21073 part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height); 21074 21075 foreach(idx, char c; part.text) { 21076 // FIXME: unicode 21077 part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x; 21078 } 21079 21080 pos.x += size.width; 21081 if(pos.x >= boundingBox.right) { 21082 pos.y += size.height; 21083 pos.x = boundingBox.left; 21084 lastHeight = 0; 21085 } else { 21086 lastHeight = size.height; 21087 } 21088 21089 if(part.text.length && part.text[$-1] == '\n') 21090 nl(); 21091 } 21092 } 21093 21094 layoutInvalidated = false; 21095 } 21096 21097 bool layoutInvalidated = true; 21098 void invalidateLayout() { 21099 layoutInvalidated = true; 21100 } 21101 21102 // FIXME: caret can remain sometimes when inserting 21103 // FIXME: inserting at the beginning once you already have something can eff it up. 21104 void drawInto(ScreenPainter painter, bool focused = false) { 21105 if(layoutInvalidated) 21106 redoLayout(painter); 21107 foreach(block; blocks) { 21108 foreach(part; block.parts) { 21109 painter.outlineColor = part.color; 21110 painter.fillColor = part.backgroundColor; 21111 21112 auto pos = part.boundingBox.upperLeft; 21113 auto size = part.boundingBox.size; 21114 21115 painter.drawText(pos, part.text); 21116 if(part.styles & TextFormat.underline) 21117 painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4)); 21118 if(part.styles & TextFormat.strikethrough) 21119 painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2)); 21120 } 21121 } 21122 21123 // on every redraw, I will force the caret to be 21124 // redrawn too, in order to eliminate perceived lag 21125 // when moving around with the mouse. 21126 eraseCaret(painter); 21127 21128 if(focused) { 21129 highlightSelection(painter); 21130 drawCaret(painter); 21131 } 21132 } 21133 21134 Color selectionXorColor = Color(255, 255, 127); 21135 21136 void highlightSelection(ScreenPainter painter) { 21137 if(selectionStart is selectionEnd) 21138 return; // no selection 21139 21140 if(selectionStart.inlineElement is null) return; 21141 if(selectionEnd.inlineElement is null) return; 21142 21143 assert(selectionStart.inlineElement !is null); 21144 assert(selectionEnd.inlineElement !is null); 21145 21146 painter.rasterOp = RasterOp.xor; 21147 painter.outlineColor = Color.transparent; 21148 painter.fillColor = selectionXorColor; 21149 21150 auto at = selectionStart.inlineElement; 21151 auto atOffset = selectionStart.offset; 21152 bool done; 21153 while(at) { 21154 auto box = at.boundingBox; 21155 if(atOffset < at.letterXs.length) 21156 box.left = at.letterXs[atOffset]; 21157 21158 if(at is selectionEnd.inlineElement) { 21159 if(selectionEnd.offset < at.letterXs.length) 21160 box.right = at.letterXs[selectionEnd.offset]; 21161 done = true; 21162 } 21163 21164 painter.drawRectangle(box.upperLeft, box.width, box.height); 21165 21166 if(done) 21167 break; 21168 21169 at = at.getNextInlineElement(); 21170 atOffset = 0; 21171 } 21172 } 21173 21174 int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2; 21175 bool caretShowingOnScreen = false; 21176 void drawCaret(ScreenPainter painter) { 21177 //painter.setClipRectangle(boundingBox); 21178 int x, y1, y2; 21179 if(caret.inlineElement is null) { 21180 x = boundingBox.left; 21181 y1 = boundingBox.top + 2; 21182 y2 = boundingBox.top + painter.fontHeight; 21183 } else { 21184 x = caret.inlineElement.xOfIndex(caret.offset); 21185 y1 = caret.inlineElement.boundingBox.top + 2; 21186 y2 = caret.inlineElement.boundingBox.bottom - 2; 21187 } 21188 21189 if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2)) 21190 eraseCaret(painter); 21191 21192 painter.pen = Pen(Color.white, 1); 21193 painter.rasterOp = RasterOp.xor; 21194 painter.drawLine( 21195 Point(x, y1), 21196 Point(x, y2) 21197 ); 21198 painter.rasterOp = RasterOp.normal; 21199 caretShowingOnScreen = !caretShowingOnScreen; 21200 21201 if(caretShowingOnScreen) { 21202 caretLastDrawnX = x; 21203 caretLastDrawnY1 = y1; 21204 caretLastDrawnY2 = y2; 21205 } 21206 } 21207 21208 Rectangle caretBoundingBox() { 21209 int x, y1, y2; 21210 if(caret.inlineElement is null) { 21211 x = boundingBox.left; 21212 y1 = boundingBox.top + 2; 21213 y2 = boundingBox.top + 16; 21214 } else { 21215 x = caret.inlineElement.xOfIndex(caret.offset); 21216 y1 = caret.inlineElement.boundingBox.top + 2; 21217 y2 = caret.inlineElement.boundingBox.bottom - 2; 21218 } 21219 21220 return Rectangle(x, y1, x + 1, y2); 21221 } 21222 21223 void eraseCaret(ScreenPainter painter) { 21224 //painter.setClipRectangle(boundingBox); 21225 if(!caretShowingOnScreen) return; 21226 painter.pen = Pen(Color.white, 1); 21227 painter.rasterOp = RasterOp.xor; 21228 painter.drawLine( 21229 Point(caretLastDrawnX, caretLastDrawnY1), 21230 Point(caretLastDrawnX, caretLastDrawnY2) 21231 ); 21232 21233 caretShowingOnScreen = false; 21234 painter.rasterOp = RasterOp.normal; 21235 } 21236 21237 /// Caret movement api 21238 /// These should give the user a logical result based on what they see on screen... 21239 /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) 21240 void moveUp() { 21241 if(caret.inlineElement is null) return; 21242 auto x = caret.inlineElement.xOfIndex(caret.offset); 21243 auto y = caret.inlineElement.boundingBox.top + 2; 21244 21245 y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 21246 if(y < 0) 21247 return; 21248 21249 auto i = identify(x, y); 21250 21251 if(i.element) { 21252 caret.inlineElement = i.element; 21253 caret.offset = i.offset; 21254 } 21255 } 21256 void moveDown() { 21257 if(caret.inlineElement is null) return; 21258 auto x = caret.inlineElement.xOfIndex(caret.offset); 21259 auto y = caret.inlineElement.boundingBox.bottom - 2; 21260 21261 y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 21262 21263 auto i = identify(x, y); 21264 if(i.element) { 21265 caret.inlineElement = i.element; 21266 caret.offset = i.offset; 21267 } 21268 } 21269 void moveLeft() { 21270 if(caret.inlineElement is null) return; 21271 if(caret.offset) 21272 caret.offset--; 21273 else { 21274 auto p = caret.inlineElement.getPreviousInlineElement(); 21275 if(p) { 21276 caret.inlineElement = p; 21277 if(p.text.length && p.text[$-1] == '\n') 21278 caret.offset = cast(int) p.text.length - 1; 21279 else 21280 caret.offset = cast(int) p.text.length; 21281 } 21282 } 21283 } 21284 void moveRight() { 21285 if(caret.inlineElement is null) return; 21286 if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { 21287 caret.offset++; 21288 } else { 21289 auto p = caret.inlineElement.getNextInlineElement(); 21290 if(p) { 21291 caret.inlineElement = p; 21292 caret.offset = 0; 21293 } 21294 } 21295 } 21296 void moveHome() { 21297 if(caret.inlineElement is null) return; 21298 auto x = 0; 21299 auto y = caret.inlineElement.boundingBox.top + 2; 21300 21301 auto i = identify(x, y); 21302 21303 if(i.element) { 21304 caret.inlineElement = i.element; 21305 caret.offset = i.offset; 21306 } 21307 } 21308 void moveEnd() { 21309 if(caret.inlineElement is null) return; 21310 auto x = int.max; 21311 auto y = caret.inlineElement.boundingBox.top + 2; 21312 21313 auto i = identify(x, y); 21314 21315 if(i.element) { 21316 caret.inlineElement = i.element; 21317 caret.offset = i.offset; 21318 } 21319 21320 } 21321 void movePageUp(ref Caret caret) {} 21322 void movePageDown(ref Caret caret) {} 21323 21324 void moveDocumentStart(ref Caret caret) { 21325 if(blocks.length && blocks[0].parts.length) 21326 caret = Caret(this, blocks[0].parts[0], 0); 21327 else 21328 caret = Caret.init; 21329 } 21330 21331 void moveDocumentEnd(ref Caret caret) { 21332 if(blocks.length) { 21333 auto parts = blocks[$-1].parts; 21334 if(parts.length) { 21335 caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length); 21336 } else { 21337 caret = Caret.init; 21338 } 21339 } else 21340 caret = Caret.init; 21341 } 21342 21343 void deleteSelection() { 21344 if(selectionStart is selectionEnd) 21345 return; 21346 21347 if(selectionStart.inlineElement is null) return; 21348 if(selectionEnd.inlineElement is null) return; 21349 21350 assert(selectionStart.inlineElement !is null); 21351 assert(selectionEnd.inlineElement !is null); 21352 21353 auto at = selectionStart.inlineElement; 21354 21355 if(selectionEnd.inlineElement is at) { 21356 // same element, need to chop out 21357 at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $]; 21358 at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $]; 21359 selectionEnd.offset -= selectionEnd.offset - selectionStart.offset; 21360 } else { 21361 // different elements, we can do it with slicing 21362 at.text = at.text[0 .. selectionStart.offset]; 21363 if(selectionStart.offset < at.letterXs.length) 21364 at.letterXs = at.letterXs[0 .. selectionStart.offset]; 21365 21366 at = at.getNextInlineElement(); 21367 21368 while(at) { 21369 if(at is selectionEnd.inlineElement) { 21370 at.text = at.text[selectionEnd.offset .. $]; 21371 if(selectionEnd.offset < at.letterXs.length) 21372 at.letterXs = at.letterXs[selectionEnd.offset .. $]; 21373 selectionEnd.offset = 0; 21374 break; 21375 } else { 21376 auto cfd = at; 21377 cfd.text = null; // delete the whole thing 21378 21379 at = at.getNextInlineElement(); 21380 21381 if(cfd.text.length == 0) { 21382 // and remove cfd 21383 for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) { 21384 if(cfd.containingBlock.parts[a] is cfd) { 21385 for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++) 21386 cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1]; 21387 cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1]; 21388 21389 } 21390 } 21391 } 21392 } 21393 } 21394 } 21395 21396 caret = selectionEnd; 21397 selectNone(); 21398 21399 invalidateLayout(); 21400 21401 } 21402 21403 /// Plain text editing api. These work at the current caret inside the selected inline element. 21404 void insert(in char[] text) { 21405 foreach(dchar ch; text) 21406 insert(ch); 21407 } 21408 /// ditto 21409 void insert(dchar ch) { 21410 21411 bool selectionDeleted = false; 21412 if(selectionStart !is selectionEnd) { 21413 deleteSelection(); 21414 selectionDeleted = true; 21415 } 21416 21417 if(ch == 127) { 21418 delete_(); 21419 return; 21420 } 21421 if(ch == 8) { 21422 if(!selectionDeleted) 21423 backspace(); 21424 return; 21425 } 21426 21427 invalidateLayout(); 21428 21429 if(ch == 13) ch = 10; 21430 auto e = caret.inlineElement; 21431 if(e is null) { 21432 addText("" ~ cast(char) ch) ; // FIXME 21433 return; 21434 } 21435 21436 if(caret.offset == e.text.length) { 21437 e.text ~= cast(char) ch; // FIXME 21438 caret.offset++; 21439 if(ch == 10) { 21440 auto c = caret.inlineElement.clone; 21441 c.text = null; 21442 c.letterXs = null; 21443 insertPartAfter(c,e); 21444 caret = Caret(this, c, 0); 21445 } 21446 } else { 21447 // FIXME cast char sucks 21448 if(ch == 10) { 21449 auto c = caret.inlineElement.clone; 21450 c.text = e.text[caret.offset .. $]; 21451 if(caret.offset < c.letterXs.length) 21452 c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox 21453 e.text = e.text[0 .. caret.offset] ~ cast(char) ch; 21454 if(caret.offset <= e.letterXs.length) { 21455 e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box 21456 } 21457 insertPartAfter(c,e); 21458 caret = Caret(this, c, 0); 21459 } else { 21460 e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $]; 21461 caret.offset++; 21462 } 21463 } 21464 } 21465 21466 void insertPartAfter(InlineElement what, InlineElement where) { 21467 foreach(idx, p; where.containingBlock.parts) { 21468 if(p is where) { 21469 if(idx + 1 == where.containingBlock.parts.length) 21470 where.containingBlock.parts ~= what; 21471 else 21472 where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $]; 21473 return; 21474 } 21475 } 21476 } 21477 21478 void cleanupStructures() { 21479 for(size_t i = 0; i < blocks.length; i++) { 21480 auto block = blocks[i]; 21481 for(size_t a = 0; a < block.parts.length; a++) { 21482 auto part = block.parts[a]; 21483 if(part.text.length == 0) { 21484 for(size_t b = a; b < block.parts.length - 1; b++) 21485 block.parts[b] = block.parts[b+1]; 21486 block.parts = block.parts[0 .. $-1]; 21487 } 21488 } 21489 if(block.parts.length == 0) { 21490 for(size_t a = i; a < blocks.length - 1; a++) 21491 blocks[a] = blocks[a+1]; 21492 blocks = blocks[0 .. $-1]; 21493 } 21494 } 21495 } 21496 21497 void backspace() { 21498 try_again: 21499 auto e = caret.inlineElement; 21500 if(e is null) 21501 return; 21502 if(caret.offset == 0) { 21503 auto prev = e.getPreviousInlineElement(); 21504 if(prev is null) 21505 return; 21506 auto newOffset = cast(int) prev.text.length; 21507 tryMerge(prev, e); 21508 caret.inlineElement = prev; 21509 caret.offset = prev is null ? 0 : newOffset; 21510 21511 goto try_again; 21512 } else if(caret.offset == e.text.length) { 21513 e.text = e.text[0 .. $-1]; 21514 caret.offset--; 21515 } else { 21516 e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $]; 21517 caret.offset--; 21518 } 21519 //cleanupStructures(); 21520 21521 invalidateLayout(); 21522 } 21523 void delete_() { 21524 if(selectionStart !is selectionEnd) 21525 deleteSelection(); 21526 else { 21527 auto before = caret; 21528 moveRight(); 21529 if(caret != before) { 21530 backspace(); 21531 } 21532 } 21533 21534 invalidateLayout(); 21535 } 21536 void overstrike() {} 21537 21538 /// Selection API. See also: caret movement. 21539 void selectAll() { 21540 moveDocumentStart(selectionStart); 21541 moveDocumentEnd(selectionEnd); 21542 } 21543 bool selectNone() { 21544 if(selectionStart != selectionEnd) { 21545 selectionStart = selectionEnd = Caret.init; 21546 return true; 21547 } 21548 return false; 21549 } 21550 21551 /// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements. 21552 /// They will modify the current selection if there is one and will splice one in if needed. 21553 void changeAttributes() {} 21554 21555 21556 /// Text search api. They manipulate the selection and/or caret. 21557 void findText(string text) {} 21558 void findIndex(size_t textIndex) {} 21559 21560 // sample event handlers 21561 21562 void handleEvent(KeyEvent event) { 21563 //if(event.type == KeyEvent.Type.KeyPressed) { 21564 21565 //} 21566 } 21567 21568 void handleEvent(dchar ch) { 21569 21570 } 21571 21572 void handleEvent(MouseEvent event) { 21573 21574 } 21575 21576 bool contentEditable; // can it be edited? 21577 bool contentCaretable; // is there a caret/cursor that moves around in there? 21578 bool contentSelectable; // selectable? 21579 21580 Caret caret; 21581 Caret selectionStart; 21582 Caret selectionEnd; 21583 21584 bool insertMode; 21585 } 21586 21587 struct Caret { 21588 TextLayout layout; 21589 InlineElement inlineElement; 21590 int offset; 21591 } 21592 21593 enum TextFormat : ushort { 21594 // decorations 21595 underline = 1, 21596 strikethrough = 2, 21597 21598 // font selectors 21599 21600 bold = 0x4000 | 1, // weight 700 21601 light = 0x4000 | 2, // weight 300 21602 veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold 21603 // bold | light is really invalid but should give weight 500 21604 // veryBoldOrLight without one of the others should just give the default for the font; it should be ignored. 21605 21606 italic = 0x4000 | 8, 21607 smallcaps = 0x4000 | 16, 21608 } 21609 21610 void* findFont(string family, int weight, TextFormat formats) { 21611 return null; 21612 } 21613 21614 } 21615 21616 /++ 21617 $(PITFALL This is not yet stable and may break in future versions without notice.) 21618 21619 History: 21620 Added February 19, 2021 21621 +/ 21622 /// Group: drag_and_drop 21623 interface DropHandler { 21624 /++ 21625 Called when the drag enters the handler's area. 21626 +/ 21627 DragAndDropAction dragEnter(DropPackage*); 21628 /++ 21629 Called when the drag leaves the handler's area or is 21630 cancelled. You should free your resources when this is called. 21631 +/ 21632 void dragLeave(); 21633 /++ 21634 Called continually as the drag moves over the handler's area. 21635 21636 Returns: feedback to the dragger 21637 +/ 21638 DropParameters dragOver(Point pt); 21639 /++ 21640 The user dropped the data and you should process it now. You can 21641 access the data through the given [DropPackage]. 21642 +/ 21643 void drop(scope DropPackage*); 21644 /++ 21645 Called when the drop is complete. You should free whatever temporary 21646 resources you were using. It is often reasonable to simply forward 21647 this call to [dragLeave]. 21648 +/ 21649 void finish(); 21650 21651 /++ 21652 Parameters returned by [DropHandler.drop]. 21653 +/ 21654 static struct DropParameters { 21655 /++ 21656 Acceptable action over this area. 21657 +/ 21658 DragAndDropAction action; 21659 /++ 21660 Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again. 21661 21662 If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources. 21663 +/ 21664 Rectangle consistentWithin; 21665 } 21666 } 21667 21668 /++ 21669 History: 21670 Added February 19, 2021 21671 +/ 21672 /// Group: drag_and_drop 21673 enum DragAndDropAction { 21674 none = 0, 21675 copy, 21676 move, 21677 link, 21678 ask, 21679 custom 21680 } 21681 21682 /++ 21683 An opaque structure representing dropped data. It contains 21684 private, platform-specific data that your `drop` function 21685 should simply forward. 21686 21687 $(PITFALL This is not yet stable and may break in future versions without notice.) 21688 21689 History: 21690 Added February 19, 2021 21691 +/ 21692 /// Group: drag_and_drop 21693 struct DropPackage { 21694 /++ 21695 Lists the available formats as magic numbers. You should compare these 21696 against looked-up formats (see [DraggableData.getFormatId]) you know you support and can 21697 understand the passed data. 21698 +/ 21699 DraggableData.FormatId[] availableFormats() { 21700 version(X11) { 21701 return xFormats; 21702 } else version(Windows) { 21703 if(pDataObj is null) 21704 return null; 21705 21706 typeof(return) ret; 21707 21708 IEnumFORMATETC ef; 21709 if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) { 21710 FORMATETC fmt; 21711 ULONG fetched; 21712 while(ef.Next(1, &fmt, &fetched) == S_OK) { 21713 if(fetched == 0) 21714 break; 21715 21716 if(fmt.lindex != -1) 21717 continue; 21718 if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT) 21719 continue; 21720 if(!(fmt.tymed & TYMED.TYMED_HGLOBAL)) 21721 continue; 21722 21723 ret ~= fmt.cfFormat; 21724 } 21725 } 21726 21727 return ret; 21728 } else throw new NotYetImplementedException(); 21729 } 21730 21731 /++ 21732 Gets data from the drop and optionally accepts it. 21733 21734 Returns: 21735 void because the data is fed asynchronously through the `dg` parameter. 21736 21737 Params: 21738 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. 21739 21740 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. 21741 21742 Calling `getData` again after accepting a drop is not permitted. 21743 21744 format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format. 21745 21746 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. 21747 21748 Throws: 21749 if `format` was not compatible with the [availableFormats] or if the drop has already been accepted. 21750 21751 History: 21752 Included in first release of [DropPackage]. 21753 +/ 21754 void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) { 21755 version(X11) { 21756 21757 auto display = XDisplayConnection.get(); 21758 auto selectionAtom = GetAtom!"XdndSelection"(display); 21759 auto best = format; 21760 21761 static class X11GetSelectionHandler_Drop : X11GetSelectionHandler { 21762 21763 XDisplay* display; 21764 Atom selectionAtom; 21765 DraggableData.FormatId best; 21766 DraggableData.FormatId format; 21767 void delegate(scope ubyte[] data) dg; 21768 DragAndDropAction acceptedAction; 21769 Window sourceWindow; 21770 SimpleWindow win; 21771 this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) { 21772 this.display = display; 21773 this.win = win; 21774 this.sourceWindow = sourceWindow; 21775 this.format = format; 21776 this.selectionAtom = selectionAtom; 21777 this.best = best; 21778 this.dg = dg; 21779 this.acceptedAction = acceptedAction; 21780 } 21781 21782 21783 mixin X11GetSelectionHandler_Basics; 21784 21785 void handleData(Atom target, in ubyte[] data) { 21786 //if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 21787 21788 dg(cast(ubyte[]) data); 21789 21790 if(acceptedAction != DragAndDropAction.none) { 21791 auto display = XDisplayConnection.get; 21792 21793 XClientMessageEvent xclient; 21794 21795 xclient.type = EventType.ClientMessage; 21796 xclient.window = sourceWindow; 21797 xclient.message_type = GetAtom!"XdndFinished"(display); 21798 xclient.format = 32; 21799 xclient.data.l[0] = win.impl.window; 21800 xclient.data.l[1] = 1; // drop successful 21801 xclient.data.l[2] = dndActionAtom(display, acceptedAction); 21802 21803 XSendEvent( 21804 display, 21805 sourceWindow, 21806 false, 21807 EventMask.NoEventMask, 21808 cast(XEvent*) &xclient 21809 ); 21810 21811 XFlush(display); 21812 } 21813 } 21814 21815 Atom findBestFormat(Atom[] answer) { 21816 Atom best = None; 21817 foreach(option; answer) { 21818 if(option == format) { 21819 best = option; 21820 break; 21821 } 21822 /* 21823 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 21824 best = option; 21825 break; 21826 } else if(option == XA_STRING) { 21827 best = option; 21828 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 21829 best = option; 21830 } 21831 */ 21832 } 21833 return best; 21834 } 21835 } 21836 21837 win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction); 21838 21839 XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp); 21840 21841 } else version(Windows) { 21842 21843 // clean up like DragLeave 21844 // pass effect back up 21845 21846 FORMATETC t; 21847 assert(format >= 0 && format <= ushort.max); 21848 t.cfFormat = cast(ushort) format; 21849 t.lindex = -1; 21850 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 21851 t.tymed = TYMED.TYMED_HGLOBAL; 21852 21853 STGMEDIUM m; 21854 21855 if(pDataObj.GetData(&t, &m) != S_OK) { 21856 // fail 21857 } else { 21858 // succeed, take the data and clean up 21859 21860 // FIXME: ensure it is legit HGLOBAL 21861 auto handle = m.hGlobal; 21862 21863 if(handle) { 21864 auto sz = GlobalSize(handle); 21865 if(auto ptr = cast(ubyte*) GlobalLock(handle)) { 21866 scope(exit) GlobalUnlock(handle); 21867 scope(exit) GlobalFree(handle); 21868 21869 auto data = ptr[0 .. sz]; 21870 21871 dg(data); 21872 } 21873 } 21874 } 21875 } 21876 } 21877 21878 private: 21879 21880 version(X11) { 21881 SimpleWindow win; 21882 Window sourceWindow; 21883 Time dataTimestamp; 21884 21885 Atom[] xFormats; 21886 } 21887 version(Windows) { 21888 IDataObject pDataObj; 21889 } 21890 } 21891 21892 /++ 21893 A generic helper base class for making a drop handler with a preference list of custom types. 21894 This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own 21895 droppers too. 21896 21897 It assumes the whole window it used, but you can subclass to change that. 21898 21899 $(PITFALL This is not yet stable and may break in future versions without notice.) 21900 21901 History: 21902 Added February 19, 2021 21903 +/ 21904 /// Group: drag_and_drop 21905 class GenericDropHandlerBase : DropHandler { 21906 // no fancy state here so no need to do anything here 21907 void finish() { } 21908 void dragLeave() { } 21909 21910 private DragAndDropAction acceptedAction; 21911 private DraggableData.FormatId acceptedFormat; 21912 private void delegate(scope ubyte[]) acceptedHandler; 21913 21914 struct FormatHandler { 21915 DraggableData.FormatId format; 21916 void delegate(scope ubyte[]) handler; 21917 } 21918 21919 protected abstract FormatHandler[] formatHandlers(); 21920 21921 DragAndDropAction dragEnter(DropPackage* pkg) { 21922 debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); } 21923 foreach(fmt; formatHandlers()) 21924 foreach(f; pkg.availableFormats()) 21925 if(f == fmt.format) { 21926 acceptedFormat = f; 21927 acceptedHandler = fmt.handler; 21928 return acceptedAction = DragAndDropAction.copy; 21929 } 21930 return acceptedAction = DragAndDropAction.none; 21931 } 21932 DropParameters dragOver(Point pt) { 21933 return DropParameters(acceptedAction); 21934 } 21935 21936 void drop(scope DropPackage* dropPackage) { 21937 if(!acceptedFormat || acceptedHandler is null) { 21938 debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); } 21939 return; // prolly shouldn't happen anyway... 21940 } 21941 21942 dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler); 21943 } 21944 } 21945 21946 /++ 21947 A simple handler for making your window accept drops of plain text. 21948 21949 $(PITFALL This is not yet stable and may break in future versions without notice.) 21950 21951 History: 21952 Added February 22, 2021 21953 +/ 21954 /// Group: drag_and_drop 21955 class TextDropHandler : GenericDropHandlerBase { 21956 private void delegate(in char[] text) dg; 21957 21958 /++ 21959 21960 +/ 21961 this(void delegate(in char[] text) dg) { 21962 this.dg = dg; 21963 } 21964 21965 protected override FormatHandler[] formatHandlers() { 21966 version(X11) 21967 return [ 21968 FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator), 21969 FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator), 21970 ]; 21971 else version(Windows) 21972 return [ 21973 FormatHandler(CF_UNICODETEXT, &translator), 21974 ]; 21975 else throw new NotYetImplementedException(); 21976 } 21977 21978 private void translator(scope ubyte[] data) { 21979 version(X11) 21980 dg(cast(char[]) data); 21981 else version(Windows) 21982 dg(makeUtf8StringFromWindowsString(cast(wchar[]) data)); 21983 } 21984 } 21985 21986 /++ 21987 A simple handler for making your window accept drops of files, issued to you as file names. 21988 21989 $(PITFALL This is not yet stable and may break in future versions without notice.) 21990 21991 History: 21992 Added February 22, 2021 21993 +/ 21994 /// Group: drag_and_drop 21995 21996 class FilesDropHandler : GenericDropHandlerBase { 21997 private void delegate(in char[][]) dg; 21998 21999 /++ 22000 22001 +/ 22002 this(void delegate(in char[][] fileNames) dg) { 22003 this.dg = dg; 22004 } 22005 22006 protected override FormatHandler[] formatHandlers() { 22007 version(X11) 22008 return [ 22009 FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator), 22010 ]; 22011 else version(Windows) 22012 return [ 22013 FormatHandler(CF_HDROP, &translator), 22014 ]; 22015 else throw new NotYetImplementedException(); 22016 } 22017 22018 private void translator(scope ubyte[] data) @system { 22019 version(X11) { 22020 char[] listString = cast(char[]) data; 22021 char[][16] buffer; 22022 int count; 22023 char[][] result = buffer[]; 22024 22025 void commit(char[] s) { 22026 if(count == result.length) 22027 result.length += 16; 22028 if(s.length > 7 && s[0 ..7] == "file://") 22029 s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding 22030 result[count++] = s; 22031 } 22032 22033 size_t last; 22034 foreach(idx, char c; listString) { 22035 if(c == '\n') { 22036 commit(listString[last .. idx - 1]); // a \r 22037 last = idx + 1; // a \n 22038 } 22039 } 22040 22041 if(last < listString.length) { 22042 commit(listString[last .. $]); 22043 } 22044 22045 // FIXME: they are uris now, should I translate it to local file names? 22046 // of course the host name is supposed to be there cuz of X rokking... 22047 22048 dg(result[0 .. count]); 22049 } else version(Windows) { 22050 22051 static struct DROPFILES { 22052 DWORD pFiles; 22053 POINT pt; 22054 BOOL fNC; 22055 BOOL fWide; 22056 } 22057 22058 22059 const(char)[][16] buffer; 22060 int count; 22061 const(char)[][] result = buffer[]; 22062 size_t last; 22063 22064 void commitA(in char[] stuff) { 22065 if(count == result.length) 22066 result.length += 16; 22067 result[count++] = stuff; 22068 } 22069 22070 void commitW(in wchar[] stuff) { 22071 commitA(makeUtf8StringFromWindowsString(stuff)); 22072 } 22073 22074 void magic(T)(T chars) { 22075 size_t idx; 22076 while(chars[idx]) { 22077 last = idx; 22078 while(chars[idx]) { 22079 idx++; 22080 } 22081 static if(is(T == char*)) 22082 commitA(chars[last .. idx]); 22083 else 22084 commitW(chars[last .. idx]); 22085 idx++; 22086 } 22087 } 22088 22089 auto df = cast(DROPFILES*) data.ptr; 22090 if(df.fWide) { 22091 wchar* chars = cast(wchar*) (data.ptr + df.pFiles); 22092 magic(chars); 22093 } else { 22094 char* chars = cast(char*) (data.ptr + df.pFiles); 22095 magic(chars); 22096 } 22097 dg(result[0 .. count]); 22098 } 22099 else throw new NotYetImplementedException(); 22100 } 22101 } 22102 22103 /++ 22104 Interface to describe data being dragged. See also [draggable] helper function. 22105 22106 $(PITFALL This is not yet stable and may break in future versions without notice.) 22107 22108 History: 22109 Added February 19, 2021 22110 +/ 22111 interface DraggableData { 22112 version(X11) 22113 alias FormatId = Atom; 22114 else 22115 alias FormatId = uint; 22116 /++ 22117 Gets the platform-specific FormatId associated with the given named format. 22118 22119 This may be a MIME type, but may also be other various strings defined by the 22120 programs you want to interoperate with. 22121 22122 FIXME: sdpy needs to offer data adapter things that look for compatible formats 22123 and convert it to some particular type for you. 22124 +/ 22125 static FormatId getFormatId(string name)() { 22126 version(X11) 22127 return GetAtom!name(XDisplayConnection.get); 22128 else version(Windows) { 22129 static UINT cache; 22130 if(!cache) 22131 cache = RegisterClipboardFormatA(name); 22132 return cache; 22133 } else 22134 throw new NotYetImplementedException(); 22135 } 22136 22137 /++ 22138 Looks up a string to represent the name for the given format, if there is one. 22139 22140 You should avoid using this function because it is slow. It is provided more for 22141 debugging than for primary use. 22142 +/ 22143 static string getFormatName(FormatId format) { 22144 version(X11) { 22145 if(format == 0) 22146 return "None"; 22147 else 22148 return getAtomName(format, XDisplayConnection.get); 22149 } else version(Windows) { 22150 switch(format) { 22151 case CF_UNICODETEXT: return "CF_UNICODETEXT"; 22152 case CF_DIBV5: return "CF_DIBV5"; 22153 case CF_RIFF: return "CF_RIFF"; 22154 case CF_WAVE: return "CF_WAVE"; 22155 case CF_HDROP: return "CF_HDROP"; 22156 default: 22157 char[1024] name; 22158 auto count = GetClipboardFormatNameA(format, name.ptr, name.length); 22159 return name[0 .. count].idup; 22160 } 22161 } else throw new NotYetImplementedException(); 22162 } 22163 22164 FormatId[] availableFormats(); 22165 // Return the slice of data you filled, empty slice if done. 22166 // this is to support the incremental thing 22167 ubyte[] getData(FormatId format, return scope ubyte[] data); 22168 22169 size_t dataLength(FormatId format); 22170 } 22171 22172 /++ 22173 $(PITFALL This is not yet stable and may break in future versions without notice.) 22174 22175 History: 22176 Added February 19, 2021 22177 +/ 22178 DraggableData draggable(string s) { 22179 version(X11) 22180 return new class X11SetSelectionHandler_Text, DraggableData { 22181 this() { 22182 super(s); 22183 } 22184 22185 override FormatId[] availableFormats() { 22186 return X11SetSelectionHandler_Text.availableFormats(); 22187 } 22188 22189 override ubyte[] getData(FormatId format, return scope ubyte[] data) { 22190 return X11SetSelectionHandler_Text.getData(format, data); 22191 } 22192 22193 size_t dataLength(FormatId format) { 22194 return s.length; 22195 } 22196 }; 22197 else version(Windows) 22198 return new class DraggableData { 22199 FormatId[] availableFormats() { 22200 return [CF_UNICODETEXT]; 22201 } 22202 22203 ubyte[] getData(FormatId format, return scope ubyte[] data) { 22204 return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 22205 } 22206 22207 size_t dataLength(FormatId format) { 22208 return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof; 22209 } 22210 }; 22211 else 22212 throw new NotYetImplementedException(); 22213 } 22214 22215 /++ 22216 $(PITFALL This is not yet stable and may break in future versions without notice.) 22217 22218 History: 22219 Added February 19, 2021 22220 +/ 22221 /// Group: drag_and_drop 22222 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) 22223 in { 22224 assert(window !is null); 22225 assert(handler !is null); 22226 } 22227 do 22228 { 22229 version(X11) { 22230 auto sh = cast(X11SetSelectionHandler) handler; 22231 if(sh is null) { 22232 // gotta make my own adapter. 22233 sh = new class X11SetSelectionHandler { 22234 mixin X11SetSelectionHandler_Basics; 22235 22236 Atom[] availableFormats() { return handler.availableFormats(); } 22237 ubyte[] getData(Atom format, return scope ubyte[] data) { 22238 return handler.getData(format, data); 22239 } 22240 22241 // since the drop selection is only ever used once it isn't important 22242 // to reset it. 22243 void done() {} 22244 }; 22245 } 22246 return doDragDropX11(window, sh, action); 22247 } else version(Windows) { 22248 return doDragDropWindows(window, handler, action); 22249 } else throw new NotYetImplementedException(); 22250 } 22251 22252 version(Windows) 22253 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) { 22254 IDataObject obj = new class IDataObject { 22255 ULONG refCount; 22256 ULONG AddRef() { 22257 return ++refCount; 22258 } 22259 ULONG Release() { 22260 return --refCount; 22261 } 22262 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22263 if (IID_IUnknown == *riid) { 22264 *ppv = cast(void*) cast(IUnknown) this; 22265 } 22266 else if (IID_IDataObject == *riid) { 22267 *ppv = cast(void*) cast(IDataObject) this; 22268 } 22269 else { 22270 *ppv = null; 22271 return E_NOINTERFACE; 22272 } 22273 22274 AddRef(); 22275 return NOERROR; 22276 } 22277 22278 HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) { 22279 // writeln("Advise"); 22280 return E_NOTIMPL; 22281 } 22282 HRESULT DUnadvise(DWORD dwConnection) { 22283 return E_NOTIMPL; 22284 } 22285 HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) { 22286 // writeln("EnumDAdvise"); 22287 return OLE_E_ADVISENOTSUPPORTED; 22288 } 22289 // tell what formats it supports 22290 22291 FORMATETC[] types; 22292 this() { 22293 FORMATETC t; 22294 foreach(ty; handler.availableFormats()) { 22295 assert(ty <= ushort.max && ty >= 0); 22296 t.cfFormat = cast(ushort) ty; 22297 t.lindex = -1; 22298 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 22299 t.tymed = TYMED.TYMED_HGLOBAL; 22300 } 22301 types ~= t; 22302 } 22303 HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) { 22304 if(dwDirection == DATADIR.DATADIR_GET) { 22305 *ppenumFormatEtc = new class IEnumFORMATETC { 22306 ULONG refCount; 22307 ULONG AddRef() { 22308 return ++refCount; 22309 } 22310 ULONG Release() { 22311 return --refCount; 22312 } 22313 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22314 if (IID_IUnknown == *riid) { 22315 *ppv = cast(void*) cast(IUnknown) this; 22316 } 22317 else if (IID_IEnumFORMATETC == *riid) { 22318 *ppv = cast(void*) cast(IEnumFORMATETC) this; 22319 } 22320 else { 22321 *ppv = null; 22322 return E_NOINTERFACE; 22323 } 22324 22325 AddRef(); 22326 return NOERROR; 22327 } 22328 22329 22330 int pos; 22331 this() { 22332 pos = 0; 22333 } 22334 22335 HRESULT Clone(IEnumFORMATETC* ppenum) { 22336 // writeln("clone"); 22337 return E_NOTIMPL; // FIXME 22338 } 22339 22340 // Caller is responsible for freeing memory 22341 HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) { 22342 // fetched may be null if celt is one 22343 if(celt != 1) 22344 return E_NOTIMPL; // FIXME 22345 22346 if(celt + pos > types.length) 22347 return S_FALSE; 22348 22349 *rgelt = types[pos++]; 22350 22351 if(pceltFetched !is null) 22352 *pceltFetched = 1; 22353 22354 // writeln("ok celt ", celt); 22355 return S_OK; 22356 } 22357 22358 HRESULT Reset() { 22359 pos = 0; 22360 return S_OK; 22361 } 22362 22363 HRESULT Skip(ULONG celt) { 22364 if(celt + pos <= types.length) { 22365 pos += celt; 22366 return S_OK; 22367 } 22368 return S_FALSE; 22369 } 22370 }; 22371 22372 return S_OK; 22373 } else 22374 return E_NOTIMPL; 22375 } 22376 // given a format, return the format you'd prefer to use cuz it is identical 22377 HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) { 22378 // FIXME: prolly could be better but meh 22379 // writeln("gcf: ", *pformatectIn); 22380 *pformatetcOut = *pformatectIn; 22381 return S_OK; 22382 } 22383 HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 22384 foreach(ty; types) { 22385 if(ty == *pformatetcIn) { 22386 auto format = ty.cfFormat; 22387 // writeln("A: ", *pformatetcIn, "\nB: ", ty); 22388 STGMEDIUM medium; 22389 medium.tymed = TYMED.TYMED_HGLOBAL; 22390 22391 auto sz = handler.dataLength(format); 22392 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz); 22393 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 22394 if(auto data = cast(wchar*) GlobalLock(handle)) { 22395 auto slice = data[0 .. sz]; 22396 scope(exit) 22397 GlobalUnlock(handle); 22398 22399 handler.getData(format, cast(ubyte[]) slice[]); 22400 } 22401 22402 22403 medium.hGlobal = handle; // FIXME 22404 *pmedium = medium; 22405 return S_OK; 22406 } 22407 } 22408 return DV_E_FORMATETC; 22409 } 22410 HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 22411 // writeln("GDH: ", *pformatetcIn); 22412 return E_NOTIMPL; // FIXME 22413 } 22414 HRESULT QueryGetData(FORMATETC* pformatetc) { 22415 auto search = *pformatetc; 22416 search.tymed &= TYMED.TYMED_HGLOBAL; 22417 foreach(ty; types) 22418 if(ty == search) { 22419 // writeln("QueryGetData ", search, " ", types[0]); 22420 return S_OK; 22421 } 22422 if(pformatetc.cfFormat==CF_UNICODETEXT) { 22423 //writeln("QueryGetData FALSE ", search, " ", types[0]); 22424 } 22425 return S_FALSE; 22426 } 22427 HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) { 22428 // writeln("SetData: "); 22429 return E_NOTIMPL; 22430 } 22431 }; 22432 22433 22434 IDropSource src = new class IDropSource { 22435 ULONG refCount; 22436 ULONG AddRef() { 22437 return ++refCount; 22438 } 22439 ULONG Release() { 22440 return --refCount; 22441 } 22442 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22443 if (IID_IUnknown == *riid) { 22444 *ppv = cast(void*) cast(IUnknown) this; 22445 } 22446 else if (IID_IDropSource == *riid) { 22447 *ppv = cast(void*) cast(IDropSource) this; 22448 } 22449 else { 22450 *ppv = null; 22451 return E_NOINTERFACE; 22452 } 22453 22454 AddRef(); 22455 return NOERROR; 22456 } 22457 22458 int QueryContinueDrag(int fEscapePressed, uint grfKeyState) { 22459 if(fEscapePressed) 22460 return DRAGDROP_S_CANCEL; 22461 if(!(grfKeyState & MK_LBUTTON)) 22462 return DRAGDROP_S_DROP; 22463 return S_OK; 22464 } 22465 22466 int GiveFeedback(uint dwEffect) { 22467 return DRAGDROP_S_USEDEFAULTCURSORS; 22468 } 22469 }; 22470 22471 DWORD effect; 22472 22473 if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect."); 22474 22475 DROPEFFECT de = win32DragAndDropAction(action); 22476 22477 // I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time 22478 // but still prolly a FIXME 22479 22480 auto ret = DoDragDrop(obj, src, de, &effect); 22481 /+ 22482 if(ret == DRAGDROP_S_DROP) 22483 writeln("drop ", effect); 22484 else if(ret == DRAGDROP_S_CANCEL) 22485 writeln("cancel"); 22486 else if(ret == S_OK) 22487 writeln("ok"); 22488 else writeln(ret); 22489 +/ 22490 22491 return ret; 22492 } 22493 22494 version(Windows) 22495 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) { 22496 DROPEFFECT de; 22497 22498 with(DragAndDropAction) 22499 with(DROPEFFECT) 22500 final switch(action) { 22501 case none: de = DROPEFFECT_NONE; break; 22502 case copy: de = DROPEFFECT_COPY; break; 22503 case move: de = DROPEFFECT_MOVE; break; 22504 case link: de = DROPEFFECT_LINK; break; 22505 case ask: throw new Exception("ask not implemented yet"); 22506 case custom: throw new Exception("custom not implemented yet"); 22507 } 22508 22509 return de; 22510 } 22511 22512 22513 /++ 22514 History: 22515 Added February 19, 2021 22516 +/ 22517 /// Group: drag_and_drop 22518 void enableDragAndDrop(SimpleWindow window, DropHandler handler) { 22519 version(X11) { 22520 auto display = XDisplayConnection.get; 22521 22522 Atom atom = 5; // right??? 22523 22524 XChangeProperty( 22525 display, 22526 window.impl.window, 22527 GetAtom!"XdndAware"(display), 22528 XA_ATOM, 22529 32 /* bits */, 22530 PropModeReplace, 22531 &atom, 22532 1); 22533 22534 window.dropHandler = handler; 22535 } else version(Windows) { 22536 22537 initDnd(); 22538 22539 auto dropTarget = new class (handler) IDropTarget { 22540 DropHandler handler; 22541 this(DropHandler handler) { 22542 this.handler = handler; 22543 } 22544 ULONG refCount; 22545 ULONG AddRef() { 22546 return ++refCount; 22547 } 22548 ULONG Release() { 22549 return --refCount; 22550 } 22551 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 22552 if (IID_IUnknown == *riid) { 22553 *ppv = cast(void*) cast(IUnknown) this; 22554 } 22555 else if (IID_IDropTarget == *riid) { 22556 *ppv = cast(void*) cast(IDropTarget) this; 22557 } 22558 else { 22559 *ppv = null; 22560 return E_NOINTERFACE; 22561 } 22562 22563 AddRef(); 22564 return NOERROR; 22565 } 22566 22567 22568 // /////////////////// 22569 22570 HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 22571 DropPackage dropPackage = DropPackage(pDataObj); 22572 *pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage)); 22573 return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter 22574 } 22575 22576 HRESULT DragLeave() { 22577 handler.dragLeave(); 22578 // release the IDataObject if needed 22579 return S_OK; 22580 } 22581 22582 HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 22583 auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates 22584 22585 *pdwEffect = win32DragAndDropAction(res.action); 22586 // same as DragEnter basically 22587 return S_OK; 22588 } 22589 22590 HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 22591 DropPackage pkg = DropPackage(pDataObj); 22592 handler.drop(&pkg); 22593 22594 return S_OK; 22595 } 22596 }; 22597 // Windows can hold on to the handler and try to call it 22598 // during which time the GC can't see it. so important to 22599 // manually manage this. At some point i'll FIXME and make 22600 // all my com instances manually managed since they supposed 22601 // to respect the refcount. 22602 import core.memory; 22603 GC.addRoot(cast(void*) dropTarget); 22604 22605 if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK) 22606 throw new WindowsApiException("RegisterDragDrop", GetLastError()); 22607 22608 window.dropHandler = handler; 22609 } else throw new NotYetImplementedException(); 22610 } 22611 22612 22613 22614 static if(UsingSimpledisplayX11) { 22615 22616 enum _NET_WM_STATE_ADD = 1; 22617 enum _NET_WM_STATE_REMOVE = 0; 22618 enum _NET_WM_STATE_TOGGLE = 2; 22619 22620 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases. 22621 void demandAttention(SimpleWindow window, bool needs = true) { 22622 demandAttention(window.impl.window, needs); 22623 } 22624 22625 /// ditto 22626 void demandAttention(Window window, bool needs = true) { 22627 setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs); 22628 } 22629 22630 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) { 22631 auto display = XDisplayConnection.get(); 22632 if(atom == None) 22633 return; // non-failure error 22634 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 22635 22636 XClientMessageEvent xclient; 22637 22638 xclient.type = EventType.ClientMessage; 22639 xclient.window = window; 22640 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 22641 xclient.format = 32; 22642 xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 22643 xclient.data.l[1] = atom; 22644 xclient.data.l[2] = atom2; 22645 xclient.data.l[3] = 1; 22646 // [3] == source. 0 == unknown, 1 == app, 2 == else 22647 22648 XSendEvent( 22649 display, 22650 RootWindow(display, DefaultScreen(display)), 22651 false, 22652 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 22653 cast(XEvent*) &xclient 22654 ); 22655 22656 /+ 22657 XChangeProperty( 22658 display, 22659 window.impl.window, 22660 GetAtom!"_NET_WM_STATE"(display), 22661 XA_ATOM, 22662 32 /* bits */, 22663 PropModeAppend, 22664 &atom, 22665 1); 22666 +/ 22667 } 22668 22669 private Atom dndActionAtom(Display* display, DragAndDropAction action) { 22670 Atom actionAtom; 22671 with(DragAndDropAction) 22672 final switch(action) { 22673 case none: actionAtom = None; break; 22674 case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break; 22675 case move: actionAtom = GetAtom!"XdndActionMove"(display); break; 22676 case link: actionAtom = GetAtom!"XdndActionLink"(display); break; 22677 case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break; 22678 case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break; 22679 } 22680 22681 return actionAtom; 22682 } 22683 22684 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) { 22685 // FIXME: I need to show user feedback somehow. 22686 auto display = XDisplayConnection.get; 22687 22688 auto actionAtom = dndActionAtom(display, action); 22689 assert(actionAtom, "Don't use action none to accept a drop"); 22690 22691 setX11Selection!"XdndSelection"(window, handler, null); 22692 22693 auto oldKeyHandler = window.handleKeyEvent; 22694 scope(exit) window.handleKeyEvent = oldKeyHandler; 22695 22696 auto oldCharHandler = window.handleCharEvent; 22697 scope(exit) window.handleCharEvent = oldCharHandler; 22698 22699 auto oldMouseHandler = window.handleMouseEvent; 22700 scope(exit) window.handleMouseEvent = oldMouseHandler; 22701 22702 Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child 22703 22704 import core.sys.posix.sys.time; 22705 timeval tv; 22706 gettimeofday(&tv, null); 22707 22708 Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; 22709 22710 Time lastMouseTimestamp; 22711 22712 bool dnding = true; 22713 Window lastIn = None; 22714 22715 void leave() { 22716 if(lastIn == None) 22717 return; 22718 22719 XEvent ev; 22720 ev.xclient.type = EventType.ClientMessage; 22721 ev.xclient.window = lastIn; 22722 ev.xclient.message_type = GetAtom!("XdndLeave", true)(display); 22723 ev.xclient.format = 32; 22724 ev.xclient.data.l[0] = window.impl.window; 22725 22726 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 22727 XFlush(display); 22728 22729 lastIn = None; 22730 } 22731 22732 void enter(Window w) { 22733 assert(lastIn == None); 22734 22735 lastIn = w; 22736 22737 XEvent ev; 22738 ev.xclient.type = EventType.ClientMessage; 22739 ev.xclient.window = lastIn; 22740 ev.xclient.message_type = GetAtom!("XdndEnter", true)(display); 22741 ev.xclient.format = 32; 22742 ev.xclient.data.l[0] = window.impl.window; 22743 ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types 22744 22745 auto types = handler.availableFormats(); 22746 assert(types.length > 0); 22747 22748 ev.xclient.data.l[2] = types[0]; 22749 if(types.length > 1) 22750 ev.xclient.data.l[3] = types[1]; 22751 if(types.length > 2) 22752 ev.xclient.data.l[4] = types[2]; 22753 22754 // FIXME: other types?!?!? and make sure we skip TARGETS 22755 22756 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 22757 XFlush(display); 22758 } 22759 22760 void position(int rootX, int rootY) { 22761 assert(lastIn != None); 22762 22763 XEvent ev; 22764 ev.xclient.type = EventType.ClientMessage; 22765 ev.xclient.window = lastIn; 22766 ev.xclient.message_type = GetAtom!("XdndPosition", true)(display); 22767 ev.xclient.format = 32; 22768 ev.xclient.data.l[0] = window.impl.window; 22769 ev.xclient.data.l[1] = 0; // reserved 22770 ev.xclient.data.l[2] = (rootX << 16) | rootY; 22771 ev.xclient.data.l[3] = dataTimestamp; 22772 ev.xclient.data.l[4] = actionAtom; 22773 22774 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 22775 XFlush(display); 22776 22777 } 22778 22779 void drop() { 22780 XEvent ev; 22781 ev.xclient.type = EventType.ClientMessage; 22782 ev.xclient.window = lastIn; 22783 ev.xclient.message_type = GetAtom!("XdndDrop", true)(display); 22784 ev.xclient.format = 32; 22785 ev.xclient.data.l[0] = window.impl.window; 22786 ev.xclient.data.l[1] = 0; // reserved 22787 ev.xclient.data.l[2] = dataTimestamp; 22788 22789 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 22790 XFlush(display); 22791 22792 lastIn = None; 22793 dnding = false; 22794 } 22795 22796 // fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler 22797 // but idk if i should... 22798 22799 window.setEventHandlers( 22800 delegate(KeyEvent ev) { 22801 if(ev.pressed == true && ev.key == Key.Escape) { 22802 // cancel 22803 dnding = false; 22804 } 22805 }, 22806 delegate(MouseEvent ev) { 22807 if(ev.timestamp < lastMouseTimestamp) 22808 return; 22809 22810 lastMouseTimestamp = ev.timestamp; 22811 22812 if(ev.type == MouseEventType.motion) { 22813 auto display = XDisplayConnection.get; 22814 auto root = RootWindow(display, DefaultScreen(display)); 22815 22816 Window topWindow; 22817 int rootX, rootY; 22818 22819 XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow); 22820 22821 if(topWindow == None) 22822 return; 22823 22824 top: 22825 if(auto result = topWindow in eligibility) { 22826 auto dropWindow = *result; 22827 if(dropWindow == None) { 22828 leave(); 22829 return; 22830 } 22831 22832 if(dropWindow != lastIn) { 22833 leave(); 22834 enter(dropWindow); 22835 position(rootX, rootY); 22836 } else { 22837 position(rootX, rootY); 22838 } 22839 } else { 22840 // determine eligibility 22841 auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM); 22842 if(data.length == 1) { 22843 // in case there is no WM or it isn't reparenting 22844 eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh 22845 } else { 22846 22847 Window tryScanChildren(Window search, int maxRecurse) { 22848 // could be reparenting window manager, so gotta check the next few children too 22849 Window child; 22850 int x; 22851 int y; 22852 XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child); 22853 22854 if(child == None) 22855 return None; 22856 auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM); 22857 if(data.length == 1) { 22858 return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh 22859 } else { 22860 if(maxRecurse) 22861 return tryScanChildren(child, maxRecurse - 1); 22862 else 22863 return None; 22864 } 22865 22866 } 22867 22868 // if a WM puts more than 3 layers on it, like wtf is it doing, screw that. 22869 auto topResult = tryScanChildren(topWindow, 3); 22870 // it is easy to have a false negative due to the mouse going over a WM 22871 // child window like the close button if separate from the frame... so I 22872 // can't really cache negatives, :( 22873 if(topResult != None) { 22874 eligibility[topWindow] = topResult; 22875 goto top; // reload to do the positioning iff eligibility changed lest we endless loop 22876 } 22877 } 22878 22879 } 22880 22881 } else if(ev.type == MouseEventType.buttonReleased) { 22882 drop(); 22883 dnding = false; 22884 } 22885 } 22886 ); 22887 22888 window.grabInput(); 22889 scope(exit) 22890 window.releaseInputGrab(); 22891 22892 22893 EventLoop.get.run(() => dnding); 22894 22895 return 0; 22896 } 22897 22898 /// X-specific 22899 TrueColorImage getWindowNetWmIcon(Window window) { 22900 try { 22901 auto display = XDisplayConnection.get; 22902 22903 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 22904 22905 if (data.length > arch_ulong.sizeof * 2) { 22906 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 22907 // these are an array of rgba images that we have to convert into pixmaps ourself 22908 22909 int width = cast(int) meta[0]; 22910 int height = cast(int) meta[1]; 22911 22912 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 22913 22914 static if(arch_ulong.sizeof == 4) { 22915 bytes = bytes[0 .. width * height * 4]; 22916 alias imageData = bytes; 22917 } else static if(arch_ulong.sizeof == 8) { 22918 bytes = bytes[0 .. width * height * 8]; 22919 auto imageData = new ubyte[](4 * width * height); 22920 } else static assert(0); 22921 22922 22923 22924 // this returns ARGB. Remember it is little-endian so 22925 // we have BGRA 22926 // our thing uses RGBA, which in little endian, is ABGR 22927 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 22928 auto r = bytes[idx + 2]; 22929 auto g = bytes[idx + 1]; 22930 auto b = bytes[idx + 0]; 22931 auto a = bytes[idx + 3]; 22932 22933 imageData[idx2 + 0] = r; 22934 imageData[idx2 + 1] = g; 22935 imageData[idx2 + 2] = b; 22936 imageData[idx2 + 3] = a; 22937 } 22938 22939 return new TrueColorImage(width, height, imageData); 22940 } 22941 22942 return null; 22943 } catch(Exception e) { 22944 return null; 22945 } 22946 } 22947 22948 } /* UsingSimpledisplayX11 */ 22949 22950 22951 void loadBinNameToWindowClassName () { 22952 import core.stdc.stdlib : realloc; 22953 version(linux) { 22954 // args[0] MAY be empty, so we'll just use this 22955 import core.sys.posix.unistd : readlink; 22956 char[1024] ebuf = void; // 1KB should be enough for everyone! 22957 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 22958 if (len < 1) return; 22959 } else /*version(Windows)*/ { 22960 import core.runtime : Runtime; 22961 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 22962 auto ebuf = Runtime.args[0]; 22963 auto len = ebuf.length; 22964 } 22965 auto pos = len; 22966 while (pos > 0 && ebuf[pos-1] != '/') --pos; 22967 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 22968 if (sdpyWindowClassStr is null) return; // oops 22969 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 22970 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 22971 } 22972 22973 /++ 22974 An interface representing a font that is drawn with custom facilities. 22975 22976 You might want [OperatingSystemFont] instead, which represents 22977 a font loaded and drawn by functions native to the operating system. 22978 22979 WARNING: I might still change this. 22980 +/ 22981 interface DrawableFont : MeasurableFont { 22982 /++ 22983 Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string. 22984 22985 Implementations must use the painter's fillColor to draw a rectangle behind the string, 22986 then use the outlineColor to draw the string. It might alpha composite if there's a transparent 22987 fill color, but that's up to the implementation. 22988 +/ 22989 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 22990 22991 /++ 22992 Requests that the given string is added to the image cache. You should only do this rarely, but 22993 if you have a string that you know will be used over and over again, adding it to a cache can 22994 improve things (assuming the implementation actually has a cache; it is also valid for an implementation 22995 to implement this as a do-nothing method). 22996 +/ 22997 void cacheString(SimpleWindow window, Color foreground, Color background, string text); 22998 } 22999 23000 /++ 23001 Loads a true type font using [arsd.ttf] that can be drawn as images on windows 23002 through a [ScreenPainter]. That module must be compiled in if you choose to use this function. 23003 23004 You should also consider [OperatingSystemFont], which loads and draws a font with 23005 facilities native to the user's operating system. You might also consider 23006 [arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind 23007 of game, as they have their own ways to draw text too. 23008 23009 Be warned: this can be slow, especially on remote connections to the X server, since 23010 it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface 23011 offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to 23012 experiment in your specific case. 23013 23014 Please note that the return type of [DrawableFont] also includes an implementation of 23015 [MeasurableFont]. 23016 +/ 23017 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 23018 import arsd.ttf; 23019 static class ArsdTtfFont : DrawableFont { 23020 TtfFont font; 23021 int size; 23022 this(in ubyte[] data, int size) { 23023 font = TtfFont(data); 23024 this.size = size; 23025 23026 23027 auto scale = stbtt_ScaleForPixelHeight(&font.font, size); 23028 int ascent_, descent_, line_gap; 23029 stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap); 23030 23031 int advance, lsb; 23032 stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb); 23033 xWidth = cast(int) (advance * scale); 23034 stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb); 23035 MWidth = cast(int) (advance * scale); 23036 } 23037 23038 private int ascent_; 23039 private int descent_; 23040 private int xWidth; 23041 private int MWidth; 23042 23043 bool isMonospace() { 23044 return xWidth == MWidth; 23045 } 23046 int averageWidth() { 23047 return xWidth; 23048 } 23049 int height() { 23050 return size; 23051 } 23052 int ascent() { 23053 return ascent_; 23054 } 23055 int descent() { 23056 return descent_; 23057 } 23058 23059 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 23060 int width, height; 23061 font.getStringSize(s, size, width, height); 23062 return width; 23063 } 23064 23065 23066 23067 Sprite[string] cache; 23068 23069 void cacheString(SimpleWindow window, Color foreground, Color background, string text) { 23070 auto sprite = new Sprite(window, stringToImage(foreground, background, text)); 23071 cache[text] = sprite; 23072 } 23073 23074 Image stringToImage(Color fg, Color bg, in char[] text) { 23075 int width, height; 23076 auto data = font.renderString(text, size, width, height); 23077 auto image = new TrueColorImage(width, height); 23078 int pos = 0; 23079 foreach(y; 0 .. height) 23080 foreach(x; 0 .. width) { 23081 fg.a = data[0]; 23082 bg.a = 255; 23083 auto color = alphaBlend(fg, bg); 23084 image.imageData.bytes[pos++] = color.r; 23085 image.imageData.bytes[pos++] = color.g; 23086 image.imageData.bytes[pos++] = color.b; 23087 image.imageData.bytes[pos++] = data[0]; 23088 data = data[1 .. $]; 23089 } 23090 assert(data.length == 0); 23091 23092 return Image.fromMemoryImage(image); 23093 } 23094 23095 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 23096 Sprite sprite = (text in cache) ? *(text in cache) : null; 23097 23098 auto fg = painter.impl._outlineColor; 23099 auto bg = painter.impl._fillColor; 23100 23101 if(sprite !is null) { 23102 auto w = cast(SimpleWindow) painter.window; 23103 assert(w !is null); 23104 23105 sprite.drawAt(painter, upperLeft); 23106 } else { 23107 painter.drawImage(upperLeft, stringToImage(fg, bg, text)); 23108 } 23109 } 23110 } 23111 23112 return new ArsdTtfFont(data, size); 23113 } 23114 23115 class NotYetImplementedException : Exception { 23116 this(string file = __FILE__, size_t line = __LINE__) { 23117 super("Not yet implemented", file, line); 23118 } 23119 } 23120 23121 /// 23122 __gshared bool librariesSuccessfullyLoaded = true; 23123 /// 23124 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 23125 23126 private mixin template DynamicLoadSupplementalOpenGL(Iface) { 23127 // mixin(staticForeachReplacement!Iface); 23128 static foreach(name; __traits(derivedMembers, Iface)) 23129 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 23130 23131 void loadDynamicLibrary() @nogc { 23132 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 23133 } 23134 23135 void loadDynamicLibraryForReal() { 23136 foreach(name; __traits(derivedMembers, Iface)) { 23137 mixin("alias tmp = " ~ name ~ ";"); 23138 tmp = cast(typeof(tmp)) glbindGetProcAddress(name); 23139 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL"); 23140 } 23141 } 23142 } 23143 23144 /+ 23145 private const(char)[] staticForeachReplacement(Iface)() pure { 23146 /* 23147 // just this for gdc 9.... 23148 // when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease 23149 23150 static foreach(name; __traits(derivedMembers, Iface)) 23151 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 23152 */ 23153 23154 char[] code = new char[](__traits(derivedMembers, Iface).length * 64); 23155 size_t pos; 23156 23157 void append(in char[] what) { 23158 if(pos + what.length > code.length) 23159 code.length = (code.length * 3) / 2; 23160 code[pos .. pos + what.length] = what[]; 23161 pos += what.length; 23162 } 23163 23164 foreach(name; __traits(derivedMembers, Iface)) { 23165 append(`__gshared typeof(&__traits(getMember, Iface, "`); 23166 append(name); 23167 append(`")) `); 23168 append(name); 23169 append(";"); 23170 } 23171 23172 return code[0 .. pos]; 23173 } 23174 +/ 23175 23176 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) { 23177 //mixin(staticForeachReplacement!Iface); 23178 static foreach(name; __traits(derivedMembers, Iface)) 23179 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 23180 23181 private __gshared void* libHandle; 23182 private __gshared bool attempted; 23183 23184 void loadDynamicLibrary() @nogc { 23185 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 23186 } 23187 23188 bool loadAttempted() { 23189 return attempted; 23190 } 23191 bool loadSuccessful() { 23192 return libHandle !is null; 23193 } 23194 23195 void loadDynamicLibraryForReal() { 23196 attempted = true; 23197 version(Posix) { 23198 import core.sys.posix.dlfcn; 23199 version(OSX) { 23200 version(X11) 23201 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 23202 else 23203 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 23204 } else { 23205 version(apitrace) { 23206 if(library == "GL" || library == "GLX") { 23207 libHandle = dlopen("glxtrace.so", RTLD_NOW); 23208 if(libHandle is null) { 23209 assert(false, "Failed to load `glxtrace.so`."); 23210 } 23211 } 23212 else { 23213 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 23214 } 23215 } 23216 else { 23217 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 23218 } 23219 if(libHandle is null) { 23220 libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW); 23221 } 23222 } 23223 23224 static void* loadsym(void* l, const char* name) { 23225 import core.stdc.stdlib; 23226 if(l is null) 23227 return &abort; 23228 return dlsym(l, name); 23229 } 23230 } else version(Windows) { 23231 import core.sys.windows.winbase; 23232 libHandle = LoadLibrary(library ~ ".dll"); 23233 static void* loadsym(void* l, const char* name) { 23234 import core.stdc.stdlib; 23235 if(l is null) 23236 return &abort; 23237 return GetProcAddress(l, name); 23238 } 23239 } 23240 if(libHandle is null) { 23241 success = false; 23242 //throw new Exception("load failure of library " ~ library); 23243 } 23244 foreach(name; __traits(derivedMembers, Iface)) { 23245 mixin("alias tmp = " ~ name ~ ";"); 23246 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 23247 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 23248 } 23249 } 23250 23251 void unloadDynamicLibrary() { 23252 version(Posix) { 23253 import core.sys.posix.dlfcn; 23254 dlclose(libHandle); 23255 } else version(Windows) { 23256 import core.sys.windows.winbase; 23257 FreeLibrary(libHandle); 23258 } 23259 foreach(name; __traits(derivedMembers, Iface)) 23260 mixin(name ~ " = null;"); 23261 } 23262 } 23263 23264 // version(X11) 23265 /++ 23266 Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"]. 23267 23268 $(WARNING 23269 This function is exempted from stability guarantees. 23270 ) 23271 +/ 23272 float customScalingFactorForMonitor(int monitorNumber) @system { 23273 import core.stdc.stdlib; 23274 auto val = getenv("ARSD_SCALING_FACTOR"); 23275 23276 // FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given 23277 if(val is null) 23278 return 1.0; 23279 23280 char[16] buffer = 0; 23281 int pos; 23282 23283 const(char)* at = val; 23284 23285 foreach(item; 0 .. monitorNumber + 1) { 23286 if(*at == 0) 23287 break; // reuse the last number when we at the end of the string 23288 pos = 0; 23289 while(pos + 1 < buffer.length && *at && *at != ';') { 23290 buffer[pos++] = *at; 23291 at++; 23292 } 23293 if(*at) 23294 at++; // skip the semicolon 23295 buffer[pos] = 0; 23296 } 23297 23298 //sdpyPrintDebugString(buffer[0 .. pos]); 23299 23300 import core.stdc.math; 23301 auto f = atof(buffer.ptr); 23302 23303 if(f <= 0.0 || isnan(f) || isinf(f)) 23304 return 1.0; 23305 23306 return f; 23307 } 23308 23309 void guiAbortProcess(string msg) { 23310 import core.stdc.stdlib; 23311 version(Windows) { 23312 WCharzBuffer t = WCharzBuffer(msg); 23313 MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0); 23314 } else { 23315 import core.stdc.stdio; 23316 fwrite(msg.ptr, 1, msg.length, stderr); 23317 msg = "\n"; 23318 fwrite(msg.ptr, 1, msg.length, stderr); 23319 fflush(stderr); 23320 } 23321 23322 abort(); 23323 } 23324 23325 private int minInternal(int a, int b) { 23326 return (a < b) ? a : b; 23327 } 23328 23329 private alias scriptable = arsd_jsvar_compatible;