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 module opengl3test; 371 import arsd.simpledisplay; 372 373 // based on https://learnopengl.com/Getting-started/Hello-Triangle 374 375 void main() { 376 // First thing we do, before creating the window, is declare what version we want. 377 setOpenGLContextVersion(3, 3); 378 // turning off legacy compat is required to use version 3.3 and newer 379 openGLContextCompatible = false; 380 381 uint VAO; 382 OpenGlShader shader; 383 384 // then we can create the window. 385 auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing); 386 387 // additional setup needs to be done when it is visible, simpledisplay offers a property 388 // for exactly that: 389 window.visibleForTheFirstTime = delegate() { 390 // now with the window loaded, we can start loading the modern opengl functions. 391 392 // you MUST set the context first. 393 window.setAsCurrentOpenGlContext; 394 // then load the remainder of the library 395 gl3.loadDynamicLibrary(); 396 397 // now you can create the shaders, etc. 398 shader = new OpenGlShader( 399 OpenGlShader.Source(GL_VERTEX_SHADER, ` 400 #version 330 core 401 layout (location = 0) in vec3 aPos; 402 void main() { 403 gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); 404 } 405 `), 406 OpenGlShader.Source(GL_FRAGMENT_SHADER, ` 407 #version 330 core 408 out vec4 FragColor; 409 uniform vec4 mycolor; 410 void main() { 411 FragColor = mycolor; 412 } 413 `), 414 ); 415 416 // and do whatever other setup you want. 417 418 float[] vertices = [ 419 0.5f, 0.5f, 0.0f, // top right 420 0.5f, -0.5f, 0.0f, // bottom right 421 -0.5f, -0.5f, 0.0f, // bottom left 422 -0.5f, 0.5f, 0.0f // top left 423 ]; 424 uint[] indices = [ // note that we start from 0! 425 0, 1, 3, // first Triangle 426 1, 2, 3 // second Triangle 427 ]; 428 uint VBO, EBO; 429 glGenVertexArrays(1, &VAO); 430 // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 431 glBindVertexArray(VAO); 432 433 glGenBuffers(1, &VBO); 434 glGenBuffers(1, &EBO); 435 436 glBindBuffer(GL_ARRAY_BUFFER, VBO); 437 glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW); 438 439 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 440 glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); 441 442 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null); 443 glEnableVertexAttribArray(0); 444 445 // the library will set the initial viewport and trigger our first draw, 446 // so these next two lines are NOT needed. they are just here as comments 447 // to show what would happen next. 448 449 // glViewport(0, 0, window.width, window.height); 450 // window.redrawOpenGlSceneNow(); 451 }; 452 453 // this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;` 454 // it is our render method. 455 window.redrawOpenGlScene = delegate() { 456 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); 457 glClear(GL_COLOR_BUFFER_BIT); 458 459 glUseProgram(shader.shaderProgram); 460 461 // the shader helper class has methods to set uniforms too 462 shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0); 463 464 glBindVertexArray(VAO); 465 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null); 466 }; 467 468 window.eventLoop(0); 469 } 470 --- 471 472 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. 473 474 $(H3 $(ID vulkan) Vulkan) 475 476 See a couple examples ported from GLFW to simpledisplay using the erupted vulkan bindings: 477 478 https://github.com/adamdruppe/VulkanSdpyDemo 479 480 https://github.com/Cy-Tek/VulkanizeD/compare/main...adamdruppe:VulkanizeDSdpy:main 481 482 $(H3 $(ID topic-images) Displaying images) 483 You can also load PNG images using [arsd.png]. 484 485 --- 486 // dmd example.d simpledisplay.d color.d png.d 487 import arsd.simpledisplay; 488 import arsd.png; 489 490 void main() { 491 auto image = Image.fromMemoryImage(readPng("image.png")); 492 displayImage(image); 493 } 494 --- 495 496 Compile with `dmd example.d simpledisplay.d png.d`. 497 498 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. 499 500 $(H3 $(ID topic-sprites) Sprites) 501 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. 502 503 [Sprite] is also the only facility that currently supports alpha blending without using OpenGL . 504 505 $(H3 $(ID topic-clipboard) Clipboard) 506 The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time. 507 508 It also has helpers for handling X-specific events. 509 510 $(H3 $(ID topic-dnd) Drag and Drop) 511 See [enableDragAndDrop] and [draggable]. 512 513 $(H3 $(ID topic-timers) Timers) 514 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]. 515 516 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. 517 518 --- 519 import arsd.simpledisplay; 520 521 void main() { 522 auto window = new SimpleWindow(400, 400); 523 // every 100 ms, it will draw a random line 524 // on the window. 525 window.eventLoop(100, { 526 auto painter = window.draw(); 527 528 import std.random; 529 // random color 530 painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256)); 531 // random line 532 painter.drawLine( 533 Point(uniform(0, window.width), uniform(0, window.height)), 534 Point(uniform(0, window.width), uniform(0, window.height))); 535 536 }); 537 } 538 --- 539 540 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. 541 542 The pulse timer and instances of the [Timer] class may be combined at will. 543 544 --- 545 import arsd.simpledisplay; 546 547 void main() { 548 auto window = new SimpleWindow(400, 400); 549 auto timer = new Timer(1000, delegate { 550 auto painter = window.draw(); 551 painter.clear(); 552 }); 553 554 window.eventLoop(0); 555 } 556 --- 557 558 Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop. 559 560 $(H3 $(ID topic-os-helpers) OS-specific helpers) 561 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. 562 563 See also: `xwindows.d` from my github. 564 565 $(H3 $(ID topic-os-extension) Extending with OS-specific functionality) 566 `handleNativeEvent` and `handleNativeGlobalEvent`. 567 568 $(H3 $(ID topic-integration) Integration with other libraries) 569 Integration with a third-party event loop is possible. 570 571 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. 572 573 $(H3 $(ID topic-guis) GUI widgets) 574 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! 575 576 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. 577 578 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.) 579 580 minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes. 581 582 $(H2 Platform-specific tips and tricks) 583 584 X_tips: 585 586 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. 587 588 Windows_tips: 589 590 You can add icons or manifest files to your exe using a resource file. 591 592 To create a Windows .ico file, use the gimp or something. I'll write a helper 593 program later. 594 595 Create `yourapp.rc`: 596 597 ```rc 598 1 ICON filename.ico 599 CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest" 600 ``` 601 602 And `yourapp.exe.manifest`: 603 604 ```xml 605 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 606 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 607 <assemblyIdentity 608 version="1.0.0.0" 609 processorArchitecture="*" 610 name="CompanyName.ProductName.YourApplication" 611 type="win32" 612 /> 613 <description>Your application description here.</description> 614 <dependency> 615 <dependentAssembly> 616 <assemblyIdentity 617 type="win32" 618 name="Microsoft.Windows.Common-Controls" 619 version="6.0.0.0" 620 processorArchitecture="*" 621 publicKeyToken="6595b64144ccf1df" 622 language="*" 623 /> 624 </dependentAssembly> 625 </dependency> 626 <application xmlns="urn:schemas-microsoft-com:asm.v3"> 627 <windowsSettings> 628 <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style --> 629 <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style --> 630 <!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text --> 631 <!-- to render crisply in DPI-unaware contexts --> 632 <!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>--> 633 </windowsSettings> 634 </application> 635 </assembly> 636 ``` 637 638 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`. 639 640 Doing this lets you opt into various new things since Windows XP. 641 642 See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests 643 644 $(H2 Tips) 645 646 $(H3 Name conflicts) 647 648 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: 649 650 --- 651 static import sdpy = arsd.simpledisplay; 652 import arsd.simpledisplay : SimpleWindow; 653 654 void main() { 655 auto window = new SimpleWindow(); 656 sdpy.EventLoop.get.run(); 657 } 658 --- 659 660 $(H2 $(ID developer-notes) Developer notes) 661 662 I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa 663 implementation though. 664 665 The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both 666 suck. If I was rewriting it, I wouldn't do it that way again. 667 668 This file must not have any more required dependencies. If you need bindings, add 669 them right to this file. Once it gets into druntime and is there for a while, remove 670 bindings from here to avoid conflicts (or put them in an appropriate version block 671 so it continues to just work on old dmd), but wait a couple releases before making the 672 transition so this module remains usable with older versions of dmd. 673 674 You may have optional dependencies if needed by putting them in version blocks or 675 template functions. You may also extend the module with other modules with UFCS without 676 actually editing this - that is nice to do if you can. 677 678 Try to make functions work the same way across operating systems. I typically make 679 it thinly wrap Windows, then emulate that on Linux. 680 681 A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding 682 Phobos! So try to avoid it. 683 684 See more comments throughout the source. 685 686 I realize this file is fairly large, but over half that is just bindings at the bottom 687 or documentation at the top. Some of the classes are a bit big too, but hopefully easy 688 to understand. I suggest you jump around the source by looking for a particular 689 declaration you're interested in, like `class SimpleWindow` using your editor's search 690 function, then look at one piece at a time. 691 692 Authors: Adam D. Ruppe with the help of others. If you need help, please email me with 693 destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can 694 ping me, adam_d_ruppe, and I'll usually see it if I'm around. 695 696 I live in the eastern United States, so I will most likely not be around at night in 697 that US east timezone. 698 699 License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License. 700 701 Building documentation: use my adrdox generator, `dub run adrdox`. 702 703 Examples: 704 705 $(DIV $(ID Event-example)) 706 $(H3 $(ID event-example) Event example) 707 This program creates a window and draws events inside them as they 708 happen, scrolling the text in the window as needed. Run this program 709 and experiment to get a feel for where basic input events take place 710 in the library. 711 712 --- 713 // dmd example.d simpledisplay.d color.d 714 import arsd.simpledisplay; 715 import std.conv; 716 717 void main() { 718 auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d"); 719 720 int y = 0; 721 722 void addLine(string text) { 723 auto painter = window.draw(); 724 725 if(y + painter.fontHeight >= window.height) { 726 painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight); 727 y -= painter.fontHeight; 728 } 729 730 painter.outlineColor = Color.red; 731 painter.fillColor = Color.black; 732 painter.drawRectangle(Point(0, y), window.width, painter.fontHeight); 733 734 painter.outlineColor = Color.white; 735 736 painter.drawText(Point(10, y), text); 737 738 y += painter.fontHeight; 739 } 740 741 window.eventLoop(1000, 742 () { 743 addLine("Timer went off!"); 744 }, 745 (KeyEvent event) { 746 addLine(to!string(event)); 747 }, 748 (MouseEvent event) { 749 addLine(to!string(event)); 750 }, 751 (dchar ch) { 752 addLine(to!string(ch)); 753 } 754 ); 755 } 756 --- 757 758 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. 759 760 $(COMMENT 761 This program displays a pie chart. Clicking on a color will increase its share of the pie. 762 763 --- 764 765 --- 766 ) 767 768 History: 769 Initial release in April 2011. 770 771 simpledisplay was stand alone until about 2015. It then added a dependency on [arsd.color] and changed its name to `arsd.simpledisplay`. 772 773 On March 4, 2023 (dub v11.0), it started importing [arsd.core] as well, making that a build-time requirement. 774 +/ 775 module arsd.simpledisplay; 776 777 import arsd.core; 778 779 // FIXME: tetris demo 780 // FIXME: space invaders demo 781 // FIXME: asteroids demo 782 783 /++ $(ID Pong-example) 784 $(H3 Pong) 785 786 This program creates a little Pong-like game. Player one is controlled 787 with the keyboard. Player two is controlled with the mouse. It demos 788 the pulse timer, event handling, and some basic drawing. 789 +/ 790 version(demos) 791 unittest { 792 // dmd example.d simpledisplay.d color.d 793 import arsd.simpledisplay; 794 795 enum paddleMovementSpeed = 8; 796 enum paddleHeight = 48; 797 798 void main() { 799 auto window = new SimpleWindow(600, 400, "Pong game!"); 800 801 int playerOnePosition, playerTwoPosition; 802 int playerOneMovement, playerTwoMovement; 803 int playerOneScore, playerTwoScore; 804 805 int ballX, ballY; 806 int ballDx, ballDy; 807 808 void serve() { 809 import std.random; 810 811 ballX = window.width / 2; 812 ballY = window.height / 2; 813 ballDx = uniform(-4, 4) * 3; 814 ballDy = uniform(-4, 4) * 3; 815 if(ballDx == 0) 816 ballDx = uniform(0, 2) == 0 ? 3 : -3; 817 } 818 819 serve(); 820 821 window.eventLoop(50, // set a 50 ms timer pulls 822 // This runs once per timer pulse 823 delegate () { 824 auto painter = window.draw(); 825 826 painter.clear(); 827 828 // Update everyone's motion 829 playerOnePosition += playerOneMovement; 830 playerTwoPosition += playerTwoMovement; 831 832 ballX += ballDx; 833 ballY += ballDy; 834 835 // Bounce off the top and bottom edges of the window 836 if(ballY + 7 >= window.height) 837 ballDy = -ballDy; 838 if(ballY - 8 <= 0) 839 ballDy = -ballDy; 840 841 // Bounce off the paddle, if it is in position 842 if(ballX - 8 <= 16) { 843 if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) { 844 ballDx = -ballDx + 1; // add some speed to keep it interesting 845 ballDy += playerOneMovement; // and y movement based on your controls too 846 ballX = 24; // move it past the paddle so it doesn't wiggle inside 847 } else { 848 // Missed it 849 playerTwoScore ++; 850 serve(); 851 } 852 } 853 854 if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1 855 if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) { 856 ballDx = -ballDx - 1; 857 ballDy += playerTwoMovement; 858 ballX = window.width - 24; 859 } else { 860 // Missed it 861 playerOneScore ++; 862 serve(); 863 } 864 } 865 866 // Draw the paddles 867 painter.outlineColor = Color.black; 868 painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight)); 869 painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight)); 870 871 // Draw the ball 872 painter.fillColor = Color.red; 873 painter.outlineColor = Color.yellow; 874 painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7)); 875 876 // Draw the score 877 painter.outlineColor = Color.blue; 878 import std.conv; 879 painter.drawText(Point(64, 4), to!string(playerOneScore)); 880 painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore)); 881 882 }, 883 delegate (KeyEvent event) { 884 // Player 1's controls are the arrow keys on the keyboard 885 if(event.key == Key.Down) 886 playerOneMovement = event.pressed ? paddleMovementSpeed : 0; 887 if(event.key == Key.Up) 888 playerOneMovement = event.pressed ? -paddleMovementSpeed : 0; 889 890 }, 891 delegate (MouseEvent event) { 892 // Player 2's controls are mouse movement while the left button is held down 893 if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) { 894 if(event.dy > 0) 895 playerTwoMovement = paddleMovementSpeed; 896 else if(event.dy < 0) 897 playerTwoMovement = -paddleMovementSpeed; 898 } else { 899 playerTwoMovement = 0; 900 } 901 } 902 ); 903 } 904 } 905 906 /++ $(H3 $(ID example-minesweeper) Minesweeper) 907 908 This minesweeper demo shows how we can implement another classic 909 game with simpledisplay and shows some mouse input and basic output 910 code. 911 +/ 912 version(demos) 913 unittest { 914 import arsd.simpledisplay; 915 916 enum GameSquare { 917 mine = 0, 918 clear, 919 m1, m2, m3, m4, m5, m6, m7, m8 920 } 921 922 enum UserSquare { 923 unknown, 924 revealed, 925 flagged, 926 questioned 927 } 928 929 enum GameState { 930 inProgress, 931 lose, 932 win 933 } 934 935 GameSquare[] board; 936 UserSquare[] userState; 937 GameState gameState; 938 int boardWidth; 939 int boardHeight; 940 941 bool isMine(int x, int y) { 942 if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight) 943 return false; 944 return board[y * boardWidth + x] == GameSquare.mine; 945 } 946 947 GameState reveal(int x, int y) { 948 if(board[y * boardWidth + x] == GameSquare.clear) { 949 floodFill(userState, boardWidth, boardHeight, 950 UserSquare.unknown, UserSquare.revealed, 951 x, y, 952 (x, y) { 953 if(board[y * boardWidth + x] == GameSquare.clear) 954 return true; 955 else { 956 userState[y * boardWidth + x] = UserSquare.revealed; 957 return false; 958 } 959 }); 960 } else { 961 userState[y * boardWidth + x] = UserSquare.revealed; 962 if(isMine(x, y)) 963 return GameState.lose; 964 } 965 966 foreach(state; userState) { 967 if(state == UserSquare.unknown || state == UserSquare.questioned) 968 return GameState.inProgress; 969 } 970 971 return GameState.win; 972 } 973 974 void initializeBoard(int width, int height, int numberOfMines) { 975 boardWidth = width; 976 boardHeight = height; 977 board.length = width * height; 978 979 userState.length = width * height; 980 userState[] = UserSquare.unknown; 981 982 import std.algorithm, std.random, std.range; 983 984 board[] = GameSquare.clear; 985 986 foreach(minePosition; randomSample(iota(0, board.length), numberOfMines)) 987 board[minePosition] = GameSquare.mine; 988 989 int x; 990 int y; 991 foreach(idx, ref square; board) { 992 if(square == GameSquare.clear) { 993 int danger = 0; 994 danger += isMine(x-1, y-1)?1:0; 995 danger += isMine(x-1, y)?1:0; 996 danger += isMine(x-1, y+1)?1:0; 997 danger += isMine(x, y-1)?1:0; 998 danger += isMine(x, y+1)?1:0; 999 danger += isMine(x+1, y-1)?1:0; 1000 danger += isMine(x+1, y)?1:0; 1001 danger += isMine(x+1, y+1)?1:0; 1002 1003 square = cast(GameSquare) (danger + 1); 1004 } 1005 1006 x++; 1007 if(x == width) { 1008 x = 0; 1009 y++; 1010 } 1011 } 1012 } 1013 1014 void redraw(SimpleWindow window) { 1015 import std.conv; 1016 1017 auto painter = window.draw(); 1018 1019 painter.clear(); 1020 1021 final switch(gameState) with(GameState) { 1022 case inProgress: 1023 break; 1024 case win: 1025 painter.fillColor = Color.green; 1026 painter.drawRectangle(Point(0, 0), window.width, window.height); 1027 return; 1028 case lose: 1029 painter.fillColor = Color.red; 1030 painter.drawRectangle(Point(0, 0), window.width, window.height); 1031 return; 1032 } 1033 1034 int x = 0; 1035 int y = 0; 1036 1037 foreach(idx, square; board) { 1038 auto state = userState[idx]; 1039 1040 final switch(state) with(UserSquare) { 1041 case unknown: 1042 painter.outlineColor = Color.black; 1043 painter.fillColor = Color(128,128,128); 1044 1045 painter.drawRectangle( 1046 Point(x * 20, y * 20), 1047 20, 20 1048 ); 1049 break; 1050 case revealed: 1051 if(square == GameSquare.clear) { 1052 painter.outlineColor = Color.white; 1053 painter.fillColor = Color.white; 1054 1055 painter.drawRectangle( 1056 Point(x * 20, y * 20), 1057 20, 20 1058 ); 1059 } else { 1060 painter.outlineColor = Color.black; 1061 painter.fillColor = Color.white; 1062 1063 painter.drawText( 1064 Point(x * 20, y * 20), 1065 to!string(square)[1..2], 1066 Point(x * 20 + 20, y * 20 + 20), 1067 TextAlignment.Center | TextAlignment.VerticalCenter); 1068 } 1069 break; 1070 case flagged: 1071 painter.outlineColor = Color.black; 1072 painter.fillColor = Color.red; 1073 painter.drawRectangle( 1074 Point(x * 20, y * 20), 1075 20, 20 1076 ); 1077 break; 1078 case questioned: 1079 painter.outlineColor = Color.black; 1080 painter.fillColor = Color.yellow; 1081 painter.drawRectangle( 1082 Point(x * 20, y * 20), 1083 20, 20 1084 ); 1085 break; 1086 } 1087 1088 x++; 1089 if(x == boardWidth) { 1090 x = 0; 1091 y++; 1092 } 1093 } 1094 1095 } 1096 1097 void main() { 1098 auto window = new SimpleWindow(200, 200); 1099 1100 initializeBoard(10, 10, 10); 1101 1102 redraw(window); 1103 window.eventLoop(0, 1104 delegate (MouseEvent me) { 1105 if(me.type != MouseEventType.buttonPressed) 1106 return; 1107 auto x = me.x / 20; 1108 auto y = me.y / 20; 1109 if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) { 1110 if(me.button == MouseButton.left) { 1111 gameState = reveal(x, y); 1112 } else { 1113 userState[y*boardWidth+x] = UserSquare.flagged; 1114 } 1115 redraw(window); 1116 } 1117 } 1118 ); 1119 } 1120 } 1121 1122 import arsd.core; 1123 1124 // FIXME: tetris demo 1125 // FIXME: space invaders demo 1126 // FIXME: asteroids demo 1127 1128 version(OSX) version(DigitalMars) version=OSXCocoa; 1129 1130 1131 version(OSXCocoa) { 1132 version=without_opengl; 1133 version=allow_unimplemented_features; 1134 // version=OSXCocoa; 1135 // pragma(linkerDirective, "-framework Cocoa"); 1136 } 1137 1138 version(without_opengl) { 1139 enum SdpyIsUsingIVGLBinds = false; 1140 } else /*version(Posix)*/ { 1141 static if (__traits(compiles, (){import iv.glbinds;})) { 1142 enum SdpyIsUsingIVGLBinds = true; 1143 public import iv.glbinds; 1144 //pragma(msg, "SDPY: using iv.glbinds"); 1145 } else { 1146 enum SdpyIsUsingIVGLBinds = false; 1147 } 1148 //} else { 1149 // enum SdpyIsUsingIVGLBinds = false; 1150 } 1151 1152 1153 version(Windows) { 1154 //import core.sys.windows.windows; 1155 import core.sys.windows.winnls; 1156 import core.sys.windows.windef; 1157 import core.sys.windows.basetyps; 1158 import core.sys.windows.winbase; 1159 import core.sys.windows.winuser; 1160 import core.sys.windows.shellapi; 1161 import core.sys.windows.wingdi; 1162 static import gdi = core.sys.windows.wingdi; // so i 1163 1164 pragma(lib, "gdi32"); 1165 pragma(lib, "user32"); 1166 1167 // for AlphaBlend... a breaking change.... 1168 version(CRuntime_DigitalMars) { } else 1169 pragma(lib, "msimg32"); 1170 } else version (linux) { 1171 //k8: this is hack for rdmd. sorry. 1172 static import core.sys.linux.epoll; 1173 static import core.sys.linux.timerfd; 1174 } 1175 1176 1177 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off. 1178 1179 // http://wiki.dlang.org/Simpledisplay.d 1180 1181 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led 1182 1183 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl 1184 // but can i control the scroll lock led 1185 1186 1187 // Note: if you are using Image on X, you might want to do: 1188 /* 1189 static if(UsingSimpledisplayX11) { 1190 if(!Image.impl.xshmAvailable) { 1191 // the images will use the slower XPutImage, you might 1192 // want to consider an alternative method to get better speed 1193 } 1194 } 1195 1196 If the shared memory extension is available though, simpledisplay uses it 1197 for a significant speed boost whenever you draw large Images. 1198 */ 1199 1200 // 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. 1201 1202 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()! 1203 1204 /* 1205 Biggest FIXME: 1206 make sure the key event numbers match between X and Windows OR provide symbolic constants on each system 1207 1208 clean up opengl contexts when their windows close 1209 1210 fix resizing the bitmaps/pixmaps 1211 */ 1212 1213 // BTW on Windows: 1214 // -L/SUBSYSTEM:WINDOWS:5.0 1215 // to dmd will make a nice windows binary w/o a console if you want that. 1216 1217 /* 1218 Stuff to add: 1219 1220 use multibyte functions everywhere we can 1221 1222 OpenGL windows 1223 more event stuff 1224 extremely basic windows w/ no decoration for tooltips, splash screens, etc. 1225 1226 1227 resizeEvent 1228 and make the windows non-resizable by default, 1229 or perhaps stretched (if I can find something in X like StretchBlt) 1230 1231 take a screenshot function! 1232 1233 Pens and brushes? 1234 Maybe a global event loop? 1235 1236 Mouse deltas 1237 Key items 1238 */ 1239 1240 /* 1241 From MSDN: 1242 1243 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate. 1244 1245 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. 1246 1247 */ 1248 1249 version(linux) { 1250 version = X11; 1251 version(without_libnotify) { 1252 // we cool 1253 } 1254 else 1255 version = libnotify; 1256 } 1257 1258 version(libnotify) { 1259 pragma(lib, "dl"); 1260 import core.sys.posix.dlfcn; 1261 1262 void delegate()[int] libnotify_action_delegates; 1263 int libnotify_action_delegates_count; 1264 extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) { 1265 auto idx = cast(int) user_data; 1266 if(auto dgptr = idx in libnotify_action_delegates) { 1267 (*dgptr)(); 1268 libnotify_action_delegates.remove(idx); 1269 } 1270 } 1271 1272 struct C_DynamicLibrary { 1273 void* handle; 1274 this(string name) { 1275 handle = dlopen((name ~ "\0").ptr, RTLD_NOW); 1276 if(handle is null) 1277 throw new Exception("dlopen"); 1278 } 1279 1280 void close() { 1281 dlclose(handle); 1282 } 1283 1284 ~this() { 1285 // close 1286 } 1287 1288 // FIXME: this looks up by name every time.... 1289 template call(string func, Ret, Args...) { 1290 extern(C) Ret function(Args) fptr; 1291 typeof(fptr) call() { 1292 fptr = cast(typeof(fptr)) dlsym(handle, func); 1293 return fptr; 1294 } 1295 } 1296 } 1297 1298 C_DynamicLibrary* libnotify; 1299 } 1300 1301 version(OSX) { 1302 version(OSXCocoa) {} 1303 else { version = X11; } 1304 } 1305 //version = OSXCocoa; // this was written by KennyTM 1306 version(FreeBSD) 1307 version = X11; 1308 version(Solaris) 1309 version = X11; 1310 1311 version(X11) { 1312 version(without_xft) {} 1313 else version=with_xft; 1314 } 1315 1316 void featureNotImplemented()() { 1317 version(allow_unimplemented_features) 1318 throw new NotYetImplementedException(); 1319 else 1320 static assert(0); 1321 } 1322 1323 // these are so the static asserts don't trigger unless you want to 1324 // add support to it for an OS 1325 version(Windows) 1326 version = with_timer; 1327 version(linux) 1328 version = with_timer; 1329 version(OSXCocoa) 1330 version = with_timer; 1331 1332 version(with_timer) 1333 enum bool SimpledisplayTimerAvailable = true; 1334 else 1335 enum bool SimpledisplayTimerAvailable = false; 1336 1337 /// 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. 1338 version(Windows) 1339 enum bool UsingSimpledisplayWindows = true; 1340 else 1341 enum bool UsingSimpledisplayWindows = false; 1342 1343 /// 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. 1344 version(X11) 1345 enum bool UsingSimpledisplayX11 = true; 1346 else 1347 enum bool UsingSimpledisplayX11 = false; 1348 1349 /// 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. 1350 version(OSXCocoa) 1351 enum bool UsingSimpledisplayCocoa = true; 1352 else 1353 enum bool UsingSimpledisplayCocoa = false; 1354 1355 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception. 1356 version(Windows) 1357 enum multipleWindowsSupported = true; 1358 else version(X11) 1359 enum multipleWindowsSupported = true; 1360 else version(OSXCocoa) 1361 enum multipleWindowsSupported = true; 1362 else 1363 static assert(0); 1364 1365 version(without_opengl) 1366 enum bool OpenGlEnabled = false; 1367 else 1368 enum bool OpenGlEnabled = true; 1369 1370 /++ 1371 Adds the necessary pragmas to your application to use the Windows gui subsystem. 1372 If you mix this in above your `main` function, you no longer need to use the linker 1373 flags explicitly. It does the necessary version blocks for various compilers and runtimes. 1374 1375 It does nothing if not compiling for Windows, so you need not version it out yourself. 1376 1377 Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and 1378 stderr writeln. It will fail and throw an exception. 1379 1380 This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`. 1381 1382 History: 1383 Added November 24, 2021 (dub v10.4) 1384 +/ 1385 mixin template EnableWindowsSubsystem() { 1386 version(Windows) 1387 version(CRuntime_Microsoft) { 1388 pragma(linkerDirective, "/subsystem:windows"); 1389 version(LDC) 1390 pragma(linkerDirective, "/entry:wmainCRTStartup"); 1391 else 1392 pragma(linkerDirective, "/entry:mainCRTStartup"); 1393 } 1394 } 1395 1396 1397 /++ 1398 After selecting a type from [WindowTypes], you may further customize 1399 its behavior by setting one or more of these flags. 1400 1401 1402 The different window types have different meanings of `normal`. If the 1403 window type already is a good match for what you want to do, you should 1404 just use [WindowFlags.normal], the default, which will do the right thing 1405 for your users. 1406 1407 The window flags will not always be honored by the operating system 1408 and window managers; they are hints, not commands. 1409 +/ 1410 enum WindowFlags : int { 1411 normal = 0, /// 1412 skipTaskbar = 1, /// 1413 alwaysOnTop = 2, /// 1414 alwaysOnBottom = 4, /// 1415 cannotBeActivated = 8, /// 1416 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. 1417 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. 1418 /++ 1419 Sets the window as a short-lived child of its parent, but unlike an ordinary child, 1420 it is still a top-level window. This should NOT be set separately for most window types. 1421 1422 A transient window will not keep the application open if its main window closes. 1423 1424 $(PITFALL This may not be correctly implemented and its behavior is subject to change.) 1425 1426 1427 From the ICCM: 1428 1429 $(BLOCKQUOTE 1430 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. 1431 1432 $(CITE https://tronche.com/gui/x/icccm/sec-4.html) 1433 ) 1434 1435 So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag. 1436 1437 History: 1438 Added February 23, 2021 but not yet stabilized. 1439 +/ 1440 transient = 64, 1441 /++ 1442 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. 1443 1444 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 1445 1446 History: 1447 Added April 1, 2022 1448 +/ 1449 managesChildWindowFocus = 128, 1450 1451 dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually. 1452 } 1453 1454 /++ 1455 When creating a window, you can pass a type to SimpleWindow's constructor, 1456 then further customize the window by changing `WindowFlags`. 1457 1458 1459 You should mostly only need [normal], [undecorated], and [eventOnly] for normal 1460 use. The others are there to build a foundation for a higher level GUI toolkit, 1461 but are themselves not as high level as you might think from their names. 1462 1463 This list is based on the EMWH spec for X11. 1464 http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896 1465 +/ 1466 enum WindowTypes : int { 1467 /// An ordinary application window. 1468 normal, 1469 /// 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. 1470 undecorated, 1471 /// 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. 1472 eventOnly, 1473 /// A drop down menu, such as from a menu bar 1474 dropdownMenu, 1475 /// A popup menu, such as from a right click 1476 popupMenu, 1477 /// A popup bubble notification 1478 notification, 1479 /* 1480 menu, /// a tearable menu bar 1481 splashScreen, /// a loading splash screen for your application 1482 tooltip, /// A tiny window showing temporary help text or something. 1483 comboBoxDropdown, 1484 dialog, 1485 toolbar 1486 */ 1487 /// a child nested inside the parent. You must pass a parent window to the ctor 1488 nestedChild, 1489 1490 /++ 1491 The type you get when you pass in an existing browser handle, which means most 1492 of simpledisplay's fancy things will not be done since they were never set up. 1493 1494 Using this to the main SimpleWindow constructor explicitly will trigger an assertion 1495 failure; you should use the existing handle constructor. 1496 1497 History: 1498 Added November 17, 2022 (previously it would have type `normal`) 1499 +/ 1500 minimallyWrapped 1501 } 1502 1503 1504 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call 1505 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features 1506 private __gshared char* sdpyWindowClassStr = null; 1507 private __gshared bool sdpyOpenGLContextAllowFallback = false; 1508 1509 /** 1510 Set OpenGL context version to use. This has no effect on non-OpenGL windows. 1511 You may want to change context version if you want to use advanced shaders or 1512 other modern OpenGL techinques. This setting doesn't affect already created 1513 windows. You may use version 2.1 as your default, which should be supported 1514 by any box since 2006, so seems to be a reasonable choice. 1515 1516 Note that by default version is set to `0`, which forces SimpleDisplay to use 1517 old context creation code without any version specified. This is the safest 1518 way to init OpenGL, but it may not give you access to advanced features. 1519 1520 See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL 1521 */ 1522 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); } 1523 1524 /** 1525 Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed 1526 pipeline functions, and without "compatible" mode you won't be able to use 1527 your old non-shader-based code with such contexts. By default SimpleDisplay 1528 creates compatible context, so you can gradually upgrade your OpenGL code if 1529 you want to (or leave it as is, as it should "just work"). 1530 */ 1531 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; } 1532 1533 /** 1534 Set to `true` to allow creating OpenGL context with lower version than requested 1535 instead of throwing. If fallback was activated (or legacy OpenGL was requested), 1536 `openGLContextFallbackActivated()` will return `true`. 1537 */ 1538 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; } 1539 1540 /** 1541 After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context. 1542 */ 1543 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); } 1544 1545 /++ 1546 History: 1547 Added April 24, 2023 (dub v11.0) 1548 +/ 1549 version(without_opengl) {} else 1550 auto openGLCurrentContext() { 1551 version(Windows) 1552 return wglGetCurrentContext(); 1553 else 1554 return glXGetCurrentContext(); 1555 } 1556 1557 1558 /** 1559 Set window class name for all following `new SimpleWindow()` calls. 1560 1561 WARNING! For Windows, you should set your class name before creating any 1562 window, and NEVER change it after that! 1563 */ 1564 void sdpyWindowClass (const(char)[] v) { 1565 import core.stdc.stdlib : realloc; 1566 if (v.length == 0) v = "SimpleDisplayWindow"; 1567 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1); 1568 if (sdpyWindowClassStr is null) return; // oops 1569 sdpyWindowClassStr[0..v.length+1] = 0; 1570 sdpyWindowClassStr[0..v.length] = v[]; 1571 } 1572 1573 /** 1574 Get current window class name. 1575 */ 1576 string sdpyWindowClass () { 1577 if (sdpyWindowClassStr is null) return null; 1578 foreach (immutable idx; 0..size_t.max-1) { 1579 if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup; 1580 } 1581 return null; 1582 } 1583 1584 /++ 1585 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. 1586 1587 If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0. 1588 +/ 1589 float[2] getDpi() { 1590 float[2] dpi; 1591 version(Windows) { 1592 HDC screen = GetDC(null); 1593 dpi[0] = GetDeviceCaps(screen, LOGPIXELSX); 1594 dpi[1] = GetDeviceCaps(screen, LOGPIXELSY); 1595 } else version(X11) { 1596 auto display = XDisplayConnection.get; 1597 auto screen = DefaultScreen(display); 1598 1599 void fallback() { 1600 /+ 1601 // 25.4 millimeters in an inch... 1602 dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4; 1603 dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4; 1604 +/ 1605 1606 // the physical size isn't actually as important as the logical size since this is 1607 // all about scaling really 1608 dpi[0] = 96; 1609 dpi[1] = 96; 1610 } 1611 1612 auto xft = getXftDpi(); 1613 if(xft is float.init) 1614 fallback(); 1615 else { 1616 dpi[0] = xft; 1617 dpi[1] = xft; 1618 } 1619 } 1620 1621 return dpi; 1622 } 1623 1624 version(X11) 1625 float getXftDpi() { 1626 auto display = XDisplayConnection.get; 1627 1628 char* resourceString = XResourceManagerString(display); 1629 XrmInitialize(); 1630 1631 if (resourceString) { 1632 auto db = XrmGetStringDatabase(resourceString); 1633 XrmValue value; 1634 char* type; 1635 if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) { 1636 if (value.addr) { 1637 import core.stdc.stdlib; 1638 return atof(cast(char*) value.addr); 1639 } 1640 } 1641 } 1642 1643 return float.init; 1644 } 1645 1646 /++ 1647 Implementation used by [SimpleWindow.takeScreenshot]. 1648 1649 Params: 1650 handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen. 1651 width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target. 1652 height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target. 1653 x = the x-offset of the image to capture, from the left. 1654 y = the y-offset of the image to capture, from the top. 1655 1656 History: 1657 Added on March 14, 2021 1658 1659 Documented public on September 23, 2021 with full support for null params (dub 10.3) 1660 1661 +/ 1662 TrueColorImage trueColorImageFromNativeHandle(PaintingHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) { 1663 TrueColorImage got; 1664 version(X11) { 1665 auto display = XDisplayConnection.get; 1666 if(handle == 0) 1667 handle = RootWindow(display, DefaultScreen(display)); 1668 1669 if(width == 0 || height == 0) { 1670 Window root; 1671 int xpos, ypos; 1672 uint widthret, heightret, borderret, depthret; 1673 XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret); 1674 1675 if(width == 0) 1676 width = widthret; 1677 if(height == 0) 1678 height = heightret; 1679 } 1680 1681 auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap); 1682 1683 // https://github.com/adamdruppe/arsd/issues/98 1684 1685 auto i = new Image(image); 1686 got = i.toTrueColorImage(); 1687 1688 XDestroyImage(image); 1689 } else version(Windows) { 1690 auto hdc = GetDC(handle); 1691 scope(exit) ReleaseDC(handle, hdc); 1692 1693 if(width == 0 || height == 0) { 1694 BITMAP bmHeader; 1695 auto bm = GetCurrentObject(hdc, OBJ_BITMAP); 1696 GetObject(bm, BITMAP.sizeof, &bmHeader); 1697 if(width == 0) 1698 width = bmHeader.bmWidth; 1699 if(height == 0) 1700 height = bmHeader.bmHeight; 1701 } 1702 1703 auto i = new Image(width, height); 1704 HDC hdcMem = CreateCompatibleDC(hdc); 1705 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 1706 BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY); 1707 SelectObject(hdcMem, hbmOld); 1708 DeleteDC(hdcMem); 1709 1710 got = i.toTrueColorImage(); 1711 } else featureNotImplemented(); 1712 1713 return got; 1714 } 1715 1716 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE); 1717 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow; 1718 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi; 1719 1720 version(Windows) 1721 shared static this() { 1722 auto lib = LoadLibrary("User32.dll"); 1723 if(lib is null) 1724 return; 1725 //scope(exit) 1726 //FreeLibrary(lib); 1727 1728 SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext"); 1729 1730 if(SetProcessDpiAwarenessContext is null) 1731 return; 1732 1733 enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4; 1734 if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { 1735 //writeln(GetLastError()); 1736 } 1737 1738 GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow"); 1739 SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi"); 1740 } 1741 1742 /++ 1743 Blocking mode for event loop calls associated with a window instance. 1744 1745 History: 1746 Added December 8, 2021 (dub v10.5). Prior to that, all calls to 1747 `window.eventLoop` were the same as calls to `EventLoop.get.run`; that 1748 is, all would block until the application quit. 1749 1750 That behavior can still be achieved here with `untilApplicationQuits`, 1751 or explicitly calling the top-level `EventLoop.get.run` function. 1752 +/ 1753 enum BlockingMode { 1754 /++ 1755 The event loop call will block until the whole application is ready 1756 to quit if it is the only one running, but if it is nested inside 1757 another one, it will only block until the window you're calling it on 1758 closes. 1759 +/ 1760 automatic = 0x00, 1761 /++ 1762 The event loop call will only return when the whole application 1763 is ready to quit. This usually means all windows have been closed. 1764 1765 This is appropriate for your main application event loop. 1766 +/ 1767 untilApplicationQuits = 0x01, 1768 /++ 1769 The event loop will return when the window you're calling it on 1770 closes. If there are other windows still open, they may be destroyed 1771 unless you have another event loop running later. 1772 1773 This might be appropriate for a modal dialog box loop. Remember that 1774 other windows are still processing input though, so you can end up 1775 with a lengthy call stack if this happens in a loop, similar to a 1776 recursive function (well, it literally is a recursive function, just 1777 not an obvious looking one). 1778 +/ 1779 untilWindowCloses = 0x02, 1780 /++ 1781 If an event loop is already running, this call will immediately 1782 return, allowing the existing loop to handle it. If not, this call 1783 will block until the condition you bitwise-or into the flag. 1784 1785 The default is to block until the application quits, same as with 1786 the `automatic` setting (since if it were nested, which triggers until 1787 window closes in automatic, this flag would instead not block at all), 1788 but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`, 1789 it will only nest until the window closes. You might want that if you are 1790 going to open two windows simultaneously and want closing just one of them 1791 to trigger the event loop return. 1792 +/ 1793 onlyIfNotNested = 0x10, 1794 } 1795 1796 /++ 1797 The flagship window class. 1798 1799 1800 SimpleWindow tries to make ordinary windows very easy to create and use without locking you 1801 out of more advanced or complex features of the underlying windowing system. 1802 1803 For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")` 1804 and get a suitable window to work with. 1805 1806 From there, you can opt into additional features, like custom resizability and OpenGL support 1807 with the next two constructor arguments. Or, if you need even more, you can set a window type 1808 and customization flags with the final two constructor arguments. 1809 1810 If none of that works for you, you can also create a window using native function calls, then 1811 wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember, 1812 though, if you do this, managing the window is still your own responsibility! Notably, you 1813 will need to destroy it yourself. 1814 +/ 1815 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { 1816 1817 /++ 1818 Copies the window's current state into a [TrueColorImage]. 1819 1820 Be warned: this can be a very slow operation 1821 1822 History: 1823 Actually implemented on March 14, 2021 1824 +/ 1825 TrueColorImage takeScreenshot() { 1826 version(Windows) 1827 return trueColorImageFromNativeHandle(impl.hwnd, _width, _height); 1828 else version(OSXCocoa) 1829 throw new NotYetImplementedException(); 1830 else 1831 return trueColorImageFromNativeHandle(impl.window, _width, _height); 1832 } 1833 1834 /++ 1835 Returns the actual logical DPI for the window on its current display monitor. If the window 1836 straddles monitors, it will return the value of one or the other in a platform-defined manner. 1837 1838 Please note this function may return zero if it doesn't know the answer! 1839 1840 1841 On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10), 1842 or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up). 1843 1844 On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems, 1845 this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the 1846 window primarily resides on by checking the center point of the window against the monitor map. 1847 1848 Returns: 1849 0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It 1850 assumes the X and Y dpi are the same. 1851 1852 History: 1853 Added November 26, 2021 (dub v10.4) 1854 1855 It said "physical dpi" in the description prior to July 29, 2022, but the behavior was 1856 always a logical value on Windows and usually one on Linux too, so now the docs reflect 1857 that. 1858 1859 Bugs: 1860 Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically 1861 just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to 1862 set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor 1863 and 1.5 on the secondary monitor. 1864 1865 The local dpi is not necessarily related to the physical dpi of the monitor. The name 1866 is a historical misnomer - the real thing of interest is the scale factor and due to 1867 compatibility concerns the scale would modify dpi values to trick applications. But since 1868 that's the terminology common out there, I used it too. 1869 1870 See_Also: 1871 [getDpi] gives the value provided for the default monitor. Not necessarily the same 1872 as this since the window many be on a different monitor, but it is a reasonable fallback 1873 to use if `actualDpi` returns 0. 1874 1875 [onDpiChanged] is changed when `actualDpi` has changed. 1876 +/ 1877 int actualDpi() { 1878 version(X11) bool useFallbackDpi = false; 1879 if(!actualDpiLoadAttempted) { 1880 // FIXME: do the actual monitor we are on 1881 // and on X this is a good chance to load the monitor map. 1882 version(Windows) { 1883 if(GetDpiForWindow) 1884 actualDpi_ = GetDpiForWindow(impl.hwnd); 1885 } else version(X11) { 1886 if(!xRandrInfoLoadAttemped) { 1887 xRandrInfoLoadAttemped = true; 1888 if(!XRandrLibrary.attempted) { 1889 XRandrLibrary.loadDynamicLibrary(); 1890 } 1891 1892 if(XRandrLibrary.loadSuccessful) { 1893 auto display = XDisplayConnection.get; 1894 int scratch; 1895 int major, minor; 1896 if(!XRRQueryExtension(display, &xrrEventBase, &scratch)) 1897 goto fallback; 1898 1899 XRRQueryVersion(display, &major, &minor); 1900 if(major <= 1 && minor < 5) 1901 goto fallback; 1902 1903 int count; 1904 XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count); 1905 if(monitors is null) 1906 goto fallback; 1907 scope(exit) XRRFreeMonitors(monitors); 1908 1909 MonitorInfo.info = MonitorInfo.info[0 .. 0]; 1910 MonitorInfo.info.assumeSafeAppend(); 1911 foreach(idx, monitor; monitors[0 .. count]) { 1912 MonitorInfo.info ~= MonitorInfo( 1913 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1914 Size(monitor.mwidth, monitor.mheight), 1915 cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0]) 1916 ); 1917 1918 /+ 1919 if(monitor.mwidth == 0 || monitor.mheight == 0) 1920 // unknown physical size, just guess 96 to avoid divide by zero 1921 MonitorInfo.info ~= MonitorInfo( 1922 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1923 Size(monitor.mwidth, monitor.mheight), 1924 96 1925 ); 1926 else 1927 // and actual thing 1928 MonitorInfo.info ~= MonitorInfo( 1929 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1930 Size(monitor.mwidth, monitor.mheight), 1931 minInternal( 1932 // millimeter to int then rounding up. 1933 cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5), 1934 cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5) 1935 ) 1936 ); 1937 +/ 1938 } 1939 // writeln("Here", MonitorInfo.info); 1940 } 1941 } 1942 1943 if(XRandrLibrary.loadSuccessful) { 1944 updateActualDpi(true); 1945 // writeln("updated"); 1946 1947 if(!requestedInput) { 1948 // this is what requests live updates should the configuration change 1949 // each time you select input, it sends an initial event, so very important 1950 // to not get into a loop of selecting input, getting event, updating data, 1951 // and reselecting input... 1952 requestedInput = true; 1953 XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask); 1954 // writeln("requested input"); 1955 } 1956 } else { 1957 fallback: 1958 // make sure we disable events that aren't coming 1959 xrrEventBase = -1; 1960 // best guess... respect the custom scaling user command to some extent at least though 1961 useFallbackDpi = true; 1962 } 1963 } 1964 actualDpiLoadAttempted = true; 1965 } else version(X11) if(MonitorInfo.info.length == 0) { 1966 useFallbackDpi = true; 1967 } 1968 1969 version(X11) 1970 if(useFallbackDpi) 1971 actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0)); 1972 1973 return actualDpi_; 1974 } 1975 1976 private int actualDpi_; 1977 private bool actualDpiLoadAttempted; 1978 1979 version(X11) private { 1980 bool requestedInput; 1981 static bool xRandrInfoLoadAttemped; 1982 struct MonitorInfo { 1983 Rectangle position; 1984 Size size; 1985 int dpi; 1986 1987 static MonitorInfo[] info; 1988 } 1989 bool screenPositionKnown; 1990 int screenPositionX; 1991 int screenPositionY; 1992 void updateActualDpi(bool loadingNow = false) { 1993 if(!loadingNow && !actualDpiLoadAttempted) 1994 actualDpi(); // just to make it do the load 1995 foreach(idx, m; MonitorInfo.info) { 1996 if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) { 1997 bool changed = actualDpi_ && actualDpi_ != m.dpi; 1998 actualDpi_ = m.dpi; 1999 // writeln("monitor ", idx); 2000 if(changed && onDpiChanged) 2001 onDpiChanged(); 2002 break; 2003 } 2004 } 2005 } 2006 } 2007 2008 /++ 2009 Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors 2010 or if the window is moved to a new remote connection or a monitor is hot-swapped. 2011 2012 History: 2013 Added November 26, 2021 (dub v10.4) 2014 2015 See_Also: 2016 [actualDpi] 2017 +/ 2018 void delegate() onDpiChanged; 2019 2020 version(X11) { 2021 void recreateAfterDisconnect() { 2022 if(!stateDiscarded) return; 2023 2024 if(_parent !is null && _parent.stateDiscarded) 2025 _parent.recreateAfterDisconnect(); 2026 2027 bool wasHidden = hidden; 2028 2029 activeScreenPainter = null; // should already be done but just to confirm 2030 2031 actualDpi_ = 0; 2032 actualDpiLoadAttempted = false; 2033 xRandrInfoLoadAttemped = false; 2034 2035 impl.createWindow(_width, _height, _title, openglMode, _parent); 2036 2037 if(auto dh = dropHandler) { 2038 dropHandler = null; 2039 enableDragAndDrop(this, dh); 2040 } 2041 2042 if(recreateAdditionalConnectionState) 2043 recreateAdditionalConnectionState(); 2044 2045 hidden = wasHidden; 2046 stateDiscarded = false; 2047 } 2048 2049 bool stateDiscarded; 2050 void discardConnectionState() { 2051 if(XDisplayConnection.display) 2052 impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway 2053 if(discardAdditionalConnectionState) 2054 discardAdditionalConnectionState(); 2055 stateDiscarded = true; 2056 } 2057 2058 void delegate() discardAdditionalConnectionState; 2059 void delegate() recreateAdditionalConnectionState; 2060 2061 } 2062 2063 private DropHandler dropHandler; 2064 2065 SimpleWindow _parent; 2066 bool beingOpenKeepsAppOpen = true; 2067 /++ 2068 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. 2069 2070 The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them. 2071 2072 Params: 2073 2074 width = the width of the window's client area, in pixels 2075 height = the height of the window's client area, in pixels 2076 title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property. 2077 opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window. 2078 resizable = [Resizability] has three options: 2079 $(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.) 2080 $(P `fixedSize` will not allow the user to resize the window.) 2081 $(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.) 2082 windowType = The type of window you want to make. 2083 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. 2084 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". 2085 +/ 2086 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) { 2087 claimGuiThread(); 2088 version(sdpy_thread_checks) assert(thisIsGuiThread); 2089 this._width = this._virtualWidth = width; 2090 this._height = this._virtualHeight = height; 2091 this.openglMode = opengl; 2092 version(X11) { 2093 // auto scale not implemented except with opengl and even there it is kinda weird 2094 if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no) 2095 resizable = Resizability.fixedSize; 2096 } 2097 this.resizability = resizable; 2098 this.windowType = windowType; 2099 this.customizationFlags = customizationFlags; 2100 this._title = (title is null ? "D Application" : title); 2101 this._parent = parent; 2102 impl.createWindow(width, height, this._title, opengl, parent); 2103 2104 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient)) 2105 beingOpenKeepsAppOpen = false; 2106 } 2107 2108 /// ditto 2109 this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 2110 this(width, height, title, opengl, resizable, windowType, customizationFlags, parent); 2111 } 2112 2113 /// Same as above, except using the `Size` struct instead of separate width and height. 2114 this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) { 2115 this(size.width, size.height, title, opengl, resizable); 2116 } 2117 2118 /// ditto 2119 this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) { 2120 this(size, title, opengl, resizable); 2121 } 2122 2123 2124 /++ 2125 Creates a window based on the given [Image]. It's client area 2126 width and height is equal to the image. (A window's client area 2127 is the drawable space inside; it excludes the title bar, etc.) 2128 2129 Windows based on images will not be resizable and do not use OpenGL. 2130 2131 It will draw the image in upon creation, but this will be overwritten 2132 upon any draws, including the initial window visible event. 2133 2134 You probably do not want to use this and it may be removed from 2135 the library eventually, or I might change it to be a "permanent" 2136 background image; one that is automatically drawn on it before any 2137 other drawing event. idk. 2138 +/ 2139 this(Image image, string title = null) { 2140 this(image.width, image.height, title); 2141 this.image = image; 2142 } 2143 2144 /++ 2145 Wraps a native window handle with very little additional processing - notably no destruction 2146 this is incomplete so don't use it for much right now. The purpose of this is to make native 2147 windows created through the low level API (so you can use platform-specific options and 2148 other details SimpleWindow does not expose) available to the event loop wrappers. 2149 +/ 2150 this(NativeWindowHandle nativeWindow) { 2151 windowType = WindowTypes.minimallyWrapped; 2152 version(Windows) 2153 impl.hwnd = nativeWindow; 2154 else version(X11) { 2155 impl.window = nativeWindow; 2156 if(nativeWindow) 2157 display = XDisplayConnection.get(); // get initial display to not segfault 2158 } else version(OSXCocoa) { 2159 if(nativeWindow !is NullWindow) throw new NotYetImplementedException(); 2160 } else featureNotImplemented(); 2161 // FIXME: set the size correctly 2162 _width = 1; 2163 _height = 1; 2164 if(nativeWindow) 2165 nativeMapping[cast(void*) nativeWindow] = this; 2166 2167 beingOpenKeepsAppOpen = false; 2168 2169 if(nativeWindow) 2170 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 2171 _suppressDestruction = true; // so it doesn't try to close 2172 } 2173 2174 /++ 2175 Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created. 2176 The delegate will be called when the window manager asks you to take focus. 2177 2178 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 2179 2180 History: 2181 Added April 1, 2022 (dub v10.8) 2182 +/ 2183 SimpleWindow delegate() setRequestedInputFocus; 2184 2185 /// Experimental, do not use yet 2186 /++ 2187 Grabs exclusive input from the user until you release it with 2188 [releaseInputGrab]. 2189 2190 2191 Note: it is extremely rude to do this without good reason. 2192 Reasons may include doing some kind of mouse drag operation 2193 or popping up a temporary menu that should get events and will 2194 be dismissed at ease by the user clicking away. 2195 2196 Params: 2197 keyboard = do you want to grab keyboard input? 2198 mouse = grab mouse input? 2199 confine = confine the mouse cursor to inside this window? 2200 2201 History: 2202 Prior to March 11, 2021, grabbing the keyboard would always also 2203 set the X input focus. Now, it only focuses if it is a non-transient 2204 window and otherwise manages the input direction internally. 2205 2206 This means spurious focus/blur events will no longer be sent and the 2207 application will not steal focus from other applications (which the 2208 window manager may have rejected anyway). 2209 +/ 2210 void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) { 2211 static if(UsingSimpledisplayX11) { 2212 XSync(XDisplayConnection.get, 0); 2213 if(keyboard) { 2214 if(isTransient && _parent) { 2215 /* 2216 FIXME: 2217 setting the keyboard focus is not actually that helpful, what I more likely want 2218 is the events from the parent window to be sent over here if we're transient. 2219 */ 2220 2221 _parent.inputProxy = this; 2222 } else { 2223 2224 SimpleWindow setTo; 2225 if(setRequestedInputFocus !is null) 2226 setTo = setRequestedInputFocus(); 2227 if(setTo is null) 2228 setTo = this; 2229 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2230 } 2231 } 2232 if(mouse) { 2233 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 2234 EventMask.PointerMotionMask // FIXME: not efficient 2235 | EventMask.ButtonPressMask 2236 | EventMask.ButtonReleaseMask 2237 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 2238 ) 2239 { 2240 XSync(XDisplayConnection.get, 0); 2241 import core.stdc.stdio; 2242 printf("Grab input failed %d\n", res); 2243 //throw new Exception("Grab input failed"); 2244 } else { 2245 // cool 2246 } 2247 } 2248 2249 } else version(Windows) { 2250 // FIXME: keyboard? 2251 SetCapture(impl.hwnd); 2252 if(confine) { 2253 RECT rcClip; 2254 //RECT rcOldClip; 2255 //GetClipCursor(&rcOldClip); 2256 GetWindowRect(hwnd, &rcClip); 2257 ClipCursor(&rcClip); 2258 } 2259 } else version(OSXCocoa) { 2260 // throw new NotYetImplementedException(); 2261 } else static assert(0); 2262 } 2263 2264 private Point imePopupLocation = Point(0, 0); 2265 2266 /++ 2267 Sets the location for the IME (input method editor) to pop up when the user activates it. 2268 2269 Bugs: 2270 Not implemented outside X11. 2271 +/ 2272 void setIMEPopupLocation(Point location) { 2273 static if(UsingSimpledisplayX11) { 2274 imePopupLocation = location; 2275 updateIMEPopupLocation(); 2276 } else { 2277 // this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least 2278 // throw new NotYetImplementedException(); 2279 } 2280 } 2281 2282 /// ditto 2283 void setIMEPopupLocation(int x, int y) { 2284 return setIMEPopupLocation(Point(x, y)); 2285 } 2286 2287 // we need to remind XIM of where we wanted to place the IME whenever the window moves 2288 // so this function gets called in setIMEPopupLocation as well as whenever the window 2289 // receives a ConfigureNotify event 2290 private void updateIMEPopupLocation() { 2291 static if(UsingSimpledisplayX11) { 2292 if (xic is null) { 2293 return; 2294 } 2295 2296 XPoint nspot; 2297 nspot.x = cast(short) imePopupLocation.x; 2298 nspot.y = cast(short) imePopupLocation.y; 2299 XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null); 2300 XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null); 2301 XFree(preeditAttr); 2302 } 2303 } 2304 2305 private bool imeFocused = true; 2306 2307 /++ 2308 Tells the IME whether or not an input field is currently focused in the window. 2309 2310 Bugs: 2311 Not implemented outside X11. 2312 +/ 2313 void setIMEFocused(bool value) { 2314 imeFocused = value; 2315 updateIMEFocused(); 2316 } 2317 2318 // used to focus/unfocus the IC if necessary when the window gains/loses focus 2319 private void updateIMEFocused() { 2320 static if(UsingSimpledisplayX11) { 2321 if (xic is null) { 2322 return; 2323 } 2324 2325 if (focused && imeFocused) { 2326 XSetICFocus(xic); 2327 } else { 2328 XUnsetICFocus(xic); 2329 } 2330 } 2331 } 2332 2333 /++ 2334 Returns the native window. 2335 2336 History: 2337 Added November 5, 2021 (dub v10.4). Prior to that, you'd have 2338 to access it through the `impl` member (which is semi-supported 2339 but platform specific and here it is simple enough to offer an accessor). 2340 2341 Bugs: 2342 Not implemented outside Windows or X11. 2343 +/ 2344 NativeWindowHandle nativeWindowHandle() { 2345 version(X11) 2346 return impl.window; 2347 else version(Windows) 2348 return impl.hwnd; 2349 else 2350 throw new NotYetImplementedException(); 2351 } 2352 2353 private bool isTransient() { 2354 with(WindowTypes) 2355 final switch(windowType) { 2356 case normal, undecorated, eventOnly: 2357 case nestedChild, minimallyWrapped: 2358 return (customizationFlags & WindowFlags.transient) ? true : false; 2359 case dropdownMenu, popupMenu, notification: 2360 return true; 2361 } 2362 } 2363 2364 private SimpleWindow inputProxy; 2365 2366 /++ 2367 Releases the grab acquired by [grabInput]. 2368 +/ 2369 void releaseInputGrab() { 2370 static if(UsingSimpledisplayX11) { 2371 XUngrabPointer(XDisplayConnection.get, CurrentTime); 2372 if(_parent) 2373 _parent.inputProxy = null; 2374 } else version(Windows) { 2375 ReleaseCapture(); 2376 ClipCursor(null); 2377 } else version(OSXCocoa) { 2378 // throw new NotYetImplementedException(); 2379 } else static assert(0); 2380 } 2381 2382 /++ 2383 Sets the input focus to this window. 2384 2385 You shouldn't call this very often - please let the user control the input focus. 2386 +/ 2387 void focus() { 2388 static if(UsingSimpledisplayX11) { 2389 SimpleWindow setTo; 2390 if(setRequestedInputFocus !is null) 2391 setTo = setRequestedInputFocus(); 2392 if(setTo is null) 2393 setTo = this; 2394 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2395 } else version(Windows) { 2396 SetFocus(this.impl.hwnd); 2397 } else version(OSXCocoa) { 2398 throw new NotYetImplementedException(); 2399 } else static assert(0); 2400 } 2401 2402 /++ 2403 Requests attention from the user for this window. 2404 2405 2406 The typical result of this function is to change the color 2407 of the taskbar icon, though it may be tweaked on specific 2408 platforms. 2409 2410 It is meant to unobtrusively tell the user that something 2411 relevant to them happened in the background and they should 2412 check the window when they get a chance. Upon receiving the 2413 keyboard focus, the window will automatically return to its 2414 natural state. 2415 2416 If the window already has the keyboard focus, this function 2417 may do nothing, because the user is presumed to already be 2418 giving the window attention. 2419 2420 Implementation_note: 2421 2422 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 2423 atom on X11 and the FlashWindow function on Windows. 2424 +/ 2425 void requestAttention() { 2426 if(_focused) 2427 return; 2428 2429 version(Windows) { 2430 FLASHWINFO info; 2431 info.cbSize = info.sizeof; 2432 info.hwnd = impl.hwnd; 2433 info.dwFlags = FLASHW_TRAY; 2434 info.uCount = 1; 2435 2436 FlashWindowEx(&info); 2437 2438 } else version(X11) { 2439 demandingAttention = true; 2440 demandAttention(this, true); 2441 } else version(OSXCocoa) { 2442 throw new NotYetImplementedException(); 2443 } else static assert(0); 2444 } 2445 2446 private bool _focused; 2447 2448 version(X11) private bool demandingAttention; 2449 2450 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 2451 /// You'll have to call `close()` manually if you set this delegate. 2452 void delegate () closeQuery; 2453 2454 /// This will be called when window visibility was changed. 2455 void delegate (bool becomesVisible) visibilityChanged; 2456 2457 /// This will be called when window becomes visible for the first time. 2458 /// You can do OpenGL initialization here. Note that in X11 you can't call 2459 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 2460 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 2461 /// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization. 2462 private bool _visibleForTheFirstTimeCalled; 2463 void delegate () visibleForTheFirstTime; 2464 2465 /// Returns true if the window has been closed. 2466 final @property bool closed() { return _closed; } 2467 2468 private final @property bool notClosed() { return !_closed; } 2469 2470 /// Returns true if the window is focused. 2471 final @property bool focused() { return _focused; } 2472 2473 private bool _visible; 2474 /// Returns true if the window is visible (mapped). 2475 final @property bool visible() { return _visible; } 2476 2477 /// Closes the window. If there are no more open windows, the event loop will terminate. 2478 void close() { 2479 if (!_closed) { 2480 runInGuiThread( { 2481 if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued 2482 if (onClosing !is null) onClosing(); 2483 impl.closeWindow(); 2484 _closed = true; 2485 } ); 2486 } 2487 } 2488 2489 /++ 2490 `close` is one of the few methods that can be called from other threads. This `shared` overload reflects that. 2491 2492 History: 2493 Overload added on March 7, 2021. 2494 +/ 2495 void close() shared { 2496 (cast() this).close(); 2497 } 2498 2499 /++ 2500 2501 +/ 2502 void maximize() { 2503 version(Windows) 2504 ShowWindow(impl.hwnd, SW_MAXIMIZE); 2505 else version(X11) { 2506 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get)); 2507 2508 // also note _NET_WM_STATE_FULLSCREEN 2509 } 2510 2511 } 2512 2513 private bool _fullscreen; 2514 version(Windows) 2515 private WINDOWPLACEMENT g_wpPrev; 2516 2517 /// not fully implemented but planned for a future release 2518 void fullscreen(bool yes) { 2519 version(Windows) { 2520 g_wpPrev.length = WINDOWPLACEMENT.sizeof; 2521 DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE); 2522 if (dwStyle & WS_OVERLAPPEDWINDOW) { 2523 MONITORINFO mi; 2524 mi.cbSize = MONITORINFO.sizeof; 2525 if (GetWindowPlacement(hwnd, &g_wpPrev) && 2526 GetMonitorInfo(MonitorFromWindow(hwnd, 2527 MONITOR_DEFAULTTOPRIMARY), &mi)) { 2528 SetWindowLong(hwnd, GWL_STYLE, 2529 dwStyle & ~WS_OVERLAPPEDWINDOW); 2530 SetWindowPos(hwnd, HWND_TOP, 2531 mi.rcMonitor.left, mi.rcMonitor.top, 2532 mi.rcMonitor.right - mi.rcMonitor.left, 2533 mi.rcMonitor.bottom - mi.rcMonitor.top, 2534 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2535 } 2536 } else { 2537 SetWindowLong(hwnd, GWL_STYLE, 2538 dwStyle | WS_OVERLAPPEDWINDOW); 2539 SetWindowPlacement(hwnd, &g_wpPrev); 2540 SetWindowPos(hwnd, null, 0, 0, 0, 0, 2541 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | 2542 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2543 } 2544 2545 } else version(X11) { 2546 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes); 2547 } 2548 2549 _fullscreen = yes; 2550 2551 } 2552 2553 bool fullscreen() { 2554 return _fullscreen; 2555 } 2556 2557 /++ 2558 Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead. 2559 2560 +/ 2561 void minimize() { 2562 version(Windows) 2563 ShowWindow(impl.hwnd, SW_MINIMIZE); 2564 //else version(X11) 2565 //setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true); 2566 } 2567 2568 /// Alias for `hidden = false` 2569 void show() { 2570 hidden = false; 2571 } 2572 2573 /// Alias for `hidden = true` 2574 void hide() { 2575 hidden = true; 2576 } 2577 2578 /// Hide cursor when it enters the window. 2579 void hideCursor() { 2580 version(OSXCocoa) throw new NotYetImplementedException(); else 2581 if (!_closed) impl.hideCursor(); 2582 } 2583 2584 /// Don't hide cursor when it enters the window. 2585 void showCursor() { 2586 version(OSXCocoa) throw new NotYetImplementedException(); else 2587 if (!_closed) impl.showCursor(); 2588 } 2589 2590 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 2591 * 2592 * Please remember that the cursor is a shared resource that should usually be left to the user's 2593 * control. Try to think for other approaches before using this function. 2594 * 2595 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 2596 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 2597 * receive "mouse moved here" event. 2598 */ 2599 bool warpMouse (int x, int y) { 2600 version(X11) { 2601 if (!_closed) { impl.warpMouse(x, y); return true; } 2602 } else version(Windows) { 2603 if (!_closed) { 2604 POINT point; 2605 point.x = x; 2606 point.y = y; 2607 if(ClientToScreen(impl.hwnd, &point)) { 2608 SetCursorPos(point.x, point.y); 2609 return true; 2610 } 2611 } 2612 } 2613 return false; 2614 } 2615 2616 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 2617 void sendDummyEvent () { 2618 version(X11) { 2619 if (!_closed) { impl.sendDummyEvent(); } 2620 } 2621 } 2622 2623 /// Set window minimal size. 2624 void setMinSize (int minwidth, int minheight) { 2625 version(OSXCocoa) throw new NotYetImplementedException(); else 2626 if (!_closed) impl.setMinSize(minwidth, minheight); 2627 } 2628 2629 /// Set window maximal size. 2630 void setMaxSize (int maxwidth, int maxheight) { 2631 version(OSXCocoa) throw new NotYetImplementedException(); else 2632 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 2633 } 2634 2635 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 2636 /// Currently only supported on X11. 2637 void setResizeGranularity (int granx, int grany) { 2638 version(OSXCocoa) throw new NotYetImplementedException(); else 2639 if (!_closed) impl.setResizeGranularity(granx, grany); 2640 } 2641 2642 /// Move window. 2643 void move(int x, int y) { 2644 version(OSXCocoa) throw new NotYetImplementedException(); else 2645 if (!_closed) impl.move(x, y); 2646 } 2647 2648 /// ditto 2649 void move(Point p) { 2650 version(OSXCocoa) throw new NotYetImplementedException(); else 2651 if (!_closed) impl.move(p.x, p.y); 2652 } 2653 2654 /++ 2655 Resize window. 2656 2657 Note that the width and height of the window are NOT instantly 2658 updated - it waits for the window manager to approve the resize 2659 request, which means you must return to the event loop before the 2660 width and height are actually changed. 2661 +/ 2662 void resize(int w, int h) { 2663 if(!_closed && _fullscreen) fullscreen = false; 2664 version(OSXCocoa) throw new NotYetImplementedException(); else 2665 if (!_closed) impl.resize(w, h); 2666 } 2667 2668 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 2669 void moveResize (int x, int y, int w, int h) { 2670 if(!_closed && _fullscreen) fullscreen = false; 2671 version(OSXCocoa) throw new NotYetImplementedException(); else 2672 if (!_closed) impl.moveResize(x, y, w, h); 2673 } 2674 2675 private bool _hidden; 2676 2677 /// Returns true if the window is hidden. 2678 final @property bool hidden() { 2679 return _hidden; 2680 } 2681 2682 /// Shows or hides the window based on the bool argument. 2683 final @property void hidden(bool b) { 2684 _hidden = b; 2685 version(Windows) { 2686 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 2687 } else version(X11) { 2688 if(b) 2689 //XUnmapWindow(impl.display, impl.window); 2690 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 2691 else 2692 XMapWindow(impl.display, impl.window); 2693 } else version(OSXCocoa) { 2694 // throw new NotYetImplementedException(); 2695 } else static assert(0); 2696 } 2697 2698 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 2699 void opacity(double opacity) @property 2700 in { 2701 assert(opacity >= 0 && opacity <= 1); 2702 } do { 2703 version (Windows) { 2704 impl.setOpacity(cast(ubyte)(255 * opacity)); 2705 } else version (X11) { 2706 impl.setOpacity(cast(uint)(uint.max * opacity)); 2707 } else throw new NotYetImplementedException(); 2708 } 2709 2710 /++ 2711 Sets your event handlers, without entering the event loop. Useful if you 2712 have multiple windows - set the handlers on each window, then only do 2713 [eventLoop] on your main window or call `EventLoop.get.run();`. 2714 2715 This assigns the given handlers to [handleKeyEvent], [handleCharEvent], 2716 [handlePulse], and [handleMouseEvent] automatically based on the provide 2717 delegate signatures. 2718 +/ 2719 void setEventHandlers(T...)(T eventHandlers) { 2720 // FIXME: add more events 2721 foreach(handler; eventHandlers) { 2722 static if(__traits(compiles, handleKeyEvent = handler)) { 2723 handleKeyEvent = handler; 2724 } else static if(__traits(compiles, handleCharEvent = handler)) { 2725 handleCharEvent = handler; 2726 } else static if(__traits(compiles, handlePulse = handler)) { 2727 handlePulse = handler; 2728 } else static if(__traits(compiles, handleMouseEvent = handler)) { 2729 handleMouseEvent = handler; 2730 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 2731 } 2732 } 2733 2734 /++ 2735 The event loop automatically returns when the window is closed 2736 pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 2737 pulse timer is created. The event loop will block until an event 2738 arrives or the pulse timer goes off. 2739 2740 The given `eventHandlers` are passed to [setEventHandlers], which in turn 2741 assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and 2742 [handleMouseEvent], based on the signature of delegates you provide. 2743 2744 Give one with no parameters to set a timer pulse handler. Give one that 2745 takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler, 2746 and one that takes `dchar` for a char event handler. You can use as many 2747 or as few handlers as you need for your application. 2748 2749 Bugs: 2750 2751 $(PITFALL 2752 You should always have one event loop live for your application. 2753 If you make two windows in sequence, the second call to eventLoop 2754 might fail: 2755 2756 --- 2757 // don't do this! 2758 auto window = new SimpleWindow(); 2759 window.eventLoop(0); 2760 2761 auto window2 = new SimpleWindow(); 2762 window2.eventLoop(0); // problematic! might crash 2763 --- 2764 2765 simpledisplay's current implementation assumes that final cleanup is 2766 done when the event loop refcount reaches zero. So after the first 2767 eventLoop returns, when there isn't already another one active, it assumes 2768 the program will exit soon and cleans up. 2769 2770 This is arguably a bug that it doesn't reinitialize, and I'll probably change 2771 it eventually, but in the mean time, there's an easy solution: 2772 2773 --- 2774 // do this 2775 EventLoop mainEventLoop = EventLoop.get; // just add this line 2776 2777 auto window = new SimpleWindow(); 2778 window.eventLoop(0); 2779 2780 auto window2 = new SimpleWindow(); 2781 window2.eventLoop(0); // perfectly fine since mainEventLoop still alive 2782 --- 2783 2784 By adding a top-level reference to the event loop, it ensures the final cleanup 2785 is not performed until it goes out of scope too, letting the individual window loops 2786 work without trouble despite the bug. 2787 ) 2788 2789 History: 2790 The overload without `pulseTimeout` was added on December 8, 2021. 2791 2792 On December 9, 2021, the default blocking mode (which is now configurable 2793 because [eventLoopWithBlockingMode] was added) switched from 2794 [BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This 2795 should almost never be noticeable to you since the typical simpledisplay 2796 paradigm has been (and I still recommend) to have one `eventLoop` call. 2797 2798 See_Also: 2799 [eventLoopWithBlockingMode] 2800 +/ 2801 final int eventLoop(T...)( 2802 long pulseTimeout, /// set to zero if you don't want a pulse. 2803 T eventHandlers) /// delegate list like std.concurrency.receive 2804 { 2805 return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers); 2806 } 2807 2808 /// ditto 2809 final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate)) 2810 { 2811 return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers); 2812 } 2813 2814 /++ 2815 This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`. 2816 2817 History: 2818 Added December 8, 2021 (dub v10.5) 2819 2820 Previously, this implementation was right inside [eventLoop], but when I wanted 2821 to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I 2822 just renamed it instead of adding as an overload. Besides, the new name makes it 2823 easier to remember the order and avoids ambiguity between two int-like params anyway. 2824 2825 See_Also: 2826 [SimpleWindow.eventLoop], [EventLoop] 2827 2828 Bugs: 2829 The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop. 2830 +/ 2831 final int eventLoopWithBlockingMode(T...)( 2832 BlockingMode blockingMode, /// when you want this function to block until 2833 long pulseTimeout, /// set to zero if you don't want a pulse. 2834 T eventHandlers) /// delegate list like std.concurrency.receive 2835 { 2836 setEventHandlers(eventHandlers); 2837 2838 version(with_eventloop) { 2839 // delegates event loop to my other module 2840 version(X11) 2841 XFlush(display); 2842 2843 import arsd.eventloop; 2844 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 2845 scope(exit) clearInterval(handle); 2846 2847 loop(); 2848 return 0; 2849 } else version(OSXCocoa) { 2850 // FIXME 2851 if (handlePulse !is null && pulseTimeout != 0) { 2852 timer = NSTimer.schedule(pulseTimeout*1e-3, 2853 cast(NSid) view, sel_registerName("simpledisplay_pulse:"), 2854 null, true); 2855 } 2856 2857 view.setNeedsDisplay(true); 2858 2859 NSApp.run(); 2860 return 0; 2861 } else { 2862 EventLoop el = EventLoop(pulseTimeout, handlePulse); 2863 2864 if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1) 2865 return 0; 2866 2867 return el.run( 2868 ((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ? 2869 null : 2870 &this.notClosed 2871 ); 2872 } 2873 } 2874 2875 /++ 2876 This lets you draw on the window (or its backing buffer) using basic 2877 2D primitives. 2878 2879 Be sure to call this in a limited scope because your changes will not 2880 actually appear on the window until ScreenPainter's destructor runs. 2881 2882 Returns: an instance of [ScreenPainter], which has the drawing methods 2883 on it to draw on this window. 2884 2885 Params: 2886 manualInvalidations = if you set this to true, you will need to 2887 set the invalid rectangle on the painter yourself. If false, it 2888 assumes the whole window has been redrawn each time you draw. 2889 2890 Only invalidated rectangles are blitted back to the window when 2891 the destructor runs. Doing this yourself can reduce flickering 2892 of child windows. 2893 2894 History: 2895 The `manualInvalidations` parameter overload was added on 2896 December 30, 2021 (dub v10.5) 2897 +/ 2898 ScreenPainter draw() { 2899 return draw(false); 2900 } 2901 /// ditto 2902 ScreenPainter draw(bool manualInvalidations) { 2903 return impl.getPainter(manualInvalidations); 2904 } 2905 2906 // This is here to implement the interface we use for various native handlers. 2907 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 2908 2909 // maps native window handles to SimpleWindow instances, if there are any 2910 // you shouldn't need this, but it is public in case you do in a native event handler or something 2911 // mac uses void* cuz NSObject opHash won't pick up in typeinfo 2912 version(OSXCocoa) 2913 public __gshared SimpleWindow[void*] nativeMapping; 2914 else 2915 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 2916 2917 // the size the user requested in the constructor, in automatic scale modes it always pretends to be this size 2918 private int _virtualWidth; 2919 private int _virtualHeight; 2920 2921 /// Width of the window's drawable client area, in pixels. 2922 @scriptable 2923 final @property int width() const pure nothrow @safe @nogc { 2924 if(resizability == Resizability.automaticallyScaleIfPossible) 2925 return _virtualWidth; 2926 else 2927 return _width; 2928 } 2929 2930 /// Height of the window's drawable client area, in pixels. 2931 @scriptable 2932 final @property int height() const pure nothrow @safe @nogc { 2933 if(resizability == Resizability.automaticallyScaleIfPossible) 2934 return _virtualHeight; 2935 else 2936 return _height; 2937 } 2938 2939 /++ 2940 Returns the actual size of the window, bypassing the logical 2941 illusions of [Resizability.automaticallyScaleIfPossible]. 2942 2943 History: 2944 Added November 11, 2022 (dub v10.10) 2945 +/ 2946 final @property Size actualWindowSize() const pure nothrow @safe @nogc { 2947 return Size(_width, _height); 2948 } 2949 2950 2951 private int _width; 2952 private int _height; 2953 2954 // HACK: making the best of some copy constructor woes with refcounting 2955 private ScreenPainterImplementation* activeScreenPainter_; 2956 2957 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 2958 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 2959 2960 private OpenGlOptions openglMode; 2961 private Resizability resizability; 2962 private WindowTypes windowType; 2963 private int customizationFlags; 2964 2965 /// `true` if OpenGL was initialized for this window. 2966 @property bool isOpenGL () const pure nothrow @safe @nogc { 2967 version(without_opengl) 2968 return false; 2969 else 2970 return (openglMode == OpenGlOptions.yes); 2971 } 2972 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 2973 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 2974 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 2975 2976 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 2977 /// to call this, as it's not recommended to share window between threads. 2978 void mtLock () { 2979 version(X11) { 2980 XLockDisplay(this.display); 2981 } 2982 } 2983 2984 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 2985 /// to call this, as it's not recommended to share window between threads. 2986 void mtUnlock () { 2987 version(X11) { 2988 XUnlockDisplay(this.display); 2989 } 2990 } 2991 2992 /// Emit a beep to get user's attention. 2993 void beep () { 2994 version(X11) { 2995 XBell(this.display, 100); 2996 } else version(Windows) { 2997 MessageBeep(0xFFFFFFFF); 2998 } 2999 } 3000 3001 3002 3003 version(without_opengl) {} else { 3004 3005 /// 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`. 3006 void delegate() redrawOpenGlScene; 3007 3008 /// This will allow you to change OpenGL vsync state. 3009 final @property void vsync (bool wait) { 3010 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3011 version(X11) { 3012 setAsCurrentOpenGlContext(); 3013 glxSetVSync(display, impl.window, wait); 3014 } else version(Windows) { 3015 setAsCurrentOpenGlContext(); 3016 wglSetVSync(wait); 3017 } 3018 } 3019 3020 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 3021 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 3022 /// enough without waiting 'em to finish their frame business. 3023 bool useGLFinish = true; 3024 3025 // FIXME: it should schedule it for the end of the current iteration of the event loop... 3026 /// 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. 3027 void redrawOpenGlSceneNow() { 3028 version(X11) if (!this._visible) return; // no need to do this if window is invisible 3029 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3030 if(redrawOpenGlScene is null) 3031 return; 3032 3033 this.mtLock(); 3034 scope(exit) this.mtUnlock(); 3035 3036 this.setAsCurrentOpenGlContext(); 3037 3038 redrawOpenGlScene(); 3039 3040 this.swapOpenGlBuffers(); 3041 // 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. 3042 if (useGLFinish) glFinish(); 3043 } 3044 3045 private bool redrawOpenGlSceneSoonSet = false; 3046 private static class RedrawOpenGlSceneEvent { 3047 SimpleWindow w; 3048 this(SimpleWindow w) { this.w = w; } 3049 } 3050 private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent; 3051 /++ 3052 Queues an opengl redraw as soon as the other pending events are cleared. 3053 +/ 3054 void redrawOpenGlSceneSoon() { 3055 if(redrawOpenGlScene is null) 3056 return; 3057 3058 if(!redrawOpenGlSceneSoonSet) { 3059 redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this); 3060 this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); }); 3061 redrawOpenGlSceneSoonSet = true; 3062 } 3063 this.postEvent(redrawOpenGlSceneEvent, true); 3064 } 3065 3066 3067 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3068 void setAsCurrentOpenGlContext() { 3069 assert(openglMode == OpenGlOptions.yes); 3070 version(X11) { 3071 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 3072 throw new Exception("glXMakeCurrent"); 3073 } else version(Windows) { 3074 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3075 if (!wglMakeCurrent(ghDC, ghRC)) 3076 throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too 3077 } 3078 } 3079 3080 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3081 /// This doesn't throw, returning success flag instead. 3082 bool setAsCurrentOpenGlContextNT() nothrow { 3083 assert(openglMode == OpenGlOptions.yes); 3084 version(X11) { 3085 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 3086 } else version(Windows) { 3087 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3088 return wglMakeCurrent(ghDC, ghRC) ? true : false; 3089 } 3090 } 3091 3092 /// 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. 3093 /// This doesn't throw, returning success flag instead. 3094 bool releaseCurrentOpenGlContext() nothrow { 3095 assert(openglMode == OpenGlOptions.yes); 3096 version(X11) { 3097 return (glXMakeCurrent(display, 0, null) != 0); 3098 } else version(Windows) { 3099 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3100 return wglMakeCurrent(ghDC, null) ? true : false; 3101 } 3102 } 3103 3104 /++ 3105 simpledisplay always uses double buffering, usually automatically. This 3106 manually swaps the OpenGL buffers. 3107 3108 3109 You should not need to call this yourself because simpledisplay will do it 3110 for you after calling your `redrawOpenGlScene`. 3111 3112 Remember that this may throw an exception, which you can catch in a multithreaded 3113 application to keep your thread from dying from an unhandled exception. 3114 +/ 3115 void swapOpenGlBuffers() { 3116 assert(openglMode == OpenGlOptions.yes); 3117 version(X11) { 3118 if (!this._visible) return; // no need to do this if window is invisible 3119 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3120 glXSwapBuffers(display, impl.window); 3121 } else version(Windows) { 3122 SwapBuffers(ghDC); 3123 } 3124 } 3125 } 3126 3127 /++ 3128 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 3129 3130 3131 --- 3132 auto window = new SimpleWindow(100, 100, "First title"); 3133 window.title = "A new title"; 3134 --- 3135 3136 You may call this function at any time. 3137 +/ 3138 @property void title(string title) { 3139 _title = title; 3140 version(OSXCocoa) throw new NotYetImplementedException(); else 3141 impl.setTitle(title); 3142 } 3143 3144 private string _title; 3145 3146 /// Gets the title 3147 @property string title() { 3148 if(_title is null) 3149 _title = getRealTitle(); 3150 return _title; 3151 } 3152 3153 /++ 3154 Get the title as set by the window manager. 3155 May not match what you attempted to set. 3156 +/ 3157 string getRealTitle() { 3158 static if(is(typeof(impl.getTitle()))) 3159 return impl.getTitle(); 3160 else 3161 return null; 3162 } 3163 3164 // don't use this generally it is not yet really released 3165 version(X11) 3166 @property Image secret_icon() { 3167 return secret_icon_inner; 3168 } 3169 private Image secret_icon_inner; 3170 3171 3172 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing. 3173 @property void icon(MemoryImage icon) { 3174 if(icon is null) 3175 return; 3176 auto tci = icon.getAsTrueColorImage(); 3177 version(Windows) { 3178 winIcon = new WindowsIcon(icon); 3179 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 3180 } else version(X11) { 3181 secret_icon_inner = Image.fromMemoryImage(icon); 3182 // FIXME: ensure this is correct 3183 auto display = XDisplayConnection.get; 3184 arch_ulong[] buffer; 3185 buffer ~= icon.width; 3186 buffer ~= icon.height; 3187 foreach(c; tci.imageData.colors) { 3188 arch_ulong b; 3189 b |= c.a << 24; 3190 b |= c.r << 16; 3191 b |= c.g << 8; 3192 b |= c.b; 3193 buffer ~= b; 3194 } 3195 3196 XChangeProperty( 3197 display, 3198 impl.window, 3199 GetAtom!("_NET_WM_ICON", true)(display), 3200 GetAtom!"CARDINAL"(display), 3201 32 /* bits */, 3202 0 /*PropModeReplace*/, 3203 buffer.ptr, 3204 cast(int) buffer.length); 3205 } else version(OSXCocoa) { 3206 throw new NotYetImplementedException(); 3207 } else static assert(0); 3208 } 3209 3210 version(Windows) 3211 private WindowsIcon winIcon; 3212 3213 bool _suppressDestruction; 3214 3215 ~this() { 3216 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 3217 if(_suppressDestruction) 3218 return; 3219 impl.dispose(); 3220 } 3221 3222 private bool _closed; 3223 3224 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 3225 /* 3226 ScreenPainter drawTransiently() { 3227 return impl.getPainter(); 3228 } 3229 */ 3230 3231 /// Draws an image on the window. This is meant to provide quick look 3232 /// of a static image generated elsewhere. 3233 @property void image(Image i) { 3234 /+ 3235 version(Windows) { 3236 BITMAP bm; 3237 HDC hdc = GetDC(hwnd); 3238 HDC hdcMem = CreateCompatibleDC(hdc); 3239 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 3240 3241 GetObject(i.handle, bm.sizeof, &bm); 3242 3243 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 3244 3245 SelectObject(hdcMem, hbmOld); 3246 DeleteDC(hdcMem); 3247 ReleaseDC(hwnd, hdc); 3248 3249 /* 3250 RECT r; 3251 r.right = i.width; 3252 r.bottom = i.height; 3253 InvalidateRect(hwnd, &r, false); 3254 */ 3255 } else 3256 version(X11) { 3257 if(!destroyed) { 3258 if(i.usingXshm) 3259 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 3260 else 3261 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 3262 } 3263 } else 3264 version(OSXCocoa) { 3265 draw().drawImage(Point(0, 0), i); 3266 setNeedsDisplay(view, true); 3267 } else static assert(0); 3268 +/ 3269 auto painter = this.draw; 3270 painter.drawImage(Point(0, 0), i); 3271 } 3272 3273 /++ 3274 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 3275 3276 --- 3277 window.cursor = GenericCursor.Help; 3278 // now the window mouse cursor is set to a generic help 3279 --- 3280 3281 +/ 3282 @property void cursor(MouseCursor cursor) { 3283 version(OSXCocoa) 3284 {} // featureNotImplemented(); 3285 else 3286 if(this.impl.curHidden <= 0) { 3287 static if(UsingSimpledisplayX11) { 3288 auto ch = cursor.cursorHandle; 3289 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 3290 } else version(Windows) { 3291 auto ch = cursor.cursorHandle; 3292 impl.currentCursor = ch; 3293 SetCursor(ch); // redraw without waiting for mouse movement to update 3294 } else featureNotImplemented(); 3295 } 3296 3297 } 3298 3299 /// What follows are the event handlers. These are set automatically 3300 /// by the eventLoop function, but are still public so you can change 3301 /// them later. wasPressed == true means key down. false == key up. 3302 3303 /// Handles a low-level keyboard event. Settable through setEventHandlers. 3304 void delegate(KeyEvent ke) handleKeyEvent; 3305 3306 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 3307 void delegate(dchar c) handleCharEvent; 3308 3309 /// Handles a timer pulse. Settable through setEventHandlers. 3310 void delegate() handlePulse; 3311 3312 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 3313 void delegate(bool) onFocusChange; 3314 3315 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 3316 * Sometimes it is easier to setup the delegate instead of subclassing. */ 3317 void delegate() onClosing; 3318 3319 /** Called when we received destroy notification. At this stage we cannot do much with our window 3320 * (as it is already dead, and it's native handle cannot be used), but we still can do some 3321 * last minute cleanup. */ 3322 void delegate() onDestroyed; 3323 3324 static if (UsingSimpledisplayX11) 3325 /** Called when Expose event comes. See Xlib manual to understand the arguments. 3326 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 3327 * You will probably never need to setup this handler, it is for very low-level stuff. 3328 * 3329 * WARNING! Xlib is multithread-locked when this handles is called! */ 3330 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 3331 3332 //version(Windows) 3333 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 3334 3335 private { 3336 int lastMouseX = int.min; 3337 int lastMouseY = int.min; 3338 void mdx(ref MouseEvent ev) { 3339 if(lastMouseX == int.min || lastMouseY == int.min) { 3340 ev.dx = 0; 3341 ev.dy = 0; 3342 } else { 3343 ev.dx = ev.x - lastMouseX; 3344 ev.dy = ev.y - lastMouseY; 3345 } 3346 3347 lastMouseX = ev.x; 3348 lastMouseY = ev.y; 3349 } 3350 } 3351 3352 /// Mouse event handler. Settable through setEventHandlers. 3353 void delegate(MouseEvent) handleMouseEvent; 3354 3355 /// use to redraw child widgets if you use system apis to add stuff 3356 void delegate() paintingFinished; 3357 3358 void delegate() paintingFinishedDg() { 3359 return paintingFinished; 3360 } 3361 3362 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 3363 /// for this to ever happen. 3364 void delegate(int width, int height) windowResized; 3365 3366 /++ 3367 Platform specific - handle any native message this window gets. 3368 3369 Note: this is called *in addition to* other event handlers, unless you either: 3370 3371 1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded. 3372 3373 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. 3374 3375 On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`. 3376 3377 On X, it takes the form of `int delegate(XEvent)`. 3378 3379 History: 3380 In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead. 3381 3382 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. 3383 +/ 3384 NativeEventHandler handleNativeEvent_; 3385 3386 @property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe { 3387 return handleNativeEvent_; 3388 } 3389 @property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe { 3390 handleNativeEvent_ = neh; 3391 } 3392 3393 version(Windows) 3394 // compatibility shim with the old deprecated way 3395 // in this one, if you return 0, it means you must return. otherwise the ret value is ignored. 3396 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) { 3397 handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) { 3398 auto ret = dg(h, m, w, l); 3399 if(ret == 0) 3400 r = 1; 3401 return ret; 3402 }; 3403 } 3404 3405 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 3406 /// If you used to use handleNativeEvent depending on it being static, just change it to use 3407 /// this instead and it will work the same way. 3408 __gshared NativeEventHandler handleNativeGlobalEvent; 3409 3410 // private: 3411 /// The native implementation is available, but you shouldn't use it unless you are 3412 /// familiar with the underlying operating system, don't mind depending on it, and 3413 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 3414 /// do what you need to do with handleNativeEvent instead. 3415 /// 3416 /// This is likely to eventually change to be just a struct holding platform-specific 3417 /// handles instead of a template mixin at some point because I'm not happy with the 3418 /// code duplication here (ironically). 3419 mixin NativeSimpleWindowImplementation!() impl; 3420 3421 /** 3422 This is in-process one-way (from anything to window) event sending mechanics. 3423 It is thread-safe, so it can be used in multi-threaded applications to send, 3424 for example, "wake up and repaint" events when thread completed some operation. 3425 This will allow to avoid using timer pulse to check events with synchronization, 3426 'cause event handler will be called in UI thread. You can stop guessing which 3427 pulse frequency will be enough for your app. 3428 Note that events handlers may be called in arbitrary order, i.e. last registered 3429 handler can be called first, and vice versa. 3430 */ 3431 public: 3432 /** Is our custom event queue empty? Can be used in simple cases to prevent 3433 * "spamming" window with events it can't cope with. 3434 * It is safe to call this from non-UI threads. 3435 */ 3436 @property bool eventQueueEmpty() () { 3437 synchronized(this) { 3438 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 3439 } 3440 return true; 3441 } 3442 3443 /** Does our custom event queue contains at least one with the given type? 3444 * Can be used in simple cases to prevent "spamming" window with events 3445 * it can't cope with. 3446 * It is safe to call this from non-UI threads. 3447 */ 3448 @property bool eventQueued(ET:Object) () { 3449 synchronized(this) { 3450 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 3451 if (!o.doProcess) { 3452 if (cast(ET)(o.evt)) return true; 3453 } 3454 } 3455 } 3456 return false; 3457 } 3458 3459 /++ 3460 Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds. 3461 3462 History: 3463 Added May 12, 2021 3464 +/ 3465 void delegate(Exception e) nothrow eventUncaughtException; 3466 3467 /** Add listener for custom event. Can be used like this: 3468 * 3469 * --------------------- 3470 * auto eid = win.addEventListener((MyStruct evt) { ... }); 3471 * ... 3472 * win.removeEventListener(eid); 3473 * --------------------- 3474 * 3475 * Returns: 0 on failure (should never happen, so ignore it) 3476 * 3477 * $(WARNING Don't use this method in object destructors!) 3478 * 3479 * $(WARNING It is better to register all event handlers and don't remove 'em, 3480 * 'cause if event handler id counter will overflow, you won't be able 3481 * to register any more events.) 3482 */ 3483 uint addEventListener(ET:Object) (void delegate (ET) dg) { 3484 if (dg is null) return 0; // ignore empty handlers 3485 synchronized(this) { 3486 //FIXME: abort on overflow? 3487 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 3488 EventHandlerEntry e; 3489 e.dg = delegate (Object o) { 3490 if (auto co = cast(ET)o) { 3491 try { 3492 dg(co); 3493 } catch (Exception e) { 3494 // sorry! 3495 if(eventUncaughtException) 3496 eventUncaughtException(e); 3497 } 3498 return true; 3499 } 3500 return false; 3501 }; 3502 e.id = lastUsedHandlerId; 3503 auto optr = eventHandlers.ptr; 3504 eventHandlers ~= e; 3505 if (eventHandlers.ptr !is optr) { 3506 import core.memory : GC; 3507 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 3508 } 3509 return lastUsedHandlerId; 3510 } 3511 } 3512 3513 /// Remove event listener. It is safe to pass invalid event id here. 3514 /// $(WARNING Don't use this method in object destructors!) 3515 void removeEventListener() (uint id) { 3516 if (id == 0 || id > lastUsedHandlerId) return; 3517 synchronized(this) { 3518 foreach (immutable idx; 0..eventHandlers.length) { 3519 if (eventHandlers[idx].id == id) { 3520 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 3521 eventHandlers[$-1].dg = null; 3522 eventHandlers.length -= 1; 3523 eventHandlers.assumeSafeAppend; 3524 return; 3525 } 3526 } 3527 } 3528 } 3529 3530 /// Post event to queue. It is safe to call this from non-UI threads. 3531 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 3532 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3533 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3534 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) { 3535 if (this.closed) return false; // closed windows can't handle events 3536 3537 // remove all events of type `ET` 3538 void removeAllET () { 3539 uint eidx = 0, ec = eventQueueUsed; 3540 auto eptr = eventQueue.ptr; 3541 while (eidx < ec) { 3542 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 3543 if (cast(ET)eptr.evt !is null) { 3544 // i found her! 3545 if (inCustomEventProcessor) { 3546 // if we're in custom event processing loop, processor will clear it for us 3547 eptr.evt = null; 3548 ++eidx; 3549 ++eptr; 3550 } else { 3551 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3552 ec = --eventQueueUsed; 3553 // clear last event (it is already copied) 3554 eventQueue.ptr[ec].evt = null; 3555 } 3556 } else { 3557 ++eidx; 3558 ++eptr; 3559 } 3560 } 3561 } 3562 3563 if (evt is null) { 3564 if (replace) { synchronized(this) removeAllET(); } 3565 // ignore empty events, they can't be handled anyway 3566 return false; 3567 } 3568 3569 // add events even if no event FD/event object created yet 3570 synchronized(this) { 3571 if (replace) removeAllET(); 3572 if (eventQueueUsed == uint.max) return false; // just in case 3573 if (eventQueueUsed < eventQueue.length) { 3574 eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs); 3575 } else { 3576 if (eventQueue.capacity == eventQueue.length) { 3577 // need to reallocate; do a trick to ensure that old array is cleared 3578 auto oarr = eventQueue; 3579 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3580 // just in case, do yet another check 3581 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 3582 import core.memory : GC; 3583 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 3584 } else { 3585 auto optr = eventQueue.ptr; 3586 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3587 assert(eventQueue.ptr is optr); 3588 } 3589 ++eventQueueUsed; 3590 assert(eventQueueUsed == eventQueue.length); 3591 } 3592 if (!eventWakeUp()) { 3593 // can't wake up event processor, so there is no reason to keep the event 3594 assert(eventQueueUsed > 0); 3595 eventQueue[--eventQueueUsed].evt = null; 3596 return false; 3597 } 3598 return true; 3599 } 3600 } 3601 3602 /// Post event to queue. It is safe to call this from non-UI threads. 3603 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3604 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3605 bool postEvent(ET:Object) (ET evt, bool replace=false) { 3606 return postTimeout!ET(evt, 0, replace); 3607 } 3608 3609 private: 3610 private import core.time : MonoTime; 3611 3612 version(Posix) { 3613 __gshared int customEventFDRead = -1; 3614 __gshared int customEventFDWrite = -1; 3615 __gshared int customSignalFD = -1; 3616 } else version(Windows) { 3617 __gshared HANDLE customEventH = null; 3618 } 3619 3620 // wake up event processor 3621 static bool eventWakeUp () { 3622 version(X11) { 3623 import core.sys.posix.unistd : write; 3624 ulong n = 1; 3625 if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof); 3626 return true; 3627 } else version(Windows) { 3628 if (customEventH !is null) SetEvent(customEventH); 3629 return true; 3630 } else version(OSXCocoa) { 3631 if(globalAppDelegate) 3632 globalAppDelegate.performSelectorOnMainThread(sel_registerName("sdpyCustomEventWakeup:"), null, false); 3633 return true; 3634 } else { 3635 // not implemented for other OSes 3636 return false; 3637 } 3638 } 3639 3640 static struct QueuedEvent { 3641 Object evt; 3642 bool timed = false; 3643 MonoTime hittime = MonoTime.zero; 3644 bool doProcess = false; // process event at the current iteration (internal flag) 3645 3646 this (Object aevt, uint toutmsecs) { 3647 evt = aevt; 3648 if (toutmsecs > 0) { 3649 import core.time : msecs; 3650 timed = true; 3651 hittime = MonoTime.currTime+toutmsecs.msecs; 3652 } 3653 } 3654 } 3655 3656 alias CustomEventHandler = bool delegate (Object o) nothrow; 3657 static struct EventHandlerEntry { 3658 CustomEventHandler dg; 3659 uint id; 3660 } 3661 3662 uint lastUsedHandlerId; 3663 EventHandlerEntry[] eventHandlers; 3664 QueuedEvent[] eventQueue = null; 3665 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 3666 bool inCustomEventProcessor = false; // required to properly remove events 3667 3668 // process queued events and call custom event handlers 3669 // this will not process events posted from called handlers (such events are postponed for the next iteration) 3670 void processCustomEvents () { 3671 bool hasSomethingToDo = false; 3672 uint ecount; 3673 bool ocep; 3674 synchronized(this) { 3675 ocep = inCustomEventProcessor; 3676 inCustomEventProcessor = true; 3677 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 3678 auto ctt = MonoTime.currTime; 3679 bool hasEmpty = false; 3680 // mark events to process (this is required for `eventQueued()`) 3681 foreach (ref qe; eventQueue[0..ecount]) { 3682 if (qe.evt is null) { hasEmpty = true; continue; } 3683 if (qe.timed) { 3684 qe.doProcess = (qe.hittime <= ctt); 3685 } else { 3686 qe.doProcess = true; 3687 } 3688 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 3689 } 3690 if (!hasSomethingToDo) { 3691 // remove empty events 3692 if (hasEmpty) { 3693 uint eidx = 0, ec = eventQueueUsed; 3694 auto eptr = eventQueue.ptr; 3695 while (eidx < ec) { 3696 if (eptr.evt is null) { 3697 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3698 ec = --eventQueueUsed; 3699 eventQueue.ptr[ec].evt = null; // make GC life easier 3700 } else { 3701 ++eidx; 3702 ++eptr; 3703 } 3704 } 3705 } 3706 inCustomEventProcessor = ocep; 3707 return; 3708 } 3709 } 3710 // process marked events 3711 uint efree = 0; // non-processed events will be put at this index 3712 EventHandlerEntry[] eh; 3713 Object evt; 3714 foreach (immutable eidx; 0..ecount) { 3715 synchronized(this) { 3716 if (!eventQueue[eidx].doProcess) { 3717 // skip this event 3718 assert(efree <= eidx); 3719 if (efree != eidx) { 3720 // copy this event to queue start 3721 eventQueue[efree] = eventQueue[eidx]; 3722 eventQueue[eidx].evt = null; // just in case 3723 } 3724 ++efree; 3725 continue; 3726 } 3727 evt = eventQueue[eidx].evt; 3728 eventQueue[eidx].evt = null; // in case event handler will hit GC 3729 if (evt is null) continue; // just in case 3730 // try all handlers; this can be slow, but meh... 3731 eh = eventHandlers; 3732 } 3733 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 3734 evt = null; 3735 eh = null; 3736 } 3737 synchronized(this) { 3738 // move all unprocessed events to queue top; efree holds first "free index" 3739 foreach (immutable eidx; ecount..eventQueueUsed) { 3740 assert(efree <= eidx); 3741 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 3742 ++efree; 3743 } 3744 eventQueueUsed = efree; 3745 // wake up event processor on next event loop iteration if we have more queued events 3746 // also, remove empty events 3747 bool awaken = false; 3748 uint eidx = 0, ec = eventQueueUsed; 3749 auto eptr = eventQueue.ptr; 3750 while (eidx < ec) { 3751 if (eptr.evt is null) { 3752 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3753 ec = --eventQueueUsed; 3754 eventQueue.ptr[ec].evt = null; // make GC life easier 3755 } else { 3756 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 3757 ++eidx; 3758 ++eptr; 3759 } 3760 } 3761 inCustomEventProcessor = ocep; 3762 } 3763 } 3764 3765 // for all windows in nativeMapping 3766 package static void processAllCustomEvents () { 3767 3768 cleanupQueue.process(); 3769 3770 justCommunication.processCustomEvents(); 3771 3772 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3773 if (sw is null || sw.closed) continue; 3774 sw.processCustomEvents(); 3775 } 3776 3777 runPendingRunInGuiThreadDelegates(); 3778 } 3779 3780 // 0: infinite (i.e. no scheduled events in queue) 3781 uint eventQueueTimeoutMSecs () { 3782 synchronized(this) { 3783 if (eventQueueUsed == 0) return 0; 3784 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3785 uint res = int.max; 3786 auto ctt = MonoTime.currTime; 3787 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 3788 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3789 if (qe.doProcess) continue; // just in case 3790 if (!qe.timed) return 1; // minimal 3791 if (qe.hittime <= ctt) return 1; // minimal 3792 auto tms = (qe.hittime-ctt).total!"msecs"; 3793 if (tms < 1) tms = 1; // safety net 3794 if (tms >= int.max) tms = int.max-1; // and another safety net 3795 if (res > tms) res = cast(uint)tms; 3796 } 3797 return (res >= int.max ? 0 : res); 3798 } 3799 } 3800 3801 // for all windows in nativeMapping 3802 static uint eventAllQueueTimeoutMSecs () { 3803 uint res = uint.max; 3804 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3805 if (sw is null || sw.closed) continue; 3806 uint to = sw.eventQueueTimeoutMSecs(); 3807 if (to && to < res) { 3808 res = to; 3809 if (to == 1) break; // can't have less than this 3810 } 3811 } 3812 return (res >= int.max ? 0 : res); 3813 } 3814 3815 version(X11) { 3816 ResizeEvent pendingResizeEvent; 3817 } 3818 3819 /++ 3820 When in opengl mode and automatically resizing, it will set the opengl viewport to stretch. 3821 3822 If you work with multiple opengl contexts and/or threads, this might be more trouble than it is 3823 worth so you can disable it by setting this to `true`. 3824 3825 History: 3826 Added November 13, 2022. 3827 +/ 3828 public bool suppressAutoOpenglViewport = false; 3829 private void updateOpenglViewportIfNeeded(int width, int height) { 3830 if(suppressAutoOpenglViewport) return; 3831 3832 version(without_opengl) {} else 3833 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 3834 // writeln(width, " ", height); 3835 setAsCurrentOpenGlContextNT(); 3836 glViewport(0, 0, width, height); 3837 } 3838 } 3839 } 3840 3841 version(OSXCocoa) 3842 enum NSWindow NullWindow = null; 3843 else 3844 enum NullWindow = NativeWindowHandle.init; 3845 3846 /++ 3847 Magic pseudo-window for just posting events to a global queue. 3848 3849 Not entirely supported, I might delete it at any time. 3850 3851 Added Nov 5, 2021. 3852 +/ 3853 __gshared SimpleWindow justCommunication = new SimpleWindow(NullWindow); 3854 3855 /* Drag and drop support { */ 3856 version(X11) { 3857 3858 } else version(Windows) { 3859 import core.sys.windows.uuid; 3860 import core.sys.windows.ole2; 3861 import core.sys.windows.oleidl; 3862 import core.sys.windows.objidl; 3863 import core.sys.windows.wtypes; 3864 3865 pragma(lib, "ole32"); 3866 void initDnd() { 3867 auto err = OleInitialize(null); 3868 if(err != S_OK && err != S_FALSE) 3869 throw new Exception("init");//err); 3870 } 3871 } 3872 /* } End drag and drop support */ 3873 3874 3875 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 3876 /// See [GenericCursor]. 3877 class MouseCursor { 3878 int osId; 3879 bool isStockCursor; 3880 private this(int osId) { 3881 this.osId = osId; 3882 this.isStockCursor = true; 3883 } 3884 3885 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 3886 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 3887 3888 version(Windows) { 3889 HCURSOR cursor_; 3890 HCURSOR cursorHandle() { 3891 if(cursor_ is null) 3892 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 3893 return cursor_; 3894 } 3895 3896 } else static if(UsingSimpledisplayX11) { 3897 Cursor cursor_ = None; 3898 int xDisplaySequence; 3899 3900 Cursor cursorHandle() { 3901 if(this.osId == None) 3902 return None; 3903 3904 // we need to reload if we on a new X connection 3905 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 3906 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 3907 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 3908 } 3909 return cursor_; 3910 } 3911 } 3912 } 3913 3914 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 3915 // https://tronche.com/gui/x/xlib/appendix/b/ 3916 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 3917 /// 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. 3918 enum GenericCursorType { 3919 Default, /// The default arrow pointer. 3920 Wait, /// A cursor indicating something is loading and the user must wait. 3921 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 3922 Help, /// A cursor indicating the user can get help about the pointer location. 3923 Cross, /// A crosshair. 3924 Text, /// An i-beam shape, typically used to indicate text selection is possible. 3925 Move, /// Pointer indicating movement is possible. May also be used as SizeAll. 3926 UpArrow, /// An arrow pointing straight up. 3927 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 3928 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 3929 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator). 3930 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator). 3931 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator). 3932 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator). 3933 3934 } 3935 3936 /* 3937 X_plus == css cell == Windows ? 3938 */ 3939 3940 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 3941 static struct GenericCursor { 3942 static: 3943 /// 3944 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 3945 static MouseCursor mc; 3946 3947 auto type = __traits(getMember, GenericCursorType, str); 3948 3949 if(mc is null) { 3950 3951 version(Windows) { 3952 int osId; 3953 final switch(type) { 3954 case GenericCursorType.Default: osId = IDC_ARROW; break; 3955 case GenericCursorType.Wait: osId = IDC_WAIT; break; 3956 case GenericCursorType.Hand: osId = IDC_HAND; break; 3957 case GenericCursorType.Help: osId = IDC_HELP; break; 3958 case GenericCursorType.Cross: osId = IDC_CROSS; break; 3959 case GenericCursorType.Text: osId = IDC_IBEAM; break; 3960 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 3961 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 3962 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 3963 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 3964 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 3965 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 3966 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 3967 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 3968 } 3969 } else static if(UsingSimpledisplayX11) { 3970 int osId; 3971 final switch(type) { 3972 case GenericCursorType.Default: osId = None; break; 3973 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 3974 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 3975 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 3976 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 3977 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 3978 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 3979 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 3980 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 3981 3982 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 3983 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 3984 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 3985 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 3986 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 3987 } 3988 3989 } else { 3990 int osId; 3991 // featureNotImplemented(); 3992 } 3993 3994 mc = new MouseCursor(osId); 3995 } 3996 return mc; 3997 } 3998 } 3999 4000 4001 /++ 4002 If you want to get more control over the event loop, you can use this. 4003 4004 Typically though, you can just call [SimpleWindow.eventLoop] which forwards 4005 to `EventLoop.get.run`. 4006 +/ 4007 struct EventLoop { 4008 @disable this(); 4009 4010 /// Gets a reference to an existing event loop 4011 static EventLoop get() { 4012 return EventLoop(0, null); 4013 } 4014 4015 static void quitApplication() { 4016 EventLoop.get().exit(); 4017 } 4018 4019 private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi 4020 4021 /// Construct an application-global event loop for yourself 4022 /// See_Also: [SimpleWindow.setEventHandlers] 4023 this(long pulseTimeout, void delegate() handlePulse) { 4024 synchronized(monitor) { 4025 if(impl is null) { 4026 claimGuiThread(); 4027 version(sdpy_thread_checks) assert(thisIsGuiThread); 4028 impl = new EventLoopImpl(pulseTimeout, handlePulse); 4029 } else { 4030 if(pulseTimeout) { 4031 impl.pulseTimeout = pulseTimeout; 4032 impl.handlePulse = handlePulse; 4033 } 4034 } 4035 impl.refcount++; 4036 } 4037 } 4038 4039 ~this() { 4040 if(impl is null) 4041 return; 4042 impl.refcount--; 4043 if(impl.refcount == 0) { 4044 impl.dispose(); 4045 if(thisIsGuiThread) 4046 guiThreadFinalize(); 4047 } 4048 4049 } 4050 4051 this(this) { 4052 if(impl is null) 4053 return; 4054 impl.refcount++; 4055 } 4056 4057 /// Runs the event loop until the whileCondition, if present, returns false 4058 int run(bool delegate() whileCondition = null) { 4059 assert(impl !is null); 4060 impl.notExited = true; 4061 return impl.run(whileCondition); 4062 } 4063 4064 /// Exits the event loop 4065 void exit() { 4066 assert(impl !is null); 4067 impl.notExited = false; 4068 } 4069 4070 version(linux) 4071 ref void delegate(int) signalHandler() { 4072 assert(impl !is null); 4073 return impl.signalHandler; 4074 } 4075 4076 __gshared static EventLoopImpl* impl; 4077 } 4078 4079 version(linux) 4080 void delegate(int, int) globalHupHandler; 4081 4082 version(Posix) 4083 void makeNonBlocking(int fd) { 4084 import fcntl = core.sys.posix.fcntl; 4085 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 4086 if(flags == -1) 4087 throw new Exception("fcntl get"); 4088 flags |= fcntl.O_NONBLOCK; 4089 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 4090 if(s == -1) 4091 throw new Exception("fcntl set"); 4092 } 4093 4094 struct EventLoopImpl { 4095 int refcount; 4096 4097 bool notExited = true; 4098 4099 version(linux) { 4100 static import ep = core.sys.linux.epoll; 4101 static import unix = core.sys.posix.unistd; 4102 static import err = core.stdc.errno; 4103 import core.sys.linux.timerfd; 4104 4105 void delegate(int) signalHandler; 4106 } 4107 4108 version(X11) { 4109 int pulseFd = -1; 4110 version(linux) ep.epoll_event[16] events = void; 4111 } else version(Windows) { 4112 Timer pulser; 4113 HANDLE[] handles; 4114 } 4115 4116 4117 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 4118 /// to call this, as it's not recommended to share window between threads. 4119 void mtLock () { 4120 version(X11) { 4121 XLockDisplay(this.display); 4122 } 4123 } 4124 4125 version(X11) 4126 auto display() { return XDisplayConnection.get; } 4127 4128 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 4129 /// to call this, as it's not recommended to share window between threads. 4130 void mtUnlock () { 4131 version(X11) { 4132 XUnlockDisplay(this.display); 4133 } 4134 } 4135 4136 version(with_eventloop) 4137 void initialize(long pulseTimeout) {} 4138 else 4139 void initialize(long pulseTimeout) { 4140 version(Windows) { 4141 if(pulseTimeout && handlePulse !is null) 4142 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 4143 4144 if (customEventH is null) { 4145 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 4146 if (customEventH !is null) { 4147 handles ~= customEventH; 4148 } else { 4149 // this is something that should not be; better be safe than sorry 4150 throw new Exception("can't create eventfd for custom event processing"); 4151 } 4152 } 4153 4154 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 4155 } 4156 4157 version(linux) { 4158 prepareEventLoop(); 4159 { 4160 auto display = XDisplayConnection.get; 4161 // adding Xlib file 4162 ep.epoll_event ev = void; 4163 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4164 ev.events = ep.EPOLLIN; 4165 ev.data.fd = display.fd; 4166 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 4167 throw new Exception("add x fd");// ~ to!string(epollFd)); 4168 displayFd = display.fd; 4169 } 4170 4171 if(pulseTimeout && handlePulse !is null) { 4172 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 4173 if(pulseFd == -1) 4174 throw new Exception("pulse timer create failed"); 4175 4176 itimerspec value; 4177 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 4178 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4179 4180 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 4181 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4182 4183 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 4184 throw new Exception("couldn't make pulse timer"); 4185 4186 ep.epoll_event ev = void; 4187 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4188 ev.events = ep.EPOLLIN; 4189 ev.data.fd = pulseFd; 4190 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 4191 } 4192 4193 // eventfd for custom events 4194 if (customEventFDWrite == -1) { 4195 customEventFDWrite = eventfd(0, 0); 4196 customEventFDRead = customEventFDWrite; 4197 if (customEventFDRead >= 0) { 4198 ep.epoll_event ev = void; 4199 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4200 ev.events = ep.EPOLLIN; 4201 ev.data.fd = customEventFDRead; 4202 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev); 4203 } else { 4204 // this is something that should not be; better be safe than sorry 4205 throw new Exception("can't create eventfd for custom event processing"); 4206 } 4207 } 4208 4209 if (customSignalFD == -1) { 4210 import core.sys.linux.sys.signalfd; 4211 4212 sigset_t sigset; 4213 auto err = sigemptyset(&sigset); 4214 assert(!err); 4215 err = sigaddset(&sigset, SIGINT); 4216 assert(!err); 4217 err = sigaddset(&sigset, SIGHUP); 4218 assert(!err); 4219 err = sigprocmask(SIG_BLOCK, &sigset, null); 4220 assert(!err); 4221 4222 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 4223 assert(customSignalFD != -1); 4224 4225 ep.epoll_event ev = void; 4226 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4227 ev.events = ep.EPOLLIN; 4228 ev.data.fd = customSignalFD; 4229 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 4230 } 4231 } else version(Posix) { 4232 prepareEventLoop(); 4233 if (customEventFDRead == -1) { 4234 int[2] bfr; 4235 import core.sys.posix.unistd; 4236 auto ret = pipe(bfr); 4237 if(ret == -1) throw new Exception("pipe"); 4238 customEventFDRead = bfr[0]; 4239 customEventFDWrite = bfr[1]; 4240 } 4241 4242 } 4243 4244 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 4245 4246 version(linux) { 4247 this.mtLock(); 4248 scope(exit) this.mtUnlock(); 4249 XPending(display); // no, really 4250 } 4251 4252 disposed = false; 4253 } 4254 4255 bool disposed = true; 4256 version(X11) 4257 int displayFd = -1; 4258 4259 version(with_eventloop) 4260 void dispose() {} 4261 else 4262 void dispose() { 4263 disposed = true; 4264 version(X11) { 4265 if(pulseFd != -1) { 4266 import unix = core.sys.posix.unistd; 4267 unix.close(pulseFd); 4268 pulseFd = -1; 4269 } 4270 4271 version(linux) 4272 if(displayFd != -1) { 4273 // 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 4274 ep.epoll_event ev = void; 4275 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4276 ev.events = ep.EPOLLIN; 4277 ev.data.fd = displayFd; 4278 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 4279 displayFd = -1; 4280 } 4281 4282 } else version(Windows) { 4283 if(pulser !is null) { 4284 pulser.destroy(); 4285 pulser = null; 4286 } 4287 if (customEventH !is null) { 4288 CloseHandle(customEventH); 4289 customEventH = null; 4290 } 4291 } 4292 } 4293 4294 this(long pulseTimeout, void delegate() handlePulse) { 4295 this.pulseTimeout = pulseTimeout; 4296 this.handlePulse = handlePulse; 4297 initialize(pulseTimeout); 4298 } 4299 4300 private long pulseTimeout; 4301 void delegate() handlePulse; 4302 4303 ~this() { 4304 dispose(); 4305 } 4306 4307 version(Posix) 4308 ref int customEventFDRead() { return SimpleWindow.customEventFDRead; } 4309 version(Posix) 4310 ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; } 4311 version(linux) 4312 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 4313 version(Windows) 4314 ref auto customEventH() { return SimpleWindow.customEventH; } 4315 4316 version(with_eventloop) { 4317 int loopHelper(bool delegate() whileCondition) { 4318 // FIXME: whileCondition 4319 import arsd.eventloop; 4320 loop(); 4321 return 0; 4322 } 4323 } else 4324 int loopHelper(bool delegate() whileCondition) { 4325 version(X11) { 4326 bool done = false; 4327 4328 XFlush(display); 4329 insideXEventLoop = true; 4330 scope(exit) insideXEventLoop = false; 4331 4332 version(linux) { 4333 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 4334 bool forceXPending = false; 4335 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4336 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 4337 { 4338 this.mtLock(); 4339 scope(exit) this.mtUnlock(); 4340 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 4341 } 4342 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 4343 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 4344 if(nfds == -1) { 4345 if(err.errno == err.EINTR) { 4346 //if(forceXPending) goto xpending; 4347 continue; // interrupted by signal, just try again 4348 } 4349 throw new Exception("epoll wait failure"); 4350 } 4351 // writeln(nfds, " ", events[0].data.fd); 4352 4353 SimpleWindow.processAllCustomEvents(); // anyway 4354 //version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 4355 foreach(idx; 0 .. nfds) { 4356 if(done) break; 4357 auto fd = events[idx].data.fd; 4358 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 4359 auto flags = events[idx].events; 4360 if(flags & ep.EPOLLIN) { 4361 if (fd == customSignalFD) { 4362 version(linux) { 4363 import core.sys.linux.sys.signalfd; 4364 import core.sys.posix.unistd : read; 4365 signalfd_siginfo info; 4366 read(customSignalFD, &info, info.sizeof); 4367 4368 auto sig = info.ssi_signo; 4369 4370 if(EventLoop.get.signalHandler !is null) { 4371 EventLoop.get.signalHandler()(sig); 4372 } else { 4373 EventLoop.get.exit(); 4374 } 4375 } 4376 } else if(fd == display.fd) { 4377 version(sdddd) { writeln("X EVENT PENDING!"); } 4378 this.mtLock(); 4379 scope(exit) this.mtUnlock(); 4380 while(!done && XPending(display)) { 4381 done = doXNextEvent(this.display); 4382 } 4383 forceXPending = false; 4384 } else if(fd == pulseFd) { 4385 long expirationCount; 4386 // 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... 4387 4388 handlePulse(); 4389 4390 // read just to clear the buffer so poll doesn't trigger again 4391 // BTW I read AFTER the pulse because if the pulse handler takes 4392 // a lot of time to execute, we don't want the app to get stuck 4393 // in a loop of timer hits without a chance to do anything else 4394 // 4395 // IOW handlePulse happens at most once per pulse interval. 4396 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4397 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 4398 } else if (fd == customEventFDRead) { 4399 // we have some custom events; process 'em 4400 import core.sys.posix.unistd : read; 4401 ulong n; 4402 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4403 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4404 //SimpleWindow.processAllCustomEvents(); 4405 4406 forceXPending = true; 4407 } else { 4408 // some other timer 4409 version(sdddd) { writeln("unknown fd: ", fd); } 4410 4411 if(Timer* t = fd in Timer.mapping) 4412 (*t).trigger(); 4413 4414 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4415 (*pfr).ready(flags); 4416 4417 // or i might add support for other FDs too 4418 // but for now it is just timer 4419 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 4420 } 4421 } 4422 if(flags & ep.EPOLLHUP) { 4423 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4424 (*pfr).hup(flags); 4425 if(globalHupHandler) 4426 globalHupHandler(fd, flags); 4427 } 4428 /+ 4429 } else { 4430 // not interested in OUT, we are just reading here. 4431 // 4432 // error or hup might also be reported 4433 // but it shouldn't here since we are only 4434 // using a few types of FD and Xlib will report 4435 // if it dies. 4436 // so instead of thoughtfully handling it, I'll 4437 // just throw. for now at least 4438 4439 throw new Exception("epoll did something else"); 4440 } 4441 +/ 4442 } 4443 // if we won't call `XPending()` here, libX may delay some internal event delivery. 4444 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 4445 xpending: 4446 if (!done && forceXPending) { 4447 this.mtLock(); 4448 scope(exit) this.mtUnlock(); 4449 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 4450 while(!done && XPending(display)) { 4451 done = doXNextEvent(this.display); 4452 } 4453 } 4454 } 4455 } else { 4456 // Generic fallback: yes to simple pulse support, 4457 // but NO timer support! 4458 4459 // FIXME: we could probably support the POSIX timer_create 4460 // signal-based option, but I'm in no rush to write it since 4461 // I prefer the fd-based functions. 4462 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 4463 4464 import core.sys.posix.poll; 4465 4466 pollfd[] pfds; 4467 pollfd[32] pfdsBuffer; 4468 auto len = PosixFdReader.mapping.length + 2; 4469 // FIXME: i should just reuse the buffer 4470 if(len < pfdsBuffer.length) 4471 pfds = pfdsBuffer[0 .. len]; 4472 else 4473 pfds = new pollfd[](len); 4474 4475 pfds[0].fd = display.fd; 4476 pfds[0].events = POLLIN; 4477 pfds[0].revents = 0; 4478 4479 int slot = 1; 4480 4481 if(customEventFDRead != -1) { 4482 pfds[slot].fd = customEventFDRead; 4483 pfds[slot].events = POLLIN; 4484 pfds[slot].revents = 0; 4485 4486 slot++; 4487 } 4488 4489 foreach(fd, obj; PosixFdReader.mapping) { 4490 if(!obj.enabled) continue; 4491 pfds[slot].fd = fd; 4492 pfds[slot].events = POLLIN; 4493 pfds[slot].revents = 0; 4494 4495 slot++; 4496 } 4497 4498 auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1); 4499 if(ret == -1) throw new Exception("poll"); 4500 4501 if(ret == 0) { 4502 // FIXME it may not necessarily time out if events keep coming 4503 if(handlePulse !is null) 4504 handlePulse(); 4505 } else { 4506 foreach(s; 0 .. slot) { 4507 if(pfds[s].revents == 0) continue; 4508 4509 if(pfds[s].fd == display.fd) { 4510 while(!done && XPending(display)) { 4511 this.mtLock(); 4512 scope(exit) this.mtUnlock(); 4513 done = doXNextEvent(this.display); 4514 } 4515 } else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) { 4516 4517 import core.sys.posix.unistd : read; 4518 ulong n; 4519 read(customEventFDRead, &n, n.sizeof); 4520 SimpleWindow.processAllCustomEvents(); 4521 } else { 4522 auto obj = PosixFdReader.mapping[pfds[s].fd]; 4523 if(pfds[s].revents & POLLNVAL) { 4524 obj.dispose(); 4525 } else { 4526 obj.ready(pfds[s].revents); 4527 } 4528 } 4529 4530 ret--; 4531 if(ret == 0) break; 4532 } 4533 } 4534 } 4535 } 4536 } 4537 4538 version(Windows) { 4539 int ret = -1; 4540 MSG message; 4541 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 4542 eventLoopRound++; 4543 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4544 auto waitResult = MsgWaitForMultipleObjectsEx( 4545 cast(int) handles.length, handles.ptr, 4546 (wto == 0 ? INFINITE : wto), /* timeout */ 4547 0x04FF, /* QS_ALLINPUT */ 4548 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 4549 4550 SimpleWindow.processAllCustomEvents(); // anyway 4551 enum WAIT_OBJECT_0 = 0; 4552 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 4553 auto h = handles[waitResult - WAIT_OBJECT_0]; 4554 if(auto e = h in WindowsHandleReader.mapping) { 4555 (*e).ready(); 4556 } 4557 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 4558 // message ready 4559 int count; 4560 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 4561 ret = GetMessage(&message, null, 0, 0); 4562 if(ret == -1) 4563 throw new WindowsApiException("GetMessage", GetLastError()); 4564 TranslateMessage(&message); 4565 DispatchMessage(&message); 4566 4567 count++; 4568 if(count > 10) 4569 break; // take the opportunity to catch up on other events 4570 4571 if(ret == 0) { // WM_QUIT 4572 EventLoop.quitApplication(); 4573 break; 4574 } 4575 } 4576 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 4577 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 4578 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 4579 // timeout, should never happen since we aren't using it 4580 } else if(waitResult == 0xFFFFFFFF) { 4581 // failed 4582 throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError()); 4583 } else { 4584 // idk.... 4585 } 4586 } 4587 4588 // return message.wParam; 4589 return 0; 4590 } else { 4591 return 0; 4592 } 4593 } 4594 4595 int run(bool delegate() whileCondition = null) { 4596 if(disposed) 4597 initialize(this.pulseTimeout); 4598 4599 version(X11) { 4600 try { 4601 return loopHelper(whileCondition); 4602 } catch(XDisconnectException e) { 4603 if(e.userRequested) { 4604 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 4605 item.discardConnectionState(); 4606 XCloseDisplay(XDisplayConnection.display); 4607 } 4608 4609 XDisplayConnection.display = null; 4610 4611 this.dispose(); 4612 4613 throw e; 4614 } 4615 } else { 4616 return loopHelper(whileCondition); 4617 } 4618 } 4619 } 4620 4621 4622 /++ 4623 Provides an icon on the system notification area (also known as the system tray). 4624 4625 4626 If a notification area is not available with the NotificationIcon object is created, 4627 it will silently succeed and simply attempt to create one when an area becomes available. 4628 4629 4630 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. Support for 4631 Windows XP was dropped on October 31, 2023. On the other hand, support for 32 bit transparency 4632 with true color was added at that time. I was just too lazy to write the fallback. 4633 4634 If this is an issue, let me know, it'd take about an hour to get it back in there, but I suggest 4635 you use arsd 10.x when targeting Windows XP. 4636 +/ 4637 version(OSXCocoa) {} else // NotYetImplementedException 4638 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 4639 4640 version(X11) { 4641 void recreateAfterDisconnect() { 4642 stateDiscarded = false; 4643 clippixmap = None; 4644 throw new Exception("NOT IMPLEMENTED"); 4645 } 4646 4647 bool stateDiscarded; 4648 void discardConnectionState() { 4649 stateDiscarded = true; 4650 } 4651 } 4652 4653 4654 version(X11) { 4655 Image img; 4656 4657 NativeEventHandler getNativeEventHandler() { 4658 return delegate int(XEvent e) { 4659 switch(e.type) { 4660 case EventType.Expose: 4661 //case EventType.VisibilityNotify: 4662 redraw(); 4663 break; 4664 case EventType.ClientMessage: 4665 version(sddddd) { 4666 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 4667 writeln("\t", e.xclient.format); 4668 writeln("\t", e.xclient.data.l); 4669 } 4670 break; 4671 case EventType.ButtonPress: 4672 auto event = e.xbutton; 4673 if (onClick !is null || onClickEx !is null) { 4674 MouseButton mb = cast(MouseButton)0; 4675 switch (event.button) { 4676 case 1: mb = MouseButton.left; break; // left 4677 case 2: mb = MouseButton.middle; break; // middle 4678 case 3: mb = MouseButton.right; break; // right 4679 case 4: mb = MouseButton.wheelUp; break; // scroll up 4680 case 5: mb = MouseButton.wheelDown; break; // scroll down 4681 case 6: break; // scroll left... 4682 case 7: break; // scroll right... 4683 case 8: mb = MouseButton.backButton; break; 4684 case 9: mb = MouseButton.forwardButton; break; 4685 default: 4686 } 4687 if (mb) { 4688 try { onClick()(mb); } catch (Exception) {} 4689 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 4690 } 4691 } 4692 break; 4693 case EventType.EnterNotify: 4694 if (onEnter !is null) { 4695 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 4696 } 4697 break; 4698 case EventType.LeaveNotify: 4699 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 4700 break; 4701 case EventType.DestroyNotify: 4702 active = false; 4703 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 4704 break; 4705 case EventType.ConfigureNotify: 4706 auto event = e.xconfigure; 4707 this.width = event.width; 4708 this.height = event.height; 4709 // writeln(width, " x " , height, " @ ", event.x, " ", event.y); 4710 redraw(); 4711 break; 4712 default: return 1; 4713 } 4714 return 1; 4715 }; 4716 } 4717 4718 /* private */ void hideBalloon() { 4719 balloon.close(); 4720 version(with_timer) 4721 timer.destroy(); 4722 balloon = null; 4723 version(with_timer) 4724 timer = null; 4725 } 4726 4727 void redraw() { 4728 if (!active) return; 4729 4730 auto display = XDisplayConnection.get; 4731 GC gc; 4732 4733 // from https://stackoverflow.com/questions/10492275/how-to-upload-32-bit-image-to-server-side-pixmap 4734 4735 int gc_depth(int depth, Display *dpy, Window root, GC *gc) { 4736 Visual *visual; 4737 XVisualInfo vis_info; 4738 XSetWindowAttributes win_attr; 4739 c_ulong win_mask; 4740 4741 if(!XMatchVisualInfo(dpy, 0, depth, 4 /*TrueColor*/, &vis_info)) { 4742 assert(0); 4743 // return 1; 4744 } 4745 4746 visual = vis_info.visual; 4747 4748 win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone); 4749 win_attr.background_pixel = 0; 4750 win_attr.border_pixel = 0; 4751 4752 win_mask = CWBackPixel | CWColormap | CWBorderPixel; 4753 4754 *gc = XCreateGC(dpy, nativeHandle, 0, null); 4755 4756 return 0; 4757 } 4758 4759 if(useAlpha) 4760 gc_depth(32, display, RootWindow(display, DefaultScreen(display)), &gc); 4761 else 4762 gc = DefaultGC(display, DefaultScreen(display)); 4763 4764 XClearWindow(display, nativeHandle); 4765 4766 if(!useAlpha && img !is null) 4767 XSetClipMask(display, gc, clippixmap); 4768 4769 /+ 4770 XSetForeground(display, gc, 4771 cast(uint) 0 << 16 | 4772 cast(uint) 0 << 8 | 4773 cast(uint) 0); 4774 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 4775 +/ 4776 4777 if (img is null) { 4778 XSetForeground(display, gc, 4779 cast(uint) 0 << 16 | 4780 cast(uint) 127 << 8 | 4781 cast(uint) 0); 4782 XFillArc(display, nativeHandle, 4783 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 4784 } else { 4785 int dx = 0; 4786 int dy = 0; 4787 if(width > img.width) 4788 dx = (width - img.width) / 2; 4789 if(height > img.height) 4790 dy = (height - img.height) / 2; 4791 // writeln(img.width, " ", img.height, " vs ", width, " ", height); 4792 XSetClipOrigin(display, gc, dx, dy); 4793 4794 int max(int a, int b) { 4795 if(a > b) return a; else return b; 4796 } 4797 4798 if (img.usingXshm) 4799 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height), false); 4800 else 4801 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, max(img.width, width), max(img.height, height)); 4802 } 4803 XSetClipMask(display, gc, None); 4804 flushGui(); 4805 } 4806 4807 static Window getTrayOwner() { 4808 auto display = XDisplayConnection.get; 4809 auto i = cast(int) DefaultScreen(display); 4810 if(i < 10 && i >= 0) { 4811 static Atom atom; 4812 if(atom == None) 4813 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 4814 return XGetSelectionOwner(display, atom); 4815 } 4816 return None; 4817 } 4818 4819 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 4820 auto to = getTrayOwner(); 4821 auto display = XDisplayConnection.get; 4822 XEvent ev; 4823 ev.xclient.type = EventType.ClientMessage; 4824 ev.xclient.window = to; 4825 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 4826 ev.xclient.format = 32; 4827 ev.xclient.data.l[0] = CurrentTime; 4828 ev.xclient.data.l[1] = message; 4829 ev.xclient.data.l[2] = d1; 4830 ev.xclient.data.l[3] = d2; 4831 ev.xclient.data.l[4] = d3; 4832 4833 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 4834 } 4835 4836 private static NotificationAreaIcon[] activeIcons; 4837 4838 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 4839 private void newManager() { 4840 close(); 4841 createXWin(); 4842 4843 if(this.clippixmap) 4844 XFreePixmap(XDisplayConnection.get, clippixmap); 4845 if(this.originalMemoryImage) 4846 this.icon = this.originalMemoryImage; 4847 else if(this.img) 4848 this.icon = this.img; 4849 } 4850 4851 private bool useAlpha = false; 4852 4853 private void createXWin () { 4854 // create window 4855 auto display = XDisplayConnection.get; 4856 4857 // to check for MANAGER on root window to catch new/changed tray owners 4858 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 4859 // so if a thing does appear, we can handle it 4860 foreach(ai; activeIcons) 4861 if(ai is this) 4862 goto alreadythere; 4863 activeIcons ~= this; 4864 alreadythere: 4865 4866 // and check for an existing tray 4867 auto trayOwner = getTrayOwner(); 4868 if(trayOwner == None) 4869 return; 4870 //throw new Exception("No notification area found"); 4871 4872 Visual* v = cast(Visual*) CopyFromParent; 4873 4874 // GNOME's default is 22x22 and KDE assumes all icons are going to match that then bitmap scales 4875 // from there. It is ugly and stupid but this gives the fewest artifacts. Good environments will send 4876 // a resize event later. 4877 width = 22; 4878 height = 22; 4879 4880 // if they system gave us a 32 bit visual we need to switch to it too 4881 int depth = 24; 4882 4883 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 4884 if(visualProp !is null) { 4885 c_ulong[] info = cast(c_ulong[]) visualProp; 4886 if(info.length == 1) { 4887 auto vid = info[0]; 4888 int returned; 4889 XVisualInfo t; 4890 t.visualid = vid; 4891 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 4892 if(got !is null) { 4893 if(returned == 1) { 4894 v = got.visual; 4895 depth = got.depth; 4896 // writeln("using special visual ", got.depth); 4897 // writeln(depth); 4898 } 4899 XFree(got); 4900 } 4901 } 4902 } 4903 4904 int CWFlags = CWBackPixel | CWBorderPixel | CWOverrideRedirect; 4905 XSetWindowAttributes attr; 4906 attr.background_pixel = 0; 4907 attr.border_pixel = 0; 4908 attr.override_redirect = 0; 4909 if(v !is cast(Visual*) CopyFromParent) { 4910 attr.colormap = XCreateColormap(display, RootWindow(display, DefaultScreen(display)), v, AllocNone); 4911 CWFlags |= CWColormap; 4912 if(depth == 32) 4913 useAlpha = true; 4914 else 4915 goto plain; 4916 } else { 4917 plain: 4918 attr.background_pixmap = 1 /* ParentRelative */; 4919 CWFlags |= CWBackPixmap; 4920 } 4921 4922 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, width, height, 0, depth, InputOutput, v, CWFlags, &attr); 4923 4924 assert(nativeWindow); 4925 4926 if(!useAlpha) 4927 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 4928 4929 nativeHandle = nativeWindow; 4930 4931 ///+ 4932 arch_ulong[2] info; 4933 info[0] = 0; 4934 info[1] = 1; 4935 4936 string title = this.name is null ? "simpledisplay.d program" : this.name; 4937 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 4938 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 4939 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 4940 4941 XChangeProperty( 4942 display, 4943 nativeWindow, 4944 GetAtom!("_XEMBED_INFO", true)(display), 4945 GetAtom!("_XEMBED_INFO", true)(display), 4946 32 /* bits */, 4947 0 /*PropModeReplace*/, 4948 info.ptr, 4949 2); 4950 4951 import core.sys.posix.unistd; 4952 arch_ulong pid = getpid(); 4953 4954 XChangeProperty( 4955 display, 4956 nativeWindow, 4957 GetAtom!("_NET_WM_PID", true)(display), 4958 XA_CARDINAL, 4959 32 /* bits */, 4960 0 /*PropModeReplace*/, 4961 &pid, 4962 1); 4963 4964 updateNetWmIcon(); 4965 4966 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 4967 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 4968 XClassHint klass; 4969 XWMHints wh; 4970 XSizeHints size; 4971 klass.res_name = sdpyWindowClassStr; 4972 klass.res_class = sdpyWindowClassStr; 4973 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 4974 } 4975 4976 // believe it or not, THIS is what xfce needed for the 9999 issue 4977 XSizeHints sh; 4978 c_long spr; 4979 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 4980 sh.flags |= PMaxSize | PMinSize; 4981 // FIXME maybe nicer resizing 4982 sh.min_width = 16; 4983 sh.min_height = 16; 4984 sh.max_width = 22; 4985 sh.max_height = 22; 4986 XSetWMNormalHints(display, nativeWindow, &sh); 4987 4988 4989 //+/ 4990 4991 4992 XSelectInput(display, nativeWindow, 4993 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 4994 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 4995 4996 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 4997 // XMapWindow(display, nativeWindow); // to demo it w/o a tray 4998 4999 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 5000 active = true; 5001 } 5002 5003 void updateNetWmIcon() { 5004 if(img is null) return; 5005 auto display = XDisplayConnection.get; 5006 // FIXME: ensure this is correct 5007 arch_ulong[] buffer; 5008 auto imgMi = img.toTrueColorImage; 5009 buffer ~= imgMi.width; 5010 buffer ~= imgMi.height; 5011 foreach(c; imgMi.imageData.colors) { 5012 arch_ulong b; 5013 b |= c.a << 24; 5014 b |= c.r << 16; 5015 b |= c.g << 8; 5016 b |= c.b; 5017 buffer ~= b; 5018 } 5019 5020 XChangeProperty( 5021 display, 5022 nativeHandle, 5023 GetAtom!"_NET_WM_ICON"(display), 5024 GetAtom!"CARDINAL"(display), 5025 32 /* bits */, 5026 0 /*PropModeReplace*/, 5027 buffer.ptr, 5028 cast(int) buffer.length); 5029 } 5030 5031 5032 5033 private SimpleWindow balloon; 5034 version(with_timer) 5035 private Timer timer; 5036 5037 private Window nativeHandle; 5038 private Pixmap clippixmap = None; 5039 private int width = 16; 5040 private int height = 16; 5041 private bool active = false; 5042 5043 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 5044 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 5045 void delegate () onLeave; /// X11 only. 5046 5047 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 5048 5049 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 5050 void getWindowRect (out int x, out int y, out int width, out int height) { 5051 if (!active) { width = 1; height = 1; return; } // 1: just in case 5052 Window dummyw; 5053 auto dpy = XDisplayConnection.get; 5054 //XWindowAttributes xwa; 5055 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 5056 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 5057 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 5058 width = this.width; 5059 height = this.height; 5060 } 5061 } 5062 5063 /+ 5064 What I actually want from this: 5065 5066 * set / change: icon, tooltip 5067 * handle: mouse click, right click 5068 * show: notification bubble. 5069 +/ 5070 5071 version(Windows) { 5072 WindowsIcon win32Icon; 5073 HWND hwnd; 5074 5075 NOTIFYICONDATAW data; 5076 5077 NativeEventHandler getNativeEventHandler() { 5078 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 5079 if(msg == WM_USER) { 5080 auto event = LOWORD(lParam); 5081 auto iconId = HIWORD(lParam); 5082 //auto x = GET_X_LPARAM(wParam); 5083 //auto y = GET_Y_LPARAM(wParam); 5084 switch(event) { 5085 case WM_LBUTTONDOWN: 5086 onClick()(MouseButton.left); 5087 break; 5088 case WM_RBUTTONDOWN: 5089 onClick()(MouseButton.right); 5090 break; 5091 case WM_MBUTTONDOWN: 5092 onClick()(MouseButton.middle); 5093 break; 5094 case WM_MOUSEMOVE: 5095 // sent, we could use it. 5096 break; 5097 case WM_MOUSEWHEEL: 5098 // NOT SENT 5099 break; 5100 //case NIN_KEYSELECT: 5101 //case NIN_SELECT: 5102 //break; 5103 default: {} 5104 } 5105 } 5106 return 0; 5107 }; 5108 } 5109 5110 enum NIF_SHOWTIP = 0x00000080; 5111 5112 private static struct NOTIFYICONDATAW { 5113 DWORD cbSize; 5114 HWND hWnd; 5115 UINT uID; 5116 UINT uFlags; 5117 UINT uCallbackMessage; 5118 HICON hIcon; 5119 WCHAR[128] szTip; 5120 DWORD dwState; 5121 DWORD dwStateMask; 5122 WCHAR[256] szInfo; 5123 union { 5124 UINT uTimeout; 5125 UINT uVersion; 5126 } 5127 WCHAR[64] szInfoTitle; 5128 DWORD dwInfoFlags; 5129 GUID guidItem; 5130 HICON hBalloonIcon; 5131 } 5132 5133 } 5134 5135 /++ 5136 Note that on Windows, only left, right, and middle buttons are sent. 5137 Mouse wheel buttons are NOT set, so don't rely on those events if your 5138 program is meant to be used on Windows too. 5139 +/ 5140 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 5141 // The canonical constructor for Windows needs the MemoryImage, so it is here, 5142 // but on X, we need an Image, so its canonical ctor is there. They should 5143 // forward to each other though. 5144 version(X11) { 5145 this.name = name; 5146 this.onClick = onClick; 5147 createXWin(); 5148 this.icon = icon; 5149 } else version(Windows) { 5150 this.onClick = onClick; 5151 this.win32Icon = new WindowsIcon(icon); 5152 5153 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 5154 5155 static bool registered = false; 5156 if(!registered) { 5157 WNDCLASSEX wc; 5158 wc.cbSize = wc.sizeof; 5159 wc.hInstance = hInstance; 5160 wc.lpfnWndProc = &WndProc; 5161 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 5162 if(!RegisterClassExW(&wc)) 5163 throw new WindowsApiException("RegisterClass", GetLastError()); 5164 registered = true; 5165 } 5166 5167 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 5168 if(hwnd is null) 5169 throw new WindowsApiException("CreateWindow", GetLastError()); 5170 5171 data.cbSize = data.sizeof; 5172 data.hWnd = hwnd; 5173 data.uID = cast(uint) cast(void*) this; 5174 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 5175 // NIF_INFO means show balloon 5176 data.uCallbackMessage = WM_USER; 5177 data.hIcon = this.win32Icon.hIcon; 5178 data.szTip = ""; // FIXME 5179 data.dwState = 0; // NIS_HIDDEN; // windows vista 5180 data.dwStateMask = NIS_HIDDEN; // windows vista 5181 5182 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 5183 5184 5185 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 5186 5187 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 5188 } else version(OSXCocoa) { 5189 throw new NotYetImplementedException(); 5190 } else static assert(0); 5191 } 5192 5193 /// ditto 5194 this(string name, Image icon, void delegate(MouseButton button) onClick) { 5195 version(X11) { 5196 this.onClick = onClick; 5197 this.name = name; 5198 createXWin(); 5199 this.icon = icon; 5200 } else version(Windows) { 5201 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 5202 } else version(OSXCocoa) { 5203 throw new NotYetImplementedException(); 5204 } else static assert(0); 5205 } 5206 5207 version(X11) { 5208 /++ 5209 X-specific extension (for now at least) 5210 +/ 5211 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5212 this.onClickEx = onClickEx; 5213 createXWin(); 5214 if (icon !is null) this.icon = icon; 5215 } 5216 5217 /// ditto 5218 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5219 this.onClickEx = onClickEx; 5220 createXWin(); 5221 this.icon = icon; 5222 } 5223 } 5224 5225 private void delegate (MouseButton button) onClick_; 5226 5227 /// 5228 @property final void delegate(MouseButton) onClick() { 5229 if(onClick_ is null) 5230 onClick_ = delegate void(MouseButton) {}; 5231 return onClick_; 5232 } 5233 5234 /// ditto 5235 @property final void onClick(void delegate(MouseButton) handler) { 5236 // I made this a property setter so we can wrap smaller arg 5237 // delegates and just forward all to onClickEx or something. 5238 onClick_ = handler; 5239 } 5240 5241 5242 string name_; 5243 @property void name(string n) { 5244 name_ = n; 5245 } 5246 5247 @property string name() { 5248 return name_; 5249 } 5250 5251 private MemoryImage originalMemoryImage; 5252 5253 /// 5254 @property void icon(MemoryImage i) { 5255 version(X11) { 5256 this.originalMemoryImage = i; 5257 if (!active) return; 5258 if (i !is null) { 5259 this.img = Image.fromMemoryImage(i, useAlpha, false); 5260 if(!useAlpha) 5261 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 5262 // writeln("using pixmap ", clippixmap); 5263 updateNetWmIcon(); 5264 redraw(); 5265 } else { 5266 if (this.img !is null) { 5267 this.img = null; 5268 redraw(); 5269 } 5270 } 5271 } else version(Windows) { 5272 this.win32Icon = new WindowsIcon(i); 5273 5274 data.uFlags = NIF_ICON; 5275 data.hIcon = this.win32Icon.hIcon; 5276 5277 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5278 } else version(OSXCocoa) { 5279 throw new NotYetImplementedException(); 5280 } else static assert(0); 5281 } 5282 5283 /// ditto 5284 @property void icon (Image i) { 5285 version(X11) { 5286 if (!active) return; 5287 if (i !is img) { 5288 originalMemoryImage = null; 5289 img = i; 5290 redraw(); 5291 } 5292 } else version(Windows) { 5293 this.icon(i is null ? null : i.toTrueColorImage()); 5294 } else version(OSXCocoa) { 5295 throw new NotYetImplementedException(); 5296 } else static assert(0); 5297 } 5298 5299 /++ 5300 Shows a balloon notification. You can only show one balloon at a time, if you call 5301 it twice while one is already up, the first balloon will be replaced. 5302 5303 5304 The user is free to block notifications and they will automatically disappear after 5305 a timeout period. 5306 5307 Params: 5308 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 5309 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 5310 icon = the icon to display with the notification. If null, it uses your existing icon. 5311 onclick = delegate called if the user clicks the balloon. (not yet implemented) 5312 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 5313 +/ 5314 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 5315 bool useCustom = true; 5316 version(libnotify) { 5317 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 5318 try { 5319 if(!active) return; 5320 5321 if(libnotify is null) { 5322 libnotify = new C_DynamicLibrary("libnotify.so"); 5323 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 5324 } 5325 5326 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 5327 5328 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 5329 5330 if(onclick) { 5331 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 5332 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); 5333 libnotify_action_delegates_count++; 5334 } 5335 5336 // FIXME icon 5337 5338 // set hint image-data 5339 // set default action for onclick 5340 5341 void* error; 5342 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 5343 5344 useCustom = false; 5345 } catch(Exception e) { 5346 5347 } 5348 } 5349 5350 version(X11) { 5351 if(useCustom) { 5352 if(!active) return; 5353 if(balloon) { 5354 hideBalloon(); 5355 } 5356 // I know there are two specs for this, but one is never 5357 // implemented by any window manager I have ever seen, and 5358 // the other is a bloated mess and too complicated for simpledisplay... 5359 // so doing my own little window instead. 5360 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 5361 5362 int x, y, width, height; 5363 getWindowRect(x, y, width, height); 5364 5365 int bx = x - balloon.width; 5366 int by = y - balloon.height; 5367 if(bx < 0) 5368 bx = x + width + balloon.width; 5369 if(by < 0) 5370 by = y + height; 5371 5372 // just in case, make sure it is actually on scren 5373 if(bx < 0) 5374 bx = 0; 5375 if(by < 0) 5376 by = 0; 5377 5378 balloon.move(bx, by); 5379 auto painter = balloon.draw(); 5380 painter.fillColor = Color(220, 220, 220); 5381 painter.outlineColor = Color.black; 5382 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 5383 auto iconWidth = icon is null ? 0 : icon.width; 5384 if(icon) 5385 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 5386 iconWidth += 6; // margin around the icon 5387 5388 // draw a close button 5389 painter.outlineColor = Color(44, 44, 44); 5390 painter.fillColor = Color(255, 255, 255); 5391 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 5392 painter.pen = Pen(Color.black, 3); 5393 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 5394 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 5395 painter.pen = Pen(Color.black, 1); 5396 painter.fillColor = Color(220, 220, 220); 5397 5398 // Draw the title and message 5399 painter.drawText(Point(4 + iconWidth, 4), title); 5400 painter.drawLine( 5401 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 5402 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 5403 ); 5404 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 5405 5406 balloon.setEventHandlers( 5407 (MouseEvent ev) { 5408 if(ev.type == MouseEventType.buttonPressed) { 5409 if(ev.x > balloon.width - 16 && ev.y < 16) 5410 hideBalloon(); 5411 else if(onclick) 5412 onclick(); 5413 } 5414 } 5415 ); 5416 balloon.show(); 5417 5418 version(with_timer) 5419 timer = new Timer(timeout, &hideBalloon); 5420 else {} // FIXME 5421 } 5422 } else version(Windows) { 5423 enum NIF_INFO = 0x00000010; 5424 5425 data.uFlags = NIF_INFO; 5426 5427 // FIXME: go back to the last valid unicode code point 5428 if(title.length > 40) 5429 title = title[0 .. 40]; 5430 if(message.length > 220) 5431 message = message[0 .. 220]; 5432 5433 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 5434 enum NIIF_LARGE_ICON = 0x00000020; 5435 enum NIIF_NOSOUND = 0x00000010; 5436 enum NIIF_USER = 0x00000004; 5437 enum NIIF_ERROR = 0x00000003; 5438 enum NIIF_WARNING = 0x00000002; 5439 enum NIIF_INFO = 0x00000001; 5440 enum NIIF_NONE = 0; 5441 5442 WCharzBuffer t = WCharzBuffer(title); 5443 WCharzBuffer m = WCharzBuffer(message); 5444 5445 t.copyInto(data.szInfoTitle); 5446 m.copyInto(data.szInfo); 5447 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 5448 5449 if(icon !is null) { 5450 auto i = new WindowsIcon(icon); 5451 data.hBalloonIcon = i.hIcon; 5452 data.dwInfoFlags |= NIIF_USER; 5453 } 5454 5455 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5456 } else version(OSXCocoa) { 5457 throw new NotYetImplementedException(); 5458 } else static assert(0); 5459 } 5460 5461 /// 5462 //version(Windows) 5463 void show() { 5464 version(X11) { 5465 if(!hidden) 5466 return; 5467 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 5468 hidden = false; 5469 } else version(Windows) { 5470 data.uFlags = NIF_STATE; 5471 data.dwState = 0; // NIS_HIDDEN; // windows vista 5472 data.dwStateMask = NIS_HIDDEN; // windows vista 5473 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5474 } else version(OSXCocoa) { 5475 throw new NotYetImplementedException(); 5476 } else static assert(0); 5477 } 5478 5479 version(X11) 5480 bool hidden = false; 5481 5482 /// 5483 //version(Windows) 5484 void hide() { 5485 version(X11) { 5486 if(hidden) 5487 return; 5488 hidden = true; 5489 XUnmapWindow(XDisplayConnection.get, nativeHandle); 5490 } else version(Windows) { 5491 data.uFlags = NIF_STATE; 5492 data.dwState = NIS_HIDDEN; // windows vista 5493 data.dwStateMask = NIS_HIDDEN; // windows vista 5494 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5495 } else version(OSXCocoa) { 5496 throw new NotYetImplementedException(); 5497 } else static assert(0); 5498 } 5499 5500 /// 5501 void close () { 5502 version(X11) { 5503 if (active) { 5504 active = false; // event handler will set this too, but meh 5505 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 5506 XDestroyWindow(XDisplayConnection.get, nativeHandle); 5507 flushGui(); 5508 } 5509 } else version(Windows) { 5510 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 5511 } else version(OSXCocoa) { 5512 throw new NotYetImplementedException(); 5513 } else static assert(0); 5514 } 5515 5516 ~this() { 5517 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 5518 version(X11) 5519 if(clippixmap != None) 5520 XFreePixmap(XDisplayConnection.get, clippixmap); 5521 close(); 5522 } 5523 } 5524 5525 version(X11) 5526 /// Call `XFreePixmap` on the return value. 5527 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 5528 char[] data = new char[](i.width * i.height / 8 + 2); 5529 data[] = 0; 5530 5531 int bitOffset = 0; 5532 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 5533 ubyte v = c.a > 128 ? 1 : 0; 5534 data[bitOffset / 8] |= v << (bitOffset%8); 5535 bitOffset++; 5536 } 5537 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 5538 return handle; 5539 } 5540 5541 5542 // basic functions to make timers 5543 /** 5544 A timer that will trigger your function on a given interval. 5545 5546 5547 You create a timer with an interval and a callback. It will continue 5548 to fire on the interval until it is destroyed. 5549 5550 There are currently no one-off timers (instead, just create one and 5551 destroy it when it is triggered) nor are there pause/resume functions - 5552 the timer must again be destroyed and recreated if you want to pause it. 5553 5554 --- 5555 auto timer = new Timer(50, { it happened!; }); 5556 timer.destroy(); 5557 --- 5558 5559 Timers can only be expected to fire when the event loop is running and only 5560 once per iteration through the event loop. 5561 5562 History: 5563 Prior to December 9, 2020, a timer pulse set too high with a handler too 5564 slow could lock up the event loop. It now guarantees other things will 5565 get a chance to run between timer calls, even if that means not keeping up 5566 with the requested interval. 5567 */ 5568 version(with_timer) { 5569 class Timer { 5570 // FIXME: needs pause and unpause 5571 // FIXME: I might add overloads for ones that take a count of 5572 // how many elapsed since last time (on Windows, it will divide 5573 // the ticks thing given, on Linux it is just available) and 5574 // maybe one that takes an instance of the Timer itself too 5575 /// Create a timer with a callback when it triggers. 5576 this(int intervalInMilliseconds, void delegate() onPulse) { 5577 assert(onPulse !is null); 5578 5579 this.intervalInMilliseconds = intervalInMilliseconds; 5580 this.onPulse = onPulse; 5581 5582 version(Windows) { 5583 /* 5584 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5585 if(handle == 0) 5586 throw new WindowsApiException("SetTimer", GetLastError()); 5587 */ 5588 5589 // thanks to Archival 998 for the WaitableTimer blocks 5590 handle = CreateWaitableTimer(null, false, null); 5591 long initialTime = -intervalInMilliseconds; 5592 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5593 throw new WindowsApiException("SetWaitableTimer", GetLastError()); 5594 5595 mapping[handle] = this; 5596 5597 } else version(linux) { 5598 static import ep = core.sys.linux.epoll; 5599 5600 import core.sys.linux.timerfd; 5601 5602 fd = timerfd_create(CLOCK_MONOTONIC, 0); 5603 if(fd == -1) 5604 throw new Exception("timer create failed"); 5605 5606 mapping[fd] = this; 5607 5608 itimerspec value; 5609 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5610 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5611 5612 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5613 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5614 5615 if(timerfd_settime(fd, 0, &value, null) == -1) 5616 throw new Exception("couldn't make pulse timer"); 5617 5618 version(with_eventloop) { 5619 import arsd.eventloop; 5620 addFileEventListeners(fd, &trigger, null, null); 5621 } else { 5622 prepareEventLoop(); 5623 5624 ep.epoll_event ev = void; 5625 ev.events = ep.EPOLLIN; 5626 ev.data.fd = fd; 5627 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5628 } 5629 } else featureNotImplemented(); 5630 } 5631 5632 private int intervalInMilliseconds; 5633 5634 // just cuz I sometimes call it this. 5635 alias dispose = destroy; 5636 5637 /// Stop and destroy the timer object. 5638 void destroy() { 5639 version(Windows) { 5640 staticDestroy(handle); 5641 handle = null; 5642 } else version(linux) { 5643 staticDestroy(fd); 5644 fd = -1; 5645 } else featureNotImplemented(); 5646 } 5647 5648 version(Windows) 5649 static void staticDestroy(HANDLE handle) { 5650 if(handle) { 5651 // KillTimer(null, handle); 5652 CancelWaitableTimer(cast(void*)handle); 5653 mapping.remove(handle); 5654 CloseHandle(handle); 5655 } 5656 } 5657 else version(linux) 5658 static void staticDestroy(int fd) { 5659 if(fd != -1) { 5660 import unix = core.sys.posix.unistd; 5661 static import ep = core.sys.linux.epoll; 5662 5663 version(with_eventloop) { 5664 import arsd.eventloop; 5665 removeFileEventListeners(fd); 5666 } else { 5667 ep.epoll_event ev = void; 5668 ev.events = ep.EPOLLIN; 5669 ev.data.fd = fd; 5670 5671 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5672 } 5673 unix.close(fd); 5674 mapping.remove(fd); 5675 } 5676 } 5677 5678 ~this() { 5679 version(Windows) { if(handle) 5680 cleanupQueue.queue!staticDestroy(handle); 5681 } else version(linux) { if(fd != -1) 5682 cleanupQueue.queue!staticDestroy(fd); 5683 } 5684 } 5685 5686 5687 void changeTime(int intervalInMilliseconds) 5688 { 5689 this.intervalInMilliseconds = intervalInMilliseconds; 5690 version(Windows) 5691 { 5692 if(handle) 5693 { 5694 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5695 long initialTime = -intervalInMilliseconds; 5696 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5697 throw new WindowsApiException("couldn't change pulse timer", GetLastError()); 5698 } 5699 } 5700 } 5701 5702 5703 private: 5704 5705 void delegate() onPulse; 5706 5707 int lastEventLoopRoundTriggered; 5708 5709 void trigger() { 5710 version(linux) { 5711 import unix = core.sys.posix.unistd; 5712 long val; 5713 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 5714 } else version(Windows) { 5715 if(this.lastEventLoopRoundTriggered == eventLoopRound) 5716 return; // never try to actually run faster than the event loop 5717 lastEventLoopRoundTriggered = eventLoopRound; 5718 } else featureNotImplemented(); 5719 5720 onPulse(); 5721 } 5722 5723 version(Windows) 5724 void rearm() { 5725 5726 } 5727 5728 version(Windows) 5729 extern(Windows) 5730 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 5731 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 5732 if(Timer* t = timer in mapping) { 5733 try 5734 (*t).trigger(); 5735 catch(Exception e) { sdpy_abort(e); assert(0); } 5736 } 5737 } 5738 5739 version(Windows) { 5740 //UINT_PTR handle; 5741 //static Timer[UINT_PTR] mapping; 5742 HANDLE handle; 5743 __gshared Timer[HANDLE] mapping; 5744 } else version(linux) { 5745 int fd = -1; 5746 __gshared Timer[int] mapping; 5747 } else version(OSXCocoa) { 5748 } else static assert(0, "timer not supported"); 5749 } 5750 } 5751 5752 version(Windows) 5753 private int eventLoopRound; 5754 5755 version(Windows) 5756 /// 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 5757 class WindowsHandleReader { 5758 /// 5759 this(void delegate() onReady, HANDLE handle) { 5760 this.onReady = onReady; 5761 this.handle = handle; 5762 5763 mapping[handle] = this; 5764 5765 enable(); 5766 } 5767 5768 /// 5769 void enable() { 5770 auto el = EventLoop.get().impl; 5771 el.handles ~= handle; 5772 } 5773 5774 /// 5775 void disable() { 5776 auto el = EventLoop.get().impl; 5777 for(int i = 0; i < el.handles.length; i++) { 5778 if(el.handles[i] is handle) { 5779 el.handles[i] = el.handles[$-1]; 5780 el.handles = el.handles[0 .. $-1]; 5781 return; 5782 } 5783 } 5784 } 5785 5786 void dispose() { 5787 disable(); 5788 if(handle) 5789 mapping.remove(handle); 5790 handle = null; 5791 } 5792 5793 void ready() { 5794 if(onReady) 5795 onReady(); 5796 } 5797 5798 HANDLE handle; 5799 void delegate() onReady; 5800 5801 __gshared WindowsHandleReader[HANDLE] mapping; 5802 } 5803 5804 version(Posix) 5805 /// Lets you add files to the event loop for reading. Use at your own risk. 5806 class PosixFdReader { 5807 /// 5808 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5809 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 5810 } 5811 5812 /// 5813 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5814 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 5815 } 5816 5817 /// 5818 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5819 this.onReady = onReady; 5820 this.fd = fd; 5821 this.captureWrites = captureWrites; 5822 this.captureReads = captureReads; 5823 5824 mapping[fd] = this; 5825 5826 version(with_eventloop) { 5827 import arsd.eventloop; 5828 addFileEventListeners(fd, &readyel); 5829 } else { 5830 enable(); 5831 } 5832 } 5833 5834 bool captureReads; 5835 bool captureWrites; 5836 5837 version(with_eventloop) {} else 5838 /// 5839 void enable() { 5840 prepareEventLoop(); 5841 5842 enabled = true; 5843 5844 version(linux) { 5845 static import ep = core.sys.linux.epoll; 5846 ep.epoll_event ev = void; 5847 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5848 // writeln("enable ", fd, " ", captureReads, " ", captureWrites); 5849 ev.data.fd = fd; 5850 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5851 } else { 5852 5853 } 5854 } 5855 5856 version(with_eventloop) {} else 5857 /// 5858 void disable() { 5859 prepareEventLoop(); 5860 5861 enabled = false; 5862 5863 version(linux) { 5864 static import ep = core.sys.linux.epoll; 5865 ep.epoll_event ev = void; 5866 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5867 // writeln("disable ", fd, " ", captureReads, " ", captureWrites); 5868 ev.data.fd = fd; 5869 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5870 } 5871 } 5872 5873 version(with_eventloop) {} else 5874 /// 5875 void dispose() { 5876 if(enabled) 5877 disable(); 5878 if(fd != -1) 5879 mapping.remove(fd); 5880 fd = -1; 5881 } 5882 5883 void delegate(int, bool, bool) onReady; 5884 5885 version(with_eventloop) 5886 void readyel() { 5887 onReady(fd, true, true); 5888 } 5889 5890 void ready(uint flags) { 5891 version(linux) { 5892 static import ep = core.sys.linux.epoll; 5893 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 5894 } else { 5895 import core.sys.posix.poll; 5896 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 5897 } 5898 } 5899 5900 void hup(uint flags) { 5901 if(onHup) 5902 onHup(); 5903 } 5904 5905 void delegate() onHup; 5906 5907 int fd = -1; 5908 private bool enabled; 5909 __gshared PosixFdReader[int] mapping; 5910 } 5911 5912 // basic functions to access the clipboard 5913 /+ 5914 5915 5916 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 5917 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 5918 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5919 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 5920 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx 5921 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5922 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx 5923 5924 +/ 5925 5926 /++ 5927 this does a delegate because it is actually an async call on X... 5928 the receiver may never be called if the clipboard is empty or unavailable 5929 gets plain text from the clipboard. 5930 +/ 5931 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) { 5932 version(Windows) { 5933 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5934 if(OpenClipboard(hwndOwner) == 0) 5935 throw new WindowsApiException("OpenClipboard", GetLastError()); 5936 scope(exit) 5937 CloseClipboard(); 5938 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat 5939 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 5940 5941 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 5942 scope(exit) 5943 GlobalUnlock(dataHandle); 5944 5945 // FIXME: CR/LF conversions 5946 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 5947 int len = 0; 5948 auto d = data; 5949 while(*d) { 5950 d++; 5951 len++; 5952 } 5953 string s; 5954 s.reserve(len); 5955 foreach(dchar ch; data[0 .. len]) { 5956 s ~= ch; 5957 } 5958 receiver(s); 5959 } 5960 } 5961 } else version(X11) { 5962 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5963 } else version(OSXCocoa) { 5964 throw new NotYetImplementedException(); 5965 } else static assert(0); 5966 } 5967 5968 // FIXME: a clipboard listener might be cool btw 5969 5970 /++ 5971 this does a delegate because it is actually an async call on X... 5972 the receiver may never be called if the clipboard is empty or unavailable 5973 gets image from the clipboard. 5974 5975 templated because it introduces an optional dependency on arsd.bmp 5976 +/ 5977 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) { 5978 version(Windows) { 5979 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5980 if(OpenClipboard(hwndOwner) == 0) 5981 throw new WindowsApiException("OpenClipboard", GetLastError()); 5982 scope(exit) 5983 CloseClipboard(); 5984 if(auto dataHandle = GetClipboardData(CF_DIBV5)) { 5985 if(auto data = cast(ubyte*) GlobalLock(dataHandle)) { 5986 scope(exit) 5987 GlobalUnlock(dataHandle); 5988 5989 auto len = GlobalSize(dataHandle); 5990 5991 import arsd.bmp; 5992 auto img = readBmp(data[0 .. len], false); 5993 receiver(img); 5994 } 5995 } 5996 } else version(X11) { 5997 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5998 } else version(OSXCocoa) { 5999 throw new NotYetImplementedException(); 6000 } else static assert(0); 6001 } 6002 6003 /// Copies some text to the clipboard. 6004 void setClipboardText(SimpleWindow clipboardOwner, string text) { 6005 assert(clipboardOwner !is null); 6006 version(Windows) { 6007 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6008 throw new WindowsApiException("OpenClipboard", GetLastError()); 6009 scope(exit) 6010 CloseClipboard(); 6011 EmptyClipboard(); 6012 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6013 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 6014 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6015 if(auto data = cast(wchar*) GlobalLock(handle)) { 6016 auto slice = data[0 .. sz]; 6017 scope(failure) 6018 GlobalUnlock(handle); 6019 6020 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 6021 6022 GlobalUnlock(handle); 6023 SetClipboardData(CF_UNICODETEXT, handle); 6024 } 6025 } else version(X11) { 6026 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 6027 } else version(OSXCocoa) { 6028 throw new NotYetImplementedException(); 6029 } else static assert(0); 6030 } 6031 6032 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) { 6033 assert(clipboardOwner !is null); 6034 version(Windows) { 6035 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 6036 throw new WindowsApiException("OpenClipboard", GetLastError()); 6037 scope(exit) 6038 CloseClipboard(); 6039 EmptyClipboard(); 6040 6041 6042 import arsd.bmp; 6043 ubyte[] mdata; 6044 mdata.reserve(img.width * img.height); 6045 void sink(ubyte b) { 6046 mdata ~= b; 6047 } 6048 writeBmpIndirect(img, &sink, false); 6049 6050 auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length); 6051 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 6052 if(auto data = cast(ubyte*) GlobalLock(handle)) { 6053 auto slice = data[0 .. mdata.length]; 6054 scope(failure) 6055 GlobalUnlock(handle); 6056 6057 slice[] = mdata[]; 6058 6059 GlobalUnlock(handle); 6060 SetClipboardData(CF_DIB, handle); 6061 } 6062 } else version(X11) { 6063 static class X11SetSelectionHandler_Image : X11SetSelectionHandler { 6064 mixin X11SetSelectionHandler_Basics; 6065 private const(ubyte)[] mdata; 6066 private const(ubyte)[] mdata_original; 6067 this(MemoryImage img) { 6068 import arsd.bmp; 6069 6070 mdata.reserve(img.width * img.height); 6071 void sink(ubyte b) { 6072 mdata ~= b; 6073 } 6074 writeBmpIndirect(img, &sink, true); 6075 6076 mdata_original = mdata; 6077 } 6078 6079 Atom[] availableFormats() { 6080 auto display = XDisplayConnection.get; 6081 return [ 6082 GetAtom!"image/bmp"(display), 6083 GetAtom!"TARGETS"(display) 6084 ]; 6085 } 6086 6087 ubyte[] getData(Atom format, return scope ubyte[] data) { 6088 if(mdata.length < data.length) { 6089 data[0 .. mdata.length] = mdata[]; 6090 auto ret = data[0 .. mdata.length]; 6091 mdata = mdata[$..$]; 6092 return ret; 6093 } else { 6094 data[] = mdata[0 .. data.length]; 6095 mdata = mdata[data.length .. $]; 6096 return data[]; 6097 } 6098 } 6099 6100 void done() { 6101 mdata = mdata_original; 6102 } 6103 } 6104 6105 setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img)); 6106 } else version(OSXCocoa) { 6107 throw new NotYetImplementedException(); 6108 } else static assert(0); 6109 } 6110 6111 6112 version(X11) { 6113 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 6114 6115 private Atom*[] interredAtoms; // for discardAndRecreate 6116 6117 // FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all. 6118 /// Platform-specific for X11. 6119 /// History: On February 21, 2021, I changed the default value of `create` to be true. 6120 @property Atom GetAtom(string name, bool create = true)(Display* display) { 6121 static Atom a; 6122 if(!a) { 6123 a = XInternAtom(display, name, !create); 6124 interredAtoms ~= &a; 6125 } 6126 if(a == None) 6127 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 6128 return a; 6129 } 6130 6131 /// Platform-specific for X11 - gets atom names as a string. 6132 string getAtomName(Atom atom, Display* display) { 6133 auto got = XGetAtomName(display, atom); 6134 scope(exit) XFree(got); 6135 import core.stdc.string; 6136 string s = got[0 .. strlen(got)].idup; 6137 return s; 6138 } 6139 6140 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later. 6141 void setPrimarySelection(SimpleWindow window, string text) { 6142 setX11Selection!"PRIMARY"(window, text); 6143 } 6144 6145 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later. 6146 void setSecondarySelection(SimpleWindow window, string text) { 6147 setX11Selection!"SECONDARY"(window, text); 6148 } 6149 6150 interface X11SetSelectionHandler { 6151 // should include TARGETS right now 6152 Atom[] availableFormats(); 6153 // Return the slice of data you filled, empty slice if done. 6154 // this is to support the incremental thing 6155 ubyte[] getData(Atom format, return scope ubyte[] data); 6156 6157 void done(); 6158 6159 void handleRequest(XEvent); 6160 6161 bool matchesIncr(Window, Atom); 6162 void sendMoreIncr(XPropertyEvent*); 6163 } 6164 6165 mixin template X11SetSelectionHandler_Basics() { 6166 Window incrWindow; 6167 Atom incrAtom; 6168 Atom selectionAtom; 6169 Atom formatAtom; 6170 ubyte[] toSend; 6171 bool matchesIncr(Window w, Atom a) { 6172 return incrAtom && incrAtom == a && w == incrWindow; 6173 } 6174 void sendMoreIncr(XPropertyEvent* event) { 6175 auto display = XDisplayConnection.get; 6176 6177 XChangeProperty (display, 6178 incrWindow, 6179 incrAtom, 6180 formatAtom, 6181 8 /* bits */, PropModeReplace, 6182 toSend.ptr, cast(int) toSend.length); 6183 6184 if(toSend.length != 0) { 6185 toSend = this.getData(formatAtom, toSend[]); 6186 } else { 6187 this.done(); 6188 incrWindow = None; 6189 incrAtom = None; 6190 selectionAtom = None; 6191 formatAtom = None; 6192 toSend = null; 6193 } 6194 } 6195 void handleRequest(XEvent ev) { 6196 6197 auto display = XDisplayConnection.get; 6198 6199 XSelectionRequestEvent* event = &ev.xselectionrequest; 6200 XSelectionEvent selectionEvent; 6201 selectionEvent.type = EventType.SelectionNotify; 6202 selectionEvent.display = event.display; 6203 selectionEvent.requestor = event.requestor; 6204 selectionEvent.selection = event.selection; 6205 selectionEvent.time = event.time; 6206 selectionEvent.target = event.target; 6207 6208 bool supportedType() { 6209 foreach(t; this.availableFormats()) 6210 if(t == event.target) 6211 return true; 6212 return false; 6213 } 6214 6215 if(event.property == None) { 6216 selectionEvent.property = event.target; 6217 6218 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6219 XFlush(display); 6220 } if(event.target == GetAtom!"TARGETS"(display)) { 6221 /* respond with the supported types */ 6222 auto tlist = this.availableFormats(); 6223 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length); 6224 selectionEvent.property = event.property; 6225 6226 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6227 XFlush(display); 6228 } else if(supportedType()) { 6229 auto buffer = new ubyte[](1024 * 64); 6230 auto toSend = this.getData(event.target, buffer[]); 6231 6232 if(toSend.length < 32 * 1024) { 6233 // small enough to send directly... 6234 selectionEvent.property = event.property; 6235 XChangeProperty (display, 6236 selectionEvent.requestor, 6237 selectionEvent.property, 6238 event.target, 6239 8 /* bits */, 0 /* PropModeReplace */, 6240 toSend.ptr, cast(int) toSend.length); 6241 6242 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6243 XFlush(display); 6244 } else { 6245 // large, let's send incrementally 6246 arch_ulong l = toSend.length; 6247 6248 // if I wanted other events from this window don't want to clear that out.... 6249 XWindowAttributes xwa; 6250 XGetWindowAttributes(display, selectionEvent.requestor, &xwa); 6251 6252 XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask)); 6253 6254 incrWindow = event.requestor; 6255 incrAtom = event.property; 6256 formatAtom = event.target; 6257 selectionAtom = event.selection; 6258 this.toSend = toSend; 6259 6260 selectionEvent.property = event.property; 6261 XChangeProperty (display, 6262 selectionEvent.requestor, 6263 selectionEvent.property, 6264 GetAtom!"INCR"(display), 6265 32 /* bits */, PropModeReplace, 6266 &l, 1); 6267 6268 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6269 XFlush(display); 6270 } 6271 //if(after) 6272 //after(); 6273 } else { 6274 debug(sdpy_clip) { 6275 writeln("Unsupported data ", getAtomName(event.target, display)); 6276 } 6277 selectionEvent.property = None; // I don't know how to handle this type... 6278 XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent); 6279 XFlush(display); 6280 } 6281 } 6282 } 6283 6284 class X11SetSelectionHandler_Text : X11SetSelectionHandler { 6285 mixin X11SetSelectionHandler_Basics; 6286 private const(ubyte)[] text; 6287 private const(ubyte)[] text_original; 6288 this(string text) { 6289 this.text = cast(const ubyte[]) text; 6290 this.text_original = this.text; 6291 } 6292 Atom[] availableFormats() { 6293 auto display = XDisplayConnection.get; 6294 return [ 6295 GetAtom!"UTF8_STRING"(display), 6296 GetAtom!"text/plain"(display), 6297 XA_STRING, 6298 GetAtom!"TARGETS"(display) 6299 ]; 6300 } 6301 6302 ubyte[] getData(Atom format, return scope ubyte[] data) { 6303 if(text.length < data.length) { 6304 data[0 .. text.length] = text[]; 6305 return data[0 .. text.length]; 6306 } else { 6307 data[] = text[0 .. data.length]; 6308 text = text[data.length .. $]; 6309 return data[]; 6310 } 6311 } 6312 6313 void done() { 6314 text = text_original; 6315 } 6316 } 6317 6318 /// 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?!) 6319 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 6320 setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after); 6321 } 6322 6323 void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) { 6324 assert(window !is null); 6325 6326 auto display = XDisplayConnection.get(); 6327 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6328 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6329 else Atom a = GetAtom!atomName(display); 6330 6331 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 6332 6333 window.impl.setSelectionHandlers[a] = data; 6334 } 6335 6336 /// 6337 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 6338 getX11Selection!"PRIMARY"(window, handler); 6339 } 6340 6341 // added July 28, 2020 6342 // undocumented as experimental tho 6343 interface X11GetSelectionHandler { 6344 void handleData(Atom target, in ubyte[] data); 6345 Atom findBestFormat(Atom[] answer); 6346 6347 void prepareIncremental(Window, Atom); 6348 bool matchesIncr(Window, Atom); 6349 void handleIncrData(Atom, in ubyte[] data); 6350 } 6351 6352 mixin template X11GetSelectionHandler_Basics() { 6353 Window incrWindow; 6354 Atom incrAtom; 6355 6356 void prepareIncremental(Window w, Atom a) { 6357 incrWindow = w; 6358 incrAtom = a; 6359 } 6360 bool matchesIncr(Window w, Atom a) { 6361 return incrWindow == w && incrAtom == a; 6362 } 6363 6364 Atom incrFormatAtom; 6365 ubyte[] incrData; 6366 void handleIncrData(Atom format, in ubyte[] data) { 6367 incrFormatAtom = format; 6368 6369 if(data.length) 6370 incrData ~= data; 6371 else 6372 handleData(incrFormatAtom, incrData); 6373 6374 } 6375 } 6376 6377 /// 6378 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) { 6379 assert(window !is null); 6380 6381 auto display = XDisplayConnection.get(); 6382 auto atom = GetAtom!atomName(display); 6383 6384 static class X11GetSelectionHandler_Text : X11GetSelectionHandler { 6385 this(void delegate(in char[]) handler) { 6386 this.handler = handler; 6387 } 6388 6389 mixin X11GetSelectionHandler_Basics; 6390 6391 void delegate(in char[]) handler; 6392 6393 void handleData(Atom target, in ubyte[] data) { 6394 if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 6395 handler(cast(const char[]) data); 6396 } 6397 6398 Atom findBestFormat(Atom[] answer) { 6399 Atom best = None; 6400 foreach(option; answer) { 6401 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 6402 best = option; 6403 break; 6404 } else if(option == XA_STRING) { 6405 best = option; 6406 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 6407 best = option; 6408 } 6409 } 6410 return best; 6411 } 6412 } 6413 6414 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler); 6415 6416 auto target = GetAtom!"TARGETS"(display); 6417 6418 // SDD_DATA is "simpledisplay.d data" 6419 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp); 6420 } 6421 6422 /// Gets the image on the clipboard, if there is one. Added July 2020. 6423 void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) { 6424 assert(window !is null); 6425 6426 auto display = XDisplayConnection.get(); 6427 auto atom = GetAtom!atomName(display); 6428 6429 static class X11GetSelectionHandler_Image : X11GetSelectionHandler { 6430 this(void delegate(MemoryImage) handler) { 6431 this.handler = handler; 6432 } 6433 6434 mixin X11GetSelectionHandler_Basics; 6435 6436 void delegate(MemoryImage) handler; 6437 6438 void handleData(Atom target, in ubyte[] data) { 6439 if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6440 import arsd.bmp; 6441 handler(readBmp(data)); 6442 } 6443 } 6444 6445 Atom findBestFormat(Atom[] answer) { 6446 Atom best = None; 6447 foreach(option; answer) { 6448 if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6449 best = option; 6450 } 6451 } 6452 return best; 6453 } 6454 6455 } 6456 6457 6458 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler); 6459 6460 auto target = GetAtom!"TARGETS"(display); 6461 6462 // SDD_DATA is "simpledisplay.d data" 6463 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 6464 } 6465 6466 6467 /// 6468 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 6469 Atom actualType; 6470 int actualFormat; 6471 arch_ulong actualItems; 6472 arch_ulong bytesRemaining; 6473 void* data; 6474 6475 auto display = XDisplayConnection.get(); 6476 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 6477 if(actualFormat == 0) 6478 return null; 6479 else { 6480 int byteLength; 6481 if(actualFormat == 32) { 6482 // 32 means it is a C long... which is variable length 6483 actualFormat = cast(int) arch_long.sizeof * 8; 6484 } 6485 6486 // then it is just a bit count 6487 byteLength = cast(int) (actualItems * actualFormat / 8); 6488 6489 auto d = new ubyte[](byteLength); 6490 d[] = cast(ubyte[]) data[0 .. byteLength]; 6491 XFree(data); 6492 return d; 6493 } 6494 } 6495 return null; 6496 } 6497 6498 /* defined in the systray spec */ 6499 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 6500 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 6501 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 6502 6503 6504 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 6505 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 6506 public class GlobalHotkey { 6507 KeyEvent key; 6508 void delegate () handler; 6509 6510 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 6511 6512 /// Create from initialzed KeyEvent object 6513 this (KeyEvent akey, void delegate () ahandler=null) { 6514 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 6515 key = akey; 6516 handler = ahandler; 6517 } 6518 6519 /// Create from emacs-like key name ("C-M-Y", etc.) 6520 this (const(char)[] akey, void delegate () ahandler=null) { 6521 key = KeyEvent.parse(akey); 6522 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 6523 handler = ahandler; 6524 } 6525 6526 } 6527 6528 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6529 //conwriteln("failed to grab key"); 6530 GlobalHotkeyManager.ghfailed = true; 6531 return 0; 6532 } 6533 6534 private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6535 Image.impl.xshmfailed = true; 6536 return 0; 6537 } 6538 6539 private __gshared int errorHappened; 6540 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6541 import core.stdc.stdio; 6542 char[265] buffer; 6543 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 6544 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); 6545 errorHappened = true; 6546 return 0; 6547 } 6548 6549 /++ 6550 Global hotkey manager. It contains static methods to manage global hotkeys. 6551 6552 --- 6553 try { 6554 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 6555 } catch (Exception e) { 6556 conwriteln("ERROR registering hotkey!"); 6557 } 6558 EventLoop.get.run(); 6559 --- 6560 6561 The key strings are based on Emacs. In practical terms, 6562 `M` means `alt` and `H` means the Windows logo key. `C` 6563 is `ctrl`. 6564 6565 $(WARNING 6566 This is X-specific right now. If you are on 6567 Windows, try [registerHotKey] instead. 6568 6569 We will probably merge these into a single 6570 interface later. 6571 ) 6572 +/ 6573 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 6574 version(X11) { 6575 void recreateAfterDisconnect() { 6576 throw new Exception("NOT IMPLEMENTED"); 6577 } 6578 void discardConnectionState() { 6579 throw new Exception("NOT IMPLEMENTED"); 6580 } 6581 } 6582 6583 private static immutable uint[8] masklist = [ 0, 6584 KeyOrButtonMask.LockMask, 6585 KeyOrButtonMask.Mod2Mask, 6586 KeyOrButtonMask.Mod3Mask, 6587 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 6588 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 6589 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6590 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6591 ]; 6592 private __gshared GlobalHotkeyManager ghmanager; 6593 private __gshared bool ghfailed = false; 6594 6595 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 6596 if (modmask == 0) return false; 6597 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 6598 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 6599 return true; 6600 } 6601 6602 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 6603 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 6604 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 6605 return modmask; 6606 } 6607 6608 private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) { 6609 uint keycode = cast(uint)ke.key; 6610 auto dpy = XDisplayConnection.get; 6611 return XKeysymToKeycode(dpy, keycode); 6612 } 6613 6614 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 6615 6616 private __gshared GlobalHotkey[ulong] globalHotkeyList; 6617 6618 NativeEventHandler getNativeEventHandler () { 6619 return delegate int (XEvent e) { 6620 if (e.type != EventType.KeyPress) return 1; 6621 auto kev = cast(const(XKeyEvent)*)&e; 6622 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 6623 if (auto ghkp = hash in globalHotkeyList) { 6624 try { 6625 ghkp.doHandle(); 6626 } catch (Exception e) { 6627 import core.stdc.stdio : stderr, fprintf; 6628 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 6629 } 6630 } 6631 return 1; 6632 }; 6633 } 6634 6635 private this () { 6636 auto dpy = XDisplayConnection.get; 6637 auto root = RootWindow(dpy, DefaultScreen(dpy)); 6638 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 6639 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 6640 } 6641 6642 /// Register new global hotkey with initialized `GlobalHotkey` object. 6643 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 6644 static void register (GlobalHotkey gh) { 6645 if (gh is null) return; 6646 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 6647 6648 auto dpy = XDisplayConnection.get; 6649 immutable keycode = keyEvent2KeyCode(gh.key); 6650 6651 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 6652 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 6653 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 6654 XSync(dpy, 0/*False*/); 6655 6656 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6657 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6658 ghfailed = false; 6659 foreach (immutable uint ormask; masklist[]) { 6660 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 6661 } 6662 XSync(dpy, 0/*False*/); 6663 XSetErrorHandler(savedErrorHandler); 6664 6665 if (ghfailed) { 6666 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6667 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 6668 XSync(dpy, 0/*False*/); 6669 XSetErrorHandler(savedErrorHandler); 6670 throw new Exception("cannot register global hotkey"); 6671 } 6672 6673 globalHotkeyList[hash] = gh; 6674 } 6675 6676 /// Ditto 6677 static void register (const(char)[] akey, void delegate () ahandler) { 6678 register(new GlobalHotkey(akey, ahandler)); 6679 } 6680 6681 private static void removeByHash (ulong hash) { 6682 if (auto ghp = hash in globalHotkeyList) { 6683 auto dpy = XDisplayConnection.get; 6684 immutable keycode = keyEvent2KeyCode(ghp.key); 6685 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6686 XSync(dpy, 0/*False*/); 6687 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6688 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 6689 XSync(dpy, 0/*False*/); 6690 XSetErrorHandler(savedErrorHandler); 6691 globalHotkeyList.remove(hash); 6692 } 6693 } 6694 6695 /// Register new global hotkey with previously used `GlobalHotkey` object. 6696 /// It is safe to unregister unknown or invalid hotkey. 6697 static void unregister (GlobalHotkey gh) { 6698 //TODO: add second AA for faster search? prolly doesn't worth it. 6699 if (gh is null) return; 6700 foreach (const ref kv; globalHotkeyList.byKeyValue) { 6701 if (kv.value is gh) { 6702 removeByHash(kv.key); 6703 return; 6704 } 6705 } 6706 } 6707 6708 /// Ditto. 6709 static void unregister (const(char)[] key) { 6710 auto kev = KeyEvent.parse(key); 6711 immutable keycode = keyEvent2KeyCode(kev); 6712 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 6713 } 6714 } 6715 } 6716 6717 version(Windows) { 6718 /++ 6719 See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications. 6720 6721 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). 6722 +/ 6723 void sendSyntheticInput(wstring s) { 6724 INPUT[] inputs; 6725 inputs.reserve(s.length * 2); 6726 6727 foreach(wchar c; s) { 6728 INPUT input; 6729 input.type = INPUT_KEYBOARD; 6730 input.ki.wScan = c; 6731 input.ki.dwFlags = KEYEVENTF_UNICODE; 6732 inputs ~= input; 6733 6734 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6735 inputs ~= input; 6736 } 6737 6738 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6739 throw new WindowsApiException("SendInput", GetLastError()); 6740 } 6741 6742 } 6743 6744 6745 // global hotkey helper function 6746 6747 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these. 6748 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) { 6749 __gshared int hotkeyId = 0; 6750 int id = ++hotkeyId; 6751 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 6752 throw new Exception("RegisterHotKey"); 6753 6754 __gshared void delegate()[WPARAM][HWND] handlers; 6755 6756 handlers[window.impl.hwnd][id] = handler; 6757 6758 int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler; 6759 6760 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 6761 switch(msg) { 6762 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 6763 case WM_HOTKEY: 6764 if(auto list = hwnd in handlers) { 6765 if(auto h = wParam in *list) { 6766 (*h)(); 6767 return 0; 6768 } 6769 } 6770 goto default; 6771 default: 6772 } 6773 if(oldHandler) 6774 return oldHandler(hwnd, msg, wParam, lParam, mustReturn); 6775 return 1; // pass it on 6776 }; 6777 6778 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 6779 oldHandler = window.handleNativeEvent; 6780 window.handleNativeEvent = nativeEventHandler; 6781 } 6782 6783 return id; 6784 } 6785 6786 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey]. 6787 void unregisterHotKey(SimpleWindow window, int id) { 6788 if(!UnregisterHotKey(window.impl.hwnd, id)) 6789 throw new WindowsApiException("UnregisterHotKey", GetLastError()); 6790 } 6791 } 6792 6793 version (X11) { 6794 pragma(lib, "dl"); 6795 import core.sys.posix.dlfcn; 6796 } 6797 6798 /++ 6799 Allows for sending synthetic input to the X server via the Xtst 6800 extension or on Windows using SendInput. 6801 6802 Please remember user input is meant to be user - don't use this 6803 if you have some other alternative! 6804 6805 History: 6806 Added May 17, 2020 with the X implementation. 6807 6808 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.) 6809 Bugs: 6810 All methods on OSX Cocoa will throw not yet implemented exceptions. 6811 +/ 6812 struct SyntheticInput { 6813 @disable this(); 6814 6815 private int* refcount; 6816 6817 version(X11) { 6818 private void* lib; 6819 6820 private extern(C) { 6821 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 6822 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 6823 } 6824 } 6825 6826 /// The dummy param must be 0. 6827 this(int dummy) { 6828 version(X11) { 6829 lib = dlopen("libXtst.so", RTLD_NOW); 6830 if(lib is null) 6831 throw new Exception("cannot load xtest lib extension"); 6832 scope(failure) 6833 dlclose(lib); 6834 6835 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 6836 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 6837 6838 if(XTestFakeKeyEvent is null) 6839 throw new Exception("No XTestFakeKeyEvent"); 6840 if(XTestFakeButtonEvent is null) 6841 throw new Exception("No XTestFakeButtonEvent"); 6842 } 6843 6844 refcount = new int; 6845 *refcount = 1; 6846 } 6847 6848 this(this) { 6849 if(refcount) 6850 *refcount += 1; 6851 } 6852 6853 ~this() { 6854 if(refcount) { 6855 *refcount -= 1; 6856 if(*refcount == 0) 6857 // I commented this because if I close the lib before 6858 // XCloseDisplay, it is liable to segfault... so just 6859 // gonna keep it loaded if it is loaded, no big deal 6860 // anyway. 6861 {} // dlclose(lib); 6862 } 6863 } 6864 6865 /++ 6866 Simulates typing a string into the keyboard. 6867 6868 Bugs: 6869 On X11, this ONLY works with basic ascii! On Windows, it can handle more. 6870 6871 Not implemented except on Windows and X11. 6872 +/ 6873 void sendSyntheticInput(string s) { 6874 version(Windows) { 6875 INPUT[] inputs; 6876 inputs.reserve(s.length * 2); 6877 6878 auto ei = GetMessageExtraInfo(); 6879 6880 foreach(wchar c; s) { 6881 INPUT input; 6882 input.type = INPUT_KEYBOARD; 6883 input.ki.wScan = c; 6884 input.ki.dwFlags = KEYEVENTF_UNICODE; 6885 input.ki.dwExtraInfo = ei; 6886 inputs ~= input; 6887 6888 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6889 inputs ~= input; 6890 } 6891 6892 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6893 throw new WindowsApiException("SendInput", GetLastError()); 6894 } 6895 } else version(X11) { 6896 int delay = 0; 6897 foreach(ch; s) { 6898 pressKey(cast(Key) ch, true, delay); 6899 pressKey(cast(Key) ch, false, delay); 6900 delay += 5; 6901 } 6902 } else throw new NotYetImplementedException(); 6903 } 6904 6905 /++ 6906 Sends a fake press or release key event. 6907 6908 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 6909 6910 Bugs: 6911 The `delay` parameter is not implemented yet on Windows. 6912 6913 Not implemented except on Windows and X11. 6914 +/ 6915 void pressKey(Key key, bool pressed, int delay = 0) { 6916 version(Windows) { 6917 INPUT input; 6918 input.type = INPUT_KEYBOARD; 6919 input.ki.wVk = cast(ushort) key; 6920 6921 input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP; 6922 input.ki.dwExtraInfo = GetMessageExtraInfo(); 6923 6924 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6925 throw new WindowsApiException("SendInput", GetLastError()); 6926 } 6927 } else version(X11) { 6928 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 6929 } else throw new NotYetImplementedException(); 6930 } 6931 6932 /++ 6933 Sends a fake mouse button press or release event. 6934 6935 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 6936 6937 `pressed` param must be `true` if button is `wheelUp` or `wheelDown`. 6938 6939 Bugs: 6940 The `delay` parameter is not implemented yet on Windows. 6941 6942 The backButton and forwardButton will throw NotYetImplementedException on Windows. 6943 6944 All arguments will throw NotYetImplementedException on OSX Cocoa. 6945 +/ 6946 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 6947 version(Windows) { 6948 INPUT input; 6949 input.type = INPUT_MOUSE; 6950 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6951 6952 // input.mi.mouseData for a wheel event 6953 6954 switch(button) { 6955 case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break; 6956 case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break; 6957 case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break; 6958 case MouseButton.wheelUp: 6959 case MouseButton.wheelDown: 6960 input.mi.dwFlags = MOUSEEVENTF_WHEEL; 6961 input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120; 6962 break; 6963 case MouseButton.backButton: throw new NotYetImplementedException(); 6964 case MouseButton.forwardButton: throw new NotYetImplementedException(); 6965 default: 6966 } 6967 6968 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6969 throw new WindowsApiException("SendInput", GetLastError()); 6970 } 6971 } else version(X11) { 6972 int btn; 6973 6974 switch(button) { 6975 case MouseButton.left: btn = 1; break; 6976 case MouseButton.middle: btn = 2; break; 6977 case MouseButton.right: btn = 3; break; 6978 case MouseButton.wheelUp: btn = 4; break; 6979 case MouseButton.wheelDown: btn = 5; break; 6980 case MouseButton.backButton: btn = 8; break; 6981 case MouseButton.forwardButton: btn = 9; break; 6982 default: 6983 } 6984 6985 assert(btn); 6986 6987 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 6988 } else throw new NotYetImplementedException(); 6989 } 6990 6991 /// 6992 static void moveMouseArrowBy(int dx, int dy) { 6993 version(Windows) { 6994 INPUT input; 6995 input.type = INPUT_MOUSE; 6996 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6997 input.mi.dx = dx; 6998 input.mi.dy = dy; 6999 input.mi.dwFlags = MOUSEEVENTF_MOVE; 7000 7001 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7002 throw new WindowsApiException("SendInput", GetLastError()); 7003 } 7004 } else version(X11) { 7005 auto disp = XDisplayConnection.get(); 7006 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 7007 XFlush(disp); 7008 } else throw new NotYetImplementedException(); 7009 } 7010 7011 /// 7012 static void moveMouseArrowTo(int x, int y) { 7013 version(Windows) { 7014 INPUT input; 7015 input.type = INPUT_MOUSE; 7016 input.mi.dwExtraInfo = GetMessageExtraInfo(); 7017 input.mi.dx = x; 7018 input.mi.dy = y; 7019 input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; 7020 7021 if(SendInput(1, &input, INPUT.sizeof) != 1) { 7022 throw new WindowsApiException("SendInput", GetLastError()); 7023 } 7024 } else version(X11) { 7025 auto disp = XDisplayConnection.get(); 7026 auto root = RootWindow(disp, DefaultScreen(disp)); 7027 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 7028 XFlush(disp); 7029 } else throw new NotYetImplementedException(); 7030 } 7031 } 7032 7033 7034 7035 /++ 7036 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 7037 7038 See_Also: 7039 $(LIST 7040 *[ScreenPainter] 7041 *[ScreenPainter.rasterOp] 7042 ) 7043 +/ 7044 enum RasterOp { 7045 normal, /// Replaces the pixel. 7046 xor, /// Uses bitwise xor to draw. 7047 } 7048 7049 // being phobos-free keeps the size WAY down 7050 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 7051 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 7052 package(arsd) const(wchar)* toWStringz(string s) { 7053 wstring r; 7054 foreach(dchar c; s) 7055 r ~= c; 7056 r ~= '\0'; 7057 return r.ptr; 7058 } 7059 private string[] split(in void[] a, char c) { 7060 string[] ret; 7061 size_t previous = 0; 7062 foreach(i, char ch; cast(ubyte[]) a) { 7063 if(ch == c) { 7064 ret ~= cast(string) a[previous .. i]; 7065 previous = i + 1; 7066 } 7067 } 7068 if(previous != a.length) 7069 ret ~= cast(string) a[previous .. $]; 7070 return ret; 7071 } 7072 7073 version(without_opengl) { 7074 enum OpenGlOptions { 7075 no, 7076 } 7077 } else { 7078 /++ 7079 Determines if you want an OpenGL context created on the new window. 7080 7081 7082 See more: [#topics-3d|in the 3d topic]. 7083 7084 --- 7085 import arsd.simpledisplay; 7086 void main() { 7087 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 7088 7089 // Set up the matrix 7090 window.setAsCurrentOpenGlContext(); // make this window active 7091 7092 // This is called on each frame, we will draw our scene 7093 window.redrawOpenGlScene = delegate() { 7094 7095 }; 7096 7097 window.eventLoop(0); 7098 } 7099 --- 7100 +/ 7101 enum OpenGlOptions { 7102 no, /// No OpenGL context is created 7103 yes, /// Yes, create an OpenGL context 7104 } 7105 7106 version(X11) { 7107 static if (!SdpyIsUsingIVGLBinds) { 7108 7109 7110 struct __GLXFBConfigRec {} 7111 alias GLXFBConfig = __GLXFBConfigRec*; 7112 7113 //pragma(lib, "GL"); 7114 //pragma(lib, "GLU"); 7115 interface GLX { 7116 extern(C) nothrow @nogc { 7117 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 7118 const int *attrib_list); 7119 7120 void glXCopyContext(Display *dpy, GLXContext src, 7121 GLXContext dst, arch_ulong mask); 7122 7123 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 7124 GLXContext share_list, Bool direct); 7125 7126 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 7127 Pixmap pixmap); 7128 7129 void glXDestroyContext(Display *dpy, GLXContext ctx); 7130 7131 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 7132 7133 int glXGetConfig(Display *dpy, XVisualInfo *vis, 7134 int attrib, int *value); 7135 7136 GLXContext glXGetCurrentContext(); 7137 7138 GLXDrawable glXGetCurrentDrawable(); 7139 7140 Bool glXIsDirect(Display *dpy, GLXContext ctx); 7141 7142 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 7143 GLXContext ctx); 7144 7145 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 7146 7147 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 7148 7149 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 7150 7151 void glXUseXFont(Font font, int first, int count, int list_base); 7152 7153 void glXWaitGL(); 7154 7155 void glXWaitX(); 7156 7157 7158 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 7159 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 7160 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 7161 7162 char* glXQueryExtensionsString (Display*, int); 7163 void* glXGetProcAddress (const(char)*); 7164 7165 } 7166 } 7167 7168 version(OSX) 7169 mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx; 7170 else 7171 mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx; 7172 shared static this() { 7173 glx.loadDynamicLibrary(); 7174 } 7175 7176 alias glbindGetProcAddress = glXGetProcAddress; 7177 } 7178 } else version(Windows) { 7179 /* it is done below by interface GL */ 7180 } else 7181 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."); 7182 } 7183 7184 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 7185 alias Resizablity = Resizability; 7186 7187 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 7188 enum Resizability { 7189 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. 7190 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. 7191 /++ 7192 $(PITFALL 7193 Planned for the future but not implemented. 7194 ) 7195 7196 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. 7197 7198 History: 7199 Added November 11, 2022, but not yet implemented and may not be for some time. 7200 +/ 7201 /*@__future*/ allowResizingMaintainingAspectRatio, 7202 /++ 7203 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. 7204 7205 History: 7206 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. 7207 7208 Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all. 7209 +/ 7210 automaticallyScaleIfPossible, 7211 } 7212 /// ditto 7213 alias Resizeability = Resizability; 7214 7215 7216 /++ 7217 Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 7218 +/ 7219 enum TextAlignment : uint { 7220 Left = 0, /// 7221 Center = 1, /// 7222 Right = 2, /// 7223 7224 VerticalTop = 0, /// 7225 VerticalCenter = 4, /// 7226 VerticalBottom = 8, /// 7227 } 7228 7229 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 7230 alias Rectangle = arsd.color.Rectangle; 7231 7232 7233 /++ 7234 Keyboard press and release events. 7235 +/ 7236 struct KeyEvent { 7237 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 7238 Key key; 7239 ubyte hardwareCode; /// A platform and hardware specific code for the key 7240 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 7241 7242 deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character; 7243 7244 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 7245 7246 SimpleWindow window; /// associated Window 7247 7248 /++ 7249 A view into the upcoming buffer holding coming character events that are sent if and only if neither 7250 the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))` 7251 to predict if char events are actually coming.. 7252 7253 Only available on X systems since this information is not given ahead of time elsewhere. 7254 (Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.) 7255 7256 I'm adding this because it is useful to the terminal emulator, but given its platform specificness 7257 and potential quirks I'd recommend avoiding it. 7258 7259 History: 7260 Added April 26, 2021 (dub v9.5) 7261 +/ 7262 version(X11) 7263 dchar[] charsPossible; 7264 7265 // convert key event to simplified string representation a-la emacs 7266 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 7267 uint dpos = 0; 7268 void put (const(char)[] s...) nothrow @trusted { 7269 static if (growdest) { 7270 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 7271 } else { 7272 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 7273 } 7274 } 7275 7276 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 7277 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 7278 } 7279 7280 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 7281 7282 // put modifiers 7283 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 7284 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 7285 putMod(ModifierState.alt, Key.Alt, "Alt+"); 7286 putMod(ModifierState.windows, Key.Shift, "Windows+"); 7287 putMod(ModifierState.shift, Key.Shift, "Shift+"); 7288 7289 if (this.key) { 7290 foreach (string kn; __traits(allMembers, Key)) { 7291 if (this.key == __traits(getMember, Key, kn)) { 7292 // HACK! 7293 static if (kn == "N0") put("0"); 7294 else static if (kn == "N1") put("1"); 7295 else static if (kn == "N2") put("2"); 7296 else static if (kn == "N3") put("3"); 7297 else static if (kn == "N4") put("4"); 7298 else static if (kn == "N5") put("5"); 7299 else static if (kn == "N6") put("6"); 7300 else static if (kn == "N7") put("7"); 7301 else static if (kn == "N8") put("8"); 7302 else static if (kn == "N9") put("9"); 7303 else put(kn); 7304 return dest[0..dpos]; 7305 } 7306 } 7307 put("Unknown"); 7308 } else { 7309 if (dpos && dest[dpos-1] == '+') --dpos; 7310 } 7311 return dest[0..dpos]; 7312 } 7313 7314 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 7315 7316 /** Parse string into key name with modifiers. It accepts things like: 7317 * 7318 * C-H-1 -- emacs style (ctrl, and windows, and 1) 7319 * 7320 * Ctrl+Win+1 -- windows style 7321 * 7322 * Ctrl-Win-1 -- '-' is a valid delimiter too 7323 * 7324 * Ctrl Win 1 -- and space 7325 * 7326 * and even "Win + 1 + Ctrl". 7327 */ 7328 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 7329 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 7330 7331 // remove trailing spaces 7332 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 7333 7334 // tokens delimited by blank, '+', or '-' 7335 // null on eol 7336 const(char)[] getToken () nothrow @trusted @nogc { 7337 // remove leading spaces and delimiters 7338 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 7339 if (name.length == 0) return null; // oops, no more tokens 7340 // get token 7341 size_t epos = 0; 7342 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 7343 assert(epos > 0 && epos <= name.length); 7344 auto res = name[0..epos]; 7345 name = name[epos..$]; 7346 return res; 7347 } 7348 7349 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 7350 if (s0.length != s1.length) return false; 7351 foreach (immutable ci, char c0; s0) { 7352 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 7353 char c1 = s1[ci]; 7354 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 7355 if (c0 != c1) return false; 7356 } 7357 return true; 7358 } 7359 7360 if (ignoreModsOut !is null) *ignoreModsOut = false; 7361 if (updown !is null) *updown = -1; 7362 KeyEvent res; 7363 res.key = cast(Key)0; // just in case 7364 const(char)[] tk, tkn; // last token 7365 bool allowEmascStyle = true; 7366 bool ignoreModifiers = false; 7367 tokenloop: for (;;) { 7368 tk = tkn; 7369 tkn = getToken(); 7370 //k8: yay, i took "Bloody Mess" trait from Fallout! 7371 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 7372 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 7373 if (allowEmascStyle && tkn.length != 0) { 7374 if (tk.length == 1) { 7375 char mdc = tk[0]; 7376 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 7377 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7378 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7379 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7380 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7381 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 7382 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 7383 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 7384 } 7385 } 7386 allowEmascStyle = false; 7387 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7388 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7389 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7390 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7391 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 7392 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 7393 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 7394 if (tk.length == 0) continue; 7395 // try key name 7396 if (res.key == 0) { 7397 // little hack 7398 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 7399 final switch (tk[0]) { 7400 case '0': tk = "N0"; break; 7401 case '1': tk = "N1"; break; 7402 case '2': tk = "N2"; break; 7403 case '3': tk = "N3"; break; 7404 case '4': tk = "N4"; break; 7405 case '5': tk = "N5"; break; 7406 case '6': tk = "N6"; break; 7407 case '7': tk = "N7"; break; 7408 case '8': tk = "N8"; break; 7409 case '9': tk = "N9"; break; 7410 } 7411 } 7412 foreach (string kn; __traits(allMembers, Key)) { 7413 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 7414 } 7415 } 7416 // unknown or duplicate key name, get out of here 7417 break; 7418 } 7419 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 7420 return res; // something 7421 } 7422 7423 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 7424 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 7425 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 7426 if (kk == k) { mask |= mst; kk = cast(Key)0; } 7427 } 7428 bool ignoreMods; 7429 int updown; 7430 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 7431 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 7432 if (this.key != ke.key) { 7433 // things like "ctrl+alt" are complicated 7434 uint tkm = this.modifierState&modmask; 7435 uint kkm = ke.modifierState&modmask; 7436 Key tk = this.key; 7437 // ke 7438 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 7439 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 7440 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 7441 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 7442 // this 7443 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 7444 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 7445 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 7446 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 7447 return (tk == ke.key && tkm == kkm); 7448 } 7449 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 7450 } 7451 } 7452 7453 /// Sets the application name. 7454 @property string ApplicationName(string name) { 7455 return _applicationName = name; 7456 } 7457 7458 string _applicationName; 7459 7460 /// ditto 7461 @property string ApplicationName() { 7462 if(_applicationName is null) { 7463 import core.runtime; 7464 return Runtime.args[0]; 7465 } 7466 return _applicationName; 7467 } 7468 7469 7470 /// Type of a [MouseEvent]. 7471 enum MouseEventType : int { 7472 motion = 0, /// The mouse moved inside the window 7473 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 7474 buttonReleased = 2, /// A mouse button was released 7475 } 7476 7477 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 7478 /++ 7479 Listen for this on your event listeners if you are interested in mouse action. 7480 7481 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. 7482 7483 Examples: 7484 7485 This will draw boxes on the window with the mouse as you hold the left button. 7486 --- 7487 import arsd.simpledisplay; 7488 7489 void main() { 7490 auto window = new SimpleWindow(); 7491 7492 window.eventLoop(0, 7493 (MouseEvent ev) { 7494 if(ev.modifierState & ModifierState.leftButtonDown) { 7495 auto painter = window.draw(); 7496 painter.fillColor = Color.red; 7497 painter.outlineColor = Color.black; 7498 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 7499 } 7500 } 7501 ); 7502 } 7503 --- 7504 +/ 7505 struct MouseEvent { 7506 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 7507 7508 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. 7509 int y; /// Current Y position of the cursor when the event fired. 7510 7511 int dx; /// Change in X position since last report 7512 int dy; /// Change in Y position since last report 7513 7514 MouseButton button; /// See [MouseButton] 7515 int modifierState; /// See [ModifierState] 7516 7517 version(X11) 7518 private Time timestamp; 7519 7520 /// Returns a linear representation of mouse button, 7521 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 7522 /// 7523 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 7524 @property ubyte buttonLinear() const { 7525 import core.bitop; 7526 if(button == 0) 7527 return 0; 7528 return (bsf(button) + 1) & 0b1111; 7529 } 7530 7531 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 7532 7533 SimpleWindow window; /// The window in which the event happened. 7534 7535 Point globalCoordinates() { 7536 Point p; 7537 if(window is null) 7538 throw new Exception("wtf"); 7539 static if(UsingSimpledisplayX11) { 7540 Window child; 7541 XTranslateCoordinates( 7542 XDisplayConnection.get, 7543 window.impl.window, 7544 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 7545 x, y, &p.x, &p.y, &child); 7546 return p; 7547 } else version(Windows) { 7548 POINT[1] points; 7549 points[0].x = x; 7550 points[0].y = y; 7551 MapWindowPoints( 7552 window.impl.hwnd, 7553 null, 7554 points.ptr, 7555 points.length 7556 ); 7557 p.x = points[0].x; 7558 p.y = points[0].y; 7559 7560 return p; 7561 } else version(OSXCocoa) { 7562 throw new NotYetImplementedException(); 7563 } else static assert(0); 7564 } 7565 7566 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 7567 7568 /** 7569 can contain emacs-like modifier prefix 7570 case-insensitive names: 7571 lmbX/leftX 7572 rmbX/rightX 7573 mmbX/middleX 7574 wheelX 7575 motion (no prefix allowed) 7576 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 7577 */ 7578 static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 7579 if (str.length == 0) return false; // just in case 7580 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 7581 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 7582 auto anchor = str; 7583 uint mods = 0; // uint.max == any 7584 // interesting bits in kmod 7585 uint kmodmask = 7586 ModifierState.shift| 7587 ModifierState.ctrl| 7588 ModifierState.alt| 7589 ModifierState.windows| 7590 ModifierState.leftButtonDown| 7591 ModifierState.middleButtonDown| 7592 ModifierState.rightButtonDown| 7593 0; 7594 uint lastButt = uint.max; // otherwise, bit 31 means "down" 7595 bool wasButtons = false; 7596 while (str.length) { 7597 if (str.ptr[0] <= ' ') { 7598 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 7599 continue; 7600 } 7601 // one-letter modifier? 7602 if (str.length >= 2 && str.ptr[1] == '-') { 7603 switch (str.ptr[0]) { 7604 case '*': // "any" modifier (cannot be undone) 7605 mods = mods.max; 7606 break; 7607 case 'C': case 'c': // emacs "ctrl" 7608 if (mods != mods.max) mods |= ModifierState.ctrl; 7609 break; 7610 case 'M': case 'm': // emacs "meta" 7611 if (mods != mods.max) mods |= ModifierState.alt; 7612 break; 7613 case 'S': case 's': // emacs "shift" 7614 if (mods != mods.max) mods |= ModifierState.shift; 7615 break; 7616 case 'H': case 'h': // emacs "hyper" (aka winkey) 7617 if (mods != mods.max) mods |= ModifierState.windows; 7618 break; 7619 default: 7620 return false; // unknown modifier 7621 } 7622 str = str[2..$]; 7623 continue; 7624 } 7625 // word 7626 char[16] buf = void; // locased 7627 auto wep = 0; 7628 while (str.length) { 7629 immutable char ch = str.ptr[0]; 7630 if (ch <= ' ' || ch == '-') break; 7631 str = str[1..$]; 7632 if (wep > buf.length) return false; // too long 7633 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7634 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7635 else return false; // invalid char 7636 } 7637 if (wep == 0) return false; // just in case 7638 uint bnum; 7639 enum UpDown { None = -1, Up, Down, Any } 7640 auto updown = UpDown.None; // 0: up; 1: down 7641 switch (buf[0..wep]) { 7642 // left button 7643 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 7644 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 7645 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 7646 case "lmb": case "left": bnum = 0; break; 7647 // middle button 7648 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 7649 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 7650 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 7651 case "mmb": case "middle": bnum = 1; break; 7652 // right button 7653 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 7654 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 7655 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 7656 case "rmb": case "right": bnum = 2; break; 7657 // wheel 7658 case "wheelup": updown = UpDown.Up; goto case "wheel"; 7659 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 7660 case "wheelany": updown = UpDown.Any; goto case "wheel"; 7661 case "wheel": bnum = 3; break; 7662 // motion 7663 case "motion": bnum = 7; break; 7664 // unknown 7665 default: return false; 7666 } 7667 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7668 // parse possible "-up" or "-down" 7669 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 7670 wep = 0; 7671 foreach (immutable idx, immutable char ch; str[1..$]) { 7672 if (ch <= ' ' || ch == '-') break; 7673 assert(idx == wep); // for now; trick 7674 if (wep > buf.length) { wep = 0; break; } // too long 7675 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7676 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7677 else { wep = 0; break; } // invalid char 7678 } 7679 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 7680 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 7681 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 7682 // remove parsed part 7683 if (updown != UpDown.None) str = str[wep+1..$]; 7684 } 7685 if (updown == UpDown.None) { 7686 updown = UpDown.Down; 7687 } 7688 wasButtons = wasButtons || (bnum <= 2); 7689 //assert(updown != UpDown.None); 7690 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7691 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 7692 if (lastButt != lastButt.max) { 7693 if ((lastButt&0xff) >= 3) return false; // wheel or motion 7694 if (mods != mods.max) { 7695 uint butbit = 0; 7696 final switch (lastButt&0x03) { 7697 case 0: butbit = ModifierState.leftButtonDown; break; 7698 case 1: butbit = ModifierState.middleButtonDown; break; 7699 case 2: butbit = ModifierState.rightButtonDown; break; 7700 } 7701 if (lastButt&Flag.Down) mods |= butbit; 7702 else if (lastButt&Flag.Up) mods &= ~butbit; 7703 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 7704 } 7705 } 7706 // remember last button 7707 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 7708 } 7709 // no button -- nothing to do 7710 if (lastButt == lastButt.max) return false; 7711 // done parsing, check if something's left 7712 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 7713 // remove action button from mask 7714 if ((lastButt&0xff) < 3) { 7715 final switch (lastButt&0x03) { 7716 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 7717 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 7718 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 7719 } 7720 } 7721 // special case: "Motion" means "ignore buttons" 7722 if ((lastButt&0xff) == 7 && !wasButtons) { 7723 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 7724 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 7725 } 7726 uint kmod = event.modifierState&kmodmask; 7727 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 7728 // check modifier state 7729 if (mods != mods.max) { 7730 if (kmod != mods) return false; 7731 } 7732 // now check type 7733 if ((lastButt&0xff) == 7) { 7734 // motion 7735 if (event.type != MouseEventType.motion) return false; 7736 } else if ((lastButt&0xff) == 3) { 7737 // wheel 7738 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 7739 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 7740 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 7741 return false; 7742 } else { 7743 // buttons 7744 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 7745 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 7746 { 7747 return false; 7748 } 7749 // button number 7750 switch (lastButt&0x03) { 7751 case 0: if (event.button != MouseButton.left) return false; break; 7752 case 1: if (event.button != MouseButton.middle) return false; break; 7753 case 2: if (event.button != MouseButton.right) return false; break; 7754 default: return false; 7755 } 7756 } 7757 return true; 7758 } 7759 } 7760 7761 version(arsd_mevent_strcmp_test) unittest { 7762 MouseEvent event; 7763 event.type = MouseEventType.buttonPressed; 7764 event.button = MouseButton.left; 7765 event.modifierState = ModifierState.ctrl; 7766 assert(event == "C-LMB"); 7767 assert(event != "C-LMBUP"); 7768 assert(event != "C-LMB-UP"); 7769 assert(event != "C-S-LMB"); 7770 assert(event == "*-LMB"); 7771 assert(event != "*-LMB-UP"); 7772 7773 event.type = MouseEventType.buttonReleased; 7774 assert(event != "C-LMB"); 7775 assert(event == "C-LMBUP"); 7776 assert(event == "C-LMB-UP"); 7777 assert(event != "C-S-LMB"); 7778 assert(event != "*-LMB"); 7779 assert(event == "*-LMB-UP"); 7780 7781 event.button = MouseButton.right; 7782 event.modifierState |= ModifierState.shift; 7783 event.type = MouseEventType.buttonPressed; 7784 assert(event != "C-LMB"); 7785 assert(event != "C-LMBUP"); 7786 assert(event != "C-LMB-UP"); 7787 assert(event != "C-S-LMB"); 7788 assert(event != "*-LMB"); 7789 assert(event != "*-LMB-UP"); 7790 7791 assert(event != "C-RMB"); 7792 assert(event != "C-RMBUP"); 7793 assert(event != "C-RMB-UP"); 7794 assert(event == "C-S-RMB"); 7795 assert(event == "*-RMB"); 7796 assert(event != "*-RMB-UP"); 7797 } 7798 7799 /// This gives a few more options to drawing lines and such 7800 struct Pen { 7801 Color color; /// the foreground color 7802 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. 7803 Style style; /// See [Style] 7804 /+ 7805 // From X.h 7806 7807 #define LineSolid 0 7808 #define LineOnOffDash 1 7809 #define LineDoubleDash 2 7810 LineDou- The full path of the line is drawn, but the 7811 bleDash even dashes are filled differently from the 7812 odd dashes (see fill-style) with CapButt 7813 style used where even and odd dashes meet. 7814 7815 7816 7817 /* capStyle */ 7818 7819 #define CapNotLast 0 7820 #define CapButt 1 7821 #define CapRound 2 7822 #define CapProjecting 3 7823 7824 /* joinStyle */ 7825 7826 #define JoinMiter 0 7827 #define JoinRound 1 7828 #define JoinBevel 2 7829 7830 /* fillStyle */ 7831 7832 #define FillSolid 0 7833 #define FillTiled 1 7834 #define FillStippled 2 7835 #define FillOpaqueStippled 3 7836 7837 7838 +/ 7839 /// Style of lines drawn 7840 enum Style { 7841 Solid, /// a solid line 7842 Dashed, /// a dashed line 7843 Dotted, /// a dotted line 7844 } 7845 } 7846 7847 7848 /++ 7849 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 7850 7851 7852 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 7853 7854 $(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.) 7855 7856 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. 7857 7858 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 7859 7860 $(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. 7861 7862 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! 7863 7864 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!) 7865 7866 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 7867 7868 --- 7869 auto image = new Image(256, 256); 7870 scope(exit) destroy(image); 7871 --- 7872 7873 As long as you don't hold on to it outside the scope. 7874 7875 I might change it to be an owned pointer at some point in the future. 7876 7877 ) 7878 7879 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 7880 you can also often get a fair amount of speedup by getting the raw data format and 7881 writing some custom code. 7882 7883 FIXME INSERT EXAMPLES HERE 7884 7885 7886 +/ 7887 final class Image { 7888 /// 7889 this(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 7890 this.width = width; 7891 this.height = height; 7892 this.enableAlpha = enableAlpha; 7893 7894 impl.createImage(width, height, forcexshm, enableAlpha); 7895 } 7896 7897 /// 7898 this(Size size, bool forcexshm=false, bool enableAlpha = false) { 7899 this(size.width, size.height, forcexshm, enableAlpha); 7900 } 7901 7902 private bool suppressDestruction; 7903 7904 version(X11) 7905 this(XImage* handle) { 7906 this.handle = handle; 7907 this.rawData = cast(ubyte*) handle.data; 7908 this.width = handle.width; 7909 this.height = handle.height; 7910 this.enableAlpha = handle.depth == 32; 7911 suppressDestruction = true; 7912 } 7913 7914 ~this() { 7915 if(suppressDestruction) return; 7916 impl.dispose(); 7917 } 7918 7919 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 7920 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 7921 pure const @system nothrow { 7922 /* 7923 To use these to draw a blue rectangle with size WxH at position X,Y... 7924 7925 // make certain that it will fit before we proceed 7926 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! 7927 7928 // gather all the values you'll need up front. These can be kept until the image changes size if you want 7929 // (though calculating them isn't really that expensive). 7930 auto nextLineAdjustment = img.adjustmentForNextLine(); 7931 auto offR = img.redByteOffset(); 7932 auto offB = img.blueByteOffset(); 7933 auto offG = img.greenByteOffset(); 7934 auto bpp = img.bytesPerPixel(); 7935 7936 auto data = img.getDataPointer(); 7937 7938 // figure out the starting byte offset 7939 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 7940 7941 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 7942 7943 // and now our drawing loop for the rectangle 7944 foreach(y; 0 .. H) { 7945 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 7946 foreach(x; 0 .. W) { 7947 // write our color 7948 data[offR] = 0; 7949 data[offG] = 0; 7950 data[offB] = 255; 7951 7952 data += bpp; // moving to the next pixel is just an addition... 7953 } 7954 startOfLine += nextLineAdjustment; 7955 } 7956 7957 7958 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 7959 7960 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 7961 can be made into a bitmask or something so we can write them as *uint... 7962 */ 7963 7964 /// 7965 int offsetForTopLeftPixel() { 7966 version(X11) { 7967 return 0; 7968 } else version(Windows) { 7969 if(enableAlpha) { 7970 return (width * 4) * (height - 1); 7971 } else { 7972 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 7973 } 7974 } else version(OSXCocoa) { 7975 return 0 ; //throw new NotYetImplementedException(); 7976 } else static assert(0, "fill in this info for other OSes"); 7977 } 7978 7979 /// 7980 int offsetForPixel(int x, int y) { 7981 version(X11) { 7982 auto offset = (y * width + x) * 4; 7983 return offset; 7984 } else version(Windows) { 7985 if(enableAlpha) { 7986 auto itemsPerLine = width * 4; 7987 // remember, bmps are upside down 7988 auto offset = itemsPerLine * (height - y - 1) + x * 4; 7989 return offset; 7990 } else { 7991 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 7992 // remember, bmps are upside down 7993 auto offset = itemsPerLine * (height - y - 1) + x * 3; 7994 return offset; 7995 } 7996 } else version(OSXCocoa) { 7997 return 0 ; //throw new NotYetImplementedException(); 7998 } else static assert(0, "fill in this info for other OSes"); 7999 } 8000 8001 /// 8002 int adjustmentForNextLine() { 8003 version(X11) { 8004 return width * 4; 8005 } else version(Windows) { 8006 // windows bmps are upside down, so the adjustment is actually negative 8007 if(enableAlpha) 8008 return - (cast(int) width * 4); 8009 else 8010 return -((cast(int) width * 3 + 3) / 4) * 4; 8011 } else version(OSXCocoa) { 8012 return 0 ; //throw new NotYetImplementedException(); 8013 } else static assert(0, "fill in this info for other OSes"); 8014 } 8015 8016 /// once you have the position of a pixel, use these to get to the proper color 8017 int redByteOffset() { 8018 version(X11) { 8019 return 2; 8020 } else version(Windows) { 8021 return 2; 8022 } else version(OSXCocoa) { 8023 return 0 ; //throw new NotYetImplementedException(); 8024 } else static assert(0, "fill in this info for other OSes"); 8025 } 8026 8027 /// 8028 int greenByteOffset() { 8029 version(X11) { 8030 return 1; 8031 } else version(Windows) { 8032 return 1; 8033 } else version(OSXCocoa) { 8034 return 0 ; //throw new NotYetImplementedException(); 8035 } else static assert(0, "fill in this info for other OSes"); 8036 } 8037 8038 /// 8039 int blueByteOffset() { 8040 version(X11) { 8041 return 0; 8042 } else version(Windows) { 8043 return 0; 8044 } else version(OSXCocoa) { 8045 return 0 ; //throw new NotYetImplementedException(); 8046 } else static assert(0, "fill in this info for other OSes"); 8047 } 8048 8049 /// Only valid if [enableAlpha] is true 8050 int alphaByteOffset() { 8051 version(X11) { 8052 return 3; 8053 } else version(Windows) { 8054 return 3; 8055 } else version(OSXCocoa) { 8056 return 3; //throw new NotYetImplementedException(); 8057 } else static assert(0, "fill in this info for other OSes"); 8058 } 8059 } 8060 8061 /// 8062 final void putPixel(int x, int y, Color c) { 8063 if(x < 0 || x >= width) 8064 return; 8065 if(y < 0 || y >= height) 8066 return; 8067 8068 impl.setPixel(x, y, c); 8069 } 8070 8071 /// 8072 final Color getPixel(int x, int y) { 8073 if(x < 0 || x >= width) 8074 return Color.transparent; 8075 if(y < 0 || y >= height) 8076 return Color.transparent; 8077 8078 version(OSXCocoa) throw new NotYetImplementedException(); else 8079 return impl.getPixel(x, y); 8080 } 8081 8082 /// 8083 final void opIndexAssign(Color c, int x, int y) { 8084 putPixel(x, y, c); 8085 } 8086 8087 /// 8088 TrueColorImage toTrueColorImage() { 8089 auto tci = new TrueColorImage(width, height); 8090 convertToRgbaBytes(tci.imageData.bytes); 8091 return tci; 8092 } 8093 8094 /// 8095 static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false, bool premultiply = true) { 8096 auto tci = i.getAsTrueColorImage(); 8097 auto img = new Image(tci.width, tci.height, false, enableAlpha); 8098 static if(UsingSimpledisplayX11) 8099 img.premultiply = premultiply; 8100 img.setRgbaBytes(tci.imageData.bytes); 8101 return img; 8102 } 8103 8104 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 8105 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 8106 /// if you pass null, it will allocate a new one. 8107 ubyte[] getRgbaBytes(ubyte[] where = null) { 8108 if(where is null) 8109 where = new ubyte[this.width*this.height*4]; 8110 convertToRgbaBytes(where); 8111 return where; 8112 } 8113 8114 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 8115 void setRgbaBytes(in ubyte[] from ) { 8116 assert(from.length == this.width * this.height * 4); 8117 setFromRgbaBytes(from); 8118 } 8119 8120 // FIXME: make properly cross platform by getting rgba right 8121 8122 /// warning: this is not portable across platforms because the data format can change 8123 ubyte* getDataPointer() { 8124 return impl.rawData; 8125 } 8126 8127 /// for use with getDataPointer 8128 final int bytesPerLine() const pure @safe nothrow { 8129 version(Windows) 8130 return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 8131 else version(X11) 8132 return 4 * width; 8133 else version(OSXCocoa) 8134 return 4 * width; 8135 else static assert(0); 8136 } 8137 8138 /// for use with getDataPointer 8139 final int bytesPerPixel() const pure @safe nothrow { 8140 version(Windows) 8141 return enableAlpha ? 4 : 3; 8142 else version(X11) 8143 return 4; 8144 else version(OSXCocoa) 8145 return 4; 8146 else static assert(0); 8147 } 8148 8149 /// 8150 immutable int width; 8151 8152 /// 8153 immutable int height; 8154 8155 /// 8156 immutable bool enableAlpha; 8157 //private: 8158 mixin NativeImageImplementation!() impl; 8159 } 8160 8161 /++ 8162 A convenience function to pop up a window displaying the image. 8163 If you pass a win, it will draw the image in it. Otherwise, it will 8164 create a window with the size of the image and run its event loop, closing 8165 when a key is pressed. 8166 8167 History: 8168 `BlockingMode` parameter added on December 8, 2021. Previously, it would 8169 always block until the application quit which could cause bizarre behavior 8170 inside a more complex application. Now, the default is to block until 8171 this window closes if it is the only event loop running, and otherwise, 8172 not to block at all and just pop up the display window asynchronously. 8173 +/ 8174 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) { 8175 if(win is null) { 8176 win = new SimpleWindow(image); 8177 { 8178 auto p = win.draw; 8179 p.drawImage(Point(0, 0), image); 8180 } 8181 win.eventLoopWithBlockingMode( 8182 bm, 0, 8183 (KeyEvent ev) { 8184 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 8185 } ); 8186 } else { 8187 win.image = image; 8188 } 8189 } 8190 8191 enum FontWeight : int { 8192 dontcare = 0, 8193 thin = 100, 8194 extralight = 200, 8195 light = 300, 8196 regular = 400, 8197 medium = 500, 8198 semibold = 600, 8199 bold = 700, 8200 extrabold = 800, 8201 heavy = 900 8202 } 8203 8204 /++ 8205 Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont]. 8206 8207 History: 8208 Added October 24, 2022. The methods were already on [OperatingSystemFont] before that. 8209 +/ 8210 interface MeasurableFont { 8211 /++ 8212 Returns true if it is a monospace font, meaning each of the 8213 glyphs (at least the ascii characters) have matching width 8214 and no kerning, so you can determine the display width of some 8215 strings by simply multiplying the string width by [averageWidth]. 8216 8217 (Please note that multiply doesn't $(I actually) work in general, 8218 consider characters like tab and newline, but it does sometimes.) 8219 +/ 8220 bool isMonospace(); 8221 8222 /++ 8223 The average width of glyphs in the font, traditionally equal to the 8224 width of the lowercase x. Can be used to estimate bounding boxes, 8225 especially if the font [isMonospace]. 8226 8227 Given in pixels. 8228 +/ 8229 int averageWidth(); 8230 /++ 8231 The height of the bounding box of a line. 8232 +/ 8233 int height(); 8234 /++ 8235 The maximum ascent of a glyph above the baseline. 8236 8237 Given in pixels. 8238 +/ 8239 int ascent(); 8240 /++ 8241 The maximum descent of a glyph below the baseline. For example, how low the g might go. 8242 8243 Given in pixels. 8244 +/ 8245 int descent(); 8246 /++ 8247 The display width of the given string, and if you provide a window, it will use it to 8248 make the pixel count on screen more accurate too, but this shouldn't generally be necessary. 8249 8250 Given in pixels. 8251 +/ 8252 int stringWidth(scope const(char)[] s, SimpleWindow window = null); 8253 8254 } 8255 8256 // FIXME: i need a font cache and it needs to handle disconnects. 8257 8258 /++ 8259 Represents a font loaded off the operating system or the X server. 8260 8261 8262 While the api here is unified cross platform, the fonts are not necessarily 8263 available, even across machines of the same platform, so be sure to always check 8264 for null (using [isNull]) and have a fallback plan. 8265 8266 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 8267 8268 Worst case, a null font will automatically fall back to the default font loaded 8269 for your system. 8270 +/ 8271 class OperatingSystemFont : MeasurableFont { 8272 // FIXME: when the X Connection is lost, these need to be invalidated! 8273 // that means I need to store the original stuff again to reconstruct it too. 8274 8275 version(X11) { 8276 XFontStruct* font; 8277 XFontSet fontset; 8278 8279 version(with_xft) { 8280 XftFont* xftFont; 8281 bool isXft; 8282 } 8283 } else version(Windows) { 8284 HFONT font; 8285 int width_; 8286 int height_; 8287 } else version(OSXCocoa) { 8288 NSFont font; 8289 } else static assert(0); 8290 8291 /++ 8292 Constructs the class and immediately calls [load]. 8293 +/ 8294 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8295 load(name, size, weight, italic); 8296 } 8297 8298 /++ 8299 Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object. 8300 8301 You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you. 8302 8303 History: 8304 Added January 24, 2021. 8305 +/ 8306 this() { 8307 // this space intentionally left blank 8308 } 8309 8310 /++ 8311 Constructs a copy of the given font object. 8312 8313 History: 8314 Added January 7, 2023. 8315 +/ 8316 this(OperatingSystemFont font) { 8317 if(font is null || font.loadedInfo is LoadedInfo.init) 8318 loadDefault(); 8319 else 8320 load(font.loadedInfo.tupleof); 8321 } 8322 8323 /++ 8324 Loads specifically with the Xft library - a freetype font from a fontconfig string. 8325 8326 History: 8327 Added November 13, 2020. 8328 +/ 8329 version(with_xft) 8330 bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8331 unload(); 8332 8333 if(!XftLibrary.attempted) { 8334 XftLibrary.loadDynamicLibrary(); 8335 } 8336 8337 if(!XftLibrary.loadSuccessful) 8338 return false; 8339 8340 auto display = XDisplayConnection.get; 8341 8342 char[256] nameBuffer = void; 8343 int nbp = 0; 8344 8345 void add(in char[] a) { 8346 nameBuffer[nbp .. nbp + a.length] = a[]; 8347 nbp += a.length; 8348 } 8349 add(name); 8350 8351 if(size) { 8352 add(":size="); 8353 add(toInternal!string(size)); 8354 } 8355 if(weight != FontWeight.dontcare) { 8356 add(":weight="); 8357 add(weightToString(weight)); 8358 } 8359 if(italic) 8360 add(":slant=100"); 8361 8362 nameBuffer[nbp] = 0; 8363 8364 this.xftFont = XftFontOpenName( 8365 display, 8366 DefaultScreen(display), 8367 nameBuffer.ptr 8368 ); 8369 8370 this.isXft = true; 8371 8372 if(xftFont !is null) { 8373 isMonospace_ = stringWidth("x") == stringWidth("M"); 8374 ascent_ = xftFont.ascent; 8375 descent_ = xftFont.descent; 8376 } 8377 8378 return !isNull(); 8379 } 8380 8381 /++ 8382 Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor. 8383 8384 8385 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. 8386 8387 If `pattern` is null, it returns all available font families. 8388 8389 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. 8390 8391 The format of the pattern is platform-specific. 8392 8393 History: 8394 Added May 1, 2021 (dub v9.5) 8395 +/ 8396 static void listFonts(string pattern, bool delegate(in char[] name) handler) { 8397 version(Windows) { 8398 auto hdc = GetDC(null); 8399 scope(exit) ReleaseDC(null, hdc); 8400 LOGFONT logfont; 8401 static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) { 8402 auto localHandler = *(cast(typeof(handler)*) p); 8403 return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0; 8404 } 8405 EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0); 8406 } else version(X11) { 8407 //import core.stdc.stdio; 8408 bool done = false; 8409 version(with_xft) { 8410 if(!XftLibrary.attempted) { 8411 XftLibrary.loadDynamicLibrary(); 8412 } 8413 8414 if(!XftLibrary.loadSuccessful) 8415 goto skipXft; 8416 8417 if(!FontConfigLibrary.attempted) 8418 FontConfigLibrary.loadDynamicLibrary(); 8419 if(!FontConfigLibrary.loadSuccessful) 8420 goto skipXft; 8421 8422 { 8423 auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null); 8424 if(got is null) 8425 goto skipXft; 8426 scope(exit) FcFontSetDestroy(got); 8427 8428 auto fontPatterns = got.fonts[0 .. got.nfont]; 8429 foreach(candidate; fontPatterns) { 8430 char* where, whereStyle; 8431 8432 char* pmg = FcNameUnparse(candidate); 8433 8434 //FcPatternGetString(candidate, "family", 0, &where); 8435 //FcPatternGetString(candidate, "style", 0, &whereStyle); 8436 //if(where && whereStyle) { 8437 if(pmg) { 8438 if(!handler(pmg.sliceCString)) 8439 return; 8440 //printf("%s || %s %s\n", pmg, where, whereStyle); 8441 } 8442 } 8443 } 8444 } 8445 8446 skipXft: 8447 8448 if(pattern is null) 8449 pattern = "*"; 8450 8451 int count; 8452 auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count); 8453 scope(exit) XFreeFontNames(coreFontsRaw); 8454 8455 auto coreFonts = coreFontsRaw[0 .. count]; 8456 8457 foreach(font; coreFonts) { 8458 char[128] tmp; 8459 tmp[0 ..5] = "core:"; 8460 auto cf = font.sliceCString; 8461 if(5 + cf.length > tmp.length) 8462 assert(0, "a font name was too long, sorry i didn't bother implementing a fallback"); 8463 tmp[5 .. 5 + cf.length] = cf; 8464 if(!handler(tmp[0 .. 5 + cf.length])) 8465 return; 8466 } 8467 } 8468 } 8469 8470 /++ 8471 Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont 8472 to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega]. 8473 8474 Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the 8475 underlying system doesn't support returning the raw bytes. 8476 8477 History: 8478 Added September 10, 2021 (dub v10.3) 8479 +/ 8480 ubyte[] getTtfBytes() { 8481 if(isNull) 8482 return null; 8483 8484 version(Windows) { 8485 auto dc = GetDC(null); 8486 auto orig = SelectObject(dc, font); 8487 8488 scope(exit) { 8489 SelectObject(dc, orig); 8490 ReleaseDC(null, dc); 8491 } 8492 8493 auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0); 8494 if(res == GDI_ERROR) 8495 return null; 8496 8497 ubyte[] buffer = new ubyte[](res); 8498 res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length); 8499 if(res == GDI_ERROR) 8500 return null; // wtf really tbh 8501 8502 return buffer; 8503 } else version(with_xft) { 8504 if(isXft && xftFont) { 8505 if(!FontConfigLibrary.attempted) 8506 FontConfigLibrary.loadDynamicLibrary(); 8507 if(!FontConfigLibrary.loadSuccessful) 8508 return null; 8509 8510 char* file; 8511 if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) { 8512 if (file !is null && file[0]) { 8513 import core.stdc.stdio; 8514 auto fp = fopen(file, "rb"); 8515 if(fp is null) 8516 return null; 8517 scope(exit) 8518 fclose(fp); 8519 fseek(fp, 0, SEEK_END); 8520 ubyte[] buffer = new ubyte[](ftell(fp)); 8521 fseek(fp, 0, SEEK_SET); 8522 8523 auto got = fread(buffer.ptr, 1, buffer.length, fp); 8524 if(got != buffer.length) 8525 return null; 8526 8527 return buffer; 8528 } 8529 } 8530 } 8531 return null; 8532 } else throw new NotYetImplementedException(); 8533 } 8534 8535 // see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352 8536 8537 private string weightToString(FontWeight weight) { 8538 with(FontWeight) 8539 final switch(weight) { 8540 case dontcare: return "*"; 8541 case thin: return "extralight"; 8542 case extralight: return "extralight"; 8543 case light: return "light"; 8544 case regular: return "regular"; 8545 case medium: return "medium"; 8546 case semibold: return "demibold"; 8547 case bold: return "bold"; 8548 case extrabold: return "demibold"; 8549 case heavy: return "black"; 8550 } 8551 } 8552 8553 /++ 8554 Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance. 8555 8556 History: 8557 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8558 +/ 8559 version(X11) 8560 bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8561 unload(); 8562 8563 string xfontstr; 8564 8565 if(name.length > 3 && name[0 .. 3] == "-*-") { 8566 // this is kinda a disgusting hack but if the user sends an exact 8567 // string I'd like to honor it... 8568 xfontstr = name; 8569 } else { 8570 string weightstr = weightToString(weight); 8571 string sizestr; 8572 if(size == 0) 8573 sizestr = "*"; 8574 else 8575 sizestr = toInternal!string(size); 8576 xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 8577 } 8578 8579 // writeln(xfontstr); 8580 8581 auto display = XDisplayConnection.get; 8582 8583 font = XLoadQueryFont(display, xfontstr.ptr); 8584 if(font is null) 8585 return false; 8586 8587 char** lol; 8588 int lol2; 8589 char* lol3; 8590 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 8591 8592 prepareFontInfo(); 8593 8594 return !isNull(); 8595 } 8596 8597 version(X11) 8598 private void prepareFontInfo() { 8599 if(font !is null) { 8600 isMonospace_ = stringWidth("l") == stringWidth("M"); 8601 ascent_ = font.max_bounds.ascent; 8602 descent_ = font.max_bounds.descent; 8603 } 8604 } 8605 8606 version(OSXCocoa) 8607 private void prepareFontInfo() { 8608 if(font !is null) { 8609 isMonospace_ = font.isFixedPitch; 8610 ascent_ = cast(int) font.ascender; 8611 descent_ = cast(int) - font.descender; 8612 } 8613 } 8614 8615 8616 /++ 8617 Loads a Windows font. You probably want to use [load] instead to be more generic. 8618 8619 History: 8620 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8621 +/ 8622 version(Windows) 8623 bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) { 8624 unload(); 8625 8626 WCharzBuffer buffer = WCharzBuffer(name); 8627 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 8628 8629 prepareFontInfo(hdc); 8630 8631 return !isNull(); 8632 } 8633 8634 version(Windows) 8635 void prepareFontInfo(HDC hdc = null) { 8636 if(font is null) 8637 return; 8638 8639 TEXTMETRIC tm; 8640 auto dc = hdc ? hdc : GetDC(null); 8641 auto orig = SelectObject(dc, font); 8642 GetTextMetrics(dc, &tm); 8643 SelectObject(dc, orig); 8644 if(hdc is null) 8645 ReleaseDC(null, dc); 8646 8647 width_ = tm.tmAveCharWidth; 8648 height_ = tm.tmHeight; 8649 ascent_ = tm.tmAscent; 8650 descent_ = tm.tmDescent; 8651 // 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. 8652 isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0; 8653 } 8654 8655 8656 /++ 8657 `name` is a font name, but it can also be a more complicated string parsed in an OS-specific way. 8658 8659 On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise, 8660 it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX]. 8661 8662 On Windows, it forwards directly to [loadWin32]. 8663 8664 Params: 8665 name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences. 8666 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. 8667 weight = approximate boldness, results may vary. 8668 italic = try to get a slanted version of the given font. 8669 8670 History: 8671 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. 8672 +/ 8673 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8674 this.loadedInfo = LoadedInfo(name, size, weight, italic); 8675 version(X11) { 8676 version(with_xft) { 8677 if(name.length > 5 && name[0 .. 5] == "core:") { 8678 goto core; 8679 } 8680 8681 if(loadXft(name, size, weight, italic)) 8682 return true; 8683 // if xft fails, fallback to core to avoid breaking 8684 // code that already depended on this. 8685 } 8686 8687 core: 8688 8689 if(name.length > 5 && name[0 .. 5] == "core:") { 8690 name = name[5 .. $]; 8691 } 8692 8693 return loadCoreX(name, size, weight, italic); 8694 } else version(Windows) { 8695 return loadWin32(name, size, weight, italic); 8696 } else version(OSXCocoa) { 8697 return loadCocoa(name, size, weight, italic); 8698 } else static assert(0); 8699 } 8700 8701 version(OSXCocoa) 8702 bool loadCocoa(string name, int size, FontWeight weight, bool italic) { 8703 unload(); 8704 8705 font = NSFont.fontWithName(MacString(name).borrow, size); // FIXME: weight and italic? 8706 prepareFontInfo(); 8707 8708 return !isNull(); 8709 } 8710 8711 private struct LoadedInfo { 8712 string name; 8713 int size; 8714 FontWeight weight; 8715 bool italic; 8716 } 8717 private LoadedInfo loadedInfo; 8718 8719 /// 8720 void unload() { 8721 if(isNull()) 8722 return; 8723 8724 version(X11) { 8725 auto display = XDisplayConnection.display; 8726 8727 if(display is null) 8728 return; 8729 8730 version(with_xft) { 8731 if(isXft) { 8732 if(xftFont) 8733 XftFontClose(display, xftFont); 8734 isXft = false; 8735 xftFont = null; 8736 return; 8737 } 8738 } 8739 8740 if(font && font !is ScreenPainterImplementation.defaultfont) 8741 XFreeFont(display, font); 8742 if(fontset && fontset !is ScreenPainterImplementation.defaultfontset) 8743 XFreeFontSet(display, fontset); 8744 8745 font = null; 8746 fontset = null; 8747 } else version(Windows) { 8748 DeleteObject(font); 8749 font = null; 8750 } else version(OSXCocoa) { 8751 font.release(); 8752 font = null; 8753 } else static assert(0); 8754 } 8755 8756 private bool isMonospace_; 8757 8758 /++ 8759 History: 8760 Added January 16, 2021 8761 +/ 8762 bool isMonospace() { 8763 return isMonospace_; 8764 } 8765 8766 /++ 8767 Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character. 8768 8769 History: 8770 Added March 26, 2020 8771 Documented January 16, 2021 8772 +/ 8773 int averageWidth() { 8774 version(X11) { 8775 return stringWidth("x"); 8776 } version(OSXCocoa) { 8777 return stringWidth("x"); 8778 } else version(Windows) 8779 return width_; 8780 else assert(0); 8781 } 8782 8783 /++ 8784 Returns the width of the string as drawn on the specified window, or the default screen if the window is null. 8785 8786 History: 8787 Added January 16, 2021 8788 +/ 8789 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 8790 // FIXME: what about tab? 8791 if(isNull) 8792 return 0; 8793 8794 version(X11) { 8795 version(with_xft) 8796 if(isXft && xftFont !is null) { 8797 //return xftFont.max_advance_width; 8798 XGlyphInfo extents; 8799 XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents); 8800 // writeln(extents); 8801 return extents.xOff; 8802 } 8803 if(font is null) 8804 return 0; 8805 else if(fontset) { 8806 XRectangle rect; 8807 Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect); 8808 8809 return rect.width; 8810 } else { 8811 return XTextWidth(font, s.ptr, cast(int) s.length); 8812 } 8813 } else version(Windows) { 8814 WCharzBuffer buffer = WCharzBuffer(s); 8815 8816 return stringWidth(buffer.slice, window); 8817 } else version(OSXCocoa) { 8818 /+ 8819 int charCount = [string length]; 8820 CGGlyph glyphs[charCount]; 8821 CGRect rects[charCount]; 8822 8823 CTFontGetGlyphsForCharacters(theCTFont, (const unichar*)[string cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, charCount); 8824 CTFontGetBoundingRectsForGlyphs(theCTFont, kCTFontDefaultOrientation, glyphs, rects, charCount); 8825 8826 int totalwidth = 0, maxheight = 0; 8827 for (int i=0; i < charCount; i++) 8828 { 8829 totalwidth += rects[i].size.width; 8830 maxheight = maxheight < rects[i].size.height ? rects[i].size.height : maxheight; 8831 } 8832 8833 dim = CGSizeMake(totalwidth, maxheight); 8834 +/ 8835 8836 return 16; // FIXME 8837 } 8838 else assert(0); 8839 } 8840 8841 version(Windows) 8842 /// ditto 8843 int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) { 8844 if(isNull) 8845 return 0; 8846 version(Windows) { 8847 SIZE size; 8848 8849 prepareContext(window); 8850 scope(exit) releaseContext(); 8851 8852 GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size); 8853 8854 return size.cx; 8855 } else { 8856 // std.conv can do this easily but it is slow to import and i don't think it is worth it 8857 static assert(0, "not implemented yet"); 8858 //return stringWidth(s, window); 8859 } 8860 } 8861 8862 private { 8863 int prepRefcount; 8864 8865 version(Windows) { 8866 HDC dc; 8867 HANDLE orig; 8868 HWND hwnd; 8869 } 8870 } 8871 /++ 8872 [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. 8873 8874 History: 8875 Added January 23, 2021 8876 +/ 8877 void prepareContext(SimpleWindow window = null) { 8878 prepRefcount++; 8879 if(prepRefcount == 1) { 8880 version(Windows) { 8881 hwnd = window is null ? null : window.impl.hwnd; 8882 dc = GetDC(hwnd); 8883 orig = SelectObject(dc, font); 8884 } 8885 } 8886 } 8887 /// ditto 8888 void releaseContext() { 8889 prepRefcount--; 8890 if(prepRefcount == 0) { 8891 version(Windows) { 8892 SelectObject(dc, orig); 8893 ReleaseDC(hwnd, dc); 8894 hwnd = null; 8895 dc = null; 8896 orig = null; 8897 } 8898 } 8899 } 8900 8901 /+ 8902 FIXME: I think I need advance and kerning pair 8903 8904 int advance(dchar from, dchar to) { } // use dchar.init for first item in string 8905 +/ 8906 8907 /++ 8908 Returns the height of the font. 8909 8910 History: 8911 Added March 26, 2020 8912 Documented January 16, 2021 8913 +/ 8914 int height() { 8915 version(X11) { 8916 version(with_xft) 8917 if(isXft && xftFont !is null) { 8918 return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel 8919 } 8920 if(font is null) 8921 return 0; 8922 return font.max_bounds.ascent + font.max_bounds.descent; 8923 } else version(Windows) { 8924 return height_; 8925 } else version(OSXCocoa) { 8926 if(font is null) 8927 return 0; 8928 return cast(int) font.capHeight; 8929 } 8930 else assert(0); 8931 } 8932 8933 private int ascent_; 8934 private int descent_; 8935 8936 /++ 8937 Max ascent above the baseline. 8938 8939 History: 8940 Added January 22, 2021 8941 +/ 8942 int ascent() { 8943 return ascent_; 8944 } 8945 8946 /++ 8947 Max descent below the baseline. 8948 8949 History: 8950 Added January 22, 2021 8951 +/ 8952 int descent() { 8953 return descent_; 8954 } 8955 8956 /++ 8957 Loads the default font used by [ScreenPainter] if none others are loaded. 8958 8959 Returns: 8960 This method mutates the `this` object, but then returns `this` for 8961 easy chaining like: 8962 8963 --- 8964 auto font = foo.isNull ? foo : foo.loadDefault 8965 --- 8966 8967 History: 8968 Added previously, but left unimplemented until January 24, 2021. 8969 +/ 8970 OperatingSystemFont loadDefault() { 8971 unload(); 8972 8973 loadedInfo = LoadedInfo.init; 8974 8975 version(X11) { 8976 // another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html 8977 // but meh since sdpy does its own thing, this should be ok too 8978 8979 ScreenPainterImplementation.ensureDefaultFontLoaded(); 8980 this.font = ScreenPainterImplementation.defaultfont; 8981 this.fontset = ScreenPainterImplementation.defaultfontset; 8982 8983 prepareFontInfo(); 8984 return this; 8985 } else version(Windows) { 8986 ScreenPainterImplementation.ensureDefaultFontLoaded(); 8987 this.font = ScreenPainterImplementation.defaultGuiFont; 8988 8989 prepareFontInfo(); 8990 return this; 8991 } else version(OSXCocoa) { 8992 this.font = NSFont.systemFontOfSize(12); 8993 8994 prepareFontInfo(); 8995 return this; 8996 } else throw new NotYetImplementedException(); 8997 } 8998 8999 /// 9000 bool isNull() { 9001 version(with_xft) 9002 if(isXft) 9003 return xftFont is null; 9004 return font is null; 9005 } 9006 9007 /* Metrics */ 9008 /+ 9009 GetABCWidth 9010 GetKerningPairs 9011 9012 if I do it right, I can size it all here, and match 9013 what happens when I draw the full string with the OS functions. 9014 9015 subclasses might do the same thing while getting the glyphs on images 9016 struct GlyphInfo { 9017 int glyph; 9018 9019 size_t stringIdxStart; 9020 size_t stringIdxEnd; 9021 9022 Rectangle boundingBox; 9023 } 9024 GlyphInfo[] getCharBoxes() { 9025 // XftTextExtentsUtf8 9026 return null; 9027 9028 } 9029 +/ 9030 9031 ~this() { 9032 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 9033 unload(); 9034 } 9035 } 9036 9037 version(Windows) 9038 private string sliceCString(const(wchar)[] w) { 9039 return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr); 9040 } 9041 9042 private inout(char)[] sliceCString(inout(char)* s) { 9043 import core.stdc.string; 9044 auto len = strlen(s); 9045 return s[0 .. len]; 9046 } 9047 9048 version(OSXCocoa) 9049 alias PaintingHandle = NSObject; 9050 else 9051 alias PaintingHandle = NativeWindowHandle; 9052 9053 /** 9054 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 9055 than constructing it directly. Then, it is reference counted so you can pass it 9056 at around and when the last ref goes out of scope, the buffered drawing activities 9057 are all carried out. 9058 9059 9060 Most functions use the outlineColor instead of taking a color themselves. 9061 ScreenPainter is reference counted and draws its buffer to the screen when its 9062 final reference goes out of scope. 9063 */ 9064 struct ScreenPainter { 9065 CapableOfBeingDrawnUpon window; 9066 this(CapableOfBeingDrawnUpon window, PaintingHandle handle, bool manualInvalidations) { 9067 this.window = window; 9068 if(window.closed) 9069 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 9070 //currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 9071 currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max); 9072 if(window.activeScreenPainter !is null) { 9073 impl = window.activeScreenPainter; 9074 if(impl.referenceCount == 0) { 9075 impl.window = window; 9076 impl.create(handle); 9077 } 9078 impl.manualInvalidations = manualInvalidations; 9079 impl.referenceCount++; 9080 // writeln("refcount ++ ", impl.referenceCount); 9081 } else { 9082 impl = new ScreenPainterImplementation; 9083 impl.window = window; 9084 impl.create(handle); 9085 impl.referenceCount = 1; 9086 impl.manualInvalidations = manualInvalidations; 9087 window.activeScreenPainter = impl; 9088 // writeln("constructed"); 9089 } 9090 9091 copyActiveOriginals(); 9092 } 9093 9094 /++ 9095 EXPERIMENTAL. subject to change. 9096 9097 When you draw a cursor, you can draw this to notify your window of where it is, 9098 for IME systems to use. 9099 +/ 9100 void notifyCursorPosition(int x, int y, int width, int height) { 9101 if(auto w = cast(SimpleWindow) window) { 9102 w.setIMEPopupLocation(x + _originX + width, y + _originY + height); 9103 } 9104 } 9105 9106 /++ 9107 If you are using manual invalidations, this informs the 9108 window system that a section needs to be redrawn. 9109 9110 If you didn't opt into manual invalidation, you don't 9111 have to call this. 9112 9113 History: 9114 Added December 30, 2021 (dub v10.5) 9115 +/ 9116 void invalidateRect(Rectangle rect) { 9117 if(impl is null) return; 9118 9119 // transform(rect) 9120 rect.left += _originX; 9121 rect.right += _originX; 9122 rect.top += _originY; 9123 rect.bottom += _originY; 9124 9125 impl.invalidateRect(rect); 9126 } 9127 9128 private Pen originalPen; 9129 private Color originalFillColor; 9130 private arsd.color.Rectangle originalClipRectangle; 9131 private OperatingSystemFont originalFont; 9132 void copyActiveOriginals() { 9133 if(impl is null) return; 9134 originalPen = impl._activePen; 9135 originalFillColor = impl._fillColor; 9136 originalClipRectangle = impl._clipRectangle; 9137 version(OSXCocoa) {} else 9138 originalFont = impl._activeFont; 9139 } 9140 9141 ~this() { 9142 if(impl is null) return; 9143 impl.referenceCount--; 9144 //writeln("refcount -- ", impl.referenceCount); 9145 if(impl.referenceCount == 0) { 9146 // writeln("destructed"); 9147 impl.dispose(); 9148 *window.activeScreenPainter = ScreenPainterImplementation.init; 9149 // writeln("paint finished"); 9150 } else { 9151 // there is still an active reference, reset stuff so the 9152 // next user doesn't get weirdness via the reference 9153 this.rasterOp = RasterOp.normal; 9154 pen = originalPen; 9155 fillColor = originalFillColor; 9156 if(originalFont) 9157 setFont(originalFont); 9158 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 9159 } 9160 } 9161 9162 this(this) { 9163 if(impl is null) return; 9164 impl.referenceCount++; 9165 //writeln("refcount ++ ", impl.referenceCount); 9166 9167 copyActiveOriginals(); 9168 } 9169 9170 private int _originX; 9171 private int _originY; 9172 @property int originX() { return _originX; } 9173 @property int originY() { return _originY; } 9174 @property int originX(int a) { 9175 _originX = a; 9176 return _originX; 9177 } 9178 @property int originY(int a) { 9179 _originY = a; 9180 return _originY; 9181 } 9182 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 9183 private void transform(ref Point p) { 9184 if(impl is null) return; 9185 p.x += _originX; 9186 p.y += _originY; 9187 } 9188 9189 // this needs to be checked BEFORE the originX/Y transformation 9190 private bool isClipped(Point p) { 9191 return !currentClipRectangle.contains(p); 9192 } 9193 private bool isClipped(Point p, int width, int height) { 9194 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 9195 } 9196 private bool isClipped(Point p, Size s) { 9197 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 9198 } 9199 private bool isClipped(Point p, Point p2) { 9200 // need to ensure the end points are actually included inside, so the +1 does that 9201 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 9202 } 9203 9204 9205 /++ 9206 Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 9207 9208 Returns: 9209 The old clip rectangle. 9210 9211 History: 9212 Return value was `void` prior to May 10, 2021. 9213 9214 +/ 9215 arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) { 9216 if(impl is null) return currentClipRectangle; 9217 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 9218 return currentClipRectangle; // no need to do anything 9219 auto old = currentClipRectangle; 9220 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 9221 transform(pt); 9222 9223 impl.setClipRectangle(pt.x, pt.y, width, height); 9224 9225 return old; 9226 } 9227 9228 /// ditto 9229 arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) { 9230 if(impl is null) return currentClipRectangle; 9231 return setClipRectangle(rect.upperLeft, rect.width, rect.height); 9232 } 9233 9234 /// 9235 void setFont(OperatingSystemFont font) { 9236 if(impl is null) return; 9237 impl.setFont(font); 9238 } 9239 9240 /// 9241 int fontHeight() { 9242 if(impl is null) return 0; 9243 return impl.fontHeight(); 9244 } 9245 9246 private Pen activePen; 9247 9248 /// 9249 @property void pen(Pen p) { 9250 if(impl is null) return; 9251 activePen = p; 9252 impl.pen(p); 9253 } 9254 9255 /// 9256 @scriptable 9257 @property void outlineColor(Color c) { 9258 if(impl is null) return; 9259 if(activePen.color == c) 9260 return; 9261 activePen.color = c; 9262 impl.pen(activePen); 9263 } 9264 9265 /// 9266 @scriptable 9267 @property void fillColor(Color c) { 9268 if(impl is null) return; 9269 impl.fillColor(c); 9270 } 9271 9272 /// 9273 @property void rasterOp(RasterOp op) { 9274 if(impl is null) return; 9275 impl.rasterOp(op); 9276 } 9277 9278 9279 void updateDisplay() { 9280 // FIXME this should do what the dtor does 9281 } 9282 9283 /// 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) 9284 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 9285 if(impl is null) return; 9286 if(isClipped(upperLeft, width, height)) return; 9287 transform(upperLeft); 9288 version(Windows) { 9289 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 9290 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 9291 RECT clip = scroll; 9292 RECT uncovered; 9293 HRGN hrgn; 9294 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 9295 throw new WindowsApiException("ScrollDC", GetLastError()); 9296 9297 } else version(X11) { 9298 // FIXME: clip stuff outside this rectangle 9299 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 9300 } else version(OSXCocoa) { 9301 throw new NotYetImplementedException(); 9302 } else static assert(0); 9303 } 9304 9305 /// 9306 void clear(Color color = Color.white()) { 9307 if(impl is null) return; 9308 fillColor = color; 9309 outlineColor = color; 9310 drawRectangle(Point(0, 0), window.width, window.height); 9311 } 9312 9313 /++ 9314 Draws a pixmap (represented by the [Sprite] class) on the drawable. 9315 9316 Params: 9317 upperLeft = point on the window where the upper left corner of the image will be drawn 9318 imageUpperLeft = point on the image to start the slice to draw 9319 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. 9320 History: 9321 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9322 +/ 9323 void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9324 if(impl is null) return; 9325 if(isClipped(upperLeft, s.width, s.height)) return; 9326 transform(upperLeft); 9327 impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height); 9328 } 9329 9330 /// 9331 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 9332 if(impl is null) return; 9333 //if(isClipped(upperLeft, w, h)) return; // FIXME 9334 transform(upperLeft); 9335 if(w == 0 || w > i.width) 9336 w = i.width; 9337 if(h == 0 || h > i.height) 9338 h = i.height; 9339 if(upperLeftOfImage.x < 0) 9340 upperLeftOfImage.x = 0; 9341 if(upperLeftOfImage.y < 0) 9342 upperLeftOfImage.y = 0; 9343 9344 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 9345 } 9346 9347 /// 9348 Size textSize(in char[] text) { 9349 if(impl is null) return Size(0, 0); 9350 return impl.textSize(text); 9351 } 9352 9353 /++ 9354 Draws a string in the window with the set font (see [setFont] to change it). 9355 9356 Params: 9357 upperLeft = the upper left point of the bounding box of the text 9358 text = the string to draw 9359 lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound. 9360 alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags 9361 +/ 9362 @scriptable 9363 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 9364 if(impl is null) return; 9365 if(lowerRight.x != 0 || lowerRight.y != 0) { 9366 if(isClipped(upperLeft, lowerRight)) return; 9367 transform(lowerRight); 9368 } else { 9369 if(isClipped(upperLeft, textSize(text))) return; 9370 } 9371 transform(upperLeft); 9372 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 9373 } 9374 9375 /++ 9376 Draws text using a custom font. 9377 9378 This is still MAJOR work in progress. 9379 9380 Creating a [DrawableFont] can be tricky and require additional dependencies. 9381 +/ 9382 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 9383 if(impl is null) return; 9384 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9385 transform(upperLeft); 9386 font.drawString(this, upperLeft, text); 9387 } 9388 9389 version(Windows) 9390 void drawText(Point upperLeft, scope const(wchar)[] text) { 9391 if(impl is null) return; 9392 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9393 transform(upperLeft); 9394 9395 if(text.length && text[$-1] == '\n') 9396 text = text[0 .. $-1]; // tailing newlines are weird on windows... 9397 9398 TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length); 9399 } 9400 9401 static struct TextDrawingContext { 9402 Point boundingBoxUpperLeft; 9403 Point boundingBoxLowerRight; 9404 9405 Point currentLocation; 9406 9407 Point lastDrewUpperLeft; 9408 Point lastDrewLowerRight; 9409 9410 // how do i do right aligned rich text? 9411 // i kinda want to do a pre-made drawing then right align 9412 // draw the whole block. 9413 // 9414 // That's exactly the diff: inline vs block stuff. 9415 9416 // I need to get coordinates of an inline section out too, 9417 // not just a bounding box, but a series of bounding boxes 9418 // should be ok. Consider what's needed to detect a click 9419 // on a link in the middle of a paragraph breaking a line. 9420 // 9421 // Generally, we should be able to get the rectangles of 9422 // any portion we draw. 9423 // 9424 // It also needs to tell what text is left if it overflows 9425 // out of the box, so we can do stuff like float images around 9426 // it. It should not attempt to draw a letter that would be 9427 // clipped. 9428 // 9429 // I might also turn off word wrap stuff. 9430 } 9431 9432 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 9433 if(impl is null) return; 9434 // FIXME 9435 } 9436 9437 /// Drawing an individual pixel is slow. Avoid it if possible. 9438 void drawPixel(Point where) { 9439 if(impl is null) return; 9440 if(isClipped(where)) return; 9441 transform(where); 9442 impl.drawPixel(where.x, where.y); 9443 } 9444 9445 9446 /// Draws a pen using the current pen / outlineColor 9447 @scriptable 9448 void drawLine(Point starting, Point ending) { 9449 if(impl is null) return; 9450 if(isClipped(starting, ending)) return; 9451 transform(starting); 9452 transform(ending); 9453 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 9454 } 9455 9456 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 9457 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 9458 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 9459 @scriptable 9460 void drawRectangle(Point upperLeft, int width, int height) { 9461 if(impl is null) return; 9462 if(isClipped(upperLeft, width, height)) return; 9463 transform(upperLeft); 9464 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 9465 } 9466 9467 /// ditto 9468 void drawRectangle(Point upperLeft, Size size) { 9469 if(impl is null) return; 9470 if(isClipped(upperLeft, size.width, size.height)) return; 9471 transform(upperLeft); 9472 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 9473 } 9474 9475 /// ditto 9476 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 9477 if(impl is null) return; 9478 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 9479 transform(upperLeft); 9480 transform(lowerRightInclusive); 9481 impl.drawRectangle(upperLeft.x, upperLeft.y, 9482 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 9483 } 9484 9485 // overload added on May 12, 2021 9486 /// ditto 9487 void drawRectangle(Rectangle rect) { 9488 drawRectangle(rect.upperLeft, rect.size); 9489 } 9490 9491 /// Arguments are the points of the bounding rectangle 9492 void drawEllipse(Point upperLeft, Point lowerRight) { 9493 if(impl is null) return; 9494 if(isClipped(upperLeft, lowerRight)) return; 9495 transform(upperLeft); 9496 transform(lowerRight); 9497 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 9498 } 9499 9500 /++ 9501 start and finish are units of degrees * 64 9502 9503 History: 9504 The Windows implementation didn't match the Linux implementation until September 24, 2021. 9505 9506 They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now). 9507 +/ 9508 void drawArc(Point upperLeft, int width, int height, int start, int finish) { 9509 if(impl is null) return; 9510 // FIXME: not actually implemented 9511 if(isClipped(upperLeft, width, height)) return; 9512 transform(upperLeft); 9513 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish); 9514 } 9515 9516 /// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 9517 void drawCircle(Point upperLeft, int diameter) { 9518 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 9519 } 9520 9521 /// . 9522 void drawPolygon(Point[] vertexes) { 9523 if(impl is null) return; 9524 assert(vertexes.length); 9525 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 9526 foreach(ref vertex; vertexes) { 9527 if(vertex.x < minX) 9528 minX = vertex.x; 9529 if(vertex.y < minY) 9530 minY = vertex.y; 9531 if(vertex.x > maxX) 9532 maxX = vertex.x; 9533 if(vertex.y > maxY) 9534 maxY = vertex.y; 9535 transform(vertex); 9536 } 9537 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 9538 impl.drawPolygon(vertexes); 9539 } 9540 9541 /// ditto 9542 void drawPolygon(Point[] vertexes...) { 9543 if(impl is null) return; 9544 drawPolygon(vertexes); 9545 } 9546 9547 9548 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 9549 9550 //mixin NativeScreenPainterImplementation!() impl; 9551 9552 9553 // HACK: if I mixin the impl directly, it won't let me override the copy 9554 // constructor! The linker complains about there being multiple definitions. 9555 // I'll make the best of it and reference count it though. 9556 ScreenPainterImplementation* impl; 9557 } 9558 9559 // HACK: I need a pointer to the implementation so it's separate 9560 struct ScreenPainterImplementation { 9561 CapableOfBeingDrawnUpon window; 9562 int referenceCount; 9563 mixin NativeScreenPainterImplementation!(); 9564 } 9565 9566 // FIXME: i haven't actually tested the sprite class on MS Windows 9567 9568 /** 9569 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 9570 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 9571 9572 9573 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 9574 though I'm not sure that's ideal and the implementation might change. 9575 9576 You create one by giving a window and an image. It optimizes for that window, 9577 and copies the image into it to use as the initial picture. Creating a sprite 9578 can be quite slow (especially over a network connection) so you should do it 9579 as little as possible and just hold on to your sprite handles after making them. 9580 simpledisplay does try to do its best though, using the XSHM extension if available, 9581 but you should still write your code as if it will always be slow. 9582 9583 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 9584 a fast operation - much faster than drawing the Image itself every time. 9585 9586 `Sprite` represents a scarce resource which should be freed when you 9587 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 9588 after it has been disposed. If you are unsure about this, don't take chances, 9589 just let the garbage collector do it for you. But ideally, you can manage its 9590 lifetime more efficiently. 9591 9592 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 9593 support alpha blending in its drawing at this time. That might change in the 9594 future, but if you need alpha blending right now, use OpenGL instead. See 9595 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 9596 9597 Update: on April 23, 2021, I finally added alpha blending support. You must opt 9598 in by setting the enableAlpha = true in the constructor. 9599 */ 9600 class Sprite : CapableOfBeingDrawnUpon { 9601 9602 /// 9603 ScreenPainter draw() { 9604 return ScreenPainter(this, handle, false); 9605 } 9606 9607 /++ 9608 Copies the sprite's current state into a [TrueColorImage]. 9609 9610 Be warned: this can be a very slow operation 9611 9612 History: 9613 Actually implemented on March 14, 2021 9614 +/ 9615 TrueColorImage takeScreenshot() { 9616 return trueColorImageFromNativeHandle(handle, width, height); 9617 } 9618 9619 void delegate() paintingFinishedDg() { return null; } 9620 bool closed() { return false; } 9621 ScreenPainterImplementation* activeScreenPainter_; 9622 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 9623 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 9624 9625 version(Windows) 9626 private ubyte* rawData; 9627 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 9628 // ditto on the XPicture stuff 9629 9630 version(X11) { 9631 private static XRenderPictFormat* RGB24; 9632 private static XRenderPictFormat* ARGB32; 9633 9634 private Picture xrenderPicture; 9635 } 9636 9637 version(X11) 9638 private static void requireXRender() { 9639 if(!XRenderLibrary.loadAttempted) { 9640 XRenderLibrary.loadDynamicLibrary(); 9641 } 9642 9643 if(!XRenderLibrary.loadSuccessful) 9644 throw new Exception("XRender library load failure"); 9645 9646 auto display = XDisplayConnection.get; 9647 9648 // FIXME: if we migrate X displays, these need to be changed 9649 if(RGB24 is null) 9650 RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); 9651 if(ARGB32 is null) 9652 ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); 9653 } 9654 9655 protected this() {} 9656 9657 this(SimpleWindow win, int width, int height, bool enableAlpha = false) { 9658 this._width = width; 9659 this._height = height; 9660 this.enableAlpha = enableAlpha; 9661 9662 version(X11) { 9663 auto display = XDisplayConnection.get(); 9664 9665 if(enableAlpha) { 9666 requireXRender(); 9667 } 9668 9669 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display)); 9670 9671 if(enableAlpha) { 9672 XRenderPictureAttributes attrs; 9673 xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs); 9674 } 9675 } else version(Windows) { 9676 version(CRuntime_DigitalMars) { 9677 //if(enableAlpha) 9678 //throw new Exception("Alpha support not available, try recompiling with -m32mscoff"); 9679 } 9680 9681 BITMAPINFO infoheader; 9682 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 9683 infoheader.bmiHeader.biWidth = width; 9684 infoheader.bmiHeader.biHeight = height; 9685 infoheader.bmiHeader.biPlanes = 1; 9686 infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24; 9687 infoheader.bmiHeader.biCompression = BI_RGB; 9688 9689 // FIXME: this should prolly be a device dependent bitmap... 9690 handle = CreateDIBSection( 9691 null, 9692 &infoheader, 9693 DIB_RGB_COLORS, 9694 cast(void**) &rawData, 9695 null, 9696 0); 9697 9698 if(handle is null) 9699 throw new WindowsApiException("couldn't create pixmap", GetLastError()); 9700 } 9701 } 9702 9703 /// Makes a sprite based on the image with the initial contents from the Image 9704 this(SimpleWindow win, Image i) { 9705 this(win, i.width, i.height, i.enableAlpha); 9706 9707 version(X11) { 9708 auto display = XDisplayConnection.get(); 9709 auto gc = XCreateGC(display, this.handle, 0, null); 9710 scope(exit) XFreeGC(display, gc); 9711 if(i.usingXshm) 9712 XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 9713 else 9714 XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 9715 } else version(Windows) { 9716 auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4); 9717 auto arrLength = itemsPerLine * height; 9718 rawData[0..arrLength] = i.rawData[0..arrLength]; 9719 } else version(OSXCocoa) { 9720 // FIXME: I have no idea if this is even any good 9721 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 9722 handle = CGBitmapContextCreate(null, width, height, 8, 4*width, 9723 colorSpace, 9724 kCGImageAlphaPremultipliedLast 9725 |kCGBitmapByteOrder32Big); 9726 CGColorSpaceRelease(colorSpace); 9727 auto rawData = CGBitmapContextGetData(handle); 9728 9729 auto rdl = (width * height * 4); 9730 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 9731 } else static assert(0); 9732 } 9733 9734 /++ 9735 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 9736 9737 Params: 9738 where = point on the window where the upper left corner of the image will be drawn 9739 imageUpperLeft = point on the image to start the slice to draw 9740 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. 9741 History: 9742 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9743 +/ 9744 void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9745 painter.drawPixmap(this, where, imageUpperLeft, sliceSize); 9746 } 9747 9748 /// Call this when you're ready to get rid of it 9749 void dispose() { 9750 version(X11) { 9751 staticDispose(xrenderPicture, handle); 9752 xrenderPicture = None; 9753 handle = None; 9754 } else version(Windows) { 9755 staticDispose(handle); 9756 handle = null; 9757 } else version(OSXCocoa) { 9758 staticDispose(handle); 9759 handle = null; 9760 } else static assert(0); 9761 9762 } 9763 9764 version(X11) 9765 static void staticDispose(Picture xrenderPicture, Pixmap handle) { 9766 if(xrenderPicture) 9767 XRenderFreePicture(XDisplayConnection.get, xrenderPicture); 9768 if(handle) 9769 XFreePixmap(XDisplayConnection.get(), handle); 9770 } 9771 else version(Windows) 9772 static void staticDispose(HBITMAP handle) { 9773 if(handle) 9774 DeleteObject(handle); 9775 } 9776 else version(OSXCocoa) 9777 static void staticDispose(CGContextRef context) { 9778 if(context) 9779 CGContextRelease(context); 9780 } 9781 9782 ~this() { 9783 version(X11) { if(xrenderPicture || handle) 9784 cleanupQueue.queue!staticDispose(xrenderPicture, handle); 9785 } else version(Windows) { if(handle) 9786 cleanupQueue.queue!staticDispose(handle); 9787 } else version(OSXCocoa) { if(handle) 9788 cleanupQueue.queue!staticDispose(handle); 9789 } else static assert(0); 9790 } 9791 9792 /// 9793 final @property int width() { return _width; } 9794 9795 /// 9796 final @property int height() { return _height; } 9797 9798 /// 9799 static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) { 9800 return new Sprite(win, Image.fromMemoryImage(img, enableAlpha)); 9801 } 9802 9803 auto nativeHandle() { 9804 return handle; 9805 } 9806 9807 private: 9808 9809 int _width; 9810 int _height; 9811 bool enableAlpha; 9812 version(X11) 9813 Pixmap handle; 9814 else version(Windows) 9815 HBITMAP handle; 9816 else version(OSXCocoa) 9817 CGContextRef handle; 9818 else static assert(0); 9819 } 9820 9821 /++ 9822 Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient]. 9823 9824 History: 9825 Added November 20, 2021 (dub v10.4) 9826 +/ 9827 version(OSXCocoa) {} else // NotYetImplementedException 9828 abstract class Gradient : Sprite { 9829 protected this(int w, int h) { 9830 version(X11) { 9831 Sprite.requireXRender(); 9832 9833 super(); 9834 enableAlpha = true; 9835 _width = w; 9836 _height = h; 9837 } else version(Windows) { 9838 super(null, w, h, true); // on Windows i'm just making a bitmap myself 9839 } 9840 } 9841 9842 version(Windows) 9843 final void forEachPixel(scope Color delegate(int x, int y) dg) { 9844 auto ptr = rawData; 9845 foreach(j; 0 .. _height) 9846 foreach(i; 0 .. _width) { 9847 auto color = dg(i, _height - j - 1); // cuz of upside down bitmap 9848 *rawData = (color.a * color.b) / 255; rawData++; 9849 *rawData = (color.a * color.g) / 255; rawData++; 9850 *rawData = (color.a * color.r) / 255; rawData++; 9851 *rawData = color.a; rawData++; 9852 } 9853 } 9854 9855 version(X11) 9856 protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) { 9857 assert(stops.length > 0); 9858 assert(stops.length <= 16, "I got lazy with buffers"); 9859 9860 XFixed[16] stopsPositions = void; 9861 XRenderColor[16] colors = void; 9862 9863 foreach(idx, stop; stops) { 9864 stopsPositions[idx] = cast(int)(stop.percentage * ushort.max); 9865 auto c = stop.c; 9866 colors[idx] = XRenderColor( 9867 cast(ushort)(c.r * ushort.max / 255), 9868 cast(ushort)(c.g * ushort.max / 255), 9869 cast(ushort)(c.b * ushort.max / 255), 9870 cast(ushort)(c.a * ubyte.max) // max value here is fractional 9871 ); 9872 } 9873 9874 xrenderPicture = dg(stopsPositions, colors); 9875 } 9876 9877 /// 9878 static struct Stop { 9879 float percentage; /// between 0 and 1.0 9880 Color c; 9881 } 9882 } 9883 9884 /++ 9885 Creates a linear gradient between p1 and p2. 9886 9887 X ONLY RIGHT NOW 9888 9889 History: 9890 Added November 20, 2021 (dub v10.4) 9891 9892 Bugs: 9893 Not yet implemented on Windows. 9894 +/ 9895 version(OSXCocoa) {} else // NotYetImplementedException 9896 class LinearGradient : Gradient { 9897 /++ 9898 9899 +/ 9900 this(Point p1, Point p2, Stop[] stops...) { 9901 super(p2.x, p2.y); 9902 9903 version(X11) { 9904 XLinearGradient gradient; 9905 gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max); 9906 gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max); 9907 9908 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9909 return XRenderCreateLinearGradient( 9910 XDisplayConnection.get, 9911 &gradient, 9912 stopsPositions.ptr, 9913 colors.ptr, 9914 cast(int) stops.length); 9915 }); 9916 } else version(Windows) { 9917 // FIXME 9918 forEachPixel((int x, int y) { 9919 import core.stdc.math; 9920 9921 //sqrtf( 9922 9923 return Color.transparent; 9924 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9925 }); 9926 } 9927 } 9928 } 9929 9930 /++ 9931 A conical gradient goes from color to color around a circumference from a center point. 9932 9933 X ONLY RIGHT NOW 9934 9935 History: 9936 Added November 20, 2021 (dub v10.4) 9937 9938 Bugs: 9939 Not yet implemented on Windows. 9940 +/ 9941 version(OSXCocoa) {} else // NotYetImplementedException 9942 class ConicalGradient : Gradient { 9943 /++ 9944 9945 +/ 9946 this(Point center, float angleInDegrees, Stop[] stops...) { 9947 super(center.x * 2, center.y * 2); 9948 9949 version(X11) { 9950 XConicalGradient gradient; 9951 gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max); 9952 gradient.angle = cast(int)(angleInDegrees * ushort.max); 9953 9954 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9955 return XRenderCreateConicalGradient( 9956 XDisplayConnection.get, 9957 &gradient, 9958 stopsPositions.ptr, 9959 colors.ptr, 9960 cast(int) stops.length); 9961 }); 9962 } else version(Windows) { 9963 // FIXME 9964 forEachPixel((int x, int y) { 9965 import core.stdc.math; 9966 9967 //sqrtf( 9968 9969 return Color.transparent; 9970 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9971 }); 9972 9973 } 9974 } 9975 } 9976 9977 /++ 9978 A radial gradient goes from color to color based on distance from the center. 9979 It is like rings of color. 9980 9981 X ONLY RIGHT NOW 9982 9983 9984 More specifically, you create two circles: an inner circle and an outer circle. 9985 The gradient is only drawn in the area outside the inner circle but inside the outer 9986 circle. The closest line between those two circles forms the line for the gradient 9987 and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around. 9988 9989 History: 9990 Added November 20, 2021 (dub v10.4) 9991 9992 Bugs: 9993 Not yet implemented on Windows. 9994 +/ 9995 version(OSXCocoa) {} else // NotYetImplementedException 9996 class RadialGradient : Gradient { 9997 /++ 9998 9999 +/ 10000 this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) { 10001 super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5)); 10002 10003 version(X11) { 10004 XRadialGradient gradient; 10005 gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max)); 10006 gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max)); 10007 10008 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 10009 return XRenderCreateRadialGradient( 10010 XDisplayConnection.get, 10011 &gradient, 10012 stopsPositions.ptr, 10013 colors.ptr, 10014 cast(int) stops.length); 10015 }); 10016 } else version(Windows) { 10017 // FIXME 10018 forEachPixel((int x, int y) { 10019 import core.stdc.math; 10020 10021 //sqrtf( 10022 10023 return Color.transparent; 10024 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 10025 }); 10026 } 10027 } 10028 } 10029 10030 10031 10032 /+ 10033 NOT IMPLEMENTED 10034 10035 A display-stored image optimized for relatively quick drawing, like 10036 [Sprite], but this one supports alpha channel blending and does NOT 10037 support direct drawing upon it with a [ScreenPainter]. 10038 10039 You can think of it as an [arsd.game.OpenGlTexture] for usage with a 10040 plain [ScreenPainter]... sort of. 10041 10042 On X11, it requires the Xrender extension and library. This is available 10043 almost everywhere though. 10044 10045 History: 10046 Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED 10047 +/ 10048 version(none) 10049 class AlphaSprite { 10050 /++ 10051 Copies the given image into it. 10052 +/ 10053 this(MemoryImage img) { 10054 10055 if(!XRenderLibrary.loadAttempted) { 10056 XRenderLibrary.loadDynamicLibrary(); 10057 10058 // FIXME: this needs to be reconstructed when the X server changes 10059 repopulateX(); 10060 } 10061 if(!XRenderLibrary.loadSuccessful) 10062 throw new Exception("XRender library load failure"); 10063 10064 // I probably need to put the alpha mask in a separate Picture 10065 // ugh 10066 // maybe the Sprite itself can have an alpha bitmask anyway 10067 10068 10069 auto display = XDisplayConnection.get(); 10070 pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 10071 10072 10073 XRenderPictureAttributes attrs; 10074 10075 handle = XRenderCreatePicture( 10076 XDisplayConnection.get, 10077 pixmap, 10078 RGBA, 10079 0, 10080 &attrs 10081 ); 10082 10083 } 10084 10085 // maybe i'll use the create gradient functions too with static factories.. 10086 10087 void drawAt(ScreenPainter painter, Point where) { 10088 //painter.drawPixmap(this, where); 10089 10090 XRenderPictureAttributes attrs; 10091 10092 auto pic = XRenderCreatePicture( 10093 XDisplayConnection.get, 10094 painter.impl.d, 10095 RGB, 10096 0, 10097 &attrs 10098 ); 10099 10100 XRenderComposite( 10101 XDisplayConnection.get, 10102 3, // PictOpOver 10103 handle, 10104 None, 10105 pic, 10106 0, // src 10107 0, 10108 0, // mask 10109 0, 10110 10, // dest 10111 10, 10112 100, // width 10113 100 10114 ); 10115 10116 /+ 10117 XRenderFreePicture( 10118 XDisplayConnection.get, 10119 pic 10120 ); 10121 10122 XRenderFreePicture( 10123 XDisplayConnection.get, 10124 fill 10125 ); 10126 +/ 10127 // on Windows you can stretch but Xrender still can't :( 10128 } 10129 10130 static XRenderPictFormat* RGB; 10131 static XRenderPictFormat* RGBA; 10132 static void repopulateX() { 10133 auto display = XDisplayConnection.get; 10134 RGB = XRenderFindStandardFormat(display, PictStandardRGB24); 10135 RGBA = XRenderFindStandardFormat(display, PictStandardARGB32); 10136 } 10137 10138 XPixmap pixmap; 10139 Picture handle; 10140 } 10141 10142 /// 10143 interface CapableOfBeingDrawnUpon { 10144 /// 10145 ScreenPainter draw(); 10146 /// 10147 int width(); 10148 /// 10149 int height(); 10150 protected ScreenPainterImplementation* activeScreenPainter(); 10151 protected void activeScreenPainter(ScreenPainterImplementation*); 10152 bool closed(); 10153 10154 void delegate() paintingFinishedDg(); 10155 10156 /// Be warned: this can be a very slow operation 10157 TrueColorImage takeScreenshot(); 10158 } 10159 10160 /// 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]. 10161 void flushGui() { 10162 version(X11) { 10163 auto dpy = XDisplayConnection.get(); 10164 XLockDisplay(dpy); 10165 scope(exit) XUnlockDisplay(dpy); 10166 XFlush(dpy); 10167 } 10168 } 10169 10170 /++ 10171 Runs the given code in the GUI thread when its event loop 10172 is available, blocking until it completes. This allows you 10173 to create and manipulate windows from another thread without 10174 invoking undefined behavior. 10175 10176 If this is the gui thread, it runs the code immediately. 10177 10178 If no gui thread exists yet, the current thread is assumed 10179 to be it. Attempting to create windows or run the event loop 10180 in any other thread will cause an assertion failure. 10181 10182 10183 $(TIP 10184 Did you know you can use UFCS on delegate literals? 10185 10186 () { 10187 // code here 10188 }.runInGuiThread; 10189 ) 10190 10191 Returns: 10192 `true` if the function was called, `false` if it was not. 10193 The function may not be called because the gui thread had 10194 already terminated by the time you called this. 10195 10196 History: 10197 Added April 10, 2020 (v7.2.0) 10198 10199 Return value added and implementation tweaked to avoid locking 10200 at program termination on February 24, 2021 (v9.2.1). 10201 +/ 10202 bool runInGuiThread(scope void delegate() dg) @trusted { 10203 claimGuiThread(); 10204 10205 if(thisIsGuiThread) { 10206 dg(); 10207 return true; 10208 } 10209 10210 if(guiThreadTerminating) 10211 return false; 10212 10213 import core.sync.semaphore; 10214 static Semaphore sc; 10215 if(sc is null) 10216 sc = new Semaphore(); 10217 10218 static RunQueueMember* rqm; 10219 if(rqm is null) 10220 rqm = new RunQueueMember; 10221 rqm.dg = cast(typeof(rqm.dg)) dg; 10222 rqm.signal = sc; 10223 rqm.thrown = null; 10224 10225 synchronized(runInGuiThreadLock) { 10226 runInGuiThreadQueue ~= rqm; 10227 } 10228 10229 if(!SimpleWindow.eventWakeUp()) 10230 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10231 10232 rqm.signal.wait(); 10233 auto t = rqm.thrown; 10234 10235 if(t) 10236 throw t; 10237 10238 return true; 10239 } 10240 10241 // note it runs sync if this is the gui thread.... 10242 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow { 10243 claimGuiThread(); 10244 10245 try { 10246 10247 if(thisIsGuiThread) { 10248 dg(); 10249 return; 10250 } 10251 10252 if(guiThreadTerminating) 10253 return; 10254 10255 RunQueueMember* rqm = new RunQueueMember; 10256 rqm.dg = cast(typeof(rqm.dg)) dg; 10257 rqm.signal = null; 10258 rqm.thrown = null; 10259 10260 synchronized(runInGuiThreadLock) { 10261 runInGuiThreadQueue ~= rqm; 10262 } 10263 10264 if(!SimpleWindow.eventWakeUp()) 10265 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10266 } catch(Exception e) { 10267 if(handleError) 10268 handleError(e); 10269 } 10270 } 10271 10272 private void runPendingRunInGuiThreadDelegates() { 10273 more: 10274 RunQueueMember* next; 10275 synchronized(runInGuiThreadLock) { 10276 if(runInGuiThreadQueue.length) { 10277 next = runInGuiThreadQueue[0]; 10278 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 10279 } else { 10280 next = null; 10281 } 10282 } 10283 10284 if(next) { 10285 try { 10286 next.dg(); 10287 next.thrown = null; 10288 } catch(Throwable t) { 10289 next.thrown = t; 10290 } 10291 10292 if(next.signal) 10293 next.signal.notify(); 10294 10295 goto more; 10296 } 10297 } 10298 10299 private void claimGuiThread() nothrow { 10300 import core.atomic; 10301 if(cas(&guiThreadExists_, false, true)) 10302 thisIsGuiThread = true; 10303 } 10304 10305 private struct RunQueueMember { 10306 void delegate() dg; 10307 import core.sync.semaphore; 10308 Semaphore signal; 10309 Throwable thrown; 10310 } 10311 10312 private __gshared RunQueueMember*[] runInGuiThreadQueue; 10313 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE 10314 private bool thisIsGuiThread = false; 10315 private shared bool guiThreadExists_ = false; 10316 private shared bool guiThreadTerminating = false; 10317 10318 /++ 10319 Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d 10320 event loop. All windows must be exclusively created and managed by a single thread. 10321 10322 If no gui thread exists, simpledisplay.d will automatically adopt the current thread 10323 when you call one of its constructors. 10324 10325 If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this 10326 one. If so, you can run gui functions on it. If not, don't. The helper functions 10327 [runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically. 10328 10329 The reason this function is available is in case you want to message pass between a gui 10330 thread and your current thread. If no gui thread exists or if this is the gui thread, 10331 you're liable to deadlock when trying to communicate since you'd end up talking to yourself. 10332 10333 History: 10334 Added December 3, 2021 (dub v10.5) 10335 +/ 10336 public bool guiThreadExists() { 10337 return guiThreadExists_; 10338 } 10339 10340 /++ 10341 Returns `true` if this thread is either running or set to be running the 10342 simpledisplay.d gui core event loop because it owns windows. 10343 10344 It is important to keep gui-related functionality in the right thread, so you will 10345 want to `runInGuiThread` when you call them (with some specific exceptions called 10346 out in those specific functions' documentation). Notably, all windows must be 10347 created and managed only from the gui thread. 10348 10349 Will return false if simpledisplay's other functions haven't been called 10350 yet; check [guiThreadExists] in addition to this. 10351 10352 History: 10353 Added December 3, 2021 (dub v10.5) 10354 +/ 10355 public bool thisThreadRunningGui() { 10356 return thisIsGuiThread; 10357 } 10358 10359 /++ 10360 Function to help temporarily print debugging info. It will bypass any stdout/err redirection 10361 and go to the controlling tty or console (attaching to the parent and/or allocating one as 10362 needed on Windows. Please note it may overwrite output from other programs in the parent and the 10363 allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log 10364 file instead if you are in one of those situations). 10365 10366 It does not support outputting very many types; just strings and ints are likely to actually work. 10367 10368 It will perform very slowly and swallows any errors that may occur. Moreover, the specific output 10369 is unspecified meaning I can change it at any time. The only point of this function is to help 10370 in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword 10371 and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use 10372 in those contexts. 10373 10374 $(WARNING 10375 I reserve the right to change this function at any time. You can use it if it helps you 10376 but do not rely on it for anything permanent. 10377 ) 10378 10379 History: 10380 Added December 3, 2021. Not formally supported under any stable tag. 10381 +/ 10382 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted { 10383 try { 10384 version(Windows) { 10385 import core.sys.windows.wincon; 10386 if(!AttachConsole(ATTACH_PARENT_PROCESS)) 10387 AllocConsole(); 10388 const(char)* fn = "CONOUT$"; 10389 } else version(Posix) { 10390 const(char)* fn = "/dev/tty"; 10391 } else static assert(0, "Function not implemented for your system"); 10392 10393 if(fileOverride.length) 10394 fn = fileOverride.ptr; 10395 10396 import core.stdc.stdio; 10397 auto fp = fopen(fn, "wt"); 10398 if(fp is null) return; 10399 scope(exit) fclose(fp); 10400 10401 string str; 10402 foreach(item; t) { 10403 static if(is(typeof(item) : const(char)[])) 10404 str ~= item; 10405 else 10406 str ~= toInternal!string(item); 10407 str ~= " "; 10408 } 10409 str ~= "\n"; 10410 10411 fwrite(str.ptr, 1, str.length, fp); 10412 fflush(fp); 10413 } catch(Exception e) { 10414 // sorry no hope 10415 } 10416 } 10417 10418 private void guiThreadFinalize() { 10419 assert(thisIsGuiThread); 10420 10421 guiThreadTerminating = true; // don't add any more from this point on 10422 runPendingRunInGuiThreadDelegates(); 10423 } 10424 10425 /+ 10426 interface IPromise { 10427 void reportProgress(int current, int max, string message); 10428 10429 /+ // not formally in cuz of templates but still 10430 IPromise Then(); 10431 IPromise Catch(); 10432 IPromise Finally(); 10433 +/ 10434 } 10435 10436 /+ 10437 auto promise = async({ ... }); 10438 promise.Then(whatever). 10439 Then(whateverelse). 10440 Catch((exception) { }); 10441 10442 10443 A promise is run inside a fiber and it looks something like: 10444 10445 try { 10446 auto res = whatever(); 10447 auto res2 = whateverelse(res); 10448 } catch(Exception e) { 10449 { }(e); 10450 } 10451 10452 When a thing succeeds, it is passed as an arg to the next 10453 +/ 10454 class Promise(T) : IPromise { 10455 auto Then() { return null; } 10456 auto Catch() { return null; } 10457 auto Finally() { return null; } 10458 10459 // wait for it to resolve and return the value, or rethrow the error if that occurred. 10460 // cannot be called from the gui thread, but this is caught at runtime instead of compile time. 10461 T await(); 10462 } 10463 10464 interface Task { 10465 } 10466 10467 interface Resolvable(T) : Task { 10468 void run(); 10469 10470 void resolve(T); 10471 10472 Resolvable!T then(void delegate(T)); // returns a new promise 10473 Resolvable!T error(Throwable); // js catch 10474 Resolvable!T completed(); // js finally 10475 10476 } 10477 10478 /++ 10479 Runs `work` in a helper thread and sends its return value back to the main gui 10480 thread as the argument to `uponCompletion`. If `work` throws, the exception is 10481 sent to the `uponThrown` if given, or if null, rethrown from the event loop to 10482 kill the program. 10483 10484 You can call reportProgress(position, max, message) to update your parent window 10485 on your progress. 10486 10487 I should also use `shared` methods. FIXME 10488 10489 History: 10490 Added March 6, 2021 (dub version 9.3). 10491 +/ 10492 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) { 10493 uponCompletion(work(null)); 10494 } 10495 10496 +/ 10497 10498 /// Used internal to dispatch events to various classes. 10499 interface CapableOfHandlingNativeEvent { 10500 NativeEventHandler getNativeEventHandler(); 10501 10502 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 10503 10504 version(X11) { 10505 // if this is impossible, you are allowed to just throw from it 10506 // Note: if you call it from another object, set a flag cuz the manger will call you again 10507 void recreateAfterDisconnect(); 10508 // discard any *connection specific* state, but keep enough that you 10509 // can be recreated if possible. discardConnectionState() is always called immediately 10510 // before recreateAfterDisconnect(), so you can set a flag there to decide if 10511 // you need initialization order 10512 void discardConnectionState(); 10513 } 10514 } 10515 10516 version(X11) 10517 /++ 10518 State of keys on mouse events, especially motion. 10519 10520 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 10521 +/ 10522 enum ModifierState : uint { 10523 shift = 1, /// 10524 capsLock = 2, /// 10525 ctrl = 4, /// 10526 alt = 8, /// Not always available on Windows 10527 windows = 64, /// ditto 10528 numLock = 16, /// 10529 10530 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10531 middleButtonDown = 512, /// ditto 10532 rightButtonDown = 1024, /// ditto 10533 } 10534 else version(Windows) 10535 /// ditto 10536 enum ModifierState : uint { 10537 shift = 4, /// 10538 ctrl = 8, /// 10539 10540 // i'm not sure if the next two are available 10541 alt = 256, /// not always available on Windows 10542 windows = 512, /// ditto 10543 10544 capsLock = 1024, /// 10545 numLock = 2048, /// 10546 10547 leftButtonDown = 1, /// not available on key events 10548 middleButtonDown = 16, /// ditto 10549 rightButtonDown = 2, /// ditto 10550 10551 backButtonDown = 0x20, /// not available on X 10552 forwardButtonDown = 0x40, /// ditto 10553 } 10554 else version(OSXCocoa) 10555 // FIXME FIXME NotYetImplementedException 10556 enum ModifierState : uint { 10557 shift = 1, /// 10558 capsLock = 2, /// 10559 ctrl = 4, /// 10560 alt = 8, /// Not always available on Windows 10561 windows = 64, /// ditto 10562 numLock = 16, /// 10563 10564 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10565 middleButtonDown = 512, /// ditto 10566 rightButtonDown = 1024, /// ditto 10567 } 10568 10569 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them. 10570 enum MouseButton : int { 10571 none = 0, 10572 left = 1, /// 10573 right = 2, /// 10574 middle = 4, /// 10575 wheelUp = 8, /// 10576 wheelDown = 16, /// 10577 backButton = 32, /// often found on the thumb and used for back in browsers 10578 forwardButton = 64, /// often found on the thumb and used for forward in browsers 10579 } 10580 10581 version(X11) { 10582 // FIXME: match ASCII whenever we can. Most of it is already there, 10583 // but there's a few exceptions and mismatches with Windows 10584 10585 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 10586 enum Key { 10587 Escape = 0xff1b, /// 10588 F1 = 0xffbe, /// 10589 F2 = 0xffbf, /// 10590 F3 = 0xffc0, /// 10591 F4 = 0xffc1, /// 10592 F5 = 0xffc2, /// 10593 F6 = 0xffc3, /// 10594 F7 = 0xffc4, /// 10595 F8 = 0xffc5, /// 10596 F9 = 0xffc6, /// 10597 F10 = 0xffc7, /// 10598 F11 = 0xffc8, /// 10599 F12 = 0xffc9, /// 10600 PrintScreen = 0xff61, /// 10601 ScrollLock = 0xff14, /// 10602 Pause = 0xff13, /// 10603 Grave = 0x60, /// The $(BACKTICK) ~ key 10604 // number keys across the top of the keyboard 10605 N1 = 0x31, /// Number key atop the keyboard 10606 N2 = 0x32, /// 10607 N3 = 0x33, /// 10608 N4 = 0x34, /// 10609 N5 = 0x35, /// 10610 N6 = 0x36, /// 10611 N7 = 0x37, /// 10612 N8 = 0x38, /// 10613 N9 = 0x39, /// 10614 N0 = 0x30, /// 10615 Dash = 0x2d, /// 10616 Equals = 0x3d, /// 10617 Backslash = 0x5c, /// The \ | key 10618 Backspace = 0xff08, /// 10619 Insert = 0xff63, /// 10620 Home = 0xff50, /// 10621 PageUp = 0xff55, /// 10622 Delete = 0xffff, /// 10623 End = 0xff57, /// 10624 PageDown = 0xff56, /// 10625 Up = 0xff52, /// 10626 Down = 0xff54, /// 10627 Left = 0xff51, /// 10628 Right = 0xff53, /// 10629 10630 Tab = 0xff09, /// 10631 Q = 0x71, /// 10632 W = 0x77, /// 10633 E = 0x65, /// 10634 R = 0x72, /// 10635 T = 0x74, /// 10636 Y = 0x79, /// 10637 U = 0x75, /// 10638 I = 0x69, /// 10639 O = 0x6f, /// 10640 P = 0x70, /// 10641 LeftBracket = 0x5b, /// the [ { key 10642 RightBracket = 0x5d, /// the ] } key 10643 CapsLock = 0xffe5, /// 10644 A = 0x61, /// 10645 S = 0x73, /// 10646 D = 0x64, /// 10647 F = 0x66, /// 10648 G = 0x67, /// 10649 H = 0x68, /// 10650 J = 0x6a, /// 10651 K = 0x6b, /// 10652 L = 0x6c, /// 10653 Semicolon = 0x3b, /// 10654 Apostrophe = 0x27, /// 10655 Enter = 0xff0d, /// 10656 Shift = 0xffe1, /// 10657 Z = 0x7a, /// 10658 X = 0x78, /// 10659 C = 0x63, /// 10660 V = 0x76, /// 10661 B = 0x62, /// 10662 N = 0x6e, /// 10663 M = 0x6d, /// 10664 Comma = 0x2c, /// 10665 Period = 0x2e, /// 10666 Slash = 0x2f, /// the / ? key 10667 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 10668 Ctrl = 0xffe3, /// 10669 Windows = 0xffeb, /// 10670 Alt = 0xffe9, /// 10671 Space = 0x20, /// 10672 Alt_r = 0xffea, /// ditto of shift_r 10673 Windows_r = 0xffec, /// 10674 Menu = 0xff67, /// 10675 Ctrl_r = 0xffe4, /// 10676 10677 NumLock = 0xff7f, /// 10678 Divide = 0xffaf, /// The / key on the number pad 10679 Multiply = 0xffaa, /// The * key on the number pad 10680 Minus = 0xffad, /// The - key on the number pad 10681 Plus = 0xffab, /// The + key on the number pad 10682 PadEnter = 0xff8d, /// Numberpad enter key 10683 Pad1 = 0xff9c, /// Numberpad keys 10684 Pad2 = 0xff99, /// 10685 Pad3 = 0xff9b, /// 10686 Pad4 = 0xff96, /// 10687 Pad5 = 0xff9d, /// 10688 Pad6 = 0xff98, /// 10689 Pad7 = 0xff95, /// 10690 Pad8 = 0xff97, /// 10691 Pad9 = 0xff9a, /// 10692 Pad0 = 0xff9e, /// 10693 PadDot = 0xff9f, /// 10694 } 10695 } else version(Windows) { 10696 // the character here is for en-us layouts and for illustration only 10697 // if you actually want to get characters, wait for character events 10698 // (the argument to your event handler is simply a dchar) 10699 // those will be converted by the OS for the right locale. 10700 10701 enum Key { 10702 Escape = 0x1b, 10703 F1 = 0x70, 10704 F2 = 0x71, 10705 F3 = 0x72, 10706 F4 = 0x73, 10707 F5 = 0x74, 10708 F6 = 0x75, 10709 F7 = 0x76, 10710 F8 = 0x77, 10711 F9 = 0x78, 10712 F10 = 0x79, 10713 F11 = 0x7a, 10714 F12 = 0x7b, 10715 PrintScreen = 0x2c, 10716 ScrollLock = 0x91, 10717 Pause = 0x13, 10718 Grave = 0xc0, 10719 // number keys across the top of the keyboard 10720 N1 = 0x31, 10721 N2 = 0x32, 10722 N3 = 0x33, 10723 N4 = 0x34, 10724 N5 = 0x35, 10725 N6 = 0x36, 10726 N7 = 0x37, 10727 N8 = 0x38, 10728 N9 = 0x39, 10729 N0 = 0x30, 10730 Dash = 0xbd, 10731 Equals = 0xbb, 10732 Backslash = 0xdc, 10733 Backspace = 0x08, 10734 Insert = 0x2d, 10735 Home = 0x24, 10736 PageUp = 0x21, 10737 Delete = 0x2e, 10738 End = 0x23, 10739 PageDown = 0x22, 10740 Up = 0x26, 10741 Down = 0x28, 10742 Left = 0x25, 10743 Right = 0x27, 10744 10745 Tab = 0x09, 10746 Q = 0x51, 10747 W = 0x57, 10748 E = 0x45, 10749 R = 0x52, 10750 T = 0x54, 10751 Y = 0x59, 10752 U = 0x55, 10753 I = 0x49, 10754 O = 0x4f, 10755 P = 0x50, 10756 LeftBracket = 0xdb, 10757 RightBracket = 0xdd, 10758 CapsLock = 0x14, 10759 A = 0x41, 10760 S = 0x53, 10761 D = 0x44, 10762 F = 0x46, 10763 G = 0x47, 10764 H = 0x48, 10765 J = 0x4a, 10766 K = 0x4b, 10767 L = 0x4c, 10768 Semicolon = 0xba, 10769 Apostrophe = 0xde, 10770 Enter = 0x0d, 10771 Shift = 0x10, 10772 Z = 0x5a, 10773 X = 0x58, 10774 C = 0x43, 10775 V = 0x56, 10776 B = 0x42, 10777 N = 0x4e, 10778 M = 0x4d, 10779 Comma = 0xbc, 10780 Period = 0xbe, 10781 Slash = 0xbf, 10782 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 10783 Ctrl = 0x11, 10784 Windows = 0x5b, 10785 Alt = -5, // FIXME 10786 Space = 0x20, 10787 Alt_r = 0xffea, // ditto of shift_r 10788 Windows_r = 0x5c, // ditto of shift_r 10789 Menu = 0x5d, 10790 Ctrl_r = 0xa3, // ditto of shift_r 10791 10792 NumLock = 0x90, 10793 Divide = 0x6f, 10794 Multiply = 0x6a, 10795 Minus = 0x6d, 10796 Plus = 0x6b, 10797 PadEnter = -8, // FIXME 10798 Pad1 = 0x61, 10799 Pad2 = 0x62, 10800 Pad3 = 0x63, 10801 Pad4 = 0x64, 10802 Pad5 = 0x65, 10803 Pad6 = 0x66, 10804 Pad7 = 0x67, 10805 Pad8 = 0x68, 10806 Pad9 = 0x69, 10807 Pad0 = 0x60, 10808 PadDot = 0x6e, 10809 } 10810 10811 // I'm keeping this around for reference purposes 10812 // ideally all these buttons will be listed for all platforms, 10813 // but now now I'm just focusing on my US keyboard 10814 version(none) 10815 enum Key { 10816 LBUTTON = 0x01, 10817 RBUTTON = 0x02, 10818 CANCEL = 0x03, 10819 MBUTTON = 0x04, 10820 //static if (_WIN32_WINNT > = 0x500) { 10821 XBUTTON1 = 0x05, 10822 XBUTTON2 = 0x06, 10823 //} 10824 BACK = 0x08, 10825 TAB = 0x09, 10826 CLEAR = 0x0C, 10827 RETURN = 0x0D, 10828 SHIFT = 0x10, 10829 CONTROL = 0x11, 10830 MENU = 0x12, 10831 PAUSE = 0x13, 10832 CAPITAL = 0x14, 10833 KANA = 0x15, 10834 HANGEUL = 0x15, 10835 HANGUL = 0x15, 10836 JUNJA = 0x17, 10837 FINAL = 0x18, 10838 HANJA = 0x19, 10839 KANJI = 0x19, 10840 ESCAPE = 0x1B, 10841 CONVERT = 0x1C, 10842 NONCONVERT = 0x1D, 10843 ACCEPT = 0x1E, 10844 MODECHANGE = 0x1F, 10845 SPACE = 0x20, 10846 PRIOR = 0x21, 10847 NEXT = 0x22, 10848 END = 0x23, 10849 HOME = 0x24, 10850 LEFT = 0x25, 10851 UP = 0x26, 10852 RIGHT = 0x27, 10853 DOWN = 0x28, 10854 SELECT = 0x29, 10855 PRINT = 0x2A, 10856 EXECUTE = 0x2B, 10857 SNAPSHOT = 0x2C, 10858 INSERT = 0x2D, 10859 DELETE = 0x2E, 10860 HELP = 0x2F, 10861 LWIN = 0x5B, 10862 RWIN = 0x5C, 10863 APPS = 0x5D, 10864 SLEEP = 0x5F, 10865 NUMPAD0 = 0x60, 10866 NUMPAD1 = 0x61, 10867 NUMPAD2 = 0x62, 10868 NUMPAD3 = 0x63, 10869 NUMPAD4 = 0x64, 10870 NUMPAD5 = 0x65, 10871 NUMPAD6 = 0x66, 10872 NUMPAD7 = 0x67, 10873 NUMPAD8 = 0x68, 10874 NUMPAD9 = 0x69, 10875 MULTIPLY = 0x6A, 10876 ADD = 0x6B, 10877 SEPARATOR = 0x6C, 10878 SUBTRACT = 0x6D, 10879 DECIMAL = 0x6E, 10880 DIVIDE = 0x6F, 10881 F1 = 0x70, 10882 F2 = 0x71, 10883 F3 = 0x72, 10884 F4 = 0x73, 10885 F5 = 0x74, 10886 F6 = 0x75, 10887 F7 = 0x76, 10888 F8 = 0x77, 10889 F9 = 0x78, 10890 F10 = 0x79, 10891 F11 = 0x7A, 10892 F12 = 0x7B, 10893 F13 = 0x7C, 10894 F14 = 0x7D, 10895 F15 = 0x7E, 10896 F16 = 0x7F, 10897 F17 = 0x80, 10898 F18 = 0x81, 10899 F19 = 0x82, 10900 F20 = 0x83, 10901 F21 = 0x84, 10902 F22 = 0x85, 10903 F23 = 0x86, 10904 F24 = 0x87, 10905 NUMLOCK = 0x90, 10906 SCROLL = 0x91, 10907 LSHIFT = 0xA0, 10908 RSHIFT = 0xA1, 10909 LCONTROL = 0xA2, 10910 RCONTROL = 0xA3, 10911 LMENU = 0xA4, 10912 RMENU = 0xA5, 10913 //static if (_WIN32_WINNT > = 0x500) { 10914 BROWSER_BACK = 0xA6, 10915 BROWSER_FORWARD = 0xA7, 10916 BROWSER_REFRESH = 0xA8, 10917 BROWSER_STOP = 0xA9, 10918 BROWSER_SEARCH = 0xAA, 10919 BROWSER_FAVORITES = 0xAB, 10920 BROWSER_HOME = 0xAC, 10921 VOLUME_MUTE = 0xAD, 10922 VOLUME_DOWN = 0xAE, 10923 VOLUME_UP = 0xAF, 10924 MEDIA_NEXT_TRACK = 0xB0, 10925 MEDIA_PREV_TRACK = 0xB1, 10926 MEDIA_STOP = 0xB2, 10927 MEDIA_PLAY_PAUSE = 0xB3, 10928 LAUNCH_MAIL = 0xB4, 10929 LAUNCH_MEDIA_SELECT = 0xB5, 10930 LAUNCH_APP1 = 0xB6, 10931 LAUNCH_APP2 = 0xB7, 10932 //} 10933 OEM_1 = 0xBA, 10934 //static if (_WIN32_WINNT > = 0x500) { 10935 OEM_PLUS = 0xBB, 10936 OEM_COMMA = 0xBC, 10937 OEM_MINUS = 0xBD, 10938 OEM_PERIOD = 0xBE, 10939 //} 10940 OEM_2 = 0xBF, 10941 OEM_3 = 0xC0, 10942 OEM_4 = 0xDB, 10943 OEM_5 = 0xDC, 10944 OEM_6 = 0xDD, 10945 OEM_7 = 0xDE, 10946 OEM_8 = 0xDF, 10947 //static if (_WIN32_WINNT > = 0x500) { 10948 OEM_102 = 0xE2, 10949 //} 10950 PROCESSKEY = 0xE5, 10951 //static if (_WIN32_WINNT > = 0x500) { 10952 PACKET = 0xE7, 10953 //} 10954 ATTN = 0xF6, 10955 CRSEL = 0xF7, 10956 EXSEL = 0xF8, 10957 EREOF = 0xF9, 10958 PLAY = 0xFA, 10959 ZOOM = 0xFB, 10960 NONAME = 0xFC, 10961 PA1 = 0xFD, 10962 OEM_CLEAR = 0xFE, 10963 } 10964 10965 } else version(OSXCocoa) { 10966 enum Key { 10967 Escape = 53, 10968 F1 = 122, 10969 F2 = 120, 10970 F3 = 99, 10971 F4 = 118, 10972 F5 = 96, 10973 F6 = 97, 10974 F7 = 98, 10975 F8 = 100, 10976 F9 = 101, 10977 F10 = 109, 10978 F11 = 103, 10979 F12 = 111, 10980 PrintScreen = 105, 10981 ScrollLock = 107, 10982 Pause = 113, 10983 Grave = 50, 10984 // number keys across the top of the keyboard 10985 N1 = 18, 10986 N2 = 19, 10987 N3 = 20, 10988 N4 = 21, 10989 N5 = 23, 10990 N6 = 22, 10991 N7 = 26, 10992 N8 = 28, 10993 N9 = 25, 10994 N0 = 29, 10995 Dash = 27, 10996 Equals = 24, 10997 Backslash = 42, 10998 Backspace = 51, 10999 Insert = 114, 11000 Home = 115, 11001 PageUp = 116, 11002 Delete = 117, 11003 End = 119, 11004 PageDown = 121, 11005 Up = 126, 11006 Down = 125, 11007 Left = 123, 11008 Right = 124, 11009 11010 Tab = 48, 11011 Q = 12, 11012 W = 13, 11013 E = 14, 11014 R = 15, 11015 T = 17, 11016 Y = 16, 11017 U = 32, 11018 I = 34, 11019 O = 31, 11020 P = 35, 11021 LeftBracket = 33, 11022 RightBracket = 30, 11023 CapsLock = 57, 11024 A = 0, 11025 S = 1, 11026 D = 2, 11027 F = 3, 11028 G = 5, 11029 H = 4, 11030 J = 38, 11031 K = 40, 11032 L = 37, 11033 Semicolon = 41, 11034 Apostrophe = 39, 11035 Enter = 36, 11036 Shift = 56, 11037 Z = 6, 11038 X = 7, 11039 C = 8, 11040 V = 9, 11041 B = 11, 11042 N = 45, 11043 M = 46, 11044 Comma = 43, 11045 Period = 47, 11046 Slash = 44, 11047 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 11048 Ctrl = 59, 11049 Windows = 55, 11050 Alt = 58, 11051 Space = 49, 11052 Alt_r = -3, // ditto of shift_r 11053 Windows_r = -2, 11054 Menu = 110, 11055 Ctrl_r = -1, 11056 11057 NumLock = 1, 11058 Divide = 75, 11059 Multiply = 67, 11060 Minus = 78, 11061 Plus = 69, 11062 PadEnter = 76, 11063 Pad1 = 83, 11064 Pad2 = 84, 11065 Pad3 = 85, 11066 Pad4 = 86, 11067 Pad5 = 87, 11068 Pad6 = 88, 11069 Pad7 = 89, 11070 Pad8 = 91, 11071 Pad9 = 92, 11072 Pad0 = 82, 11073 PadDot = 65, 11074 } 11075 11076 } 11077 11078 /* Additional utilities */ 11079 11080 11081 Color fromHsl(real h, real s, real l) { 11082 return arsd.color.fromHsl([h,s,l]); 11083 } 11084 11085 11086 11087 /* ********** What follows is the system-specific implementations *********/ 11088 version(Windows) { 11089 11090 11091 // helpers for making HICONs from MemoryImages 11092 class WindowsIcon { 11093 struct Win32Icon { 11094 align(1): 11095 uint biSize; 11096 int biWidth; 11097 int biHeight; 11098 ushort biPlanes; 11099 ushort biBitCount; 11100 uint biCompression; 11101 uint biSizeImage; 11102 int biXPelsPerMeter; 11103 int biYPelsPerMeter; 11104 uint biClrUsed; 11105 uint biClrImportant; 11106 // RGBQUAD[colorCount] biColors; 11107 /* Pixels: 11108 Uint8 pixels[] 11109 */ 11110 /* Mask: 11111 Uint8 mask[] 11112 */ 11113 } 11114 11115 ubyte[] fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 11116 11117 assert(mi.width <= 256, "image too wide"); 11118 assert(mi.height <= 256, "image too tall"); 11119 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 11120 assert(mi.height % 4 == 0, "image not multiple of 4 height"); 11121 11122 int icon_plen = mi.width * mi.height * 4; 11123 int icon_mlen = mi.width * mi.height / 8; 11124 11125 int colorCount = 0; 11126 icon_len = 40 + icon_plen + icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 11127 11128 ubyte[] memory = new ubyte[](Win32Icon.sizeof + icon_plen + icon_mlen); 11129 Win32Icon* icon_win32 = cast(Win32Icon*) memory.ptr; 11130 11131 auto data = memory[Win32Icon.sizeof .. $]; 11132 11133 width = mi.width; 11134 height = mi.height; 11135 11136 auto trueColorImage = mi.getAsTrueColorImage(); 11137 11138 icon_win32.biSize = 40; 11139 icon_win32.biWidth = mi.width; 11140 icon_win32.biHeight = mi.height*2; 11141 icon_win32.biPlanes = 1; 11142 icon_win32.biBitCount = 32; 11143 icon_win32.biSizeImage = icon_plen + icon_mlen; 11144 11145 int offset = 0; 11146 int andOff = icon_plen * 8; // the and offset is in bits 11147 11148 // leaving the and mask as the default 0 so the rgba alpha blend 11149 // does its thing instead 11150 for(int y = height - 1; y >= 0; y--) { 11151 int off2 = y * width * 4; 11152 foreach(x; 0 .. width) { 11153 data[offset + 2] = trueColorImage.imageData.bytes[off2 + 0]; 11154 data[offset + 1] = trueColorImage.imageData.bytes[off2 + 1]; 11155 data[offset + 0] = trueColorImage.imageData.bytes[off2 + 2]; 11156 data[offset + 3] = trueColorImage.imageData.bytes[off2 + 3]; 11157 11158 offset += 4; 11159 off2 += 4; 11160 } 11161 } 11162 11163 return memory; 11164 } 11165 11166 this(MemoryImage mi) { 11167 int icon_len, width, height; 11168 11169 auto icon_win32 = fromMemoryImage(mi, icon_len, width, height); 11170 11171 /* 11172 PNG* png = readPnpngData); 11173 PNGHeader pngh = getHeader(png); 11174 void* icon_win32; 11175 if(pngh.depth == 4) { 11176 auto i = new Win32Icon!(16); 11177 i.fromPNG(png, pngh, icon_len, width, height); 11178 icon_win32 = i; 11179 } 11180 else if(pngh.depth == 8) { 11181 auto i = new Win32Icon!(256); 11182 i.fromPNG(png, pngh, icon_len, width, height); 11183 icon_win32 = i; 11184 } else assert(0); 11185 */ 11186 11187 hIcon = CreateIconFromResourceEx(icon_win32.ptr, icon_len, true, 0x00030000, width, height, 0); 11188 11189 if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError()); 11190 } 11191 11192 ~this() { 11193 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 11194 DestroyIcon(hIcon); 11195 } 11196 11197 HICON hIcon; 11198 } 11199 11200 11201 11202 11203 11204 11205 alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler; 11206 alias HWND NativeWindowHandle; 11207 11208 extern(Windows) 11209 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 11210 try { 11211 if(SimpleWindow.handleNativeGlobalEvent !is null) { 11212 // it returns zero if the message is handled, so we won't do anything more there 11213 // do I like that though? 11214 int mustReturn; 11215 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn); 11216 if(mustReturn) 11217 return ret; 11218 } 11219 11220 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 11221 if(window.getNativeEventHandler !is null) { 11222 int mustReturn; 11223 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn); 11224 if(mustReturn) 11225 return ret; 11226 } 11227 if(auto w = cast(SimpleWindow) (*window)) 11228 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 11229 else 11230 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11231 } else { 11232 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11233 } 11234 } catch (Exception e) { 11235 try { 11236 sdpy_abort(e); 11237 return 0; 11238 } catch(Exception e) { assert(0); } 11239 } 11240 } 11241 11242 void sdpy_abort(Throwable e) nothrow { 11243 try 11244 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 11245 catch(Exception e) 11246 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 11247 ExitProcess(1); 11248 } 11249 11250 mixin template NativeScreenPainterImplementation() { 11251 HDC hdc; 11252 HWND hwnd; 11253 //HDC windowHdc; 11254 HBITMAP oldBmp; 11255 11256 void create(PaintingHandle window) { 11257 hwnd = window; 11258 11259 if(auto sw = cast(SimpleWindow) this.window) { 11260 // drawing on a window, double buffer 11261 auto windowHdc = GetDC(hwnd); 11262 11263 auto buffer = sw.impl.buffer; 11264 if(buffer is null) { 11265 hdc = windowHdc; 11266 windowDc = true; 11267 } else { 11268 hdc = CreateCompatibleDC(windowHdc); 11269 11270 ReleaseDC(hwnd, windowHdc); 11271 11272 oldBmp = SelectObject(hdc, buffer); 11273 } 11274 } else { 11275 // drawing on something else, draw directly 11276 hdc = CreateCompatibleDC(null); 11277 SelectObject(hdc, window); 11278 } 11279 11280 // X doesn't draw a text background, so neither should we 11281 SetBkMode(hdc, TRANSPARENT); 11282 11283 ensureDefaultFontLoaded(); 11284 11285 if(defaultGuiFont) { 11286 SelectObject(hdc, defaultGuiFont); 11287 // DeleteObject(defaultGuiFont); 11288 } 11289 } 11290 11291 static HFONT defaultGuiFont; 11292 static void ensureDefaultFontLoaded() { 11293 static bool triedDefaultGuiFont = false; 11294 if(!triedDefaultGuiFont) { 11295 NONCLIENTMETRICS params; 11296 params.cbSize = params.sizeof; 11297 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 11298 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 11299 } 11300 triedDefaultGuiFont = true; 11301 } 11302 } 11303 11304 private OperatingSystemFont _activeFont; 11305 11306 void setFont(OperatingSystemFont font) { 11307 _activeFont = font; 11308 if(font && font.font) { 11309 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 11310 // error... how to handle tho? 11311 } else { 11312 11313 } 11314 } 11315 else if(defaultGuiFont) 11316 SelectObject(hdc, defaultGuiFont); 11317 } 11318 11319 arsd.color.Rectangle _clipRectangle; 11320 11321 void setClipRectangle(int x, int y, int width, int height) { 11322 auto old = _clipRectangle; 11323 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 11324 if(old == _clipRectangle) 11325 return; 11326 11327 if(width == 0 || height == 0) { 11328 SelectClipRgn(hdc, null); 11329 } else { 11330 auto region = CreateRectRgn(x, y, x + width, y + height); 11331 SelectClipRgn(hdc, region); 11332 DeleteObject(region); 11333 } 11334 } 11335 11336 11337 // just because we can on Windows... 11338 //void create(Image image); 11339 11340 void invalidateRect(Rectangle invalidRect) { 11341 RECT rect; 11342 rect.left = invalidRect.left; 11343 rect.right = invalidRect.right; 11344 rect.top = invalidRect.top; 11345 rect.bottom = invalidRect.bottom; 11346 InvalidateRect(hwnd, &rect, false); 11347 } 11348 bool manualInvalidations; 11349 11350 void dispose() { 11351 // FIXME: this.window.width/height is probably wrong 11352 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 11353 // ReleaseDC(hwnd, windowHdc); 11354 11355 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 11356 if(cast(SimpleWindow) this.window) { 11357 if(!manualInvalidations) 11358 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 11359 } 11360 11361 if(originalPen !is null) 11362 SelectObject(hdc, originalPen); 11363 if(currentPen !is null) 11364 DeleteObject(currentPen); 11365 if(originalBrush !is null) 11366 SelectObject(hdc, originalBrush); 11367 if(currentBrush !is null) 11368 DeleteObject(currentBrush); 11369 11370 SelectObject(hdc, oldBmp); 11371 11372 if(windowDc) 11373 ReleaseDC(hwnd, hdc); 11374 else 11375 DeleteDC(hdc); 11376 11377 if(window.paintingFinishedDg !is null) 11378 window.paintingFinishedDg()(); 11379 } 11380 11381 bool windowDc; 11382 HPEN originalPen; 11383 HPEN currentPen; 11384 11385 Pen _activePen; 11386 11387 Color _outlineColor; 11388 11389 @property void pen(Pen p) { 11390 _activePen = p; 11391 _outlineColor = p.color; 11392 11393 HPEN pen; 11394 if(p.color.a == 0) { 11395 pen = GetStockObject(NULL_PEN); 11396 } else { 11397 int style = PS_SOLID; 11398 final switch(p.style) { 11399 case Pen.Style.Solid: 11400 style = PS_SOLID; 11401 break; 11402 case Pen.Style.Dashed: 11403 style = PS_DASH; 11404 break; 11405 case Pen.Style.Dotted: 11406 style = PS_DOT; 11407 break; 11408 } 11409 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 11410 } 11411 auto orig = SelectObject(hdc, pen); 11412 if(originalPen is null) 11413 originalPen = orig; 11414 11415 if(currentPen !is null) 11416 DeleteObject(currentPen); 11417 11418 currentPen = pen; 11419 11420 // the outline is like a foreground since it's done that way on X 11421 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 11422 11423 } 11424 11425 @property void rasterOp(RasterOp op) { 11426 int mode; 11427 final switch(op) { 11428 case RasterOp.normal: 11429 mode = R2_COPYPEN; 11430 break; 11431 case RasterOp.xor: 11432 mode = R2_XORPEN; 11433 break; 11434 } 11435 SetROP2(hdc, mode); 11436 } 11437 11438 HBRUSH originalBrush; 11439 HBRUSH currentBrush; 11440 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 11441 @property void fillColor(Color c) { 11442 if(c == _fillColor) 11443 return; 11444 _fillColor = c; 11445 HBRUSH brush; 11446 if(c.a == 0) { 11447 brush = GetStockObject(HOLLOW_BRUSH); 11448 } else { 11449 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 11450 } 11451 auto orig = SelectObject(hdc, brush); 11452 if(originalBrush is null) 11453 originalBrush = orig; 11454 11455 if(currentBrush !is null) 11456 DeleteObject(currentBrush); 11457 11458 currentBrush = brush; 11459 11460 // background color is NOT set because X doesn't draw text backgrounds 11461 // SetBkColor(hdc, RGB(255, 255, 255)); 11462 } 11463 11464 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 11465 BITMAP bm; 11466 11467 HDC hdcMem = CreateCompatibleDC(hdc); 11468 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 11469 11470 GetObject(i.handle, bm.sizeof, &bm); 11471 11472 // or should I AlphaBlend!??!?! 11473 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 11474 11475 SelectObject(hdcMem, hbmOld); 11476 DeleteDC(hdcMem); 11477 } 11478 11479 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 11480 BITMAP bm; 11481 11482 HDC hdcMem = CreateCompatibleDC(hdc); 11483 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 11484 11485 GetObject(s.handle, bm.sizeof, &bm); 11486 11487 version(CRuntime_DigitalMars) goto noalpha; 11488 11489 // or should I AlphaBlend!??!?! note it is supposed to be premultiplied http://www.fengyuan.com/article/alphablend.html 11490 if(s.enableAlpha) { 11491 auto dw = w ? w : bm.bmWidth; 11492 auto dh = h ? h : bm.bmHeight; 11493 BLENDFUNCTION bf; 11494 bf.BlendOp = AC_SRC_OVER; 11495 bf.SourceConstantAlpha = 255; 11496 bf.AlphaFormat = AC_SRC_ALPHA; 11497 AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf); 11498 } else { 11499 noalpha: 11500 BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY); 11501 } 11502 11503 SelectObject(hdcMem, hbmOld); 11504 DeleteDC(hdcMem); 11505 } 11506 11507 Size textSize(scope const(char)[] text) { 11508 bool dummyX; 11509 if(text.length == 0) { 11510 text = " "; 11511 dummyX = true; 11512 } 11513 RECT rect; 11514 WCharzBuffer buffer = WCharzBuffer(text); 11515 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX); 11516 return Size(dummyX ? 0 : rect.right, rect.bottom); 11517 } 11518 11519 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 11520 if(text.length && text[$-1] == '\n') 11521 text = text[0 .. $-1]; // tailing newlines are weird on windows... 11522 if(text.length && text[$-1] == '\r') 11523 text = text[0 .. $-1]; 11524 11525 WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines); 11526 if(x2 == 0 && y2 == 0) { 11527 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 11528 } else { 11529 RECT rect; 11530 rect.left = x; 11531 rect.top = y; 11532 rect.right = x2; 11533 rect.bottom = y2; 11534 11535 uint mode = DT_LEFT; 11536 if(alignment & TextAlignment.Right) 11537 mode = DT_RIGHT; 11538 else if(alignment & TextAlignment.Center) 11539 mode = DT_CENTER; 11540 11541 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 11542 if(alignment & TextAlignment.VerticalCenter) 11543 mode |= DT_VCENTER | DT_SINGLELINE; 11544 11545 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX); 11546 } 11547 11548 /* 11549 uint mode; 11550 11551 if(alignment & TextAlignment.Center) 11552 mode = TA_CENTER; 11553 11554 SetTextAlign(hdc, mode); 11555 */ 11556 } 11557 11558 int fontHeight() { 11559 TEXTMETRIC metric; 11560 if(GetTextMetricsW(hdc, &metric)) { 11561 return metric.tmHeight; 11562 } 11563 11564 return 16; // idk just guessing here, maybe we should throw 11565 } 11566 11567 void drawPixel(int x, int y) { 11568 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 11569 } 11570 11571 // The basic shapes, outlined 11572 11573 void drawLine(int x1, int y1, int x2, int y2) { 11574 MoveToEx(hdc, x1, y1, null); 11575 LineTo(hdc, x2, y2); 11576 } 11577 11578 void drawRectangle(int x, int y, int width, int height) { 11579 // FIXME: with a wider pen this might not draw quite right. im not sure. 11580 gdi.Rectangle(hdc, x, y, x + width, y + height); 11581 } 11582 11583 /// Arguments are the points of the bounding rectangle 11584 void drawEllipse(int x1, int y1, int x2, int y2) { 11585 Ellipse(hdc, x1, y1, x2, y2); 11586 } 11587 11588 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 11589 if((start % (360*64)) == (finish % (360*64))) 11590 drawEllipse(x1, y1, x1 + width, y1 + height); 11591 else { 11592 import core.stdc.math; 11593 float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323; 11594 float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323; 11595 11596 auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2); 11597 auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2); 11598 auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2); 11599 auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2); 11600 11601 if(_activePen.color.a) 11602 Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11603 if(_fillColor.a) 11604 Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11605 } 11606 } 11607 11608 void drawPolygon(Point[] vertexes) { 11609 POINT[] points; 11610 points.length = vertexes.length; 11611 11612 foreach(i, p; vertexes) { 11613 points[i].x = p.x; 11614 points[i].y = p.y; 11615 } 11616 11617 Polygon(hdc, points.ptr, cast(int) points.length); 11618 } 11619 } 11620 11621 11622 // Mix this into the SimpleWindow class 11623 mixin template NativeSimpleWindowImplementation() { 11624 int curHidden = 0; // counter 11625 __gshared static bool[string] knownWinClasses; 11626 static bool altPressed = false; 11627 11628 HANDLE oldCursor; 11629 11630 void hideCursor () { 11631 if(curHidden == 0) 11632 oldCursor = SetCursor(null); 11633 ++curHidden; 11634 } 11635 11636 void showCursor () { 11637 --curHidden; 11638 if(curHidden == 0) { 11639 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 11640 } 11641 } 11642 11643 11644 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 11645 11646 void setMinSize (int minwidth, int minheight) { 11647 minWidth = minwidth; 11648 minHeight = minheight; 11649 } 11650 void setMaxSize (int maxwidth, int maxheight) { 11651 maxWidth = maxwidth; 11652 maxHeight = maxheight; 11653 } 11654 11655 // FIXME i'm not sure that Windows has this functionality 11656 // though it is nonessential anyway. 11657 void setResizeGranularity (int granx, int grany) {} 11658 11659 ScreenPainter getPainter(bool manualInvalidations) { 11660 return ScreenPainter(this, hwnd, manualInvalidations); 11661 } 11662 11663 HBITMAP buffer; 11664 11665 void setTitle(string title) { 11666 WCharzBuffer bfr = WCharzBuffer(title); 11667 SetWindowTextW(hwnd, bfr.ptr); 11668 } 11669 11670 string getTitle() { 11671 auto len = GetWindowTextLengthW(hwnd); 11672 if (!len) 11673 return null; 11674 wchar[256] tmpBuffer; 11675 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 11676 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 11677 auto str = buffer[0 .. len2]; 11678 return makeUtf8StringFromWindowsString(str); 11679 } 11680 11681 void move(int x, int y) { 11682 RECT rect; 11683 GetWindowRect(hwnd, &rect); 11684 // move it while maintaining the same size... 11685 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 11686 } 11687 11688 void resize(int w, int h) { 11689 RECT rect; 11690 GetWindowRect(hwnd, &rect); 11691 11692 RECT client; 11693 GetClientRect(hwnd, &client); 11694 11695 rect.right = rect.right - client.right + w; 11696 rect.bottom = rect.bottom - client.bottom + h; 11697 11698 // same position, new size for the client rectangle 11699 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 11700 11701 updateOpenglViewportIfNeeded(w, h); 11702 } 11703 11704 void moveResize (int x, int y, int w, int h) { 11705 // what's given is the client rectangle, we need to adjust 11706 11707 RECT rect; 11708 rect.left = x; 11709 rect.top = y; 11710 rect.right = w + x; 11711 rect.bottom = h + y; 11712 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 11713 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 11714 11715 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 11716 updateOpenglViewportIfNeeded(w, h); 11717 if (windowResized !is null) windowResized(w, h); 11718 } 11719 11720 version(without_opengl) {} else { 11721 HGLRC ghRC; 11722 HDC ghDC; 11723 } 11724 11725 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 11726 string cnamec; 11727 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 11728 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 11729 cnamec = "DSimpleWindow"; 11730 } else { 11731 cnamec = sdpyWindowClass; 11732 } 11733 11734 WCharzBuffer cn = WCharzBuffer(cnamec); 11735 11736 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 11737 11738 if(cnamec !in knownWinClasses) { 11739 WNDCLASSEX wc; 11740 11741 // FIXME: I might be able to use cbWndExtra to hold the pointer back 11742 // to the object. Maybe. 11743 wc.cbSize = wc.sizeof; 11744 wc.cbClsExtra = 0; 11745 wc.cbWndExtra = 0; 11746 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 11747 wc.hCursor = LoadCursorW(null, IDC_ARROW); 11748 wc.hIcon = LoadIcon(hInstance, null); 11749 wc.hInstance = hInstance; 11750 wc.lpfnWndProc = &WndProc; 11751 wc.lpszClassName = cn.ptr; 11752 wc.hIconSm = null; 11753 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 11754 if(!RegisterClassExW(&wc)) 11755 throw new WindowsApiException("RegisterClassExW", GetLastError()); 11756 knownWinClasses[cnamec] = true; 11757 } 11758 11759 int style; 11760 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 11761 11762 // FIXME: windowType and customizationFlags 11763 final switch(windowType) { 11764 case WindowTypes.normal: 11765 if(resizability == Resizability.fixedSize) { 11766 style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION; 11767 } else { 11768 style = WS_OVERLAPPEDWINDOW; 11769 } 11770 break; 11771 case WindowTypes.undecorated: 11772 style = WS_POPUP | WS_SYSMENU; 11773 break; 11774 case WindowTypes.eventOnly: 11775 _hidden = true; 11776 break; 11777 case WindowTypes.dropdownMenu: 11778 case WindowTypes.popupMenu: 11779 case WindowTypes.notification: 11780 style = WS_POPUP; 11781 flags |= WS_EX_NOACTIVATE; 11782 break; 11783 case WindowTypes.nestedChild: 11784 style = WS_CHILD; 11785 break; 11786 case WindowTypes.minimallyWrapped: 11787 assert(0, "construct minimally wrapped through the other ctor overlad"); 11788 } 11789 11790 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11791 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 11792 11793 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 11794 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 11795 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 11796 11797 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11798 setOpacity(255); 11799 11800 SimpleWindow.nativeMapping[hwnd] = this; 11801 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 11802 11803 if(windowType == WindowTypes.eventOnly) 11804 return; 11805 11806 HDC hdc = GetDC(hwnd); 11807 11808 11809 version(without_opengl) {} 11810 else { 11811 if(opengl == OpenGlOptions.yes) { 11812 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 11813 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 11814 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 11815 ghDC = hdc; 11816 PIXELFORMATDESCRIPTOR pfd; 11817 11818 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 11819 pfd.nVersion = 1; 11820 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 11821 pfd.dwLayerMask = PFD_MAIN_PLANE; 11822 pfd.iPixelType = PFD_TYPE_RGBA; 11823 pfd.cColorBits = 24; 11824 pfd.cDepthBits = 24; 11825 pfd.cAccumBits = 0; 11826 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 11827 11828 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 11829 11830 if (pixelformat == 0) 11831 throw new WindowsApiException("ChoosePixelFormat", GetLastError()); 11832 11833 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 11834 throw new WindowsApiException("SetPixelFormat", GetLastError()); 11835 11836 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 11837 // windoze is idiotic: we have to have OpenGL context to get function addresses 11838 // so we will create fake context to get that stupid address 11839 auto tmpcc = wglCreateContext(ghDC); 11840 if (tmpcc !is null) { 11841 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 11842 wglMakeCurrent(ghDC, tmpcc); 11843 wglInitOtherFunctions(); 11844 } 11845 } 11846 11847 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 11848 int[9] contextAttribs = [ 11849 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 11850 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 11851 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 11852 // for modern context, set "forward compatibility" flag too 11853 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 11854 0/*None*/, 11855 ]; 11856 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 11857 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 11858 // activate fallback mode 11859 // 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; 11860 ghRC = wglCreateContext(ghDC); 11861 } 11862 if (ghRC is null) 11863 throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError()); 11864 } else { 11865 // try to do at least something 11866 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 11867 sdpyOpenGLContextVersion = 0; 11868 ghRC = wglCreateContext(ghDC); 11869 } 11870 if (ghRC is null) 11871 throw new WindowsApiException("wglCreateContext", GetLastError()); 11872 } 11873 } 11874 } 11875 11876 if(opengl == OpenGlOptions.no) { 11877 buffer = CreateCompatibleBitmap(hdc, width, height); 11878 11879 auto hdcBmp = CreateCompatibleDC(hdc); 11880 // make sure it's filled with a blank slate 11881 auto oldBmp = SelectObject(hdcBmp, buffer); 11882 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 11883 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 11884 gdi.Rectangle(hdcBmp, 0, 0, width, height); 11885 SelectObject(hdcBmp, oldBmp); 11886 SelectObject(hdcBmp, oldBrush); 11887 SelectObject(hdcBmp, oldPen); 11888 DeleteDC(hdcBmp); 11889 11890 bmpWidth = width; 11891 bmpHeight = height; 11892 11893 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 11894 } 11895 11896 // We want the window's client area to match the image size 11897 RECT rcClient, rcWindow; 11898 POINT ptDiff; 11899 GetClientRect(hwnd, &rcClient); 11900 GetWindowRect(hwnd, &rcWindow); 11901 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 11902 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 11903 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 11904 11905 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 11906 ShowWindow(hwnd, SW_SHOWNORMAL); 11907 } else { 11908 _hidden = true; 11909 } 11910 this._visibleForTheFirstTimeCalled = false; // hack! 11911 } 11912 11913 11914 void dispose() { 11915 if(buffer) 11916 DeleteObject(buffer); 11917 } 11918 11919 void closeWindow() { 11920 if(ghRC) { 11921 wglDeleteContext(ghRC); 11922 ghRC = null; 11923 } 11924 DestroyWindow(hwnd); 11925 } 11926 11927 bool setOpacity(ubyte alpha) { 11928 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 11929 } 11930 11931 HANDLE currentCursor; 11932 11933 // returns zero if it recognized the event 11934 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 11935 MouseEvent mouse; 11936 11937 void mouseEvent(bool isScreen, ulong mods) { 11938 auto x = LOWORD(lParam); 11939 auto y = HIWORD(lParam); 11940 if(isScreen) { 11941 POINT p; 11942 p.x = x; 11943 p.y = y; 11944 ScreenToClient(hwnd, &p); 11945 x = cast(ushort) p.x; 11946 y = cast(ushort) p.y; 11947 } 11948 11949 if(wind.resizability == Resizability.automaticallyScaleIfPossible) { 11950 x = cast(ushort)( x * wind._virtualWidth / wind._width ); 11951 y = cast(ushort)( y * wind._virtualHeight / wind._height ); 11952 } 11953 11954 mouse.x = x + offsetX; 11955 mouse.y = y + offsetY; 11956 11957 wind.mdx(mouse); 11958 mouse.modifierState = cast(int) mods; 11959 mouse.window = wind; 11960 11961 if(wind.handleMouseEvent) 11962 wind.handleMouseEvent(mouse); 11963 } 11964 11965 switch(msg) { 11966 case WM_GETMINMAXINFO: 11967 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 11968 11969 if(wind.minWidth > 0) { 11970 RECT rect; 11971 rect.left = 100; 11972 rect.top = 100; 11973 rect.right = wind.minWidth + 100; 11974 rect.bottom = wind.minHeight + 100; 11975 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 11976 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 11977 11978 mmi.ptMinTrackSize.x = rect.right - rect.left; 11979 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 11980 } 11981 11982 if(wind.maxWidth < int.max) { 11983 RECT rect; 11984 rect.left = 100; 11985 rect.top = 100; 11986 rect.right = wind.maxWidth + 100; 11987 rect.bottom = wind.maxHeight + 100; 11988 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 11989 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 11990 11991 mmi.ptMaxTrackSize.x = rect.right - rect.left; 11992 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 11993 } 11994 break; 11995 case WM_CHAR: 11996 wchar c = cast(wchar) wParam; 11997 if(wind.handleCharEvent) 11998 wind.handleCharEvent(cast(dchar) c); 11999 break; 12000 case WM_SETFOCUS: 12001 case WM_KILLFOCUS: 12002 wind._focused = (msg == WM_SETFOCUS); 12003 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 12004 if(wind.onFocusChange) 12005 wind.onFocusChange(msg == WM_SETFOCUS); 12006 break; 12007 12008 case WM_SYSKEYDOWN: 12009 goto case; 12010 case WM_SYSKEYUP: 12011 if(lParam & (1 << 29)) { 12012 goto case; 12013 } else { 12014 // no window has keyboard focus 12015 goto default; 12016 } 12017 case WM_KEYDOWN: 12018 case WM_KEYUP: 12019 KeyEvent ev; 12020 ev.key = cast(Key) wParam; 12021 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 12022 if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way 12023 12024 ev.hardwareCode = (lParam & 0xff0000) >> 16; 12025 12026 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 12027 ev.modifierState |= ModifierState.shift; 12028 //k8: this doesn't work; thanks for nothing, windows 12029 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 12030 ev.modifierState |= ModifierState.alt;*/ 12031 // this never seems to actually be set 12032 // if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12033 12034 if (wParam == 0x12) { 12035 altPressed = (msg == WM_SYSKEYDOWN); 12036 } 12037 12038 if(msg == WM_KEYDOWN || msg == WM_KEYUP) { 12039 altPressed = false; 12040 } 12041 // sdpyPrintDebugString(altPressed ? "alt down" : " up "); 12042 12043 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 12044 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 12045 ev.modifierState |= ModifierState.ctrl; 12046 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 12047 ev.modifierState |= ModifierState.windows; 12048 if(GetKeyState(Key.NumLock)) 12049 ev.modifierState |= ModifierState.numLock; 12050 if(GetKeyState(Key.CapsLock)) 12051 ev.modifierState |= ModifierState.capsLock; 12052 12053 /+ 12054 // we always want to send the character too, so let's convert it 12055 ubyte[256] state; 12056 wchar[16] buffer; 12057 GetKeyboardState(state.ptr); 12058 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 12059 12060 foreach(dchar d; buffer) { 12061 ev.character = d; 12062 break; 12063 } 12064 +/ 12065 12066 ev.window = wind; 12067 if(wind.handleKeyEvent) 12068 wind.handleKeyEvent(ev); 12069 break; 12070 case 0x020a /*WM_MOUSEWHEEL*/: 12071 // send click 12072 mouse.type = cast(MouseEventType) 1; 12073 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12074 mouseEvent(true, LOWORD(wParam)); 12075 12076 // also send release 12077 mouse.type = cast(MouseEventType) 2; 12078 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 12079 mouseEvent(true, LOWORD(wParam)); 12080 break; 12081 case WM_MOUSEMOVE: 12082 mouse.type = cast(MouseEventType) 0; 12083 mouseEvent(false, wParam); 12084 break; 12085 case WM_LBUTTONDOWN: 12086 case WM_LBUTTONDBLCLK: 12087 mouse.type = cast(MouseEventType) 1; 12088 mouse.button = MouseButton.left; 12089 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 12090 mouseEvent(false, wParam); 12091 break; 12092 case WM_LBUTTONUP: 12093 mouse.type = cast(MouseEventType) 2; 12094 mouse.button = MouseButton.left; 12095 mouseEvent(false, wParam); 12096 break; 12097 case WM_RBUTTONDOWN: 12098 case WM_RBUTTONDBLCLK: 12099 mouse.type = cast(MouseEventType) 1; 12100 mouse.button = MouseButton.right; 12101 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 12102 mouseEvent(false, wParam); 12103 break; 12104 case WM_RBUTTONUP: 12105 mouse.type = cast(MouseEventType) 2; 12106 mouse.button = MouseButton.right; 12107 mouseEvent(false, wParam); 12108 break; 12109 case WM_MBUTTONDOWN: 12110 case WM_MBUTTONDBLCLK: 12111 mouse.type = cast(MouseEventType) 1; 12112 mouse.button = MouseButton.middle; 12113 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 12114 mouseEvent(false, wParam); 12115 break; 12116 case WM_MBUTTONUP: 12117 mouse.type = cast(MouseEventType) 2; 12118 mouse.button = MouseButton.middle; 12119 mouseEvent(false, wParam); 12120 break; 12121 case WM_XBUTTONDOWN: 12122 case WM_XBUTTONDBLCLK: 12123 mouse.type = cast(MouseEventType) 1; 12124 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12125 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 12126 mouseEvent(false, wParam); 12127 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 12128 case WM_XBUTTONUP: 12129 mouse.type = cast(MouseEventType) 2; 12130 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 12131 mouseEvent(false, wParam); 12132 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 12133 12134 default: return 1; 12135 } 12136 return 0; 12137 } 12138 12139 HWND hwnd; 12140 private int oldWidth; 12141 private int oldHeight; 12142 private bool inSizeMove; 12143 12144 /++ 12145 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. 12146 12147 History: 12148 Added November 23, 2021 12149 12150 Not fully stable, may be moved out of the impl struct. 12151 12152 Default value changed to `true` on February 15, 2021 12153 +/ 12154 bool doLiveResizing = true; 12155 12156 package int bmpWidth; 12157 package int bmpHeight; 12158 12159 // the extern(Windows) wndproc should just forward to this 12160 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 12161 try { 12162 assert(hwnd is this.hwnd); 12163 12164 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 12165 switch(msg) { 12166 case WM_MENUCHAR: // menu active but key not associated with a thing. 12167 // you would ideally use this for like a search function but sdpy not that ideally designed. alas. 12168 // The main things we can do are select, execute, close, or ignore 12169 // the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to 12170 // the user. This can be a bit annoying for sdpy things so instead im overriding and setting it 12171 // to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy 12172 // that's the lesser bad choice rn. Can always override by returning true in triggerEvents.... 12173 12174 // returns the value in the *high order word* of the return value 12175 // hence the << 16 12176 return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user 12177 case WM_SETCURSOR: 12178 if(cast(HWND) wParam !is hwnd) 12179 return 0; // further processing elsewhere 12180 12181 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 12182 SetCursor(this.curHidden > 0 ? null : currentCursor); 12183 return 1; 12184 } else { 12185 return DefWindowProc(hwnd, msg, wParam, lParam); 12186 } 12187 //break; 12188 12189 case WM_CLOSE: 12190 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 12191 break; 12192 case WM_DESTROY: 12193 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 12194 SimpleWindow.nativeMapping.remove(hwnd); 12195 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 12196 12197 bool anyImportant = false; 12198 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 12199 if(w.beingOpenKeepsAppOpen) { 12200 anyImportant = true; 12201 break; 12202 } 12203 if(!anyImportant) { 12204 PostQuitMessage(0); 12205 } 12206 break; 12207 case 0x02E0 /*WM_DPICHANGED*/: 12208 this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs 12209 12210 RECT* prcNewWindow = cast(RECT*)lParam; 12211 // docs say this is the recommended position and we should honor it 12212 SetWindowPos(hwnd, 12213 null, 12214 prcNewWindow.left, 12215 prcNewWindow.top, 12216 prcNewWindow.right - prcNewWindow.left, 12217 prcNewWindow.bottom - prcNewWindow.top, 12218 SWP_NOZORDER | SWP_NOACTIVATE); 12219 12220 // doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp 12221 // im not sure it is completely correct 12222 // but without it the tabs and such do look weird as things change. 12223 if(SystemParametersInfoForDpi) { 12224 LOGFONT lfText; 12225 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_); 12226 HFONT hFontNew = CreateFontIndirect(&lfText); 12227 if (hFontNew) 12228 { 12229 //DeleteObject(hFontOld); 12230 static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) { 12231 SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0)); 12232 return TRUE; 12233 } 12234 EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew); 12235 } 12236 } 12237 12238 if(this.onDpiChanged) 12239 this.onDpiChanged(); 12240 break; 12241 case WM_ENTERIDLE: 12242 // when a menu is up, it stops normal event processing (modal message loop) 12243 // but this at least gives us a chance to SOMETIMES catch up 12244 // FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it. 12245 SimpleWindow.processAllCustomEvents; 12246 SimpleWindow.processAllCustomEvents; 12247 SleepEx(0, true); 12248 break; 12249 case WM_SIZE: 12250 if(wParam == 1 /* SIZE_MINIMIZED */) 12251 break; 12252 _width = LOWORD(lParam); 12253 _height = HIWORD(lParam); 12254 12255 // I want to avoid tearing in the windows (my code is inefficient 12256 // so this is a hack around that) so while sizing, we don't trigger, 12257 // but we do want to trigger on events like mazimize. 12258 if(!inSizeMove || doLiveResizing) 12259 goto size_changed; 12260 break; 12261 /+ 12262 case WM_SIZING: 12263 writeln("size"); 12264 break; 12265 +/ 12266 // I don't like the tearing I get when redrawing on WM_SIZE 12267 // (I know there's other ways to fix that but I don't like that behavior anyway) 12268 // so instead it is going to redraw only at the end of a size. 12269 case 0x0231: /* WM_ENTERSIZEMOVE */ 12270 inSizeMove = true; 12271 break; 12272 case 0x0232: /* WM_EXITSIZEMOVE */ 12273 inSizeMove = false; 12274 12275 size_changed: 12276 12277 // nothing relevant changed, don't bother redrawing 12278 if(oldWidth == _width && oldHeight == _height) { 12279 break; 12280 } 12281 12282 // note: OpenGL windows don't use a backing bmp, so no need to change them 12283 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 12284 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 12285 // gotta get the double buffer bmp to match the window 12286 // FIXME: could this be more efficient? it never relinquishes a large bitmap 12287 12288 // if it is auto-scaled, we keep the backing bitmap the same size all the time 12289 if(resizability != Resizability.automaticallyScaleIfPossible) 12290 if(_width > bmpWidth || _height > bmpHeight) { 12291 auto hdc = GetDC(hwnd); 12292 auto oldBuffer = buffer; 12293 buffer = CreateCompatibleBitmap(hdc, _width, _height); 12294 12295 auto hdcBmp = CreateCompatibleDC(hdc); 12296 auto oldBmp = SelectObject(hdcBmp, buffer); 12297 12298 auto hdcOldBmp = CreateCompatibleDC(hdc); 12299 auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer); 12300 12301 /+ 12302 RECT r; 12303 r.left = 0; 12304 r.top = 0; 12305 r.right = width; 12306 r.bottom = height; 12307 auto c = Color.green; 12308 auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 12309 FillRect(hdcBmp, &r, brush); 12310 DeleteObject(brush); 12311 +/ 12312 12313 BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY); 12314 12315 bmpWidth = _width; 12316 bmpHeight = _height; 12317 12318 SelectObject(hdcOldBmp, oldOldBmp); 12319 DeleteDC(hdcOldBmp); 12320 12321 SelectObject(hdcBmp, oldBmp); 12322 DeleteDC(hdcBmp); 12323 12324 ReleaseDC(hwnd, hdc); 12325 12326 DeleteObject(oldBuffer); 12327 } 12328 } 12329 12330 updateOpenglViewportIfNeeded(_width, _height); 12331 12332 if(resizability != Resizability.automaticallyScaleIfPossible) 12333 if(windowResized !is null) 12334 windowResized(_width, _height); 12335 12336 if(inSizeMove) { 12337 SimpleWindow.processAllCustomEvents(); 12338 SimpleWindow.processAllCustomEvents(); 12339 } else { 12340 // when it is all done, make sure everything is freshly drawn or there might be 12341 // weird bugs left. 12342 RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 12343 } 12344 12345 oldWidth = this._width; 12346 oldHeight = this._height; 12347 break; 12348 case WM_ERASEBKGND: 12349 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 12350 if (!this._visibleForTheFirstTimeCalled) { 12351 this._visibleForTheFirstTimeCalled = true; 12352 if (this.visibleForTheFirstTime !is null) { 12353 this.visibleForTheFirstTime(); 12354 } 12355 } 12356 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 12357 version(without_opengl) {} else { 12358 if (openglMode == OpenGlOptions.yes) return 1; 12359 } 12360 // call windows default handler, so it can paint standard controls 12361 goto default; 12362 case WM_CTLCOLORBTN: 12363 case WM_CTLCOLORSTATIC: 12364 SetBkMode(cast(HDC) wParam, TRANSPARENT); 12365 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 12366 GetSysColorBrush(COLOR_3DFACE); 12367 //break; 12368 case WM_SHOWWINDOW: 12369 this._visible = (wParam != 0); 12370 if (!this._visibleForTheFirstTimeCalled && this._visible) { 12371 this._visibleForTheFirstTimeCalled = true; 12372 if (this.visibleForTheFirstTime !is null) { 12373 this.visibleForTheFirstTime(); 12374 } 12375 } 12376 if (this.visibilityChanged !is null) this.visibilityChanged(this._visible); 12377 break; 12378 case WM_PAINT: { 12379 if (!this._visibleForTheFirstTimeCalled) { 12380 this._visibleForTheFirstTimeCalled = true; 12381 if (this.visibleForTheFirstTime !is null) { 12382 this.visibleForTheFirstTime(); 12383 } 12384 } 12385 12386 BITMAP bm; 12387 PAINTSTRUCT ps; 12388 12389 HDC hdc = BeginPaint(hwnd, &ps); 12390 12391 if(openglMode == OpenGlOptions.no) { 12392 12393 HDC hdcMem = CreateCompatibleDC(hdc); 12394 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 12395 12396 GetObject(buffer, bm.sizeof, &bm); 12397 12398 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 12399 if(resizability == Resizability.automaticallyScaleIfPossible) 12400 StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 12401 else 12402 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 12403 //BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY); 12404 12405 SelectObject(hdcMem, hbmOld); 12406 DeleteDC(hdcMem); 12407 EndPaint(hwnd, &ps); 12408 } else { 12409 EndPaint(hwnd, &ps); 12410 version(without_opengl) {} else 12411 redrawOpenGlSceneSoon(); 12412 } 12413 } break; 12414 default: 12415 return DefWindowProc(hwnd, msg, wParam, lParam); 12416 } 12417 return 0; 12418 12419 } 12420 catch(Throwable t) { 12421 sdpyPrintDebugString(t.toString); 12422 return 0; 12423 } 12424 } 12425 } 12426 12427 mixin template NativeImageImplementation() { 12428 HBITMAP handle; 12429 ubyte* rawData; 12430 12431 final: 12432 12433 Color getPixel(int x, int y) { 12434 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12435 // remember, bmps are upside down 12436 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12437 12438 Color c; 12439 if(enableAlpha) 12440 c.a = rawData[offset + 3]; 12441 else 12442 c.a = 255; 12443 c.b = rawData[offset + 0]; 12444 c.g = rawData[offset + 1]; 12445 c.r = rawData[offset + 2]; 12446 c.unPremultiply(); 12447 return c; 12448 } 12449 12450 void setPixel(int x, int y, Color c) { 12451 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12452 // remember, bmps are upside down 12453 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12454 12455 if(enableAlpha) 12456 c.premultiply(); 12457 12458 rawData[offset + 0] = c.b; 12459 rawData[offset + 1] = c.g; 12460 rawData[offset + 2] = c.r; 12461 if(enableAlpha) 12462 rawData[offset + 3] = c.a; 12463 } 12464 12465 void convertToRgbaBytes(ubyte[] where) { 12466 assert(where.length == this.width * this.height * 4); 12467 12468 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12469 int idx = 0; 12470 int offset = itemsPerLine * (height - 1); 12471 // remember, bmps are upside down 12472 for(int y = height - 1; y >= 0; y--) { 12473 auto offsetStart = offset; 12474 for(int x = 0; x < width; x++) { 12475 where[idx + 0] = rawData[offset + 2]; // r 12476 where[idx + 1] = rawData[offset + 1]; // g 12477 where[idx + 2] = rawData[offset + 0]; // b 12478 if(enableAlpha) { 12479 where[idx + 3] = rawData[offset + 3]; // a 12480 unPremultiplyRgba(where[idx .. idx + 4]); 12481 offset++; 12482 } else 12483 where[idx + 3] = 255; // a 12484 idx += 4; 12485 offset += 3; 12486 } 12487 12488 offset = offsetStart - itemsPerLine; 12489 } 12490 } 12491 12492 void setFromRgbaBytes(in ubyte[] what) { 12493 assert(what.length == this.width * this.height * 4); 12494 12495 auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 12496 int idx = 0; 12497 int offset = itemsPerLine * (height - 1); 12498 // remember, bmps are upside down 12499 for(int y = height - 1; y >= 0; y--) { 12500 auto offsetStart = offset; 12501 for(int x = 0; x < width; x++) { 12502 if(enableAlpha) { 12503 auto a = what[idx + 3]; 12504 12505 rawData[offset + 2] = (a * what[idx + 0]) / 255; // r 12506 rawData[offset + 1] = (a * what[idx + 1]) / 255; // g 12507 rawData[offset + 0] = (a * what[idx + 2]) / 255; // b 12508 rawData[offset + 3] = a; // a 12509 //premultiplyBgra(rawData[offset .. offset + 4]); 12510 offset++; 12511 } else { 12512 rawData[offset + 2] = what[idx + 0]; // r 12513 rawData[offset + 1] = what[idx + 1]; // g 12514 rawData[offset + 0] = what[idx + 2]; // b 12515 } 12516 idx += 4; 12517 offset += 3; 12518 } 12519 12520 offset = offsetStart - itemsPerLine; 12521 } 12522 } 12523 12524 12525 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 12526 BITMAPINFO infoheader; 12527 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 12528 infoheader.bmiHeader.biWidth = width; 12529 infoheader.bmiHeader.biHeight = height; 12530 infoheader.bmiHeader.biPlanes = 1; 12531 infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24; 12532 infoheader.bmiHeader.biCompression = BI_RGB; 12533 12534 handle = CreateDIBSection( 12535 null, 12536 &infoheader, 12537 DIB_RGB_COLORS, 12538 cast(void**) &rawData, 12539 null, 12540 0); 12541 if(handle is null) 12542 throw new WindowsApiException("create image failed", GetLastError()); 12543 12544 } 12545 12546 void dispose() { 12547 DeleteObject(handle); 12548 } 12549 } 12550 12551 enum KEY_ESCAPE = 27; 12552 } 12553 version(X11) { 12554 /// This is the default font used. You might change this before doing anything else with 12555 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 12556 /// for cross-platform compatibility. 12557 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12558 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12559 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 12560 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 12561 12562 alias int delegate(XEvent) NativeEventHandler; 12563 alias Window NativeWindowHandle; 12564 12565 enum KEY_ESCAPE = 9; 12566 12567 mixin template NativeScreenPainterImplementation() { 12568 Display* display; 12569 Drawable d; 12570 Drawable destiny; 12571 12572 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 12573 GC gc; 12574 12575 __gshared bool fontAttempted; 12576 12577 __gshared XFontStruct* defaultfont; 12578 __gshared XFontSet defaultfontset; 12579 12580 XFontStruct* font; 12581 XFontSet fontset; 12582 12583 void create(PaintingHandle window) { 12584 this.display = XDisplayConnection.get(); 12585 12586 Drawable buffer = None; 12587 if(auto sw = cast(SimpleWindow) this.window) { 12588 buffer = sw.impl.buffer; 12589 this.destiny = cast(Drawable) window; 12590 } else { 12591 buffer = cast(Drawable) window; 12592 this.destiny = None; 12593 } 12594 12595 this.d = cast(Drawable) buffer; 12596 12597 auto dgc = DefaultGC(display, DefaultScreen(display)); 12598 12599 this.gc = XCreateGC(display, d, 0, null); 12600 12601 XCopyGC(display, dgc, 0xffffffff, this.gc); 12602 12603 ensureDefaultFontLoaded(); 12604 12605 font = defaultfont; 12606 fontset = defaultfontset; 12607 12608 if(font) { 12609 XSetFont(display, gc, font.fid); 12610 } 12611 } 12612 12613 static void ensureDefaultFontLoaded() { 12614 if(!fontAttempted) { 12615 auto display = XDisplayConnection.get; 12616 auto font = XLoadQueryFont(display, xfontstr.ptr); 12617 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 12618 if(font is null) { 12619 xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"; 12620 font = XLoadQueryFont(display, xfontstr.ptr); 12621 } 12622 12623 char** lol; 12624 int lol2; 12625 char* lol3; 12626 auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 12627 12628 fontAttempted = true; 12629 12630 defaultfont = font; 12631 defaultfontset = fontset; 12632 } 12633 } 12634 12635 arsd.color.Rectangle _clipRectangle; 12636 void setClipRectangle(int x, int y, int width, int height) { 12637 auto old = _clipRectangle; 12638 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 12639 if(old == _clipRectangle) 12640 return; 12641 12642 if(width == 0 || height == 0) { 12643 XSetClipMask(display, gc, None); 12644 12645 if(xrenderPicturePainter) { 12646 12647 XRectangle[1] rects; 12648 rects[0] = XRectangle(short.min, short.min, short.max, short.max); 12649 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12650 } 12651 12652 version(with_xft) { 12653 if(xftFont is null || xftDraw is null) 12654 return; 12655 XftDrawSetClip(xftDraw, null); 12656 } 12657 } else { 12658 XRectangle[1] rects; 12659 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 12660 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 12661 12662 if(xrenderPicturePainter) 12663 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12664 12665 version(with_xft) { 12666 if(xftFont is null || xftDraw is null) 12667 return; 12668 XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1); 12669 } 12670 } 12671 } 12672 12673 version(with_xft) { 12674 XftFont* xftFont; 12675 XftDraw* xftDraw; 12676 12677 XftColor xftColor; 12678 12679 void updateXftColor() { 12680 if(xftFont is null) 12681 return; 12682 12683 // not bothering with XftColorFree since p sure i don't need it on 24 bit displays.... 12684 XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255); 12685 12686 XftColorAllocValue( 12687 display, 12688 DefaultVisual(display, DefaultScreen(display)), 12689 DefaultColormap(display, 0), 12690 &colorIn, 12691 &xftColor 12692 ); 12693 } 12694 } 12695 12696 private OperatingSystemFont _activeFont; 12697 void setFont(OperatingSystemFont font) { 12698 _activeFont = font; 12699 version(with_xft) { 12700 if(font && font.isXft && font.xftFont) 12701 this.xftFont = font.xftFont; 12702 else 12703 this.xftFont = null; 12704 12705 if(this.xftFont) { 12706 if(xftDraw is null) { 12707 xftDraw = XftDrawCreate( 12708 display, 12709 d, 12710 DefaultVisual(display, DefaultScreen(display)), 12711 DefaultColormap(display, 0) 12712 ); 12713 12714 updateXftColor(); 12715 } 12716 12717 return; 12718 } 12719 } 12720 12721 if(font && font.font) { 12722 this.font = font.font; 12723 this.fontset = font.fontset; 12724 XSetFont(display, gc, font.font.fid); 12725 } else { 12726 this.font = defaultfont; 12727 this.fontset = defaultfontset; 12728 } 12729 12730 } 12731 12732 private Picture xrenderPicturePainter; 12733 12734 bool manualInvalidations; 12735 void invalidateRect(Rectangle invalidRect) { 12736 // FIXME if manualInvalidations 12737 } 12738 12739 void dispose() { 12740 this.rasterOp = RasterOp.normal; 12741 12742 if(xrenderPicturePainter) { 12743 XRenderFreePicture(display, xrenderPicturePainter); 12744 xrenderPicturePainter = None; 12745 } 12746 12747 // FIXME: this.window.width/height is probably wrong 12748 12749 // src x,y then dest x, y 12750 if(destiny != None) { 12751 // FIXME: if manual invalidations we can actually only copy some of the area. 12752 // if(manualInvalidations) 12753 XSetClipMask(display, gc, None); 12754 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 12755 } 12756 12757 XFreeGC(display, gc); 12758 12759 version(with_xft) 12760 if(xftDraw) { 12761 XftDrawDestroy(xftDraw); 12762 xftDraw = null; 12763 } 12764 12765 /+ 12766 // this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource. 12767 if(font && font !is defaultfont) { 12768 XFreeFont(display, font); 12769 font = null; 12770 } 12771 if(fontset && fontset !is defaultfontset) { 12772 XFreeFontSet(display, fontset); 12773 fontset = null; 12774 } 12775 +/ 12776 XFlush(display); 12777 12778 if(window.paintingFinishedDg !is null) 12779 window.paintingFinishedDg()(); 12780 } 12781 12782 bool backgroundIsNotTransparent = true; 12783 bool foregroundIsNotTransparent = true; 12784 12785 bool _penInitialized = false; 12786 Pen _activePen; 12787 12788 Color _outlineColor; 12789 Color _fillColor; 12790 12791 @property void pen(Pen p) { 12792 if(_penInitialized && p == _activePen) { 12793 return; 12794 } 12795 _penInitialized = true; 12796 _activePen = p; 12797 _outlineColor = p.color; 12798 12799 int style; 12800 12801 byte dashLength; 12802 12803 final switch(p.style) { 12804 case Pen.Style.Solid: 12805 style = 0 /*LineSolid*/; 12806 break; 12807 case Pen.Style.Dashed: 12808 style = 1 /*LineOnOffDash*/; 12809 dashLength = 4; 12810 break; 12811 case Pen.Style.Dotted: 12812 style = 1 /*LineOnOffDash*/; 12813 dashLength = 1; 12814 break; 12815 } 12816 12817 XSetLineAttributes(display, gc, p.width, style, style == 0 ? 3 : 0, 0); 12818 if(dashLength) 12819 XSetDashes(display, gc, 0, &dashLength, 1); 12820 12821 if(p.color.a == 0) { 12822 foregroundIsNotTransparent = false; 12823 return; 12824 } 12825 12826 foregroundIsNotTransparent = true; 12827 12828 XSetForeground(display, gc, colorToX(p.color, display)); 12829 12830 version(with_xft) 12831 updateXftColor(); 12832 } 12833 12834 RasterOp _currentRasterOp; 12835 bool _currentRasterOpInitialized = false; 12836 @property void rasterOp(RasterOp op) { 12837 if(_currentRasterOpInitialized && _currentRasterOp == op) 12838 return; 12839 _currentRasterOp = op; 12840 _currentRasterOpInitialized = true; 12841 int mode; 12842 final switch(op) { 12843 case RasterOp.normal: 12844 mode = GXcopy; 12845 break; 12846 case RasterOp.xor: 12847 mode = GXxor; 12848 break; 12849 } 12850 XSetFunction(display, gc, mode); 12851 } 12852 12853 12854 bool _fillColorInitialized = false; 12855 12856 @property void fillColor(Color c) { 12857 if(_fillColorInitialized && _fillColor == c) 12858 return; // already good, no need to waste time calling it 12859 _fillColor = c; 12860 _fillColorInitialized = true; 12861 if(c.a == 0) { 12862 backgroundIsNotTransparent = false; 12863 return; 12864 } 12865 12866 backgroundIsNotTransparent = true; 12867 12868 XSetBackground(display, gc, colorToX(c, display)); 12869 12870 } 12871 12872 void swapColors() { 12873 auto tmp = _fillColor; 12874 fillColor = _outlineColor; 12875 auto newPen = _activePen; 12876 newPen.color = tmp; 12877 pen(newPen); 12878 } 12879 12880 uint colorToX(Color c, Display* display) { 12881 auto visual = DefaultVisual(display, DefaultScreen(display)); 12882 import core.bitop; 12883 uint color = 0; 12884 { 12885 auto startBit = bsf(visual.red_mask); 12886 auto lastBit = bsr(visual.red_mask); 12887 auto r = cast(uint) c.r; 12888 r >>= 7 - (lastBit - startBit); 12889 r <<= startBit; 12890 color |= r; 12891 } 12892 { 12893 auto startBit = bsf(visual.green_mask); 12894 auto lastBit = bsr(visual.green_mask); 12895 auto g = cast(uint) c.g; 12896 g >>= 7 - (lastBit - startBit); 12897 g <<= startBit; 12898 color |= g; 12899 } 12900 { 12901 auto startBit = bsf(visual.blue_mask); 12902 auto lastBit = bsr(visual.blue_mask); 12903 auto b = cast(uint) c.b; 12904 b >>= 7 - (lastBit - startBit); 12905 b <<= startBit; 12906 color |= b; 12907 } 12908 12909 12910 12911 return color; 12912 } 12913 12914 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 12915 // source x, source y 12916 if(ix >= i.width) return; 12917 if(iy >= i.height) return; 12918 if(ix + w > i.width) w = i.width - ix; 12919 if(iy + h > i.height) h = i.height - iy; 12920 if(i.usingXshm) 12921 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 12922 else 12923 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 12924 } 12925 12926 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 12927 if(s.enableAlpha) { 12928 // the Sprite must be created first, meaning if we're here, XRender is already loaded 12929 if(this.xrenderPicturePainter == None) { 12930 XRenderPictureAttributes attrs; 12931 // FIXME: I can prolly reuse this as long as the pixmap itself is valid. 12932 xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs); 12933 12934 // need to initialize the clip 12935 XRectangle[1] rects; 12936 rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height); 12937 12938 if(_clipRectangle != Rectangle.init) 12939 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12940 } 12941 12942 XRenderComposite( 12943 display, 12944 3, // PicOpOver 12945 s.xrenderPicture, 12946 None, 12947 this.xrenderPicturePainter, 12948 ix, 12949 iy, 12950 0, 12951 0, 12952 x, 12953 y, 12954 w ? w : s.width, 12955 h ? h : s.height 12956 ); 12957 } else { 12958 XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y); 12959 } 12960 } 12961 12962 int fontHeight() { 12963 version(with_xft) 12964 if(xftFont !is null) 12965 return xftFont.height; 12966 if(font) 12967 return font.max_bounds.ascent + font.max_bounds.descent; 12968 return 12; // pretty common default... 12969 } 12970 12971 int textWidth(in char[] line) { 12972 version(with_xft) 12973 if(xftFont) { 12974 if(line.length == 0) 12975 return 0; 12976 XGlyphInfo extents; 12977 XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents); 12978 return extents.width; 12979 } 12980 12981 if(fontset) { 12982 if(line.length == 0) 12983 return 0; 12984 XRectangle rect; 12985 Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect); 12986 12987 return rect.width; 12988 } 12989 12990 if(font) 12991 // FIXME: unicode 12992 return XTextWidth( font, line.ptr, cast(int) line.length); 12993 else 12994 return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 12995 } 12996 12997 Size textSize(in char[] text) { 12998 auto maxWidth = 0; 12999 auto lineHeight = fontHeight; 13000 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 13001 foreach(line; text.split('\n')) { 13002 int textWidth = this.textWidth(line); 13003 if(textWidth > maxWidth) 13004 maxWidth = textWidth; 13005 h += lineHeight + 4; 13006 } 13007 return Size(maxWidth, h); 13008 } 13009 13010 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 13011 const(char)[] text; 13012 version(with_xft) 13013 if(xftFont) { 13014 text = originalText; 13015 goto loaded; 13016 } 13017 13018 if(fontset) 13019 text = originalText; 13020 else { 13021 text.reserve(originalText.length); 13022 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 13023 // then strip the rest so there isn't garbage 13024 foreach(dchar ch; originalText) 13025 if(ch < 256) 13026 text ~= cast(ubyte) ch; 13027 else 13028 text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space 13029 } 13030 loaded: 13031 if(text.length == 0) 13032 return; 13033 13034 // FIXME: should we clip it to the bounding box? 13035 int textHeight = fontHeight; 13036 13037 auto lines = text.split('\n'); 13038 13039 const lineHeight = textHeight; 13040 textHeight *= lines.length; 13041 13042 int cy = y; 13043 13044 if(alignment & TextAlignment.VerticalBottom) { 13045 if(y2 <= 0) 13046 return; 13047 auto h = y2 - y; 13048 if(h > textHeight) { 13049 cy += h - textHeight; 13050 cy -= lineHeight / 2; 13051 } 13052 } else if(alignment & TextAlignment.VerticalCenter) { 13053 if(y2 <= 0) 13054 return; 13055 auto h = y2 - y; 13056 if(textHeight < h) { 13057 cy += (h - textHeight) / 2; 13058 //cy -= lineHeight / 4; 13059 } 13060 } 13061 13062 foreach(line; text.split('\n')) { 13063 int textWidth = this.textWidth(line); 13064 13065 int px = x, py = cy; 13066 13067 if(alignment & TextAlignment.Center) { 13068 if(x2 <= 0) 13069 return; 13070 auto w = x2 - x; 13071 if(w > textWidth) 13072 px += (w - textWidth) / 2; 13073 } else if(alignment & TextAlignment.Right) { 13074 if(x2 <= 0) 13075 return; 13076 auto pos = x2 - textWidth; 13077 if(pos > x) 13078 px = pos; 13079 } 13080 13081 version(with_xft) 13082 if(xftFont) { 13083 XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length); 13084 13085 goto carry_on; 13086 } 13087 13088 if(fontset) 13089 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13090 else 13091 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 13092 carry_on: 13093 cy += lineHeight + 4; 13094 } 13095 } 13096 13097 void drawPixel(int x, int y) { 13098 XDrawPoint(display, d, gc, x, y); 13099 } 13100 13101 // The basic shapes, outlined 13102 13103 void drawLine(int x1, int y1, int x2, int y2) { 13104 if(foregroundIsNotTransparent) 13105 XDrawLine(display, d, gc, x1, y1, x2, y2); 13106 } 13107 13108 void drawRectangle(int x, int y, int width, int height) { 13109 if(backgroundIsNotTransparent) { 13110 swapColors(); 13111 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 13112 swapColors(); 13113 } 13114 // 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 13115 if(foregroundIsNotTransparent) 13116 XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2); 13117 } 13118 13119 /// Arguments are the points of the bounding rectangle 13120 void drawEllipse(int x1, int y1, int x2, int y2) { 13121 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 13122 } 13123 13124 // NOTE: start and finish are in units of degrees * 64 13125 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 13126 if(backgroundIsNotTransparent) { 13127 swapColors(); 13128 XFillArc(display, d, gc, x1, y1, width, height, start, finish); 13129 swapColors(); 13130 } 13131 if(foregroundIsNotTransparent) { 13132 XDrawArc(display, d, gc, x1, y1, width, height, start, finish); 13133 13134 // Windows draws the straight lines on the edges too so FIXME sort of 13135 } 13136 } 13137 13138 void drawPolygon(Point[] vertexes) { 13139 XPoint[16] pointsBuffer; 13140 XPoint[] points; 13141 if(vertexes.length <= pointsBuffer.length) 13142 points = pointsBuffer[0 .. vertexes.length]; 13143 else 13144 points.length = vertexes.length; 13145 13146 foreach(i, p; vertexes) { 13147 points[i].x = cast(short) p.x; 13148 points[i].y = cast(short) p.y; 13149 } 13150 13151 if(backgroundIsNotTransparent) { 13152 swapColors(); 13153 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 13154 swapColors(); 13155 } 13156 if(foregroundIsNotTransparent) { 13157 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 13158 } 13159 } 13160 } 13161 13162 /* XRender { */ 13163 13164 struct XRenderColor { 13165 ushort red; 13166 ushort green; 13167 ushort blue; 13168 ushort alpha; 13169 } 13170 13171 alias Picture = XID; 13172 alias PictFormat = XID; 13173 13174 struct XGlyphInfo { 13175 ushort width; 13176 ushort height; 13177 short x; 13178 short y; 13179 short xOff; 13180 short yOff; 13181 } 13182 13183 struct XRenderDirectFormat { 13184 short red; 13185 short redMask; 13186 short green; 13187 short greenMask; 13188 short blue; 13189 short blueMask; 13190 short alpha; 13191 short alphaMask; 13192 } 13193 13194 struct XRenderPictFormat { 13195 PictFormat id; 13196 int type; 13197 int depth; 13198 XRenderDirectFormat direct; 13199 Colormap colormap; 13200 } 13201 13202 enum PictFormatID = (1 << 0); 13203 enum PictFormatType = (1 << 1); 13204 enum PictFormatDepth = (1 << 2); 13205 enum PictFormatRed = (1 << 3); 13206 enum PictFormatRedMask =(1 << 4); 13207 enum PictFormatGreen = (1 << 5); 13208 enum PictFormatGreenMask=(1 << 6); 13209 enum PictFormatBlue = (1 << 7); 13210 enum PictFormatBlueMask =(1 << 8); 13211 enum PictFormatAlpha = (1 << 9); 13212 enum PictFormatAlphaMask=(1 << 10); 13213 enum PictFormatColormap =(1 << 11); 13214 13215 struct XRenderPictureAttributes { 13216 int repeat; 13217 Picture alpha_map; 13218 int alpha_x_origin; 13219 int alpha_y_origin; 13220 int clip_x_origin; 13221 int clip_y_origin; 13222 Pixmap clip_mask; 13223 Bool graphics_exposures; 13224 int subwindow_mode; 13225 int poly_edge; 13226 int poly_mode; 13227 Atom dither; 13228 Bool component_alpha; 13229 } 13230 13231 alias int XFixed; 13232 13233 struct XPointFixed { 13234 XFixed x, y; 13235 } 13236 13237 struct XCircle { 13238 XFixed x; 13239 XFixed y; 13240 XFixed radius; 13241 } 13242 13243 struct XTransform { 13244 XFixed[3][3] matrix; 13245 } 13246 13247 struct XFilters { 13248 int nfilter; 13249 char **filter; 13250 int nalias; 13251 short *alias_; 13252 } 13253 13254 struct XIndexValue { 13255 c_ulong pixel; 13256 ushort red, green, blue, alpha; 13257 } 13258 13259 struct XAnimCursor { 13260 Cursor cursor; 13261 c_ulong delay; 13262 } 13263 13264 struct XLinearGradient { 13265 XPointFixed p1; 13266 XPointFixed p2; 13267 } 13268 13269 struct XRadialGradient { 13270 XCircle inner; 13271 XCircle outer; 13272 } 13273 13274 struct XConicalGradient { 13275 XPointFixed center; 13276 XFixed angle; /* in degrees */ 13277 } 13278 13279 enum PictStandardARGB32 = 0; 13280 enum PictStandardRGB24 = 1; 13281 enum PictStandardA8 = 2; 13282 enum PictStandardA4 = 3; 13283 enum PictStandardA1 = 4; 13284 enum PictStandardNUM = 5; 13285 13286 interface XRender { 13287 extern(C) @nogc: 13288 13289 Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep); 13290 13291 Status XRenderQueryVersion (Display *dpy, 13292 int *major_versionp, 13293 int *minor_versionp); 13294 13295 Status XRenderQueryFormats (Display *dpy); 13296 13297 int XRenderQuerySubpixelOrder (Display *dpy, int screen); 13298 13299 Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel); 13300 13301 XRenderPictFormat * 13302 XRenderFindVisualFormat (Display *dpy, const Visual *visual); 13303 13304 XRenderPictFormat * 13305 XRenderFindFormat (Display *dpy, 13306 c_ulong mask, 13307 const XRenderPictFormat *templ, 13308 int count); 13309 XRenderPictFormat * 13310 XRenderFindStandardFormat (Display *dpy, 13311 int format); 13312 13313 XIndexValue * 13314 XRenderQueryPictIndexValues(Display *dpy, 13315 const XRenderPictFormat *format, 13316 int *num); 13317 13318 Picture XRenderCreatePicture( 13319 Display *dpy, 13320 Drawable drawable, 13321 const XRenderPictFormat *format, 13322 c_ulong valuemask, 13323 const XRenderPictureAttributes *attributes); 13324 13325 void XRenderChangePicture (Display *dpy, 13326 Picture picture, 13327 c_ulong valuemask, 13328 const XRenderPictureAttributes *attributes); 13329 13330 void 13331 XRenderSetPictureClipRectangles (Display *dpy, 13332 Picture picture, 13333 int xOrigin, 13334 int yOrigin, 13335 const XRectangle *rects, 13336 int n); 13337 13338 void 13339 XRenderSetPictureClipRegion (Display *dpy, 13340 Picture picture, 13341 Region r); 13342 13343 void 13344 XRenderSetPictureTransform (Display *dpy, 13345 Picture picture, 13346 XTransform *transform); 13347 13348 void 13349 XRenderFreePicture (Display *dpy, 13350 Picture picture); 13351 13352 void 13353 XRenderComposite (Display *dpy, 13354 int op, 13355 Picture src, 13356 Picture mask, 13357 Picture dst, 13358 int src_x, 13359 int src_y, 13360 int mask_x, 13361 int mask_y, 13362 int dst_x, 13363 int dst_y, 13364 uint width, 13365 uint height); 13366 13367 13368 Picture XRenderCreateSolidFill (Display *dpy, 13369 const XRenderColor *color); 13370 13371 Picture XRenderCreateLinearGradient (Display *dpy, 13372 const XLinearGradient *gradient, 13373 const XFixed *stops, 13374 const XRenderColor *colors, 13375 int nstops); 13376 13377 Picture XRenderCreateRadialGradient (Display *dpy, 13378 const XRadialGradient *gradient, 13379 const XFixed *stops, 13380 const XRenderColor *colors, 13381 int nstops); 13382 13383 Picture XRenderCreateConicalGradient (Display *dpy, 13384 const XConicalGradient *gradient, 13385 const XFixed *stops, 13386 const XRenderColor *colors, 13387 int nstops); 13388 13389 13390 13391 Cursor 13392 XRenderCreateCursor (Display *dpy, 13393 Picture source, 13394 uint x, 13395 uint y); 13396 13397 XFilters * 13398 XRenderQueryFilters (Display *dpy, Drawable drawable); 13399 13400 void 13401 XRenderSetPictureFilter (Display *dpy, 13402 Picture picture, 13403 const char *filter, 13404 XFixed *params, 13405 int nparams); 13406 13407 Cursor 13408 XRenderCreateAnimCursor (Display *dpy, 13409 int ncursor, 13410 XAnimCursor *cursors); 13411 } 13412 13413 __gshared bool XRenderLibrarySuccessfullyLoaded = true; 13414 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary; 13415 13416 /* XRender } */ 13417 13418 /* Xrandr { */ 13419 13420 struct XRRMonitorInfo { 13421 Atom name; 13422 Bool primary; 13423 Bool automatic; 13424 int noutput; 13425 int x; 13426 int y; 13427 int width; 13428 int height; 13429 int mwidth; 13430 int mheight; 13431 /*RROutput*/ void *outputs; 13432 } 13433 13434 struct XRRScreenChangeNotifyEvent { 13435 int type; /* event base */ 13436 c_ulong serial; /* # of last request processed by server */ 13437 Bool send_event; /* true if this came from a SendEvent request */ 13438 Display *display; /* Display the event was read from */ 13439 Window window; /* window which selected for this event */ 13440 Window root; /* Root window for changed screen */ 13441 Time timestamp; /* when the screen change occurred */ 13442 Time config_timestamp; /* when the last configuration change */ 13443 ushort/*SizeID*/ size_index; 13444 ushort/*SubpixelOrder*/ subpixel_order; 13445 ushort/*Rotation*/ rotation; 13446 int width; 13447 int height; 13448 int mwidth; 13449 int mheight; 13450 } 13451 13452 enum RRScreenChangeNotify = 0; 13453 13454 enum RRScreenChangeNotifyMask = 1; 13455 13456 __gshared int xrrEventBase = -1; 13457 13458 13459 interface XRandr { 13460 extern(C) @nogc: 13461 Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return); 13462 Status XRRQueryVersion (Display *dpy, int *major_version_return, int *minor_version_return); 13463 13464 XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors); 13465 void XRRFreeMonitors(XRRMonitorInfo *monitors); 13466 13467 void XRRSelectInput(Display *dpy, Window window, int mask); 13468 } 13469 13470 __gshared bool XRandrLibrarySuccessfullyLoaded = true; 13471 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary; 13472 /* Xrandr } */ 13473 13474 /* Xft { */ 13475 13476 // actually freetype 13477 alias void FT_Face; 13478 13479 // actually fontconfig 13480 private alias FcBool = int; 13481 alias void FcCharSet; 13482 alias void FcPattern; 13483 alias void FcResult; 13484 enum FcEndian { FcEndianBig, FcEndianLittle } 13485 struct FcFontSet { 13486 int nfont; 13487 int sfont; 13488 FcPattern** fonts; 13489 } 13490 13491 // actually XRegion 13492 struct BOX { 13493 short x1, x2, y1, y2; 13494 } 13495 struct _XRegion { 13496 c_long size; 13497 c_long numRects; 13498 BOX* rects; 13499 BOX extents; 13500 } 13501 13502 alias Region = _XRegion*; 13503 13504 // ok actually Xft 13505 13506 struct XftFontInfo; 13507 13508 struct XftFont { 13509 int ascent; 13510 int descent; 13511 int height; 13512 int max_advance_width; 13513 FcCharSet* charset; 13514 FcPattern* pattern; 13515 } 13516 13517 struct XftDraw; 13518 13519 struct XftColor { 13520 c_ulong pixel; 13521 XRenderColor color; 13522 } 13523 13524 struct XftCharSpec { 13525 dchar ucs4; 13526 short x; 13527 short y; 13528 } 13529 13530 struct XftCharFontSpec { 13531 XftFont *font; 13532 dchar ucs4; 13533 short x; 13534 short y; 13535 } 13536 13537 struct XftGlyphSpec { 13538 uint glyph; 13539 short x; 13540 short y; 13541 } 13542 13543 struct XftGlyphFontSpec { 13544 XftFont *font; 13545 uint glyph; 13546 short x; 13547 short y; 13548 } 13549 13550 interface Xft { 13551 extern(C) @nogc pure: 13552 13553 Bool XftColorAllocName (Display *dpy, 13554 const Visual *visual, 13555 Colormap cmap, 13556 const char *name, 13557 XftColor *result); 13558 13559 Bool XftColorAllocValue (Display *dpy, 13560 Visual *visual, 13561 Colormap cmap, 13562 const XRenderColor *color, 13563 XftColor *result); 13564 13565 void XftColorFree (Display *dpy, 13566 Visual *visual, 13567 Colormap cmap, 13568 XftColor *color); 13569 13570 Bool XftDefaultHasRender (Display *dpy); 13571 13572 Bool XftDefaultSet (Display *dpy, FcPattern *defaults); 13573 13574 void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern); 13575 13576 XftDraw * XftDrawCreate (Display *dpy, 13577 Drawable drawable, 13578 Visual *visual, 13579 Colormap colormap); 13580 13581 XftDraw * XftDrawCreateBitmap (Display *dpy, 13582 Pixmap bitmap); 13583 13584 XftDraw * XftDrawCreateAlpha (Display *dpy, 13585 Pixmap pixmap, 13586 int depth); 13587 13588 void XftDrawChange (XftDraw *draw, 13589 Drawable drawable); 13590 13591 Display * XftDrawDisplay (XftDraw *draw); 13592 13593 Drawable XftDrawDrawable (XftDraw *draw); 13594 13595 Colormap XftDrawColormap (XftDraw *draw); 13596 13597 Visual * XftDrawVisual (XftDraw *draw); 13598 13599 void XftDrawDestroy (XftDraw *draw); 13600 13601 Picture XftDrawPicture (XftDraw *draw); 13602 13603 Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color); 13604 13605 void XftDrawGlyphs (XftDraw *draw, 13606 const XftColor *color, 13607 XftFont *pub, 13608 int x, 13609 int y, 13610 const uint *glyphs, 13611 int nglyphs); 13612 13613 void XftDrawString8 (XftDraw *draw, 13614 const XftColor *color, 13615 XftFont *pub, 13616 int x, 13617 int y, 13618 const char *string, 13619 int len); 13620 13621 void XftDrawString16 (XftDraw *draw, 13622 const XftColor *color, 13623 XftFont *pub, 13624 int x, 13625 int y, 13626 const wchar *string, 13627 int len); 13628 13629 void XftDrawString32 (XftDraw *draw, 13630 const XftColor *color, 13631 XftFont *pub, 13632 int x, 13633 int y, 13634 const dchar *string, 13635 int len); 13636 13637 void XftDrawStringUtf8 (XftDraw *draw, 13638 const XftColor *color, 13639 XftFont *pub, 13640 int x, 13641 int y, 13642 const char *string, 13643 int len); 13644 void XftDrawStringUtf16 (XftDraw *draw, 13645 const XftColor *color, 13646 XftFont *pub, 13647 int x, 13648 int y, 13649 const char *string, 13650 FcEndian endian, 13651 int len); 13652 13653 void XftDrawCharSpec (XftDraw *draw, 13654 const XftColor *color, 13655 XftFont *pub, 13656 const XftCharSpec *chars, 13657 int len); 13658 13659 void XftDrawCharFontSpec (XftDraw *draw, 13660 const XftColor *color, 13661 const XftCharFontSpec *chars, 13662 int len); 13663 13664 void XftDrawGlyphSpec (XftDraw *draw, 13665 const XftColor *color, 13666 XftFont *pub, 13667 const XftGlyphSpec *glyphs, 13668 int len); 13669 13670 void XftDrawGlyphFontSpec (XftDraw *draw, 13671 const XftColor *color, 13672 const XftGlyphFontSpec *glyphs, 13673 int len); 13674 13675 void XftDrawRect (XftDraw *draw, 13676 const XftColor *color, 13677 int x, 13678 int y, 13679 uint width, 13680 uint height); 13681 13682 Bool XftDrawSetClip (XftDraw *draw, 13683 Region r); 13684 13685 13686 Bool XftDrawSetClipRectangles (XftDraw *draw, 13687 int xOrigin, 13688 int yOrigin, 13689 const XRectangle *rects, 13690 int n); 13691 13692 void XftDrawSetSubwindowMode (XftDraw *draw, 13693 int mode); 13694 13695 void XftGlyphExtents (Display *dpy, 13696 XftFont *pub, 13697 const uint *glyphs, 13698 int nglyphs, 13699 XGlyphInfo *extents); 13700 13701 void XftTextExtents8 (Display *dpy, 13702 XftFont *pub, 13703 const char *string, 13704 int len, 13705 XGlyphInfo *extents); 13706 13707 void XftTextExtents16 (Display *dpy, 13708 XftFont *pub, 13709 const wchar *string, 13710 int len, 13711 XGlyphInfo *extents); 13712 13713 void XftTextExtents32 (Display *dpy, 13714 XftFont *pub, 13715 const dchar *string, 13716 int len, 13717 XGlyphInfo *extents); 13718 13719 void XftTextExtentsUtf8 (Display *dpy, 13720 XftFont *pub, 13721 const char *string, 13722 int len, 13723 XGlyphInfo *extents); 13724 13725 void XftTextExtentsUtf16 (Display *dpy, 13726 XftFont *pub, 13727 const char *string, 13728 FcEndian endian, 13729 int len, 13730 XGlyphInfo *extents); 13731 13732 FcPattern * XftFontMatch (Display *dpy, 13733 int screen, 13734 const FcPattern *pattern, 13735 FcResult *result); 13736 13737 XftFont * XftFontOpen (Display *dpy, int screen, ...); 13738 13739 XftFont * XftFontOpenName (Display *dpy, int screen, const char *name); 13740 13741 XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd); 13742 13743 FT_Face XftLockFace (XftFont *pub); 13744 13745 void XftUnlockFace (XftFont *pub); 13746 13747 XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern); 13748 13749 void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi); 13750 13751 dchar XftFontInfoHash (const XftFontInfo *fi); 13752 13753 FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b); 13754 13755 XftFont * XftFontOpenInfo (Display *dpy, 13756 FcPattern *pattern, 13757 XftFontInfo *fi); 13758 13759 XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern); 13760 13761 XftFont * XftFontCopy (Display *dpy, XftFont *pub); 13762 13763 void XftFontClose (Display *dpy, XftFont *pub); 13764 13765 FcBool XftInitFtLibrary(); 13766 void XftFontLoadGlyphs (Display *dpy, 13767 XftFont *pub, 13768 FcBool need_bitmaps, 13769 const uint *glyphs, 13770 int nglyph); 13771 13772 void XftFontUnloadGlyphs (Display *dpy, 13773 XftFont *pub, 13774 const uint *glyphs, 13775 int nglyph); 13776 13777 FcBool XftFontCheckGlyph (Display *dpy, 13778 XftFont *pub, 13779 FcBool need_bitmaps, 13780 uint glyph, 13781 uint *missing, 13782 int *nmissing); 13783 13784 FcBool XftCharExists (Display *dpy, 13785 XftFont *pub, 13786 dchar ucs4); 13787 13788 uint XftCharIndex (Display *dpy, 13789 XftFont *pub, 13790 dchar ucs4); 13791 FcBool XftInit (const char *config); 13792 13793 int XftGetVersion (); 13794 13795 FcFontSet * XftListFonts (Display *dpy, 13796 int screen, 13797 ...); 13798 13799 FcPattern *XftNameParse (const char *name); 13800 13801 void XftGlyphRender (Display *dpy, 13802 int op, 13803 Picture src, 13804 XftFont *pub, 13805 Picture dst, 13806 int srcx, 13807 int srcy, 13808 int x, 13809 int y, 13810 const uint *glyphs, 13811 int nglyphs); 13812 13813 void XftGlyphSpecRender (Display *dpy, 13814 int op, 13815 Picture src, 13816 XftFont *pub, 13817 Picture dst, 13818 int srcx, 13819 int srcy, 13820 const XftGlyphSpec *glyphs, 13821 int nglyphs); 13822 13823 void XftCharSpecRender (Display *dpy, 13824 int op, 13825 Picture src, 13826 XftFont *pub, 13827 Picture dst, 13828 int srcx, 13829 int srcy, 13830 const XftCharSpec *chars, 13831 int len); 13832 void XftGlyphFontSpecRender (Display *dpy, 13833 int op, 13834 Picture src, 13835 Picture dst, 13836 int srcx, 13837 int srcy, 13838 const XftGlyphFontSpec *glyphs, 13839 int nglyphs); 13840 13841 void XftCharFontSpecRender (Display *dpy, 13842 int op, 13843 Picture src, 13844 Picture dst, 13845 int srcx, 13846 int srcy, 13847 const XftCharFontSpec *chars, 13848 int len); 13849 13850 void XftTextRender8 (Display *dpy, 13851 int op, 13852 Picture src, 13853 XftFont *pub, 13854 Picture dst, 13855 int srcx, 13856 int srcy, 13857 int x, 13858 int y, 13859 const char *string, 13860 int len); 13861 void XftTextRender16 (Display *dpy, 13862 int op, 13863 Picture src, 13864 XftFont *pub, 13865 Picture dst, 13866 int srcx, 13867 int srcy, 13868 int x, 13869 int y, 13870 const wchar *string, 13871 int len); 13872 13873 void XftTextRender16BE (Display *dpy, 13874 int op, 13875 Picture src, 13876 XftFont *pub, 13877 Picture dst, 13878 int srcx, 13879 int srcy, 13880 int x, 13881 int y, 13882 const char *string, 13883 int len); 13884 13885 void XftTextRender16LE (Display *dpy, 13886 int op, 13887 Picture src, 13888 XftFont *pub, 13889 Picture dst, 13890 int srcx, 13891 int srcy, 13892 int x, 13893 int y, 13894 const char *string, 13895 int len); 13896 13897 void XftTextRender32 (Display *dpy, 13898 int op, 13899 Picture src, 13900 XftFont *pub, 13901 Picture dst, 13902 int srcx, 13903 int srcy, 13904 int x, 13905 int y, 13906 const dchar *string, 13907 int len); 13908 13909 void XftTextRender32BE (Display *dpy, 13910 int op, 13911 Picture src, 13912 XftFont *pub, 13913 Picture dst, 13914 int srcx, 13915 int srcy, 13916 int x, 13917 int y, 13918 const char *string, 13919 int len); 13920 13921 void XftTextRender32LE (Display *dpy, 13922 int op, 13923 Picture src, 13924 XftFont *pub, 13925 Picture dst, 13926 int srcx, 13927 int srcy, 13928 int x, 13929 int y, 13930 const char *string, 13931 int len); 13932 13933 void XftTextRenderUtf8 (Display *dpy, 13934 int op, 13935 Picture src, 13936 XftFont *pub, 13937 Picture dst, 13938 int srcx, 13939 int srcy, 13940 int x, 13941 int y, 13942 const char *string, 13943 int len); 13944 13945 void XftTextRenderUtf16 (Display *dpy, 13946 int op, 13947 Picture src, 13948 XftFont *pub, 13949 Picture dst, 13950 int srcx, 13951 int srcy, 13952 int x, 13953 int y, 13954 const char *string, 13955 FcEndian endian, 13956 int len); 13957 FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete); 13958 13959 } 13960 13961 interface FontConfig { 13962 extern(C) @nogc pure: 13963 int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s); 13964 void FcFontSetDestroy(FcFontSet*); 13965 char* FcNameUnparse(const FcPattern *); 13966 } 13967 13968 mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary; 13969 mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary; 13970 13971 13972 /* Xft } */ 13973 13974 class XDisconnectException : Exception { 13975 bool userRequested; 13976 this(bool userRequested = true) { 13977 this.userRequested = userRequested; 13978 super("X disconnected"); 13979 } 13980 } 13981 13982 /++ 13983 Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this. 13984 13985 Please note that it returns 13986 +/ 13987 XErrorEvent[] trapXErrors(scope void delegate() dg) { 13988 13989 static XErrorEvent[] errorBuffer; 13990 13991 static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow { 13992 errorBuffer ~= *evt; 13993 return 0; 13994 } 13995 13996 auto savedErrorHandler = XSetErrorHandler(&handler); 13997 13998 try { 13999 dg(); 14000 } finally { 14001 XSync(XDisplayConnection.get, 0/*False*/); 14002 XSetErrorHandler(savedErrorHandler); 14003 } 14004 14005 auto bfr = errorBuffer; 14006 errorBuffer = null; 14007 14008 return bfr; 14009 } 14010 14011 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`. 14012 class XDisplayConnection { 14013 private __gshared Display* display; 14014 private __gshared XIM xim; 14015 private __gshared char* displayName; 14016 14017 private __gshared int connectionSequence_; 14018 private __gshared bool isLocal_; 14019 14020 /// use this for lazy caching when reconnection 14021 static int connectionSequenceNumber() { return connectionSequence_; } 14022 14023 /++ 14024 Guesses if the connection appears to be local. 14025 14026 History: 14027 Added June 3, 2021 14028 +/ 14029 static @property bool isLocal() nothrow @trusted @nogc { 14030 return isLocal_; 14031 } 14032 14033 /// Attempts recreation of state, may require application assistance 14034 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 14035 /// then call this, and if successful, reenter the loop. 14036 static void discardAndRecreate(string newDisplayString = null) { 14037 if(insideXEventLoop) 14038 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 14039 14040 // 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 14041 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 14042 14043 foreach(handle; chnenhm) { 14044 handle.discardConnectionState(); 14045 } 14046 14047 discardState(); 14048 14049 if(newDisplayString !is null) 14050 setDisplayName(newDisplayString); 14051 14052 auto display = get(); 14053 14054 foreach(handle; chnenhm) { 14055 handle.recreateAfterDisconnect(); 14056 } 14057 } 14058 14059 private __gshared EventMask rootEventMask; 14060 14061 /++ 14062 Requests the specified input from the root window on the connection, in addition to any other request. 14063 14064 14065 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. 14066 14067 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 14068 +/ 14069 static void addRootInput(EventMask mask) { 14070 auto old = rootEventMask; 14071 rootEventMask |= mask; 14072 get(); // to ensure display connected 14073 if(display !is null && rootEventMask != old) 14074 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 14075 } 14076 14077 static void discardState() { 14078 freeImages(); 14079 14080 foreach(atomPtr; interredAtoms) 14081 *atomPtr = 0; 14082 interredAtoms = null; 14083 interredAtoms.assumeSafeAppend(); 14084 14085 ScreenPainterImplementation.fontAttempted = false; 14086 ScreenPainterImplementation.defaultfont = null; 14087 ScreenPainterImplementation.defaultfontset = null; 14088 14089 Image.impl.xshmQueryCompleted = false; 14090 Image.impl._xshmAvailable = false; 14091 14092 SimpleWindow.nativeMapping = null; 14093 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 14094 // GlobalHotkeyManager 14095 14096 display = null; 14097 xim = null; 14098 } 14099 14100 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 14101 private static void createXIM () { 14102 import core.stdc.locale : setlocale, LC_ALL; 14103 import core.stdc.stdio : stderr, fprintf; 14104 import core.stdc.stdlib : free; 14105 import core.stdc.string : strdup; 14106 14107 static immutable string[3] mtry = [ "", "@im=local", "@im=" ]; 14108 14109 auto olocale = strdup(setlocale(LC_ALL, null)); 14110 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 14111 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 14112 14113 //fprintf(stderr, "opening IM...\n"); 14114 foreach (string s; mtry) { 14115 XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 14116 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 14117 } 14118 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 14119 } 14120 14121 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 14122 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 14123 static struct ImgList { 14124 size_t img; // class; hide it from GC 14125 ImgList* next; 14126 } 14127 14128 static __gshared ImgList* imglist = null; 14129 static __gshared bool imglistLocked = false; // true: don't register and unregister images 14130 14131 static void registerImage (Image img) { 14132 if (!imglistLocked && img !is null) { 14133 import core.stdc.stdlib : malloc; 14134 auto it = cast(ImgList*)malloc(ImgList.sizeof); 14135 assert(it !is null); // do proper checks 14136 it.img = cast(size_t)cast(void*)img; 14137 it.next = imglist; 14138 imglist = it; 14139 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 14140 } 14141 } 14142 14143 static void unregisterImage (Image img) { 14144 if (!imglistLocked && img !is null) { 14145 import core.stdc.stdlib : free; 14146 ImgList* prev = null; 14147 ImgList* cur = imglist; 14148 while (cur !is null) { 14149 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 14150 prev = cur; 14151 cur = cur.next; 14152 } 14153 if (cur !is null) { 14154 if (prev is null) imglist = cur.next; else prev.next = cur.next; 14155 free(cur); 14156 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 14157 } else { 14158 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 14159 } 14160 } 14161 } 14162 14163 static void freeImages () { // needed for discardAndRecreate 14164 imglistLocked = true; 14165 scope(exit) imglistLocked = false; 14166 ImgList* cur = imglist; 14167 ImgList* next = null; 14168 while (cur !is null) { 14169 import core.stdc.stdlib : free; 14170 next = cur.next; 14171 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 14172 (cast(Image)cast(void*)cur.img).dispose(); 14173 free(cur); 14174 cur = next; 14175 } 14176 imglist = null; 14177 } 14178 14179 /// can be used to override normal handling of display name 14180 /// from environment and/or command line 14181 static setDisplayName(string newDisplayName) { 14182 displayName = cast(char*) (newDisplayName ~ '\0'); 14183 } 14184 14185 /// resets to the default display string 14186 static resetDisplayName() { 14187 displayName = null; 14188 } 14189 14190 /// 14191 static Display* get() { 14192 if(display is null) { 14193 if(!librariesSuccessfullyLoaded) 14194 throw new Exception("Unable to load X11 client libraries"); 14195 display = XOpenDisplay(displayName); 14196 14197 isLocal_ = false; 14198 14199 connectionSequence_++; 14200 if(display is null) 14201 throw new Exception("Unable to open X display"); 14202 14203 auto str = display.display_name; 14204 // this is a bit of a hack but like if it looks like a unix socket we assume it is local 14205 // and otherwise it probably isn't 14206 if(str is null || (str[0] != ':' && str[0] != '/')) 14207 isLocal_ = false; 14208 else 14209 isLocal_ = true; 14210 14211 debug(sdpy_x_errors) { 14212 XSetErrorHandler(&adrlogger); 14213 XSynchronize(display, true); 14214 14215 extern(C) int wtf() { 14216 if(errorHappened) { 14217 asm { int 3; } 14218 errorHappened = false; 14219 } 14220 return 0; 14221 } 14222 XSetAfterFunction(display, &wtf); 14223 } 14224 14225 14226 XSetIOErrorHandler(&x11ioerrCB); 14227 Bool sup; 14228 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 14229 createXIM(); 14230 version(with_eventloop) { 14231 import arsd.eventloop; 14232 addFileEventListeners(display.fd, &eventListener, null, null); 14233 } 14234 } 14235 14236 return display; 14237 } 14238 14239 extern(C) 14240 static int x11ioerrCB(Display* dpy) { 14241 throw new XDisconnectException(false); 14242 } 14243 14244 version(with_eventloop) { 14245 import arsd.eventloop; 14246 static void eventListener(OsFileHandle fd) { 14247 //this.mtLock(); 14248 //scope(exit) this.mtUnlock(); 14249 while(XPending(display)) 14250 doXNextEvent(display); 14251 } 14252 } 14253 14254 // close connection on program exit -- we need this to properly free all images 14255 static ~this () { 14256 // the gui thread must clean up after itself or else Xlib might deadlock 14257 // using this flag on any thread destruction is the easiest way i know of 14258 // (shared static this is run by the LAST thread to exit, which may not be 14259 // the gui thread, and normal static this run by ALL threads, so we gotta check.) 14260 if(thisIsGuiThread) 14261 close(); 14262 } 14263 14264 /// 14265 static void close() { 14266 if(display is null) 14267 return; 14268 14269 version(with_eventloop) { 14270 import arsd.eventloop; 14271 removeFileEventListeners(display.fd); 14272 } 14273 14274 // now remove all registered images to prevent shared memory leaks 14275 freeImages(); 14276 14277 // tbh I don't know why it is doing this but like if this happens to run 14278 // from the other thread there's frequent hanging inside here. 14279 if(thisIsGuiThread) 14280 XCloseDisplay(display); 14281 display = null; 14282 } 14283 } 14284 14285 mixin template NativeImageImplementation() { 14286 XImage* handle; 14287 ubyte* rawData; 14288 14289 XShmSegmentInfo shminfo; 14290 bool premultiply = true; 14291 14292 __gshared bool xshmQueryCompleted; 14293 __gshared bool _xshmAvailable; 14294 public static @property bool xshmAvailable() { 14295 if(!xshmQueryCompleted) { 14296 int i1, i2, i3; 14297 xshmQueryCompleted = true; 14298 14299 if(!XDisplayConnection.isLocal) 14300 _xshmAvailable = false; 14301 else 14302 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 14303 } 14304 return _xshmAvailable; 14305 } 14306 14307 bool usingXshm; 14308 final: 14309 14310 private __gshared bool xshmfailed; 14311 14312 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 14313 auto display = XDisplayConnection.get(); 14314 assert(display !is null); 14315 auto screen = DefaultScreen(display); 14316 14317 // it will only use shared memory for somewhat largish images, 14318 // since otherwise we risk wasting shared memory handles on a lot of little ones 14319 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 14320 14321 14322 // it is possible for the query extension to return true, the DISPLAY check to pass, yet 14323 // the actual use still fails. For example, if the program is in a container and permission denied 14324 // on shared memory, or if it is a local thing forwarded to a remote server, etc. 14325 // 14326 // If it does fail, we need to detect it now, abort the xshm and fall back to core protocol. 14327 14328 14329 // synchronize so preexisting buffers are clear 14330 XSync(display, false); 14331 xshmfailed = false; 14332 14333 auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler); 14334 14335 14336 usingXshm = true; 14337 handle = XShmCreateImage( 14338 display, 14339 DefaultVisual(display, screen), 14340 enableAlpha ? 32: 24, 14341 ImageFormat.ZPixmap, 14342 null, 14343 &shminfo, 14344 width, height); 14345 if(handle is null) 14346 goto abortXshm1; 14347 14348 if(handle.bytes_per_line != 4 * width) 14349 goto abortXshm2; 14350 14351 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 14352 if(shminfo.shmid < 0) 14353 goto abortXshm3; 14354 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 14355 if(rawData == cast(ubyte*) -1) 14356 goto abortXshm4; 14357 shminfo.readOnly = 0; 14358 XShmAttach(display, &shminfo); 14359 14360 // and now to the final error check to ensure it actually worked. 14361 XSync(display, false); 14362 if(xshmfailed) 14363 goto abortXshm5; 14364 14365 XSetErrorHandler(oldErrorHandler); 14366 14367 XDisplayConnection.registerImage(this); 14368 // if I don't flush here there's a chance the dtor will run before the 14369 // ctor and lead to a bad value X error. While this hurts the efficiency 14370 // it is local anyway so prolly better to keep it simple 14371 XFlush(display); 14372 14373 return; 14374 14375 abortXshm5: 14376 shmdt(shminfo.shmaddr); 14377 rawData = null; 14378 14379 abortXshm4: 14380 shmctl(shminfo.shmid, IPC_RMID, null); 14381 14382 abortXshm3: 14383 // nothing needed, the shmget failed so there's nothing to free 14384 14385 abortXshm2: 14386 XDestroyImage(handle); 14387 handle = null; 14388 14389 abortXshm1: 14390 XSetErrorHandler(oldErrorHandler); 14391 usingXshm = false; 14392 handle = null; 14393 14394 shminfo = typeof(shminfo).init; 14395 14396 _xshmAvailable = false; // don't try again in the future 14397 14398 // writeln("fallingback"); 14399 14400 goto fallback; 14401 14402 } else { 14403 fallback: 14404 14405 if (forcexshm) throw new Exception("can't create XShm Image"); 14406 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 14407 import core.stdc.stdlib : malloc; 14408 rawData = cast(ubyte*) malloc(width * height * 4); 14409 14410 handle = XCreateImage( 14411 display, 14412 DefaultVisual(display, screen), 14413 enableAlpha ? 32 : 24, // bpp 14414 ImageFormat.ZPixmap, 14415 0, // offset 14416 rawData, 14417 width, height, 14418 enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line 14419 } 14420 } 14421 14422 void dispose() { 14423 // note: this calls free(rawData) for us 14424 if(handle) { 14425 if (usingXshm) { 14426 XDisplayConnection.unregisterImage(this); 14427 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 14428 } 14429 XDestroyImage(handle); 14430 if(usingXshm) { 14431 shmdt(shminfo.shmaddr); 14432 shmctl(shminfo.shmid, IPC_RMID, null); 14433 } 14434 handle = null; 14435 } 14436 } 14437 14438 Color getPixel(int x, int y) { 14439 auto offset = (y * width + x) * 4; 14440 Color c; 14441 c.a = enableAlpha ? rawData[offset + 3] : 255; 14442 c.b = rawData[offset + 0]; 14443 c.g = rawData[offset + 1]; 14444 c.r = rawData[offset + 2]; 14445 if(enableAlpha && premultiply) 14446 c.unPremultiply; 14447 return c; 14448 } 14449 14450 void setPixel(int x, int y, Color c) { 14451 if(enableAlpha && premultiply) 14452 c.premultiply(); 14453 auto offset = (y * width + x) * 4; 14454 rawData[offset + 0] = c.b; 14455 rawData[offset + 1] = c.g; 14456 rawData[offset + 2] = c.r; 14457 if(enableAlpha) 14458 rawData[offset + 3] = c.a; 14459 } 14460 14461 void convertToRgbaBytes(ubyte[] where) { 14462 assert(where.length == this.width * this.height * 4); 14463 14464 // if rawData had a length.... 14465 //assert(rawData.length == where.length); 14466 for(int idx = 0; idx < where.length; idx += 4) { 14467 where[idx + 0] = rawData[idx + 2]; // r 14468 where[idx + 1] = rawData[idx + 1]; // g 14469 where[idx + 2] = rawData[idx + 0]; // b 14470 where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a 14471 14472 if(enableAlpha && premultiply) 14473 unPremultiplyRgba(where[idx .. idx + 4]); 14474 } 14475 } 14476 14477 void setFromRgbaBytes(in ubyte[] where) { 14478 assert(where.length == this.width * this.height * 4); 14479 14480 // if rawData had a length.... 14481 //assert(rawData.length == where.length); 14482 for(int idx = 0; idx < where.length; idx += 4) { 14483 rawData[idx + 2] = where[idx + 0]; // r 14484 rawData[idx + 1] = where[idx + 1]; // g 14485 rawData[idx + 0] = where[idx + 2]; // b 14486 if(enableAlpha) { 14487 rawData[idx + 3] = where[idx + 3]; // a 14488 if(premultiply) 14489 premultiplyBgra(rawData[idx .. idx + 4]); 14490 } 14491 } 14492 } 14493 14494 } 14495 14496 mixin template NativeSimpleWindowImplementation() { 14497 GC gc; 14498 Window window; 14499 Display* display; 14500 14501 Pixmap buffer; 14502 int bufferw, bufferh; // size of the buffer; can be bigger than window 14503 XIC xic; // input context 14504 int curHidden = 0; // counter 14505 Cursor blankCurPtr = 0; 14506 int cursorSequenceNumber = 0; 14507 int warpEventCount = 0; // number of mouse movement events to eat 14508 14509 __gshared X11SetSelectionHandler[Atom] setSelectionHandlers; 14510 X11GetSelectionHandler[Atom] getSelectionHandlers; 14511 14512 version(without_opengl) {} else 14513 GLXContext glc; 14514 14515 private void fixFixedSize(bool forced=false) (int width, int height) { 14516 if (forced || this.resizability == Resizability.fixedSize) { 14517 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 14518 XSizeHints sh; 14519 static if (!forced) { 14520 c_long spr; 14521 XGetWMNormalHints(display, window, &sh, &spr); 14522 sh.flags |= PMaxSize | PMinSize; 14523 } else { 14524 sh.flags = PMaxSize | PMinSize; 14525 } 14526 sh.min_width = width; 14527 sh.min_height = height; 14528 sh.max_width = width; 14529 sh.max_height = height; 14530 XSetWMNormalHints(display, window, &sh); 14531 //XFlush(display); 14532 } 14533 } 14534 14535 ScreenPainter getPainter(bool manualInvalidations) { 14536 return ScreenPainter(this, window, manualInvalidations); 14537 } 14538 14539 void move(int x, int y) { 14540 XMoveWindow(display, window, x, y); 14541 } 14542 14543 void resize(int w, int h) { 14544 if (w < 1) w = 1; 14545 if (h < 1) h = 1; 14546 XResizeWindow(display, window, w, h); 14547 14548 // calling this now to avoid waiting for the server to 14549 // acknowledge the resize; draws without returning to the 14550 // event loop will thus actually work. the server's event 14551 // btw might overrule this and resize it again 14552 recordX11Resize(display, this, w, h); 14553 14554 updateOpenglViewportIfNeeded(w, h); 14555 } 14556 14557 void moveResize (int x, int y, int w, int h) { 14558 if (w < 1) w = 1; 14559 if (h < 1) h = 1; 14560 XMoveResizeWindow(display, window, x, y, w, h); 14561 updateOpenglViewportIfNeeded(w, h); 14562 } 14563 14564 void hideCursor () { 14565 if (curHidden++ == 0) { 14566 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 14567 static const(char)[1] cmbmp = 0; 14568 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 14569 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 14570 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 14571 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 14572 XFreePixmap(display, pm); 14573 } 14574 XDefineCursor(display, window, blankCurPtr); 14575 } 14576 } 14577 14578 void showCursor () { 14579 if (--curHidden == 0) XUndefineCursor(display, window); 14580 } 14581 14582 void warpMouse (int x, int y) { 14583 // here i will send dummy "ignore next mouse motion" event, 14584 // 'cause `XWarpPointer()` sends synthesised mouse motion, 14585 // and we don't need to report it to the user (as warping is 14586 // used when the user needs movement deltas). 14587 //XClientMessageEvent xclient; 14588 XEvent e; 14589 e.xclient.type = EventType.ClientMessage; 14590 e.xclient.window = window; 14591 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14592 e.xclient.format = 32; 14593 e.xclient.data.l[0] = 0; 14594 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 14595 //{ 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]); } 14596 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14597 // now warp pointer... 14598 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 14599 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 14600 // ...and flush 14601 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 14602 XFlush(display); 14603 } 14604 14605 void sendDummyEvent () { 14606 // here i will send dummy event to ping event queue 14607 XEvent e; 14608 e.xclient.type = EventType.ClientMessage; 14609 e.xclient.window = window; 14610 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14611 e.xclient.format = 32; 14612 e.xclient.data.l[0] = 0; 14613 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14614 XFlush(display); 14615 } 14616 14617 void setTitle(string title) { 14618 if (title.ptr is null) title = ""; 14619 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14620 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14621 XTextProperty windowName; 14622 windowName.value = title.ptr; 14623 windowName.encoding = XA_UTF8; //XA_STRING; 14624 windowName.format = 8; 14625 windowName.nitems = cast(uint)title.length; 14626 XSetWMName(display, window, &windowName); 14627 char[1024] namebuf = 0; 14628 auto maxlen = namebuf.length-1; 14629 if (maxlen > title.length) maxlen = title.length; 14630 namebuf[0..maxlen] = title[0..maxlen]; 14631 XStoreName(display, window, namebuf.ptr); 14632 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 14633 flushGui(); // without this OpenGL windows has a LONG delay before changing title 14634 } 14635 14636 string[] getTitles() { 14637 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14638 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14639 XTextProperty textProp; 14640 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 14641 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 14642 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 14643 } else 14644 return []; 14645 } else 14646 return null; 14647 } 14648 14649 string getTitle() { 14650 auto titles = getTitles(); 14651 return titles.length ? titles[0] : null; 14652 } 14653 14654 void setMinSize (int minwidth, int minheight) { 14655 import core.stdc.config : c_long; 14656 if (minwidth < 1) minwidth = 1; 14657 if (minheight < 1) minheight = 1; 14658 XSizeHints sh; 14659 c_long spr; 14660 XGetWMNormalHints(display, window, &sh, &spr); 14661 sh.min_width = minwidth; 14662 sh.min_height = minheight; 14663 sh.flags |= PMinSize; 14664 XSetWMNormalHints(display, window, &sh); 14665 flushGui(); 14666 } 14667 14668 void setMaxSize (int maxwidth, int maxheight) { 14669 import core.stdc.config : c_long; 14670 if (maxwidth < 1) maxwidth = 1; 14671 if (maxheight < 1) maxheight = 1; 14672 XSizeHints sh; 14673 c_long spr; 14674 XGetWMNormalHints(display, window, &sh, &spr); 14675 sh.max_width = maxwidth; 14676 sh.max_height = maxheight; 14677 sh.flags |= PMaxSize; 14678 XSetWMNormalHints(display, window, &sh); 14679 flushGui(); 14680 } 14681 14682 void setResizeGranularity (int granx, int grany) { 14683 import core.stdc.config : c_long; 14684 if (granx < 1) granx = 1; 14685 if (grany < 1) grany = 1; 14686 XSizeHints sh; 14687 c_long spr; 14688 XGetWMNormalHints(display, window, &sh, &spr); 14689 sh.width_inc = granx; 14690 sh.height_inc = grany; 14691 sh.flags |= PResizeInc; 14692 XSetWMNormalHints(display, window, &sh); 14693 flushGui(); 14694 } 14695 14696 void setOpacity (uint opacity) { 14697 arch_ulong o = opacity; 14698 if (opacity == uint.max) 14699 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 14700 else 14701 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 14702 XA_CARDINAL, 32, PropModeReplace, &o, 1); 14703 } 14704 14705 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) { 14706 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 14707 display = XDisplayConnection.get(); 14708 auto screen = DefaultScreen(display); 14709 14710 bool overrideRedirect = false; 14711 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild) 14712 overrideRedirect = true; 14713 14714 version(without_opengl) {} 14715 else { 14716 if(opengl == OpenGlOptions.yes) { 14717 GLXFBConfig fbconf = null; 14718 XVisualInfo* vi = null; 14719 bool useLegacy = false; 14720 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 14721 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 14722 int[23] visualAttribs = [ 14723 GLX_X_RENDERABLE , 1/*True*/, 14724 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 14725 GLX_RENDER_TYPE , GLX_RGBA_BIT, 14726 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 14727 GLX_RED_SIZE , 8, 14728 GLX_GREEN_SIZE , 8, 14729 GLX_BLUE_SIZE , 8, 14730 GLX_ALPHA_SIZE , 8, 14731 GLX_DEPTH_SIZE , 24, 14732 GLX_STENCIL_SIZE , 8, 14733 GLX_DOUBLEBUFFER , 1/*True*/, 14734 0/*None*/, 14735 ]; 14736 int fbcount; 14737 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 14738 if (fbcount == 0) { 14739 useLegacy = true; // try to do at least something 14740 } else { 14741 // pick the FB config/visual with the most samples per pixel 14742 int bestidx = -1, bestns = -1; 14743 foreach (int fbi; 0..fbcount) { 14744 int sb, samples; 14745 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 14746 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 14747 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 14748 } 14749 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 14750 fbconf = fbc[bestidx]; 14751 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 14752 XFree(fbc); 14753 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 14754 } 14755 } 14756 if (vi is null || useLegacy) { 14757 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 14758 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 14759 useLegacy = true; 14760 } 14761 if (vi is null) throw new Exception("no open gl visual found"); 14762 14763 XSetWindowAttributes swa; 14764 auto root = RootWindow(display, screen); 14765 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 14766 14767 swa.override_redirect = overrideRedirect; 14768 14769 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14770 0, 0, width, height, 14771 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa); 14772 14773 // now try to use `glXCreateContextAttribsARB()` if it's here 14774 if (!useLegacy) { 14775 // request fairly advanced context, even with stencil buffer! 14776 int[9] contextAttribs = [ 14777 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 14778 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 14779 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 14780 // for modern context, set "forward compatibility" flag too 14781 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 14782 0/*None*/, 14783 ]; 14784 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 14785 if (glc is null && sdpyOpenGLContextAllowFallback) { 14786 sdpyOpenGLContextVersion = 0; 14787 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14788 } 14789 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 14790 } else { 14791 // fallback to old GLX call 14792 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 14793 sdpyOpenGLContextVersion = 0; 14794 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14795 } 14796 } 14797 // sync to ensure any errors generated are processed 14798 XSync(display, 0/*False*/); 14799 //{ import core.stdc.stdio; printf("ogl is here\n"); } 14800 if(glc is null) 14801 throw new Exception("glc"); 14802 } 14803 } 14804 14805 if(opengl == OpenGlOptions.no) { 14806 14807 XSetWindowAttributes swa; 14808 swa.background_pixel = WhitePixel(display, screen); 14809 swa.border_pixel = BlackPixel(display, screen); 14810 swa.override_redirect = overrideRedirect; 14811 auto root = RootWindow(display, screen); 14812 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 14813 14814 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14815 0, 0, width, height, 14816 // I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit. 14817 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa); 14818 14819 14820 14821 /* 14822 window = XCreateSimpleWindow( 14823 display, 14824 parent is null ? RootWindow(display, screen) : parent.impl.window, 14825 0, 0, // x, y 14826 width, height, 14827 1, // border width 14828 BlackPixel(display, screen), // border 14829 WhitePixel(display, screen)); // background 14830 */ 14831 14832 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 14833 bufferw = width; 14834 bufferh = height; 14835 14836 gc = DefaultGC(display, screen); 14837 14838 // clear out the buffer to get us started... 14839 XSetForeground(display, gc, WhitePixel(display, screen)); 14840 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 14841 XSetForeground(display, gc, BlackPixel(display, screen)); 14842 } 14843 14844 // input context 14845 //TODO: create this only for top-level windows, and reuse that? 14846 populateXic(); 14847 14848 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 14849 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 14850 // window class 14851 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 14852 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 14853 XClassHint klass; 14854 XWMHints wh; 14855 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14856 wh.input = true; 14857 wh.flags |= InputHint; 14858 } 14859 XSizeHints size; 14860 klass.res_name = sdpyWindowClassStr; 14861 klass.res_class = sdpyWindowClassStr; 14862 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 14863 } 14864 14865 setTitle(title); 14866 SimpleWindow.nativeMapping[window] = this; 14867 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 14868 14869 // This gives our window a close button 14870 if (windowType != WindowTypes.eventOnly) { 14871 Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)]; 14872 int useAtoms; 14873 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14874 useAtoms = 2; 14875 } else { 14876 useAtoms = 1; 14877 } 14878 assert(useAtoms <= atoms.length); 14879 XSetWMProtocols(display, window, atoms.ptr, useAtoms); 14880 } 14881 14882 // FIXME: windowType and customizationFlags 14883 Atom[8] wsatoms; // here, due to goto 14884 int wmsacount = 0; // here, due to goto 14885 14886 try 14887 final switch(windowType) { 14888 case WindowTypes.normal: 14889 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14890 break; 14891 case WindowTypes.undecorated: 14892 motifHideDecorations(); 14893 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14894 break; 14895 case WindowTypes.eventOnly: 14896 _hidden = true; 14897 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 14898 goto hiddenWindow; 14899 //break; 14900 case WindowTypes.nestedChild: 14901 // handled in XCreateWindow calls 14902 break; 14903 14904 case WindowTypes.dropdownMenu: 14905 motifHideDecorations(); 14906 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 14907 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14908 break; 14909 case WindowTypes.popupMenu: 14910 motifHideDecorations(); 14911 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 14912 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14913 break; 14914 case WindowTypes.notification: 14915 motifHideDecorations(); 14916 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 14917 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14918 break; 14919 case WindowTypes.minimallyWrapped: 14920 assert(0, "don't create a minimallyWrapped thing explicitly!"); 14921 /+ 14922 case WindowTypes.menu: 14923 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14924 motifHideDecorations(); 14925 break; 14926 case WindowTypes.desktop: 14927 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 14928 break; 14929 case WindowTypes.dock: 14930 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 14931 break; 14932 case WindowTypes.toolbar: 14933 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 14934 break; 14935 case WindowTypes.menu: 14936 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14937 break; 14938 case WindowTypes.utility: 14939 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 14940 break; 14941 case WindowTypes.splash: 14942 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 14943 break; 14944 case WindowTypes.dialog: 14945 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display); 14946 break; 14947 case WindowTypes.tooltip: 14948 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display); 14949 break; 14950 case WindowTypes.notification: 14951 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 14952 break; 14953 case WindowTypes.combo: 14954 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display); 14955 break; 14956 case WindowTypes.dnd: 14957 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display); 14958 break; 14959 +/ 14960 } 14961 catch(Exception e) { 14962 // XInternAtom failed, prolly a WM 14963 // that doesn't support these things 14964 } 14965 14966 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 14967 // the two following flags may be ignored by WM 14968 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 14969 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 14970 14971 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 14972 14973 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 14974 14975 // What would be ideal here is if they only were 14976 // selected if there was actually an event handler 14977 // for them... 14978 14979 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 14980 14981 hiddenWindow: 14982 14983 // set the pid property for lookup later by window managers 14984 // a standard convenience 14985 import core.sys.posix.unistd; 14986 arch_ulong pid = getpid(); 14987 14988 XChangeProperty( 14989 display, 14990 impl.window, 14991 GetAtom!("_NET_WM_PID", true)(display), 14992 XA_CARDINAL, 14993 32 /* bits */, 14994 0 /*PropModeReplace*/, 14995 &pid, 14996 1); 14997 14998 if(isTransient && parent) { // customizationFlags & WindowFlags.transient) { 14999 if(parent is null) assert(0); 15000 XChangeProperty( 15001 display, 15002 impl.window, 15003 GetAtom!("WM_TRANSIENT_FOR", true)(display), 15004 XA_WINDOW, 15005 32 /* bits */, 15006 0 /*PropModeReplace*/, 15007 &parent.impl.window, 15008 1); 15009 15010 } 15011 15012 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 15013 XMapWindow(display, window); 15014 } else { 15015 _hidden = true; 15016 } 15017 } 15018 15019 void populateXic() { 15020 if (XDisplayConnection.xim !is null) { 15021 xic = XCreateIC(XDisplayConnection.xim, 15022 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 15023 /*XNClientWindow*/"clientWindow".ptr, window, 15024 /*XNFocusWindow*/"focusWindow".ptr, window, 15025 null); 15026 if (xic is null) { 15027 import core.stdc.stdio : stderr, fprintf; 15028 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 15029 } 15030 } 15031 } 15032 15033 void selectDefaultInput(bool forceIncludeMouseMotion) { 15034 auto mask = EventMask.ExposureMask | 15035 EventMask.KeyPressMask | 15036 EventMask.KeyReleaseMask | 15037 EventMask.PropertyChangeMask | 15038 EventMask.FocusChangeMask | 15039 EventMask.StructureNotifyMask | 15040 EventMask.SubstructureNotifyMask | 15041 EventMask.VisibilityChangeMask 15042 | EventMask.ButtonPressMask 15043 | EventMask.ButtonReleaseMask 15044 ; 15045 15046 // xshm is our shortcut for local connections 15047 if(XDisplayConnection.isLocal || forceIncludeMouseMotion) 15048 mask |= EventMask.PointerMotionMask; 15049 else 15050 mask |= EventMask.ButtonMotionMask; 15051 15052 XSelectInput(display, window, mask); 15053 } 15054 15055 15056 void setNetWMWindowType(Atom type) { 15057 Atom[2] atoms; 15058 15059 atoms[0] = type; 15060 // generic fallback 15061 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 15062 15063 XChangeProperty( 15064 display, 15065 impl.window, 15066 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 15067 XA_ATOM, 15068 32 /* bits */, 15069 0 /*PropModeReplace*/, 15070 atoms.ptr, 15071 cast(int) atoms.length); 15072 } 15073 15074 void motifHideDecorations(bool hide = true) { 15075 MwmHints hints; 15076 hints.flags = MWM_HINTS_DECORATIONS; 15077 hints.decorations = hide ? 0 : 1; 15078 15079 XChangeProperty( 15080 display, 15081 impl.window, 15082 GetAtom!"_MOTIF_WM_HINTS"(display), 15083 GetAtom!"_MOTIF_WM_HINTS"(display), 15084 32 /* bits */, 15085 0 /*PropModeReplace*/, 15086 &hints, 15087 hints.sizeof / 4); 15088 } 15089 15090 /*k8: unused 15091 void createOpenGlContext() { 15092 15093 } 15094 */ 15095 15096 void closeWindow() { 15097 // I can't close this or a child window closing will 15098 // break events for everyone. So I'm just leaking it right 15099 // now and that is probably perfectly fine... 15100 version(none) 15101 if (customEventFDRead != -1) { 15102 import core.sys.posix.unistd : close; 15103 auto same = customEventFDRead == customEventFDWrite; 15104 15105 close(customEventFDRead); 15106 if(!same) 15107 close(customEventFDWrite); 15108 customEventFDRead = -1; 15109 customEventFDWrite = -1; 15110 } 15111 15112 version(without_opengl) {} else 15113 if(glc !is null) { 15114 glXDestroyContext(display, glc); 15115 glc = null; 15116 } 15117 15118 if(buffer) 15119 XFreePixmap(display, buffer); 15120 bufferw = bufferh = 0; 15121 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 15122 XDestroyWindow(display, window); 15123 XFlush(display); 15124 } 15125 15126 void dispose() { 15127 } 15128 15129 bool destroyed = false; 15130 } 15131 15132 bool insideXEventLoop; 15133 } 15134 15135 version(X11) { 15136 15137 int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this. 15138 15139 private class ResizeEvent { 15140 int width, height; 15141 } 15142 15143 void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) { 15144 if(win.windowType == WindowTypes.minimallyWrapped) 15145 return; 15146 15147 if(win.pendingResizeEvent is null) { 15148 win.pendingResizeEvent = new ResizeEvent(); 15149 win.addEventListener((ResizeEvent re) { 15150 recordX11Resize(XDisplayConnection.get, win, re.width, re.height); 15151 }); 15152 } 15153 win.pendingResizeEvent.width = width; 15154 win.pendingResizeEvent.height = height; 15155 if(!win.eventQueued!ResizeEvent) { 15156 win.postEvent(win.pendingResizeEvent); 15157 } 15158 } 15159 15160 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 15161 if(win.windowType == WindowTypes.minimallyWrapped) 15162 return; 15163 if(win.closed) 15164 return; 15165 15166 if(width != win.width || height != win.height) { 15167 15168 // writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window); 15169 win._width = width; 15170 win._height = height; 15171 15172 if(win.openglMode == OpenGlOptions.no) { 15173 // FIXME: could this be more efficient? 15174 15175 if (win.bufferw < width || win.bufferh < height) { 15176 //{ 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); } 15177 // grow the internal buffer to match the window... 15178 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 15179 { 15180 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15181 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15182 scope(exit) XFreeGC(win.display, xgc); 15183 XSetClipMask(win.display, xgc, None); 15184 XSetForeground(win.display, xgc, 0); 15185 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 15186 } 15187 XCopyArea(display, 15188 cast(Drawable) win.buffer, 15189 cast(Drawable) newPixmap, 15190 win.gc, 0, 0, 15191 win.bufferw < width ? win.bufferw : win.width, 15192 win.bufferh < height ? win.bufferh : win.height, 15193 0, 0); 15194 15195 XFreePixmap(display, win.buffer); 15196 win.buffer = newPixmap; 15197 win.bufferw = width; 15198 win.bufferh = height; 15199 } 15200 15201 // clear unused parts of the buffer 15202 if (win.bufferw > width || win.bufferh > height) { 15203 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15204 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15205 scope(exit) XFreeGC(win.display, xgc); 15206 XSetClipMask(win.display, xgc, None); 15207 XSetForeground(win.display, xgc, 0); 15208 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 15209 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 15210 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 15211 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 15212 } 15213 15214 } 15215 15216 win.updateOpenglViewportIfNeeded(width, height); 15217 15218 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 15219 15220 if(win.resizability != Resizability.automaticallyScaleIfPossible) 15221 if(win.windowResized !is null) { 15222 XUnlockDisplay(display); 15223 scope(exit) XLockDisplay(display); 15224 win.windowResized(width, height); 15225 } 15226 } 15227 } 15228 15229 15230 /// Platform-specific, you might use it when doing a custom event loop. 15231 bool doXNextEvent(Display* display) { 15232 bool done; 15233 XEvent e; 15234 XNextEvent(display, &e); 15235 version(sddddd) { 15236 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15237 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 15238 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 15239 } 15240 } 15241 15242 // filter out compose events 15243 if (XFilterEvent(&e, None)) { 15244 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 15245 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 15246 return false; 15247 } 15248 // process keyboard mapping changes 15249 if (e.type == EventType.KeymapNotify) { 15250 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 15251 XRefreshKeyboardMapping(&e.xmapping); 15252 return false; 15253 } 15254 15255 version(with_eventloop) 15256 import arsd.eventloop; 15257 15258 if(SimpleWindow.handleNativeGlobalEvent !is null) { 15259 // see windows impl's comments 15260 XUnlockDisplay(display); 15261 scope(exit) XLockDisplay(display); 15262 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 15263 if(ret == 0) 15264 return done; 15265 } 15266 15267 15268 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15269 if(win.getNativeEventHandler !is null) { 15270 XUnlockDisplay(display); 15271 scope(exit) XLockDisplay(display); 15272 auto ret = win.getNativeEventHandler()(e); 15273 if(ret == 0) 15274 return done; 15275 } 15276 } 15277 15278 if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) { 15279 if(auto win = e.xany.window in SimpleWindow.nativeMapping) { 15280 // we get this because of the RRScreenChangeNotifyMask 15281 15282 // this isn't actually an ideal way to do it since it wastes time 15283 // but meh it is simple and it works. 15284 win.actualDpiLoadAttempted = false; 15285 SimpleWindow.xRandrInfoLoadAttemped = false; 15286 win.updateActualDpi(); // trigger a reload 15287 } 15288 } 15289 15290 switch(e.type) { 15291 case EventType.SelectionClear: 15292 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) { 15293 // FIXME so it is supposed to finish any in progress transfers... but idk... 15294 // writeln("SelectionClear"); 15295 SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection); 15296 } 15297 break; 15298 case EventType.SelectionRequest: 15299 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 15300 if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) { 15301 // printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target)); 15302 XUnlockDisplay(display); 15303 scope(exit) XLockDisplay(display); 15304 (*ssh).handleRequest(e); 15305 } 15306 break; 15307 case EventType.PropertyNotify: 15308 // printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state); 15309 15310 foreach(ssh; SimpleWindow.impl.setSelectionHandlers) { 15311 if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete) 15312 ssh.sendMoreIncr(&e.xproperty); 15313 } 15314 15315 15316 if(auto win = e.xproperty.window in SimpleWindow.nativeMapping) 15317 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15318 if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) { 15319 Atom target; 15320 int format; 15321 arch_ulong bytesafter, length; 15322 void* value; 15323 15324 ubyte[] s; 15325 Atom targetToKeep; 15326 15327 XGetWindowProperty( 15328 e.xproperty.display, 15329 e.xproperty.window, 15330 e.xproperty.atom, 15331 0, 15332 100000 /* length */, 15333 true, /* erase it to signal we got it and want more */ 15334 0 /*AnyPropertyType*/, 15335 &target, &format, &length, &bytesafter, &value); 15336 15337 if(!targetToKeep) 15338 targetToKeep = target; 15339 15340 auto id = (cast(ubyte*) value)[0 .. length]; 15341 15342 handler.handleIncrData(targetToKeep, id); 15343 15344 XFree(value); 15345 } 15346 } 15347 break; 15348 case EventType.SelectionNotify: 15349 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 15350 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15351 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 15352 XUnlockDisplay(display); 15353 scope(exit) XLockDisplay(display); 15354 handler.handleData(None, null); 15355 } else { 15356 Atom target; 15357 int format; 15358 arch_ulong bytesafter, length; 15359 void* value; 15360 XGetWindowProperty( 15361 e.xselection.display, 15362 e.xselection.requestor, 15363 e.xselection.property, 15364 0, 15365 100000 /* length */, 15366 //false, /* don't erase it */ 15367 true, /* do erase it lol */ 15368 0 /*AnyPropertyType*/, 15369 &target, &format, &length, &bytesafter, &value); 15370 15371 // FIXME: I don't have to copy it now since it is in char[] instead of string 15372 15373 { 15374 XUnlockDisplay(display); 15375 scope(exit) XLockDisplay(display); 15376 15377 if(target == XA_ATOM) { 15378 // initial request, see what they are able to work with and request the best one 15379 // we can handle, if available 15380 15381 Atom[] answer = (cast(Atom*) value)[0 .. length]; 15382 Atom best = handler.findBestFormat(answer); 15383 15384 /+ 15385 writeln("got ", answer); 15386 foreach(a; answer) 15387 printf("%s\n", XGetAtomName(display, a)); 15388 writeln("best ", best); 15389 +/ 15390 15391 if(best != None) { 15392 // actually request the best format 15393 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 15394 } 15395 } else if(target == GetAtom!"INCR"(display)) { 15396 // incremental 15397 15398 handler.prepareIncremental(e.xselection.requestor, e.xselection.property); 15399 15400 // signal the sending program that we see 15401 // the incr and are ready to receive more. 15402 XDeleteProperty( 15403 e.xselection.display, 15404 e.xselection.requestor, 15405 e.xselection.property); 15406 } else { 15407 // unsupported type... maybe, forward 15408 handler.handleData(target, cast(ubyte[]) value[0 .. length]); 15409 } 15410 } 15411 XFree(value); 15412 /* 15413 XDeleteProperty( 15414 e.xselection.display, 15415 e.xselection.requestor, 15416 e.xselection.property); 15417 */ 15418 } 15419 } 15420 break; 15421 case EventType.ConfigureNotify: 15422 auto event = e.xconfigure; 15423 if(auto win = event.window in SimpleWindow.nativeMapping) { 15424 if(win.windowType == WindowTypes.minimallyWrapped) 15425 break; 15426 //version(sdddd) { writeln(" w=", event.width, "; h=", event.height); } 15427 15428 /+ 15429 The ICCCM says window managers must send a synthetic event when the window 15430 is moved but NOT when it is resized. In the resize case, an event is sent 15431 with position (0, 0) which can be wrong and break the dpi calculations. 15432 15433 So we only consider the synthetic events from the WM and otherwise 15434 need to wait for some other event to get the position which... sucks. 15435 15436 I'd rather not have windows changing their layout on mouse motion after 15437 switching monitors... might be forced to but for now just ignoring it. 15438 15439 Easiest way to switch monitors without sending a size position is by 15440 maximize or fullscreen in a setup like mine, but on most setups those 15441 work on the monitor it is already living on, so it should be ok most the 15442 time. 15443 +/ 15444 if(event.send_event) { 15445 win.screenPositionKnown = true; 15446 win.screenPositionX = event.x; 15447 win.screenPositionY = event.y; 15448 win.updateActualDpi(); 15449 } 15450 15451 win.updateIMEPopupLocation(); 15452 recordX11ResizeAsync(display, *win, event.width, event.height); 15453 } 15454 break; 15455 case EventType.Expose: 15456 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 15457 if(win.windowType == WindowTypes.minimallyWrapped) 15458 break; 15459 // if it is closing from a popup menu, it can get 15460 // an Expose event right by the end and trigger a 15461 // BadDrawable error ... we'll just check 15462 // closed to handle that. 15463 if((*win).closed) break; 15464 if((*win).openglMode == OpenGlOptions.no) { 15465 bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh 15466 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 15467 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); 15468 } else { 15469 // need to redraw the scene somehow 15470 if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all 15471 XUnlockDisplay(display); 15472 scope(exit) XLockDisplay(display); 15473 version(without_opengl) {} else 15474 win.redrawOpenGlSceneSoon(); 15475 } 15476 } 15477 } 15478 break; 15479 case EventType.FocusIn: 15480 case EventType.FocusOut: 15481 15482 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15483 /+ 15484 15485 void info(string detail) { 15486 string s; 15487 // import std.conv; 15488 // import std.datetime; 15489 s ~= to!string(Clock.currTime); 15490 s ~= " "; 15491 s ~= e.type == EventType.FocusIn ? "in " : "out"; 15492 s ~= " "; 15493 s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main "; 15494 s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed "; 15495 s ~= detail; 15496 s ~= " "; 15497 15498 sdpyPrintDebugString(s); 15499 15500 } 15501 15502 switch(e.xfocus.detail) { 15503 case NotifyDetail.NotifyAncestor: info("Ancestor"); break; 15504 case NotifyDetail.NotifyVirtual: info("Virtual"); break; 15505 case NotifyDetail.NotifyInferior: info("Inferior"); break; 15506 case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break; 15507 case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break; 15508 case NotifyDetail.NotifyPointer: info("pointer"); break; 15509 case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break; 15510 case NotifyDetail.NotifyDetailNone: info("none"); break; 15511 default: 15512 15513 } 15514 +/ 15515 15516 15517 if(e.xfocus.detail == NotifyDetail.NotifyPointer) 15518 break; // just ignore these they seem irrelevant 15519 15520 auto old = win._focused; 15521 win._focused = e.type == EventType.FocusIn; 15522 15523 // yes, we are losing the focus, but to our own child. that's actually kinda keeping it. 15524 if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior) 15525 win._focused = true; 15526 15527 if(win.demandingAttention) 15528 demandAttention(*win, false); 15529 15530 win.updateIMEFocused(); 15531 15532 if(old != win._focused && win.onFocusChange) { 15533 XUnlockDisplay(display); 15534 scope(exit) XLockDisplay(display); 15535 win.onFocusChange(win._focused); 15536 } 15537 } 15538 break; 15539 case EventType.VisibilityNotify: 15540 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15541 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 15542 if (win.visibilityChanged !is null) { 15543 XUnlockDisplay(display); 15544 scope(exit) XLockDisplay(display); 15545 win.visibilityChanged(false); 15546 } 15547 } else { 15548 if (win.visibilityChanged !is null) { 15549 XUnlockDisplay(display); 15550 scope(exit) XLockDisplay(display); 15551 win.visibilityChanged(true); 15552 } 15553 } 15554 } 15555 break; 15556 case EventType.ClientMessage: 15557 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 15558 // "ignore next mouse motion" event, increment ignore counter for teh window 15559 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15560 ++(*win).warpEventCount; 15561 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 15562 } else { 15563 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 15564 } 15565 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 15566 // user clicked the close button on the window manager 15567 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15568 XUnlockDisplay(display); 15569 scope(exit) XLockDisplay(display); 15570 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 15571 } 15572 15573 } else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) { 15574 // writeln("HAPPENED"); 15575 // user clicked the close button on the window manager 15576 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15577 XUnlockDisplay(display); 15578 scope(exit) XLockDisplay(display); 15579 15580 auto setTo = *win; 15581 15582 if(win.setRequestedInputFocus !is null) { 15583 auto s = win.setRequestedInputFocus(); 15584 if(s !is null) { 15585 setTo = s; 15586 } 15587 } 15588 15589 assert(setTo !is null); 15590 15591 // FIXME: so this is actually supposed to focus to a relevant child window if appropriate 15592 15593 XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]); 15594 } 15595 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 15596 foreach(nai; NotificationAreaIcon.activeIcons) 15597 nai.newManager(); 15598 } else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15599 15600 bool xDragWindow = true; 15601 if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) { 15602 //XDefineCursor(display, xDragWindow.impl.window, 15603 //writeln("XdndStatus ", e.xclient.data.l); 15604 } 15605 if(auto dh = win.dropHandler) { 15606 15607 static Atom[3] xFormatsBuffer; 15608 static Atom[] xFormats; 15609 15610 void resetXFormats() { 15611 xFormatsBuffer[] = 0; 15612 xFormats = xFormatsBuffer[]; 15613 } 15614 15615 if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) { 15616 // on Windows it is supposed to return the effect you actually do FIXME 15617 15618 auto sourceWindow = e.xclient.data.l[0]; 15619 15620 xFormatsBuffer[0] = e.xclient.data.l[2]; 15621 xFormatsBuffer[1] = e.xclient.data.l[3]; 15622 xFormatsBuffer[2] = e.xclient.data.l[4]; 15623 15624 if(e.xclient.data.l[1] & 1) { 15625 // can just grab it all but like we don't necessarily need them... 15626 xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM); 15627 } else { 15628 int len; 15629 foreach(fmt; xFormatsBuffer) 15630 if(fmt) len++; 15631 xFormats = xFormatsBuffer[0 .. len]; 15632 } 15633 15634 auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats); 15635 15636 dh.dragEnter(&pkg); 15637 } else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) { 15638 15639 auto pack = e.xclient.data.l[2]; 15640 15641 auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords 15642 15643 15644 XClientMessageEvent xclient; 15645 15646 xclient.type = EventType.ClientMessage; 15647 xclient.window = e.xclient.data.l[0]; 15648 xclient.message_type = GetAtom!"XdndStatus"(display); 15649 xclient.format = 32; 15650 xclient.data.l[0] = win.impl.window; 15651 xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept 15652 auto r = result.consistentWithin; 15653 xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top); 15654 xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height); 15655 xclient.data.l[4] = dndActionAtom(e.xany.display, result.action); 15656 15657 XSendEvent( 15658 display, 15659 e.xclient.data.l[0], 15660 false, 15661 EventMask.NoEventMask, 15662 cast(XEvent*) &xclient 15663 ); 15664 15665 15666 } else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) { 15667 //writeln("XdndLeave"); 15668 // drop cancelled. 15669 // data.l[0] is the source window 15670 dh.dragLeave(); 15671 15672 resetXFormats(); 15673 } else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) { 15674 // drop happening, should fetch data, then send finished 15675 // writeln("XdndDrop"); 15676 15677 auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats); 15678 15679 dh.drop(&pkg); 15680 15681 resetXFormats(); 15682 } else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) { 15683 // writeln("XdndFinished"); 15684 15685 dh.finish(); 15686 } 15687 15688 } 15689 } 15690 break; 15691 case EventType.MapNotify: 15692 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 15693 (*win)._visible = true; 15694 if (!(*win)._visibleForTheFirstTimeCalled) { 15695 (*win)._visibleForTheFirstTimeCalled = true; 15696 if ((*win).visibleForTheFirstTime !is null) { 15697 XUnlockDisplay(display); 15698 scope(exit) XLockDisplay(display); 15699 (*win).visibleForTheFirstTime(); 15700 } 15701 } 15702 if ((*win).visibilityChanged !is null) { 15703 XUnlockDisplay(display); 15704 scope(exit) XLockDisplay(display); 15705 (*win).visibilityChanged(true); 15706 } 15707 } 15708 break; 15709 case EventType.UnmapNotify: 15710 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 15711 win._visible = false; 15712 if (win.visibilityChanged !is null) { 15713 XUnlockDisplay(display); 15714 scope(exit) XLockDisplay(display); 15715 win.visibilityChanged(false); 15716 } 15717 } 15718 break; 15719 case EventType.DestroyNotify: 15720 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 15721 if(win.destroyed) 15722 break; // might get a notification both for itself and from its parent 15723 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 15724 win._closed = true; // just in case 15725 win.destroyed = true; 15726 if (win.xic !is null) { 15727 XDestroyIC(win.xic); 15728 win.xic = null; // just in case 15729 } 15730 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 15731 bool anyImportant = false; 15732 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 15733 if(w.beingOpenKeepsAppOpen) { 15734 anyImportant = true; 15735 break; 15736 } 15737 if(!anyImportant) { 15738 EventLoop.quitApplication(); 15739 done = true; 15740 } 15741 } 15742 auto window = e.xdestroywindow.window; 15743 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 15744 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 15745 15746 version(with_eventloop) { 15747 if(done) exit(); 15748 } 15749 break; 15750 15751 case EventType.MotionNotify: 15752 MouseEvent mouse; 15753 auto event = e.xmotion; 15754 15755 mouse.type = MouseEventType.motion; 15756 mouse.x = event.x; 15757 mouse.y = event.y; 15758 mouse.modifierState = event.state; 15759 15760 mouse.timestamp = event.time; 15761 15762 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 15763 mouse.window = *win; 15764 if (win.warpEventCount > 0) { 15765 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 15766 --(*win).warpEventCount; 15767 (*win).mdx(mouse); // so deltas will be correctly updated 15768 } else { 15769 win.warpEventCount = 0; // just in case 15770 (*win).mdx(mouse); 15771 if((*win).handleMouseEvent) { 15772 XUnlockDisplay(display); 15773 scope(exit) XLockDisplay(display); 15774 (*win).handleMouseEvent(mouse); 15775 } 15776 } 15777 } 15778 15779 version(with_eventloop) 15780 send(mouse); 15781 break; 15782 case EventType.ButtonPress: 15783 case EventType.ButtonRelease: 15784 MouseEvent mouse; 15785 auto event = e.xbutton; 15786 15787 mouse.timestamp = event.time; 15788 15789 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 15790 mouse.x = event.x; 15791 mouse.y = event.y; 15792 15793 static Time lastMouseDownTime = 0; 15794 static int lastMouseDownButton = -1; 15795 15796 mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 15797 if(e.type == EventType.ButtonPress) { 15798 lastMouseDownTime = event.time; 15799 lastMouseDownButton = event.button; 15800 } 15801 15802 switch(event.button) { 15803 case 1: mouse.button = MouseButton.left; break; // left 15804 case 2: mouse.button = MouseButton.middle; break; // middle 15805 case 3: mouse.button = MouseButton.right; break; // right 15806 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 15807 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 15808 case 6: break; // idk 15809 case 7: break; // idk 15810 case 8: mouse.button = MouseButton.backButton; break; 15811 case 9: mouse.button = MouseButton.forwardButton; break; 15812 default: 15813 } 15814 15815 // FIXME: double check this 15816 mouse.modifierState = event.state; 15817 15818 //mouse.modifierState = event.detail; 15819 15820 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 15821 mouse.window = *win; 15822 (*win).mdx(mouse); 15823 if((*win).handleMouseEvent) { 15824 XUnlockDisplay(display); 15825 scope(exit) XLockDisplay(display); 15826 (*win).handleMouseEvent(mouse); 15827 } 15828 } 15829 version(with_eventloop) 15830 send(mouse); 15831 break; 15832 15833 case EventType.KeyPress: 15834 case EventType.KeyRelease: 15835 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 15836 KeyEvent ke; 15837 ke.pressed = e.type == EventType.KeyPress; 15838 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 15839 15840 auto sym = XKeycodeToKeysym( 15841 XDisplayConnection.get(), 15842 e.xkey.keycode, 15843 0); 15844 15845 ke.key = cast(Key) sym;//e.xkey.keycode; 15846 15847 ke.modifierState = e.xkey.state; 15848 15849 // writefln("%x", sym); 15850 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 15851 int charbuflen = 0; // return value of XwcLookupString 15852 if (ke.pressed) { 15853 auto win = e.xkey.window in SimpleWindow.nativeMapping; 15854 if (win !is null && win.xic !is null) { 15855 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 15856 Status status; 15857 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 15858 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 15859 } else { 15860 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 15861 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 15862 char[16] buffer; 15863 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 15864 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 15865 } 15866 } 15867 15868 // if there's no char, subst one 15869 if (charbuflen == 0) { 15870 switch (sym) { 15871 case 0xff09: charbuf[charbuflen++] = '\t'; break; 15872 case 0xff8d: // keypad enter 15873 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 15874 default : // ignore 15875 } 15876 } 15877 15878 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 15879 ke.window = *win; 15880 15881 15882 if(win.inputProxy) 15883 win = &win.inputProxy; 15884 15885 // char events are separate since they are on Windows too 15886 // also, xcompose can generate long char sequences 15887 // don't send char events if Meta and/or Hyper is pressed 15888 // TODO: ctrl+char should only send control chars; not yet 15889 if ((e.xkey.state&ModifierState.ctrl) != 0) { 15890 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 15891 } 15892 15893 dchar[32] charsComingBuffer; 15894 int charsComingPosition; 15895 dchar[] charsComing = charsComingBuffer[]; 15896 15897 if (ke.pressed && charbuflen > 0) { 15898 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 15899 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 15900 if(charsComingPosition >= charsComing.length) 15901 charsComing.length = charsComingPosition + 8; 15902 15903 charsComing[charsComingPosition++] = ch; 15904 } 15905 15906 charsComing = charsComing[0 .. charsComingPosition]; 15907 } else { 15908 charsComing = null; 15909 } 15910 15911 ke.charsPossible = charsComing; 15912 15913 if (win.handleKeyEvent) { 15914 XUnlockDisplay(display); 15915 scope(exit) XLockDisplay(display); 15916 win.handleKeyEvent(ke); 15917 } 15918 15919 // Super and alt modifier keys never actually send the chars, they are assumed to be special. 15920 if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) { 15921 XUnlockDisplay(display); 15922 scope(exit) XLockDisplay(display); 15923 foreach(ch; charsComing) 15924 win.handleCharEvent(ch); 15925 } 15926 } 15927 15928 version(with_eventloop) 15929 send(ke); 15930 break; 15931 default: 15932 } 15933 15934 return done; 15935 } 15936 } 15937 15938 /* *************************************** */ 15939 /* Done with simpledisplay stuff */ 15940 /* *************************************** */ 15941 15942 // Necessary C library bindings follow 15943 version(Windows) {} else 15944 version(X11) { 15945 15946 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 15947 15948 // X11 bindings needed here 15949 /* 15950 A little of this is from the bindings project on 15951 D Source and some of it is copy/paste from the C 15952 header. 15953 15954 The DSource listing consistently used D's long 15955 where C used long. That's wrong - C long is 32 bit, so 15956 it should be int in D. I changed that here. 15957 15958 Note: 15959 This isn't complete, just took what I needed for myself. 15960 */ 15961 15962 import core.stdc.stddef : wchar_t; 15963 15964 interface XLib { 15965 extern(C) nothrow @nogc { 15966 char* XResourceManagerString(Display*); 15967 void XrmInitialize(); 15968 XrmDatabase XrmGetStringDatabase(char* data); 15969 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 15970 15971 Cursor XCreateFontCursor(Display*, uint shape); 15972 int XDefineCursor(Display* display, Window w, Cursor cursor); 15973 int XUndefineCursor(Display* display, Window w); 15974 15975 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 15976 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 15977 int XFreeCursor(Display* display, Cursor cursor); 15978 15979 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 15980 15981 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 15982 15983 XVaNestedList XVaCreateNestedList(int unused, ...); 15984 15985 char *XKeysymToString(KeySym keysym); 15986 KeySym XKeycodeToKeysym( 15987 Display* /* display */, 15988 KeyCode /* keycode */, 15989 int /* index */ 15990 ); 15991 15992 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 15993 15994 int XFree(void*); 15995 int XDeleteProperty(Display *display, Window w, Atom property); 15996 15997 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements); 15998 15999 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 16000 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 16001 *actual_type_return, int *actual_format_return, arch_ulong 16002 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 16003 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 16004 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 16005 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 16006 16007 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 16008 16009 Window XGetSelectionOwner(Display *display, Atom selection); 16010 16011 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 16012 16013 char** XListFonts(Display*, const char*, int, int*); 16014 void XFreeFontNames(char**); 16015 16016 Display* XOpenDisplay(const char*); 16017 int XCloseDisplay(Display*); 16018 16019 int function() XSynchronize(Display*, bool); 16020 int function() XSetAfterFunction(Display*, int function() proc); 16021 16022 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 16023 16024 Bool XSupportsLocale(); 16025 char* XSetLocaleModifiers(const(char)* modifier_list); 16026 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16027 Status XCloseOM(XOM om); 16028 16029 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 16030 Status XCloseIM(XIM im); 16031 16032 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16033 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 16034 Display* XDisplayOfIM(XIM im); 16035 char* XLocaleOfIM(XIM im); 16036 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 16037 void XDestroyIC(XIC ic); 16038 void XSetICFocus(XIC ic); 16039 void XUnsetICFocus(XIC ic); 16040 //wchar_t* XwcResetIC(XIC ic); 16041 char* XmbResetIC(XIC ic); 16042 char* Xutf8ResetIC(XIC ic); 16043 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16044 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 16045 XIM XIMOfIC(XIC ic); 16046 16047 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 16048 16049 16050 XFontStruct *XLoadQueryFont(Display *display, scope const char *name); 16051 int XFreeFont(Display *display, XFontStruct *font_struct); 16052 int XSetFont(Display* display, GC gc, Font font); 16053 int XTextWidth(XFontStruct*, scope const char*, int); 16054 16055 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 16056 int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n); 16057 16058 Window XCreateSimpleWindow( 16059 Display* /* display */, 16060 Window /* parent */, 16061 int /* x */, 16062 int /* y */, 16063 uint /* width */, 16064 uint /* height */, 16065 uint /* border_width */, 16066 uint /* border */, 16067 uint /* background */ 16068 ); 16069 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); 16070 16071 int XReparentWindow(Display*, Window, Window, int, int); 16072 int XClearWindow(Display*, Window); 16073 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 16074 int XMoveWindow(Display*, Window, int, int); 16075 int XResizeWindow(Display *display, Window w, uint width, uint height); 16076 16077 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 16078 16079 Status XMatchVisualInfo(Display *display, int screen, int depth, int class_, XVisualInfo *vinfo_return); 16080 16081 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 16082 16083 XImage *XCreateImage( 16084 Display* /* display */, 16085 Visual* /* visual */, 16086 uint /* depth */, 16087 int /* format */, 16088 int /* offset */, 16089 ubyte* /* data */, 16090 uint /* width */, 16091 uint /* height */, 16092 int /* bitmap_pad */, 16093 int /* bytes_per_line */ 16094 ); 16095 16096 Status XInitImage (XImage* image); 16097 16098 Atom XInternAtom( 16099 Display* /* display */, 16100 const char* /* atom_name */, 16101 Bool /* only_if_exists */ 16102 ); 16103 16104 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 16105 char* XGetAtomName(Display*, Atom); 16106 Status XGetAtomNames(Display*, Atom*, int count, char**); 16107 16108 int XPutImage( 16109 Display* /* display */, 16110 Drawable /* d */, 16111 GC /* gc */, 16112 XImage* /* image */, 16113 int /* src_x */, 16114 int /* src_y */, 16115 int /* dest_x */, 16116 int /* dest_y */, 16117 uint /* width */, 16118 uint /* height */ 16119 ); 16120 16121 XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format); 16122 16123 16124 int XDestroyWindow( 16125 Display* /* display */, 16126 Window /* w */ 16127 ); 16128 16129 int XDestroyImage(XImage*); 16130 16131 int XSelectInput( 16132 Display* /* display */, 16133 Window /* w */, 16134 EventMask /* event_mask */ 16135 ); 16136 16137 int XMapWindow( 16138 Display* /* display */, 16139 Window /* w */ 16140 ); 16141 16142 Status XIconifyWindow(Display*, Window, int); 16143 int XMapRaised(Display*, Window); 16144 int XMapSubwindows(Display*, Window); 16145 16146 int XNextEvent( 16147 Display* /* display */, 16148 XEvent* /* event_return */ 16149 ); 16150 16151 int XMaskEvent(Display*, arch_long, XEvent*); 16152 16153 Bool XFilterEvent(XEvent *event, Window window); 16154 int XRefreshKeyboardMapping(XMappingEvent *event_map); 16155 16156 Status XSetWMProtocols( 16157 Display* /* display */, 16158 Window /* w */, 16159 Atom* /* protocols */, 16160 int /* count */ 16161 ); 16162 16163 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 16164 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 16165 16166 16167 Status XInitThreads(); 16168 void XLockDisplay (Display* display); 16169 void XUnlockDisplay (Display* display); 16170 16171 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 16172 16173 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 16174 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 16175 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 16176 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 16177 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 16178 16179 16180 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 16181 int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int); 16182 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 16183 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 16184 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16185 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 16186 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16187 int XDrawPoint(Display*, Drawable, GC, int, int); 16188 int XSetForeground(Display*, GC, uint); 16189 int XSetBackground(Display*, GC, uint); 16190 16191 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 16192 void XFreeFontSet(Display*, XFontSet); 16193 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int); 16194 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 16195 16196 int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 16197 16198 16199 //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); 16200 16201 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 16202 int XSetFunction(Display*, GC, int); 16203 16204 GC XCreateGC(Display*, Drawable, uint, void*); 16205 int XCopyGC(Display*, GC, uint, GC); 16206 int XFreeGC(Display*, GC); 16207 16208 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 16209 bool XCheckMaskEvent(Display*, int, XEvent*); 16210 16211 int XPending(Display*); 16212 int XEventsQueued(Display* display, int mode); 16213 16214 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 16215 int XFreePixmap(Display*, Pixmap); 16216 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 16217 int XFlush(Display*); 16218 int XBell(Display*, int); 16219 int XSync(Display*, bool); 16220 16221 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 16222 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 16223 16224 int XGrabKeyboard(Display*, Window, Bool, int, int, Time); 16225 int XUngrabKeyboard(Display*, Time); 16226 16227 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 16228 16229 KeySym XStringToKeysym(const char *string); 16230 16231 Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return); 16232 16233 Window XDefaultRootWindow(Display*); 16234 16235 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); 16236 16237 int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 16238 16239 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 16240 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 16241 16242 Status XAllocColor(Display*, Colormap, XColor*); 16243 16244 int XWithdrawWindow(Display*, Window, int); 16245 int XUnmapWindow(Display*, Window); 16246 int XLowerWindow(Display*, Window); 16247 int XRaiseWindow(Display*, Window); 16248 16249 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); 16250 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); 16251 16252 int XGetInputFocus(Display*, Window*, int*); 16253 int XSetInputFocus(Display*, Window, int, Time); 16254 16255 XErrorHandler XSetErrorHandler(XErrorHandler); 16256 16257 int XGetErrorText(Display*, int, char*, int); 16258 16259 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 16260 16261 16262 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); 16263 int XUngrabPointer(Display *display, Time time); 16264 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 16265 16266 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 16267 16268 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 16269 int XSetClipMask(Display*, GC, Pixmap); 16270 int XSetClipOrigin(Display*, GC, int, int); 16271 16272 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 16273 16274 void XSetWMName(Display*, Window, XTextProperty*); 16275 Status XGetWMName(Display*, Window, XTextProperty*); 16276 int XStoreName(Display* display, Window w, const(char)* window_name); 16277 16278 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 16279 16280 } 16281 } 16282 16283 interface Xext { 16284 extern(C) nothrow @nogc { 16285 Status XShmAttach(Display*, XShmSegmentInfo*); 16286 Status XShmDetach(Display*, XShmSegmentInfo*); 16287 Status XShmPutImage( 16288 Display* /* dpy */, 16289 Drawable /* d */, 16290 GC /* gc */, 16291 XImage* /* image */, 16292 int /* src_x */, 16293 int /* src_y */, 16294 int /* dst_x */, 16295 int /* dst_y */, 16296 uint /* src_width */, 16297 uint /* src_height */, 16298 Bool /* send_event */ 16299 ); 16300 16301 Status XShmQueryExtension(Display*); 16302 16303 XImage *XShmCreateImage( 16304 Display* /* dpy */, 16305 Visual* /* visual */, 16306 uint /* depth */, 16307 int /* format */, 16308 char* /* data */, 16309 XShmSegmentInfo* /* shminfo */, 16310 uint /* width */, 16311 uint /* height */ 16312 ); 16313 16314 Pixmap XShmCreatePixmap( 16315 Display* /* dpy */, 16316 Drawable /* d */, 16317 char* /* data */, 16318 XShmSegmentInfo* /* shminfo */, 16319 uint /* width */, 16320 uint /* height */, 16321 uint /* depth */ 16322 ); 16323 16324 } 16325 } 16326 16327 // this requires -lXpm 16328 //int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 16329 16330 16331 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib; 16332 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext; 16333 shared static this() { 16334 xlib.loadDynamicLibrary(); 16335 xext.loadDynamicLibrary(); 16336 } 16337 16338 16339 extern(C) nothrow @nogc { 16340 16341 alias XrmDatabase = void*; 16342 struct XrmValue { 16343 uint size; 16344 void* addr; 16345 } 16346 16347 struct XVisualInfo { 16348 Visual* visual; 16349 VisualID visualid; 16350 int screen; 16351 uint depth; 16352 int c_class; 16353 c_ulong red_mask; 16354 c_ulong green_mask; 16355 c_ulong blue_mask; 16356 int colormap_size; 16357 int bits_per_rgb; 16358 } 16359 16360 enum VisualNoMask= 0x0; 16361 enum VisualIDMask= 0x1; 16362 enum VisualScreenMask=0x2; 16363 enum VisualDepthMask= 0x4; 16364 enum VisualClassMask= 0x8; 16365 enum VisualRedMaskMask=0x10; 16366 enum VisualGreenMaskMask=0x20; 16367 enum VisualBlueMaskMask=0x40; 16368 enum VisualColormapSizeMask=0x80; 16369 enum VisualBitsPerRGBMask=0x100; 16370 enum VisualAllMask= 0x1FF; 16371 16372 enum AnyKey = 0; 16373 enum AnyModifier = 1 << 15; 16374 16375 // XIM and other crap 16376 struct _XOM {} 16377 struct _XIM {} 16378 struct _XIC {} 16379 alias XOM = _XOM*; 16380 alias XIM = _XIM*; 16381 alias XIC = _XIC*; 16382 16383 alias XVaNestedList = void*; 16384 16385 alias XIMStyle = arch_ulong; 16386 enum : arch_ulong { 16387 XIMPreeditArea = 0x0001, 16388 XIMPreeditCallbacks = 0x0002, 16389 XIMPreeditPosition = 0x0004, 16390 XIMPreeditNothing = 0x0008, 16391 XIMPreeditNone = 0x0010, 16392 XIMStatusArea = 0x0100, 16393 XIMStatusCallbacks = 0x0200, 16394 XIMStatusNothing = 0x0400, 16395 XIMStatusNone = 0x0800, 16396 } 16397 16398 16399 /* X Shared Memory Extension functions */ 16400 //pragma(lib, "Xshm"); 16401 alias arch_ulong ShmSeg; 16402 struct XShmSegmentInfo { 16403 ShmSeg shmseg; 16404 int shmid; 16405 ubyte* shmaddr; 16406 Bool readOnly; 16407 } 16408 16409 // and the necessary OS functions 16410 int shmget(int, size_t, int); 16411 void* shmat(int, scope const void*, int); 16412 int shmdt(scope const void*); 16413 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 16414 16415 enum IPC_PRIVATE = 0; 16416 enum IPC_CREAT = 512; 16417 enum IPC_RMID = 0; 16418 16419 /* MIT-SHM end */ 16420 16421 16422 enum MappingType:int { 16423 MappingModifier =0, 16424 MappingKeyboard =1, 16425 MappingPointer =2 16426 } 16427 16428 /* ImageFormat -- PutImage, GetImage */ 16429 enum ImageFormat:int { 16430 XYBitmap =0, /* depth 1, XYFormat */ 16431 XYPixmap =1, /* depth == drawable depth */ 16432 ZPixmap =2 /* depth == drawable depth */ 16433 } 16434 16435 enum ModifierName:int { 16436 ShiftMapIndex =0, 16437 LockMapIndex =1, 16438 ControlMapIndex =2, 16439 Mod1MapIndex =3, 16440 Mod2MapIndex =4, 16441 Mod3MapIndex =5, 16442 Mod4MapIndex =6, 16443 Mod5MapIndex =7 16444 } 16445 16446 enum ButtonMask:int { 16447 Button1Mask =1<<8, 16448 Button2Mask =1<<9, 16449 Button3Mask =1<<10, 16450 Button4Mask =1<<11, 16451 Button5Mask =1<<12, 16452 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16453 } 16454 16455 enum KeyOrButtonMask:uint { 16456 ShiftMask =1<<0, 16457 LockMask =1<<1, 16458 ControlMask =1<<2, 16459 Mod1Mask =1<<3, 16460 Mod2Mask =1<<4, 16461 Mod3Mask =1<<5, 16462 Mod4Mask =1<<6, 16463 Mod5Mask =1<<7, 16464 Button1Mask =1<<8, 16465 Button2Mask =1<<9, 16466 Button3Mask =1<<10, 16467 Button4Mask =1<<11, 16468 Button5Mask =1<<12, 16469 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16470 } 16471 16472 enum ButtonName:int { 16473 Button1 =1, 16474 Button2 =2, 16475 Button3 =3, 16476 Button4 =4, 16477 Button5 =5 16478 } 16479 16480 /* Notify modes */ 16481 enum NotifyModes:int 16482 { 16483 NotifyNormal =0, 16484 NotifyGrab =1, 16485 NotifyUngrab =2, 16486 NotifyWhileGrabbed =3 16487 } 16488 enum NotifyHint = 1; /* for MotionNotify events */ 16489 16490 /* Notify detail */ 16491 enum NotifyDetail:int 16492 { 16493 NotifyAncestor =0, 16494 NotifyVirtual =1, 16495 NotifyInferior =2, 16496 NotifyNonlinear =3, 16497 NotifyNonlinearVirtual =4, 16498 NotifyPointer =5, 16499 NotifyPointerRoot =6, 16500 NotifyDetailNone =7 16501 } 16502 16503 /* Visibility notify */ 16504 16505 enum VisibilityNotify:int 16506 { 16507 VisibilityUnobscured =0, 16508 VisibilityPartiallyObscured =1, 16509 VisibilityFullyObscured =2 16510 } 16511 16512 16513 enum WindowStackingMethod:int 16514 { 16515 Above =0, 16516 Below =1, 16517 TopIf =2, 16518 BottomIf =3, 16519 Opposite =4 16520 } 16521 16522 /* Circulation request */ 16523 enum CirculationRequest:int 16524 { 16525 PlaceOnTop =0, 16526 PlaceOnBottom =1 16527 } 16528 16529 enum PropertyNotification:int 16530 { 16531 PropertyNewValue =0, 16532 PropertyDelete =1 16533 } 16534 16535 enum ColorMapNotification:int 16536 { 16537 ColormapUninstalled =0, 16538 ColormapInstalled =1 16539 } 16540 16541 16542 struct _XPrivate {} 16543 struct _XrmHashBucketRec {} 16544 16545 alias void* XPointer; 16546 alias void* XExtData; 16547 16548 version( X86_64 ) { 16549 alias ulong XID; 16550 alias ulong arch_ulong; 16551 alias long arch_long; 16552 } else version (AArch64) { 16553 alias ulong XID; 16554 alias ulong arch_ulong; 16555 alias long arch_long; 16556 } else { 16557 alias uint XID; 16558 alias uint arch_ulong; 16559 alias int arch_long; 16560 } 16561 16562 alias XID Window; 16563 alias XID Drawable; 16564 alias XID Pixmap; 16565 16566 alias arch_ulong Atom; 16567 alias int Bool; 16568 alias Display XDisplay; 16569 16570 alias int ByteOrder; 16571 alias arch_ulong Time; 16572 alias void ScreenFormat; 16573 16574 struct XImage { 16575 int width, height; /* size of image */ 16576 int xoffset; /* number of pixels offset in X direction */ 16577 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 16578 void *data; /* pointer to image data */ 16579 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 16580 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 16581 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 16582 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 16583 int depth; /* depth of image */ 16584 int bytes_per_line; /* accelarator to next line */ 16585 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 16586 arch_ulong red_mask; /* bits in z arrangment */ 16587 arch_ulong green_mask; 16588 arch_ulong blue_mask; 16589 XPointer obdata; /* hook for the object routines to hang on */ 16590 static struct F { /* image manipulation routines */ 16591 XImage* function( 16592 XDisplay* /* display */, 16593 Visual* /* visual */, 16594 uint /* depth */, 16595 int /* format */, 16596 int /* offset */, 16597 ubyte* /* data */, 16598 uint /* width */, 16599 uint /* height */, 16600 int /* bitmap_pad */, 16601 int /* bytes_per_line */) create_image; 16602 int function(XImage *) destroy_image; 16603 arch_ulong function(XImage *, int, int) get_pixel; 16604 int function(XImage *, int, int, arch_ulong) put_pixel; 16605 XImage* function(XImage *, int, int, uint, uint) sub_image; 16606 int function(XImage *, arch_long) add_pixel; 16607 } 16608 F f; 16609 } 16610 version(X86_64) static assert(XImage.sizeof == 136); 16611 else version(X86) static assert(XImage.sizeof == 88); 16612 16613 struct XCharStruct { 16614 short lbearing; /* origin to left edge of raster */ 16615 short rbearing; /* origin to right edge of raster */ 16616 short width; /* advance to next char's origin */ 16617 short ascent; /* baseline to top edge of raster */ 16618 short descent; /* baseline to bottom edge of raster */ 16619 ushort attributes; /* per char flags (not predefined) */ 16620 } 16621 16622 /* 16623 * To allow arbitrary information with fonts, there are additional properties 16624 * returned. 16625 */ 16626 struct XFontProp { 16627 Atom name; 16628 arch_ulong card32; 16629 } 16630 16631 alias Atom Font; 16632 16633 struct XFontStruct { 16634 XExtData *ext_data; /* Hook for extension to hang data */ 16635 Font fid; /* Font ID for this font */ 16636 uint direction; /* Direction the font is painted */ 16637 uint min_char_or_byte2; /* First character */ 16638 uint max_char_or_byte2; /* Last character */ 16639 uint min_byte1; /* First row that exists (for two-byte fonts) */ 16640 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 16641 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 16642 uint default_char; /* Char to print for undefined character */ 16643 int n_properties; /* How many properties there are */ 16644 XFontProp *properties; /* Pointer to array of additional properties*/ 16645 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 16646 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 16647 XCharStruct *per_char; /* first_char to last_char information */ 16648 int ascent; /* Max extent above baseline for spacing */ 16649 int descent; /* Max descent below baseline for spacing */ 16650 } 16651 16652 16653 /* 16654 * Definitions of specific events. 16655 */ 16656 struct XKeyEvent 16657 { 16658 int type; /* of event */ 16659 arch_ulong serial; /* # of last request processed by server */ 16660 Bool send_event; /* true if this came from a SendEvent request */ 16661 Display *display; /* Display the event was read from */ 16662 Window window; /* "event" window it is reported relative to */ 16663 Window root; /* root window that the event occurred on */ 16664 Window subwindow; /* child window */ 16665 Time time; /* milliseconds */ 16666 int x, y; /* pointer x, y coordinates in event window */ 16667 int x_root, y_root; /* coordinates relative to root */ 16668 KeyOrButtonMask state; /* key or button mask */ 16669 uint keycode; /* detail */ 16670 Bool same_screen; /* same screen flag */ 16671 } 16672 version(X86_64) static assert(XKeyEvent.sizeof == 96); 16673 alias XKeyEvent XKeyPressedEvent; 16674 alias XKeyEvent XKeyReleasedEvent; 16675 16676 struct XButtonEvent 16677 { 16678 int type; /* of event */ 16679 arch_ulong serial; /* # of last request processed by server */ 16680 Bool send_event; /* true if this came from a SendEvent request */ 16681 Display *display; /* Display the event was read from */ 16682 Window window; /* "event" window it is reported relative to */ 16683 Window root; /* root window that the event occurred on */ 16684 Window subwindow; /* child window */ 16685 Time time; /* milliseconds */ 16686 int x, y; /* pointer x, y coordinates in event window */ 16687 int x_root, y_root; /* coordinates relative to root */ 16688 KeyOrButtonMask state; /* key or button mask */ 16689 uint button; /* detail */ 16690 Bool same_screen; /* same screen flag */ 16691 } 16692 alias XButtonEvent XButtonPressedEvent; 16693 alias XButtonEvent XButtonReleasedEvent; 16694 16695 struct XMotionEvent{ 16696 int type; /* of event */ 16697 arch_ulong serial; /* # of last request processed by server */ 16698 Bool send_event; /* true if this came from a SendEvent request */ 16699 Display *display; /* Display the event was read from */ 16700 Window window; /* "event" window reported relative to */ 16701 Window root; /* root window that the event occurred on */ 16702 Window subwindow; /* child window */ 16703 Time time; /* milliseconds */ 16704 int x, y; /* pointer x, y coordinates in event window */ 16705 int x_root, y_root; /* coordinates relative to root */ 16706 KeyOrButtonMask state; /* key or button mask */ 16707 byte is_hint; /* detail */ 16708 Bool same_screen; /* same screen flag */ 16709 } 16710 alias XMotionEvent XPointerMovedEvent; 16711 16712 struct XCrossingEvent{ 16713 int type; /* of event */ 16714 arch_ulong serial; /* # of last request processed by server */ 16715 Bool send_event; /* true if this came from a SendEvent request */ 16716 Display *display; /* Display the event was read from */ 16717 Window window; /* "event" window reported relative to */ 16718 Window root; /* root window that the event occurred on */ 16719 Window subwindow; /* child window */ 16720 Time time; /* milliseconds */ 16721 int x, y; /* pointer x, y coordinates in event window */ 16722 int x_root, y_root; /* coordinates relative to root */ 16723 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 16724 NotifyDetail detail; 16725 /* 16726 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16727 * NotifyNonlinear,NotifyNonlinearVirtual 16728 */ 16729 Bool same_screen; /* same screen flag */ 16730 Bool focus; /* Boolean focus */ 16731 KeyOrButtonMask state; /* key or button mask */ 16732 } 16733 alias XCrossingEvent XEnterWindowEvent; 16734 alias XCrossingEvent XLeaveWindowEvent; 16735 16736 struct XFocusChangeEvent{ 16737 int type; /* FocusIn or FocusOut */ 16738 arch_ulong serial; /* # of last request processed by server */ 16739 Bool send_event; /* true if this came from a SendEvent request */ 16740 Display *display; /* Display the event was read from */ 16741 Window window; /* window of event */ 16742 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 16743 NotifyGrab, NotifyUngrab */ 16744 NotifyDetail detail; 16745 /* 16746 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16747 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 16748 * NotifyPointerRoot, NotifyDetailNone 16749 */ 16750 } 16751 alias XFocusChangeEvent XFocusInEvent; 16752 alias XFocusChangeEvent XFocusOutEvent; 16753 16754 enum CWBackPixmap = (1L<<0); 16755 enum CWBackPixel = (1L<<1); 16756 enum CWBorderPixmap = (1L<<2); 16757 enum CWBorderPixel = (1L<<3); 16758 enum CWBitGravity = (1L<<4); 16759 enum CWWinGravity = (1L<<5); 16760 enum CWBackingStore = (1L<<6); 16761 enum CWBackingPlanes = (1L<<7); 16762 enum CWBackingPixel = (1L<<8); 16763 enum CWOverrideRedirect = (1L<<9); 16764 enum CWSaveUnder = (1L<<10); 16765 enum CWEventMask = (1L<<11); 16766 enum CWDontPropagate = (1L<<12); 16767 enum CWColormap = (1L<<13); 16768 enum CWCursor = (1L<<14); 16769 16770 struct XWindowAttributes { 16771 int x, y; /* location of window */ 16772 int width, height; /* width and height of window */ 16773 int border_width; /* border width of window */ 16774 int depth; /* depth of window */ 16775 Visual *visual; /* the associated visual structure */ 16776 Window root; /* root of screen containing window */ 16777 int class_; /* InputOutput, InputOnly*/ 16778 int bit_gravity; /* one of the bit gravity values */ 16779 int win_gravity; /* one of the window gravity values */ 16780 int backing_store; /* NotUseful, WhenMapped, Always */ 16781 arch_ulong backing_planes; /* planes to be preserved if possible */ 16782 arch_ulong backing_pixel; /* value to be used when restoring planes */ 16783 Bool save_under; /* boolean, should bits under be saved? */ 16784 Colormap colormap; /* color map to be associated with window */ 16785 Bool map_installed; /* boolean, is color map currently installed*/ 16786 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 16787 arch_long all_event_masks; /* set of events all people have interest in*/ 16788 arch_long your_event_mask; /* my event mask */ 16789 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 16790 Bool override_redirect; /* boolean value for override-redirect */ 16791 Screen *screen; /* back pointer to correct screen */ 16792 } 16793 16794 enum IsUnmapped = 0; 16795 enum IsUnviewable = 1; 16796 enum IsViewable = 2; 16797 16798 struct XSetWindowAttributes { 16799 Pixmap background_pixmap;/* background, None, or ParentRelative */ 16800 arch_ulong background_pixel;/* background pixel */ 16801 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 16802 arch_ulong border_pixel;/* border pixel value */ 16803 int bit_gravity; /* one of bit gravity values */ 16804 int win_gravity; /* one of the window gravity values */ 16805 int backing_store; /* NotUseful, WhenMapped, Always */ 16806 arch_ulong backing_planes;/* planes to be preserved if possible */ 16807 arch_ulong backing_pixel;/* value to use in restoring planes */ 16808 Bool save_under; /* should bits under be saved? (popups) */ 16809 arch_long event_mask; /* set of events that should be saved */ 16810 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 16811 Bool override_redirect; /* boolean value for override_redirect */ 16812 Colormap colormap; /* color map to be associated with window */ 16813 Cursor cursor; /* cursor to be displayed (or None) */ 16814 } 16815 16816 16817 alias int Status; 16818 16819 16820 enum EventMask:int 16821 { 16822 NoEventMask =0, 16823 KeyPressMask =1<<0, 16824 KeyReleaseMask =1<<1, 16825 ButtonPressMask =1<<2, 16826 ButtonReleaseMask =1<<3, 16827 EnterWindowMask =1<<4, 16828 LeaveWindowMask =1<<5, 16829 PointerMotionMask =1<<6, 16830 PointerMotionHintMask =1<<7, 16831 Button1MotionMask =1<<8, 16832 Button2MotionMask =1<<9, 16833 Button3MotionMask =1<<10, 16834 Button4MotionMask =1<<11, 16835 Button5MotionMask =1<<12, 16836 ButtonMotionMask =1<<13, 16837 KeymapStateMask =1<<14, 16838 ExposureMask =1<<15, 16839 VisibilityChangeMask =1<<16, 16840 StructureNotifyMask =1<<17, 16841 ResizeRedirectMask =1<<18, 16842 SubstructureNotifyMask =1<<19, 16843 SubstructureRedirectMask=1<<20, 16844 FocusChangeMask =1<<21, 16845 PropertyChangeMask =1<<22, 16846 ColormapChangeMask =1<<23, 16847 OwnerGrabButtonMask =1<<24 16848 } 16849 16850 struct MwmHints { 16851 c_ulong flags; 16852 c_ulong functions; 16853 c_ulong decorations; 16854 c_long input_mode; 16855 c_ulong status; 16856 } 16857 16858 enum { 16859 MWM_HINTS_FUNCTIONS = (1L << 0), 16860 MWM_HINTS_DECORATIONS = (1L << 1), 16861 16862 MWM_FUNC_ALL = (1L << 0), 16863 MWM_FUNC_RESIZE = (1L << 1), 16864 MWM_FUNC_MOVE = (1L << 2), 16865 MWM_FUNC_MINIMIZE = (1L << 3), 16866 MWM_FUNC_MAXIMIZE = (1L << 4), 16867 MWM_FUNC_CLOSE = (1L << 5), 16868 16869 MWM_DECOR_ALL = (1L << 0), 16870 MWM_DECOR_BORDER = (1L << 1), 16871 MWM_DECOR_RESIZEH = (1L << 2), 16872 MWM_DECOR_TITLE = (1L << 3), 16873 MWM_DECOR_MENU = (1L << 4), 16874 MWM_DECOR_MINIMIZE = (1L << 5), 16875 MWM_DECOR_MAXIMIZE = (1L << 6), 16876 } 16877 16878 import core.stdc.config : c_long, c_ulong; 16879 16880 /* Size hints mask bits */ 16881 16882 enum USPosition = (1L << 0) /* user specified x, y */; 16883 enum USSize = (1L << 1) /* user specified width, height */; 16884 enum PPosition = (1L << 2) /* program specified position */; 16885 enum PSize = (1L << 3) /* program specified size */; 16886 enum PMinSize = (1L << 4) /* program specified minimum size */; 16887 enum PMaxSize = (1L << 5) /* program specified maximum size */; 16888 enum PResizeInc = (1L << 6) /* program specified resize increments */; 16889 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 16890 enum PBaseSize = (1L << 8); 16891 enum PWinGravity = (1L << 9); 16892 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 16893 struct XSizeHints { 16894 arch_long flags; /* marks which fields in this structure are defined */ 16895 int x, y; /* Obsolete */ 16896 int width, height; /* Obsolete */ 16897 int min_width, min_height; 16898 int max_width, max_height; 16899 int width_inc, height_inc; 16900 struct Aspect { 16901 int x; /* numerator */ 16902 int y; /* denominator */ 16903 } 16904 16905 Aspect min_aspect; 16906 Aspect max_aspect; 16907 int base_width, base_height; 16908 int win_gravity; 16909 /* this structure may be extended in the future */ 16910 } 16911 16912 16913 16914 enum EventType:int 16915 { 16916 KeyPress =2, 16917 KeyRelease =3, 16918 ButtonPress =4, 16919 ButtonRelease =5, 16920 MotionNotify =6, 16921 EnterNotify =7, 16922 LeaveNotify =8, 16923 FocusIn =9, 16924 FocusOut =10, 16925 KeymapNotify =11, 16926 Expose =12, 16927 GraphicsExpose =13, 16928 NoExpose =14, 16929 VisibilityNotify =15, 16930 CreateNotify =16, 16931 DestroyNotify =17, 16932 UnmapNotify =18, 16933 MapNotify =19, 16934 MapRequest =20, 16935 ReparentNotify =21, 16936 ConfigureNotify =22, 16937 ConfigureRequest =23, 16938 GravityNotify =24, 16939 ResizeRequest =25, 16940 CirculateNotify =26, 16941 CirculateRequest =27, 16942 PropertyNotify =28, 16943 SelectionClear =29, 16944 SelectionRequest =30, 16945 SelectionNotify =31, 16946 ColormapNotify =32, 16947 ClientMessage =33, 16948 MappingNotify =34, 16949 LASTEvent =35 /* must be bigger than any event # */ 16950 } 16951 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 16952 struct XKeymapEvent 16953 { 16954 int type; 16955 arch_ulong serial; /* # of last request processed by server */ 16956 Bool send_event; /* true if this came from a SendEvent request */ 16957 Display *display; /* Display the event was read from */ 16958 Window window; 16959 byte[32] key_vector; 16960 } 16961 16962 struct XExposeEvent 16963 { 16964 int type; 16965 arch_ulong serial; /* # of last request processed by server */ 16966 Bool send_event; /* true if this came from a SendEvent request */ 16967 Display *display; /* Display the event was read from */ 16968 Window window; 16969 int x, y; 16970 int width, height; 16971 int count; /* if non-zero, at least this many more */ 16972 } 16973 16974 struct XGraphicsExposeEvent{ 16975 int type; 16976 arch_ulong serial; /* # of last request processed by server */ 16977 Bool send_event; /* true if this came from a SendEvent request */ 16978 Display *display; /* Display the event was read from */ 16979 Drawable drawable; 16980 int x, y; 16981 int width, height; 16982 int count; /* if non-zero, at least this many more */ 16983 int major_code; /* core is CopyArea or CopyPlane */ 16984 int minor_code; /* not defined in the core */ 16985 } 16986 16987 struct XNoExposeEvent{ 16988 int type; 16989 arch_ulong serial; /* # of last request processed by server */ 16990 Bool send_event; /* true if this came from a SendEvent request */ 16991 Display *display; /* Display the event was read from */ 16992 Drawable drawable; 16993 int major_code; /* core is CopyArea or CopyPlane */ 16994 int minor_code; /* not defined in the core */ 16995 } 16996 16997 struct XVisibilityEvent{ 16998 int type; 16999 arch_ulong serial; /* # of last request processed by server */ 17000 Bool send_event; /* true if this came from a SendEvent request */ 17001 Display *display; /* Display the event was read from */ 17002 Window window; 17003 VisibilityNotify state; /* Visibility state */ 17004 } 17005 17006 struct XCreateWindowEvent{ 17007 int type; 17008 arch_ulong serial; /* # of last request processed by server */ 17009 Bool send_event; /* true if this came from a SendEvent request */ 17010 Display *display; /* Display the event was read from */ 17011 Window parent; /* parent of the window */ 17012 Window window; /* window id of window created */ 17013 int x, y; /* window location */ 17014 int width, height; /* size of window */ 17015 int border_width; /* border width */ 17016 Bool override_redirect; /* creation should be overridden */ 17017 } 17018 17019 struct XDestroyWindowEvent 17020 { 17021 int type; 17022 arch_ulong serial; /* # of last request processed by server */ 17023 Bool send_event; /* true if this came from a SendEvent request */ 17024 Display *display; /* Display the event was read from */ 17025 Window event; 17026 Window window; 17027 } 17028 17029 struct XUnmapEvent 17030 { 17031 int type; 17032 arch_ulong serial; /* # of last request processed by server */ 17033 Bool send_event; /* true if this came from a SendEvent request */ 17034 Display *display; /* Display the event was read from */ 17035 Window event; 17036 Window window; 17037 Bool from_configure; 17038 } 17039 17040 struct XMapEvent 17041 { 17042 int type; 17043 arch_ulong serial; /* # of last request processed by server */ 17044 Bool send_event; /* true if this came from a SendEvent request */ 17045 Display *display; /* Display the event was read from */ 17046 Window event; 17047 Window window; 17048 Bool override_redirect; /* Boolean, is override set... */ 17049 } 17050 17051 struct XMapRequestEvent 17052 { 17053 int type; 17054 arch_ulong serial; /* # of last request processed by server */ 17055 Bool send_event; /* true if this came from a SendEvent request */ 17056 Display *display; /* Display the event was read from */ 17057 Window parent; 17058 Window window; 17059 } 17060 17061 struct XReparentEvent 17062 { 17063 int type; 17064 arch_ulong serial; /* # of last request processed by server */ 17065 Bool send_event; /* true if this came from a SendEvent request */ 17066 Display *display; /* Display the event was read from */ 17067 Window event; 17068 Window window; 17069 Window parent; 17070 int x, y; 17071 Bool override_redirect; 17072 } 17073 17074 struct XConfigureEvent 17075 { 17076 int type; 17077 arch_ulong serial; /* # of last request processed by server */ 17078 Bool send_event; /* true if this came from a SendEvent request */ 17079 Display *display; /* Display the event was read from */ 17080 Window event; 17081 Window window; 17082 int x, y; 17083 int width, height; 17084 int border_width; 17085 Window above; 17086 Bool override_redirect; 17087 } 17088 17089 struct XGravityEvent 17090 { 17091 int type; 17092 arch_ulong serial; /* # of last request processed by server */ 17093 Bool send_event; /* true if this came from a SendEvent request */ 17094 Display *display; /* Display the event was read from */ 17095 Window event; 17096 Window window; 17097 int x, y; 17098 } 17099 17100 struct XResizeRequestEvent 17101 { 17102 int type; 17103 arch_ulong serial; /* # of last request processed by server */ 17104 Bool send_event; /* true if this came from a SendEvent request */ 17105 Display *display; /* Display the event was read from */ 17106 Window window; 17107 int width, height; 17108 } 17109 17110 struct XConfigureRequestEvent 17111 { 17112 int type; 17113 arch_ulong serial; /* # of last request processed by server */ 17114 Bool send_event; /* true if this came from a SendEvent request */ 17115 Display *display; /* Display the event was read from */ 17116 Window parent; 17117 Window window; 17118 int x, y; 17119 int width, height; 17120 int border_width; 17121 Window above; 17122 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 17123 arch_ulong value_mask; 17124 } 17125 17126 struct XCirculateEvent 17127 { 17128 int type; 17129 arch_ulong serial; /* # of last request processed by server */ 17130 Bool send_event; /* true if this came from a SendEvent request */ 17131 Display *display; /* Display the event was read from */ 17132 Window event; 17133 Window window; 17134 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 17135 } 17136 17137 struct XCirculateRequestEvent 17138 { 17139 int type; 17140 arch_ulong serial; /* # of last request processed by server */ 17141 Bool send_event; /* true if this came from a SendEvent request */ 17142 Display *display; /* Display the event was read from */ 17143 Window parent; 17144 Window window; 17145 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 17146 } 17147 17148 struct XPropertyEvent 17149 { 17150 int type; 17151 arch_ulong serial; /* # of last request processed by server */ 17152 Bool send_event; /* true if this came from a SendEvent request */ 17153 Display *display; /* Display the event was read from */ 17154 Window window; 17155 Atom atom; 17156 Time time; 17157 PropertyNotification state; /* NewValue, Deleted */ 17158 } 17159 17160 struct XSelectionClearEvent 17161 { 17162 int type; 17163 arch_ulong serial; /* # of last request processed by server */ 17164 Bool send_event; /* true if this came from a SendEvent request */ 17165 Display *display; /* Display the event was read from */ 17166 Window window; 17167 Atom selection; 17168 Time time; 17169 } 17170 17171 struct XSelectionRequestEvent 17172 { 17173 int type; 17174 arch_ulong serial; /* # of last request processed by server */ 17175 Bool send_event; /* true if this came from a SendEvent request */ 17176 Display *display; /* Display the event was read from */ 17177 Window owner; 17178 Window requestor; 17179 Atom selection; 17180 Atom target; 17181 Atom property; 17182 Time time; 17183 } 17184 17185 struct XSelectionEvent 17186 { 17187 int type; 17188 arch_ulong serial; /* # of last request processed by server */ 17189 Bool send_event; /* true if this came from a SendEvent request */ 17190 Display *display; /* Display the event was read from */ 17191 Window requestor; 17192 Atom selection; 17193 Atom target; 17194 Atom property; /* ATOM or None */ 17195 Time time; 17196 } 17197 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 17198 17199 struct XColormapEvent 17200 { 17201 int type; 17202 arch_ulong serial; /* # of last request processed by server */ 17203 Bool send_event; /* true if this came from a SendEvent request */ 17204 Display *display; /* Display the event was read from */ 17205 Window window; 17206 Colormap colormap; /* COLORMAP or None */ 17207 Bool new_; /* C++ */ 17208 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 17209 } 17210 version(X86_64) static assert(XColormapEvent.sizeof == 56); 17211 17212 struct XClientMessageEvent 17213 { 17214 int type; 17215 arch_ulong serial; /* # of last request processed by server */ 17216 Bool send_event; /* true if this came from a SendEvent request */ 17217 Display *display; /* Display the event was read from */ 17218 Window window; 17219 Atom message_type; 17220 int format; 17221 union Data{ 17222 byte[20] b; 17223 short[10] s; 17224 arch_ulong[5] l; 17225 } 17226 Data data; 17227 17228 } 17229 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 17230 17231 struct XMappingEvent 17232 { 17233 int type; 17234 arch_ulong serial; /* # of last request processed by server */ 17235 Bool send_event; /* true if this came from a SendEvent request */ 17236 Display *display; /* Display the event was read from */ 17237 Window window; /* unused */ 17238 MappingType request; /* one of MappingModifier, MappingKeyboard, 17239 MappingPointer */ 17240 int first_keycode; /* first keycode */ 17241 int count; /* defines range of change w. first_keycode*/ 17242 } 17243 17244 struct XErrorEvent 17245 { 17246 int type; 17247 Display *display; /* Display the event was read from */ 17248 XID resourceid; /* resource id */ 17249 arch_ulong serial; /* serial number of failed request */ 17250 ubyte error_code; /* error code of failed request */ 17251 ubyte request_code; /* Major op-code of failed request */ 17252 ubyte minor_code; /* Minor op-code of failed request */ 17253 } 17254 17255 struct XAnyEvent 17256 { 17257 int type; 17258 arch_ulong serial; /* # of last request processed by server */ 17259 Bool send_event; /* true if this came from a SendEvent request */ 17260 Display *display;/* Display the event was read from */ 17261 Window window; /* window on which event was requested in event mask */ 17262 } 17263 17264 union XEvent{ 17265 int type; /* must not be changed; first element */ 17266 XAnyEvent xany; 17267 XKeyEvent xkey; 17268 XButtonEvent xbutton; 17269 XMotionEvent xmotion; 17270 XCrossingEvent xcrossing; 17271 XFocusChangeEvent xfocus; 17272 XExposeEvent xexpose; 17273 XGraphicsExposeEvent xgraphicsexpose; 17274 XNoExposeEvent xnoexpose; 17275 XVisibilityEvent xvisibility; 17276 XCreateWindowEvent xcreatewindow; 17277 XDestroyWindowEvent xdestroywindow; 17278 XUnmapEvent xunmap; 17279 XMapEvent xmap; 17280 XMapRequestEvent xmaprequest; 17281 XReparentEvent xreparent; 17282 XConfigureEvent xconfigure; 17283 XGravityEvent xgravity; 17284 XResizeRequestEvent xresizerequest; 17285 XConfigureRequestEvent xconfigurerequest; 17286 XCirculateEvent xcirculate; 17287 XCirculateRequestEvent xcirculaterequest; 17288 XPropertyEvent xproperty; 17289 XSelectionClearEvent xselectionclear; 17290 XSelectionRequestEvent xselectionrequest; 17291 XSelectionEvent xselection; 17292 XColormapEvent xcolormap; 17293 XClientMessageEvent xclient; 17294 XMappingEvent xmapping; 17295 XErrorEvent xerror; 17296 XKeymapEvent xkeymap; 17297 arch_ulong[24] pad; 17298 } 17299 17300 17301 struct Display { 17302 XExtData *ext_data; /* hook for extension to hang data */ 17303 _XPrivate *private1; 17304 int fd; /* Network socket. */ 17305 int private2; 17306 int proto_major_version;/* major version of server's X protocol */ 17307 int proto_minor_version;/* minor version of servers X protocol */ 17308 char *vendor; /* vendor of the server hardware */ 17309 XID private3; 17310 XID private4; 17311 XID private5; 17312 int private6; 17313 XID function(Display*)resource_alloc;/* allocator function */ 17314 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 17315 int bitmap_unit; /* padding and data requirements */ 17316 int bitmap_pad; /* padding requirements on bitmaps */ 17317 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 17318 int nformats; /* number of pixmap formats in list */ 17319 ScreenFormat *pixmap_format; /* pixmap format list */ 17320 int private8; 17321 int release; /* release of the server */ 17322 _XPrivate *private9; 17323 _XPrivate *private10; 17324 int qlen; /* Length of input event queue */ 17325 arch_ulong last_request_read; /* seq number of last event read */ 17326 arch_ulong request; /* sequence number of last request. */ 17327 XPointer private11; 17328 XPointer private12; 17329 XPointer private13; 17330 XPointer private14; 17331 uint max_request_size; /* maximum number 32 bit words in request*/ 17332 _XrmHashBucketRec *db; 17333 int function (Display*)private15; 17334 char *display_name; /* "host:display" string used on this connect*/ 17335 int default_screen; /* default screen for operations */ 17336 int nscreens; /* number of screens on this server*/ 17337 Screen *screens; /* pointer to list of screens */ 17338 arch_ulong motion_buffer; /* size of motion buffer */ 17339 arch_ulong private16; 17340 int min_keycode; /* minimum defined keycode */ 17341 int max_keycode; /* maximum defined keycode */ 17342 XPointer private17; 17343 XPointer private18; 17344 int private19; 17345 byte *xdefaults; /* contents of defaults from server */ 17346 /* there is more to this structure, but it is private to Xlib */ 17347 } 17348 17349 // I got these numbers from a C program as a sanity test 17350 version(X86_64) { 17351 static assert(Display.sizeof == 296); 17352 static assert(XPointer.sizeof == 8); 17353 static assert(XErrorEvent.sizeof == 40); 17354 static assert(XAnyEvent.sizeof == 40); 17355 static assert(XMappingEvent.sizeof == 56); 17356 static assert(XEvent.sizeof == 192); 17357 } else version (AArch64) { 17358 // omit check for aarch64 17359 } else { 17360 static assert(Display.sizeof == 176); 17361 static assert(XPointer.sizeof == 4); 17362 static assert(XEvent.sizeof == 96); 17363 } 17364 17365 struct Depth 17366 { 17367 int depth; /* this depth (Z) of the depth */ 17368 int nvisuals; /* number of Visual types at this depth */ 17369 Visual *visuals; /* list of visuals possible at this depth */ 17370 } 17371 17372 alias void* GC; 17373 alias c_ulong VisualID; 17374 alias XID Colormap; 17375 alias XID Cursor; 17376 alias XID KeySym; 17377 alias uint KeyCode; 17378 enum None = 0; 17379 } 17380 17381 version(without_opengl) {} 17382 else { 17383 extern(C) nothrow @nogc { 17384 17385 17386 static if(!SdpyIsUsingIVGLBinds) { 17387 enum GLX_USE_GL= 1; /* support GLX rendering */ 17388 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 17389 enum GLX_LEVEL= 3; /* level in plane stacking */ 17390 enum GLX_RGBA= 4; /* true if RGBA mode */ 17391 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 17392 enum GLX_STEREO= 6; /* stereo buffering supported */ 17393 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 17394 enum GLX_RED_SIZE= 8; /* number of red component bits */ 17395 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 17396 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 17397 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 17398 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 17399 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 17400 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 17401 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 17402 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 17403 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 17404 17405 17406 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 17407 17408 17409 17410 enum GL_TRUE = 1; 17411 enum GL_FALSE = 0; 17412 } 17413 17414 alias XID GLXContextID; 17415 alias XID GLXPixmap; 17416 alias XID GLXDrawable; 17417 alias XID GLXPbuffer; 17418 alias XID GLXWindow; 17419 alias XID GLXFBConfigID; 17420 alias void* GLXContext; 17421 17422 } 17423 } 17424 17425 enum AllocNone = 0; 17426 17427 extern(C) { 17428 /* WARNING, this type not in Xlib spec */ 17429 extern(C) alias XIOErrorHandler = int function (Display* display); 17430 } 17431 17432 extern(C) nothrow 17433 alias XErrorHandler = int function(Display*, XErrorEvent*); 17434 17435 extern(C) nothrow @nogc { 17436 struct Screen{ 17437 XExtData *ext_data; /* hook for extension to hang data */ 17438 Display *display; /* back pointer to display structure */ 17439 Window root; /* Root window id. */ 17440 int width, height; /* width and height of screen */ 17441 int mwidth, mheight; /* width and height of in millimeters */ 17442 int ndepths; /* number of depths possible */ 17443 Depth *depths; /* list of allowable depths on the screen */ 17444 int root_depth; /* bits per pixel */ 17445 Visual *root_visual; /* root visual */ 17446 GC default_gc; /* GC for the root root visual */ 17447 Colormap cmap; /* default color map */ 17448 uint white_pixel; 17449 uint black_pixel; /* White and Black pixel values */ 17450 int max_maps, min_maps; /* max and min color maps */ 17451 int backing_store; /* Never, WhenMapped, Always */ 17452 bool save_unders; 17453 int root_input_mask; /* initial root input mask */ 17454 } 17455 17456 struct Visual 17457 { 17458 XExtData *ext_data; /* hook for extension to hang data */ 17459 VisualID visualid; /* visual id of this visual */ 17460 int class_; /* class of screen (monochrome, etc.) */ 17461 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 17462 int bits_per_rgb; /* log base 2 of distinct color values */ 17463 int map_entries; /* color map entries */ 17464 } 17465 17466 alias Display* _XPrivDisplay; 17467 17468 extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) { 17469 assert(dpy !is null); 17470 return &dpy.screens[scr]; 17471 } 17472 17473 extern(D) Window RootWindow(Display *dpy,int scr) { 17474 return ScreenOfDisplay(dpy,scr).root; 17475 } 17476 17477 struct XWMHints { 17478 arch_long flags; 17479 Bool input; 17480 int initial_state; 17481 Pixmap icon_pixmap; 17482 Window icon_window; 17483 int icon_x, icon_y; 17484 Pixmap icon_mask; 17485 XID window_group; 17486 } 17487 17488 struct XClassHint { 17489 char* res_name; 17490 char* res_class; 17491 } 17492 17493 extern(D) int DefaultScreen(Display *dpy) { 17494 return dpy.default_screen; 17495 } 17496 17497 extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 17498 extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 17499 extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 17500 extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 17501 extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 17502 extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 17503 17504 extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; } 17505 17506 enum int AnyPropertyType = 0; 17507 enum int Success = 0; 17508 17509 enum int RevertToNone = None; 17510 enum int PointerRoot = 1; 17511 enum Time CurrentTime = 0; 17512 enum int RevertToPointerRoot = PointerRoot; 17513 enum int RevertToParent = 2; 17514 17515 extern(D) int DefaultDepthOfDisplay(Display* dpy) { 17516 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 17517 } 17518 17519 extern(D) Visual* DefaultVisual(Display *dpy,int scr) { 17520 return ScreenOfDisplay(dpy,scr).root_visual; 17521 } 17522 17523 extern(D) GC DefaultGC(Display *dpy,int scr) { 17524 return ScreenOfDisplay(dpy,scr).default_gc; 17525 } 17526 17527 extern(D) uint BlackPixel(Display *dpy,int scr) { 17528 return ScreenOfDisplay(dpy,scr).black_pixel; 17529 } 17530 17531 extern(D) uint WhitePixel(Display *dpy,int scr) { 17532 return ScreenOfDisplay(dpy,scr).white_pixel; 17533 } 17534 17535 alias void* XFontSet; // i think 17536 struct XmbTextItem { 17537 char* chars; 17538 int nchars; 17539 int delta; 17540 XFontSet font_set; 17541 } 17542 17543 struct XTextItem { 17544 char* chars; 17545 int nchars; 17546 int delta; 17547 Font font; 17548 } 17549 17550 enum { 17551 GXclear = 0x0, /* 0 */ 17552 GXand = 0x1, /* src AND dst */ 17553 GXandReverse = 0x2, /* src AND NOT dst */ 17554 GXcopy = 0x3, /* src */ 17555 GXandInverted = 0x4, /* NOT src AND dst */ 17556 GXnoop = 0x5, /* dst */ 17557 GXxor = 0x6, /* src XOR dst */ 17558 GXor = 0x7, /* src OR dst */ 17559 GXnor = 0x8, /* NOT src AND NOT dst */ 17560 GXequiv = 0x9, /* NOT src XOR dst */ 17561 GXinvert = 0xa, /* NOT dst */ 17562 GXorReverse = 0xb, /* src OR NOT dst */ 17563 GXcopyInverted = 0xc, /* NOT src */ 17564 GXorInverted = 0xd, /* NOT src OR dst */ 17565 GXnand = 0xe, /* NOT src OR NOT dst */ 17566 GXset = 0xf, /* 1 */ 17567 } 17568 enum QueueMode : int { 17569 QueuedAlready, 17570 QueuedAfterReading, 17571 QueuedAfterFlush 17572 } 17573 17574 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 17575 17576 struct XPoint { 17577 short x; 17578 short y; 17579 } 17580 17581 enum CoordMode:int { 17582 CoordModeOrigin = 0, 17583 CoordModePrevious = 1 17584 } 17585 17586 enum PolygonShape:int { 17587 Complex = 0, 17588 Nonconvex = 1, 17589 Convex = 2 17590 } 17591 17592 struct XTextProperty { 17593 const(char)* value; /* same as Property routines */ 17594 Atom encoding; /* prop type */ 17595 int format; /* prop data format: 8, 16, or 32 */ 17596 arch_ulong nitems; /* number of data items in value */ 17597 } 17598 17599 version( X86_64 ) { 17600 static assert(XTextProperty.sizeof == 32); 17601 } 17602 17603 17604 struct XGCValues { 17605 int function_; /* logical operation */ 17606 arch_ulong plane_mask;/* plane mask */ 17607 arch_ulong foreground;/* foreground pixel */ 17608 arch_ulong background;/* background pixel */ 17609 int line_width; /* line width */ 17610 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 17611 int cap_style; /* CapNotLast, CapButt, 17612 CapRound, CapProjecting */ 17613 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 17614 int fill_style; /* FillSolid, FillTiled, 17615 FillStippled, FillOpaeueStippled */ 17616 int fill_rule; /* EvenOddRule, WindingRule */ 17617 int arc_mode; /* ArcChord, ArcPieSlice */ 17618 Pixmap tile; /* tile pixmap for tiling operations */ 17619 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 17620 int ts_x_origin; /* offset for tile or stipple operations */ 17621 int ts_y_origin; 17622 Font font; /* default text font for text operations */ 17623 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 17624 Bool graphics_exposures;/* boolean, should exposures be generated */ 17625 int clip_x_origin; /* origin for clipping */ 17626 int clip_y_origin; 17627 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 17628 int dash_offset; /* patterned/dashed line information */ 17629 char dashes; 17630 } 17631 17632 struct XColor { 17633 arch_ulong pixel; 17634 ushort red, green, blue; 17635 byte flags; 17636 byte pad; 17637 } 17638 17639 struct XRectangle { 17640 short x; 17641 short y; 17642 ushort width; 17643 ushort height; 17644 } 17645 17646 enum ClipByChildren = 0; 17647 enum IncludeInferiors = 1; 17648 17649 enum Atom XA_PRIMARY = 1; 17650 enum Atom XA_SECONDARY = 2; 17651 enum Atom XA_STRING = 31; 17652 enum Atom XA_CARDINAL = 6; 17653 enum Atom XA_WM_NAME = 39; 17654 enum Atom XA_ATOM = 4; 17655 enum Atom XA_WINDOW = 33; 17656 enum Atom XA_WM_HINTS = 35; 17657 enum int PropModeAppend = 2; 17658 enum int PropModeReplace = 0; 17659 enum int PropModePrepend = 1; 17660 17661 enum int CopyFromParent = 0; 17662 enum int InputOutput = 1; 17663 17664 // XWMHints 17665 enum InputHint = 1 << 0; 17666 enum StateHint = 1 << 1; 17667 enum IconPixmapHint = (1L << 2); 17668 enum IconWindowHint = (1L << 3); 17669 enum IconPositionHint = (1L << 4); 17670 enum IconMaskHint = (1L << 5); 17671 enum WindowGroupHint = (1L << 6); 17672 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 17673 enum XUrgencyHint = (1L << 8); 17674 17675 // GC Components 17676 enum GCFunction = (1L<<0); 17677 enum GCPlaneMask = (1L<<1); 17678 enum GCForeground = (1L<<2); 17679 enum GCBackground = (1L<<3); 17680 enum GCLineWidth = (1L<<4); 17681 enum GCLineStyle = (1L<<5); 17682 enum GCCapStyle = (1L<<6); 17683 enum GCJoinStyle = (1L<<7); 17684 enum GCFillStyle = (1L<<8); 17685 enum GCFillRule = (1L<<9); 17686 enum GCTile = (1L<<10); 17687 enum GCStipple = (1L<<11); 17688 enum GCTileStipXOrigin = (1L<<12); 17689 enum GCTileStipYOrigin = (1L<<13); 17690 enum GCFont = (1L<<14); 17691 enum GCSubwindowMode = (1L<<15); 17692 enum GCGraphicsExposures= (1L<<16); 17693 enum GCClipXOrigin = (1L<<17); 17694 enum GCClipYOrigin = (1L<<18); 17695 enum GCClipMask = (1L<<19); 17696 enum GCDashOffset = (1L<<20); 17697 enum GCDashList = (1L<<21); 17698 enum GCArcMode = (1L<<22); 17699 enum GCLastBit = 22; 17700 17701 17702 enum int WithdrawnState = 0; 17703 enum int NormalState = 1; 17704 enum int IconicState = 3; 17705 17706 } 17707 } else version (OSXCocoa) { 17708 17709 /+ 17710 DON'T FORGET TO MARK THE CLASSES `extern`!! can cause "unrecognized selector sent to class" errors if you do. 17711 +/ 17712 17713 private __gshared AppDelegate globalAppDelegate; 17714 17715 extern(Objective-C) 17716 class AppDelegate : NSObject, NSApplicationDelegate { 17717 override static AppDelegate alloc() @selector("alloc"); 17718 17719 17720 void sdpyCustomEventWakeup(NSid arg) @selector("sdpyCustomEventWakeup:") { 17721 SimpleWindow.processAllCustomEvents(); 17722 } 17723 17724 override void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:") { 17725 immutable style = NSWindowStyleMask.resizable | 17726 NSWindowStyleMask.closable | 17727 NSWindowStyleMask.miniaturizable | 17728 NSWindowStyleMask.titled; 17729 17730 NSMenu mainMenu = NSMenu.alloc.init(MacString("Main").borrow); 17731 17732 { 17733 auto item = mainMenu.addItem(MacString("Test").borrow, null, MacString("").borrow); 17734 auto menu = NSMenu.alloc.init(MacString("Test2").borrow); 17735 mainMenu.setSubmenu(menu, item); 17736 17737 auto newItem = menu.addItem(MacString("Quit").borrow, sel_registerName("terminate:"), MacString("q").borrow); 17738 newItem.target = NSApp; 17739 auto newItem2 = menu.addItem(MacString("Disabled").borrow, sel_registerName("doesnotexist:"), MacString("x").borrow); 17740 newItem2.target = NSApp; 17741 } 17742 17743 { 17744 auto item = mainMenu.addItem(MacString("Test3").borrow, null, MacString("").borrow); 17745 auto menu = NSMenu.alloc.init(MacString("Test4").borrow); // this is the title actually used 17746 mainMenu.setSubmenu(menu, item); 17747 17748 auto newItem = menu.addItem(MacString("Quit2").borrow, sel_registerName("stop:"), MacString("s").borrow); 17749 menu.addItem(MacString("Pulse").borrow, sel_registerName("simpledisplay_pulse:"), MacString("p").borrow); 17750 } 17751 17752 17753 NSApp.menu = mainMenu; 17754 17755 17756 // auto controller = ViewController.alloc.init; 17757 17758 // auto timer = NSTimer.schedule(1.0, cast(NSid) view, sel_registerName("simpledisplay_pulse:"), null, true); 17759 17760 /+ 17761 this.window = window; 17762 this.controller = controller; 17763 +/ 17764 } 17765 17766 override void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:") { 17767 NSApplication.shared_.activateIgnoringOtherApps(true); 17768 } 17769 override bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:") { 17770 return true; 17771 } 17772 } 17773 17774 extern(Objective-C) 17775 class SDWindowDelegate : NSObject, NSWindowDelegate { 17776 override static SDWindowDelegate alloc() @selector("alloc"); 17777 override SDWindowDelegate init() @selector("init"); 17778 17779 SimpleWindow simpleWindow; 17780 17781 override void windowWillClose(NSNotification notification) @selector("windowWillClose:") { 17782 auto window = cast(void*) notification.object; 17783 17784 // FIXME: do i need to release it? 17785 SimpleWindow.nativeMapping.remove(window); 17786 } 17787 17788 override NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:") { 17789 if(simpleWindow.windowResized) { 17790 // FIXME: automaticallyScaleIfPossible behaviors 17791 17792 simpleWindow._width = cast(int) frameSize.width; 17793 simpleWindow._height = cast(int) frameSize.height; 17794 17795 simpleWindow.view.setFrameSize(frameSize); 17796 17797 /+ 17798 auto size = simpleWindow.view.frame.size; 17799 writeln(cast(int) size.width, "x", cast(int) size.height); 17800 +/ 17801 17802 simpleWindow.createNewDrawingContext(simpleWindow._width, simpleWindow._height); 17803 17804 simpleWindow.windowResized(simpleWindow._width, simpleWindow._height); 17805 17806 // simpleWindow.view.setNeedsDisplay(true); 17807 } 17808 17809 return frameSize; 17810 } 17811 17812 /+ 17813 override void windowDidResize(NSNotification notification) @selector("windowDidResize:") { 17814 if(simpleWindow.windowResized) { 17815 auto window = simpleWindow.window; 17816 auto rect = window.contentRectForFrameRect(window.frame); 17817 import std.stdio; writeln(window.frame.size); 17818 simpleWindow.windowResized(cast(int) rect.size.width, cast(int) rect.size.height); 17819 } 17820 } 17821 +/ 17822 } 17823 17824 extern(Objective-C) 17825 class SDGraphicsView : NSView { 17826 SimpleWindow simpleWindow; 17827 17828 override static SDGraphicsView alloc() @selector("alloc"); 17829 override SDGraphicsView init() @selector("init") { 17830 super.init(); 17831 return this; 17832 } 17833 17834 override void drawRect(NSRect rect) @selector("drawRect:") { 17835 auto curCtx = NSGraphicsContext.currentContext.graphicsPort; 17836 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 17837 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), CGBitmapContextGetHeight(simpleWindow.drawingContext)); 17838 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 17839 CGImageRelease(cgImage); 17840 } 17841 17842 private void mouseHelper(NSEvent event, MouseEventType type, MouseButton button) { 17843 MouseEvent me; 17844 me.type = type; 17845 17846 auto pos = event.locationInWindow; 17847 17848 me.x = cast(int) pos.x; 17849 me.y = cast(int) (simpleWindow.height - pos.y); 17850 17851 me.dx = 0; // FIXME 17852 me.dy = 0; // FIXME 17853 17854 me.button = button; 17855 me.modifierState = cast(uint) event.modifierFlags; 17856 me.window = simpleWindow; 17857 17858 me.doubleClick = false; 17859 17860 if(simpleWindow && simpleWindow.handleMouseEvent) 17861 simpleWindow.handleMouseEvent(me); 17862 } 17863 17864 override void mouseDown(NSEvent event) @selector("mouseDown:") { 17865 // writeln(event.pressedMouseButtons); 17866 17867 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 17868 } 17869 override void mouseDragged(NSEvent event) @selector("mouseDragged:") { 17870 mouseHelper(event, MouseEventType.motion, MouseButton.left); 17871 } 17872 override void mouseUp(NSEvent event) @selector("mouseUp:") { 17873 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.left); 17874 } 17875 override void mouseMoved(NSEvent event) @selector("mouseMoved:") { 17876 mouseHelper(event, MouseEventType.motion, MouseButton.left); // button wrong prolly 17877 } 17878 /+ 17879 // FIXME 17880 override void mouseEntered(NSEvent event) @selector("mouseEntered:") { 17881 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 17882 } 17883 override void mouseExited(NSEvent event) @selector("mouseExited:") { 17884 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.left); 17885 } 17886 +/ 17887 17888 override void rightMouseDown(NSEvent event) @selector("rightMouseDown:") { 17889 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.right); 17890 } 17891 override void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:") { 17892 mouseHelper(event, MouseEventType.motion, MouseButton.right); 17893 } 17894 override void rightMouseUp(NSEvent event) @selector("rightMouseUp:") { 17895 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.right); 17896 } 17897 17898 override void otherMouseDown(NSEvent event) @selector("otherMouseDown:") { 17899 mouseHelper(event, MouseEventType.buttonPressed, MouseButton.middle); 17900 } 17901 override void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:") { 17902 mouseHelper(event, MouseEventType.motion, MouseButton.middle); 17903 } 17904 override void otherMouseUp(NSEvent event) @selector("otherMouseUp:") { 17905 mouseHelper(event, MouseEventType.buttonReleased, MouseButton.middle); 17906 } 17907 17908 override void scrollWheel(NSEvent event) @selector("scrollWheel:") { 17909 import std.stdio; 17910 writeln(event.deltaY); 17911 } 17912 17913 override void keyDown(NSEvent event) @selector("keyDown:") { 17914 // the event may have multiple characters, and we send them all at once. 17915 if (simpleWindow.handleCharEvent) { 17916 auto chars = DeifiedNSString(event.characters); 17917 foreach (dchar dc; chars.str) 17918 simpleWindow.handleCharEvent(dc); 17919 } 17920 17921 keyHelper(event, true); 17922 } 17923 17924 override void keyUp(NSEvent event) @selector("keyUp:") { 17925 keyHelper(event, false); 17926 } 17927 17928 private void keyHelper(NSEvent event, bool pressed) { 17929 if(simpleWindow.handleKeyEvent) { 17930 KeyEvent ev; 17931 ev.key = cast(Key) event.keyCode;// (event.specialKey ? event.specialKey : event.keyCode); 17932 ev.pressed = pressed; 17933 ev.hardwareCode = cast(ubyte) event.keyCode; 17934 ev.modifierState = cast(uint) event.modifierFlags; 17935 ev.window = simpleWindow; 17936 17937 simpleWindow.handleKeyEvent(ev); 17938 } 17939 } 17940 17941 override bool isFlipped() @selector("isFlipped") { 17942 return true; 17943 } 17944 override bool acceptsFirstResponder() @selector("acceptsFirstResponder") { 17945 return true; 17946 } 17947 17948 void simpledisplay_pulse(NSTimer timer) @selector("simpledisplay_pulse:") { 17949 if(simpleWindow && simpleWindow.handlePulse) 17950 simpleWindow.handlePulse(); 17951 /+ 17952 setNeedsDisplay = true; 17953 +/ 17954 } 17955 } 17956 17957 private: 17958 alias const(void)* CFStringRef; 17959 alias const(void)* CFAllocatorRef; 17960 alias const(void)* CFTypeRef; 17961 alias const(void)* CGColorSpaceRef; 17962 alias const(void)* CGImageRef; 17963 alias ulong CGBitmapInfo; 17964 alias NSGraphicsContext CGContextRef; 17965 17966 alias NSPoint CGPoint; 17967 alias NSSize CGSize; 17968 alias NSRect CGRect; 17969 17970 struct CGAffineTransform { 17971 double a, b, c, d, tx, ty; 17972 } 17973 17974 enum NSApplicationActivationPolicyRegular = 0; 17975 enum NSBackingStoreBuffered = 2; 17976 enum kCFStringEncodingUTF8 = 0x08000100; 17977 17978 enum : size_t { 17979 NSBorderlessWindowMask = 0, 17980 NSTitledWindowMask = 1 << 0, 17981 NSClosableWindowMask = 1 << 1, 17982 NSMiniaturizableWindowMask = 1 << 2, 17983 NSResizableWindowMask = 1 << 3, 17984 NSTexturedBackgroundWindowMask = 1 << 8 17985 } 17986 17987 enum : ulong { 17988 kCGImageAlphaNone, 17989 kCGImageAlphaPremultipliedLast, 17990 kCGImageAlphaPremultipliedFirst, 17991 kCGImageAlphaLast, 17992 kCGImageAlphaFirst, 17993 kCGImageAlphaNoneSkipLast, 17994 kCGImageAlphaNoneSkipFirst 17995 } 17996 enum : ulong { 17997 kCGBitmapAlphaInfoMask = 0x1F, 17998 kCGBitmapFloatComponents = (1 << 8), 17999 kCGBitmapByteOrderMask = 0x7000, 18000 kCGBitmapByteOrderDefault = (0 << 12), 18001 kCGBitmapByteOrder16Little = (1 << 12), 18002 kCGBitmapByteOrder32Little = (2 << 12), 18003 kCGBitmapByteOrder16Big = (3 << 12), 18004 kCGBitmapByteOrder32Big = (4 << 12) 18005 } 18006 enum CGPathDrawingMode { 18007 kCGPathFill, 18008 kCGPathEOFill, 18009 kCGPathStroke, 18010 kCGPathFillStroke, 18011 kCGPathEOFillStroke 18012 } 18013 enum objc_AssociationPolicy : size_t { 18014 OBJC_ASSOCIATION_ASSIGN = 0, 18015 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 18016 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 18017 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 18018 OBJC_ASSOCIATION_COPY = 0x303 //01403 18019 } 18020 18021 extern(C) { 18022 CGContextRef CGBitmapContextCreate(void* data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef colorspace, CGBitmapInfo bitmapInfo); 18023 void CGContextRelease(CGContextRef c); 18024 ubyte* CGBitmapContextGetData(CGContextRef c); 18025 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 18026 size_t CGBitmapContextGetWidth(CGContextRef c); 18027 size_t CGBitmapContextGetHeight(CGContextRef c); 18028 18029 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 18030 void CGColorSpaceRelease(CGColorSpaceRef cs); 18031 18032 void CGContextSetRGBStrokeColor(CGContextRef c, double red, double green, double blue, double alpha); 18033 void CGContextSetRGBFillColor(CGContextRef c, double red, double green, double blue, double alpha); 18034 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 18035 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, const(char)* str, size_t length); 18036 void CGContextStrokeLineSegments(CGContextRef c, const(CGPoint)* points, size_t count); 18037 18038 void CGContextBeginPath(CGContextRef c); 18039 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 18040 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 18041 void CGContextAddArc(CGContextRef c, double x, double y, double radius, double startAngle, double endAngle, long clockwise); 18042 void CGContextAddRect(CGContextRef c, CGRect rect); 18043 void CGContextAddLines(CGContextRef c, const(CGPoint)* points, size_t count); 18044 void CGContextSaveGState(CGContextRef c); 18045 void CGContextRestoreGState(CGContextRef c); 18046 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, ulong textEncoding); 18047 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 18048 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 18049 18050 void CGImageRelease(CGImageRef image); 18051 } 18052 } else static assert(0, "Unsupported operating system"); 18053 18054 18055 version(OSXCocoa) { 18056 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 18057 // 18058 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 18059 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 18060 // 18061 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 18062 // Probably won't even fully compile right now 18063 18064 private enum double PI = 3.14159265358979323; 18065 18066 alias NSWindow NativeWindowHandle; 18067 alias void delegate(NSid) NativeEventHandler; 18068 18069 enum KEY_ESCAPE = 27; 18070 18071 mixin template NativeImageImplementation() { 18072 CGContextRef context; 18073 ubyte* rawData; 18074 18075 final: 18076 18077 void convertToRgbaBytes(ubyte[] where) { 18078 assert(where.length == this.width * this.height * 4); 18079 18080 // if rawData had a length.... 18081 //assert(rawData.length == where.length); 18082 for(long idx = 0; idx < where.length; idx += 4) { 18083 auto alpha = rawData[idx + 3]; 18084 if(alpha == 255) { 18085 where[idx + 0] = rawData[idx + 0]; // r 18086 where[idx + 1] = rawData[idx + 1]; // g 18087 where[idx + 2] = rawData[idx + 2]; // b 18088 where[idx + 3] = rawData[idx + 3]; // a 18089 } else { 18090 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 18091 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 18092 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 18093 where[idx + 3] = rawData[idx + 3]; // a 18094 18095 } 18096 } 18097 } 18098 18099 void setFromRgbaBytes(in ubyte[] where) { 18100 // FIXME: this is probably wrong 18101 assert(where.length == this.width * this.height * 4); 18102 18103 // if rawData had a length.... 18104 //assert(rawData.length == where.length); 18105 for(long idx = 0; idx < where.length; idx += 4) { 18106 auto alpha = where[idx + 3]; 18107 if(alpha == 255) { 18108 rawData[idx + 0] = where[idx + 0]; // r 18109 rawData[idx + 1] = where[idx + 1]; // g 18110 rawData[idx + 2] = where[idx + 2]; // b 18111 rawData[idx + 3] = where[idx + 3]; // a 18112 } else if(alpha == 0) { 18113 rawData[idx + 0] = 0; 18114 rawData[idx + 1] = 0; 18115 rawData[idx + 2] = 0; 18116 rawData[idx + 3] = 0; 18117 } else { 18118 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 18119 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 18120 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 18121 rawData[idx + 3] = where[idx + 3]; // a 18122 } 18123 } 18124 } 18125 18126 18127 void createImage(int width, int height, bool forcexshm=false, bool ignored = false) { 18128 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 18129 context = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 18130 CGColorSpaceRelease(colorSpace); 18131 rawData = CGBitmapContextGetData(context); 18132 } 18133 void dispose() { 18134 CGContextRelease(context); 18135 } 18136 18137 void setPixel(int x, int y, Color c) { 18138 auto offset = (y * width + x) * 4; 18139 if (c.a == 255) { 18140 rawData[offset + 0] = c.r; 18141 rawData[offset + 1] = c.g; 18142 rawData[offset + 2] = c.b; 18143 rawData[offset + 3] = c.a; 18144 } else { 18145 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 18146 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 18147 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 18148 rawData[offset + 3] = c.a; 18149 } 18150 } 18151 } 18152 18153 mixin template NativeScreenPainterImplementation() { 18154 CGContextRef context; 18155 ubyte[4] _outlineComponents; 18156 NSView view; 18157 18158 void create(PaintingHandle window) { 18159 // this.destiny = window; 18160 if(auto sw = cast(SimpleWindow) this.window) { 18161 context = sw.drawingContext; 18162 view = sw.view; 18163 } else { 18164 throw new NotYetImplementedException(); 18165 } 18166 } 18167 18168 void dispose() { 18169 view.setNeedsDisplay(true); 18170 } 18171 18172 bool manualInvalidations; 18173 void invalidateRect(Rectangle invalidRect) { } 18174 18175 // NotYetImplementedException 18176 Size textSize(in char[] txt) { 18177 return Size(32, 16); /*throw new NotYetImplementedException();*/ 18178 } 18179 void rasterOp(RasterOp op) { 18180 } 18181 Pen _activePen; 18182 Color _fillColor; 18183 Rectangle _clipRectangle; 18184 void setClipRectangle(int, int, int, int) { 18185 } 18186 void setFont(OperatingSystemFont) { 18187 } 18188 int fontHeight() { 18189 return 14; 18190 } 18191 18192 // end 18193 18194 void pen(Pen pen) { 18195 _activePen = pen; 18196 auto color = pen.color; // FIXME 18197 double alphaComponent = color.a/255.0f; 18198 CGContextSetRGBStrokeColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 18199 18200 if (color.a != 255) { 18201 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 18202 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 18203 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 18204 _outlineComponents[3] = color.a; 18205 } else { 18206 _outlineComponents[0] = color.r; 18207 _outlineComponents[1] = color.g; 18208 _outlineComponents[2] = color.b; 18209 _outlineComponents[3] = color.a; 18210 } 18211 } 18212 18213 @property void fillColor(Color color) { 18214 CGContextSetRGBFillColor(context, color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 18215 } 18216 18217 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 18218 // NotYetImplementedException for upper left/width/height 18219 auto cgImage = CGBitmapContextCreateImage(image.context); 18220 auto size = CGSize(CGBitmapContextGetWidth(image.context), CGBitmapContextGetHeight(image.context)); 18221 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 18222 CGImageRelease(cgImage); 18223 } 18224 18225 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 18226 // FIXME: is this efficient? 18227 auto cgImage = CGBitmapContextCreateImage(s.handle); 18228 auto size = CGSize(CGBitmapContextGetWidth(s.handle), CGBitmapContextGetHeight(s.handle)); 18229 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 18230 CGImageRelease(cgImage); 18231 } 18232 18233 18234 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 18235 // FIXME: alignment 18236 if (_outlineComponents[3] != 0) { 18237 CGContextSaveGState(context); 18238 auto invAlpha = 1.0f/_outlineComponents[3]; 18239 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 18240 _outlineComponents[1]*invAlpha, 18241 _outlineComponents[2]*invAlpha, 18242 _outlineComponents[3]/255.0f); 18243 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length); 18244 // auto cfstr = cast(NSid)createCFString(text); 18245 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 18246 // NSPoint(x, y), null); 18247 // CFRelease(cfstr); 18248 CGContextRestoreGState(context); 18249 } 18250 } 18251 18252 void drawPixel(int x, int y) { 18253 auto rawData = CGBitmapContextGetData(context); 18254 auto width = CGBitmapContextGetWidth(context); 18255 auto height = CGBitmapContextGetHeight(context); 18256 auto offset = ((height - y - 1) * width + x) * 4; 18257 rawData[offset .. offset+4] = _outlineComponents; 18258 } 18259 18260 void drawLine(int x1, int y1, int x2, int y2) { 18261 CGPoint[2] linePoints; 18262 linePoints[0] = CGPoint(x1, y1); 18263 linePoints[1] = CGPoint(x2, y2); 18264 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 18265 } 18266 18267 void drawRectangle(int x, int y, int width, int height) { 18268 CGContextBeginPath(context); 18269 auto rect = CGRect(CGPoint(x, y), CGSize(width, height)); 18270 CGContextAddRect(context, rect); 18271 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18272 } 18273 18274 void drawEllipse(int x1, int y1, int x2, int y2) { 18275 CGContextBeginPath(context); 18276 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 18277 CGContextAddEllipseInRect(context, rect); 18278 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18279 } 18280 18281 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 18282 // @@@BUG@@@ Does not support elliptic arc (width != height). 18283 CGContextBeginPath(context); 18284 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 18285 start*PI/(180*64), finish*PI/(180*64), 0); 18286 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18287 } 18288 18289 void drawPolygon(Point[] intPoints) { 18290 CGContextBeginPath(context); 18291 CGPoint[16] pointsBuffer; 18292 CGPoint[] points; 18293 if(intPoints.length <= pointsBuffer.length) 18294 points = pointsBuffer[0 .. intPoints.length]; 18295 else 18296 points = new CGPoint[](intPoints.length); 18297 18298 foreach(idx, pt; intPoints) 18299 points[idx] = CGPoint(pt.x, pt.y); 18300 18301 CGContextAddLines(context, points.ptr, points.length); 18302 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18303 } 18304 } 18305 18306 private bool appInitialized = false; 18307 void initializeApp() { 18308 if(appInitialized) 18309 return; 18310 synchronized { 18311 if(appInitialized) 18312 return; 18313 18314 auto app = NSApp(); // ensure the is initialized 18315 18316 auto dg = AppDelegate.alloc; 18317 globalAppDelegate = dg; 18318 NSApp.delegate_ = dg; 18319 18320 NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular); 18321 18322 appInitialized = true; 18323 } 18324 } 18325 18326 mixin template NativeSimpleWindowImplementation() { 18327 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 18328 initializeApp(); 18329 18330 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 18331 18332 auto window = NSWindow.alloc.initWithContentRect( 18333 contentRect, 18334 NSWindowStyleMask.resizable | NSWindowStyleMask.closable | NSWindowStyleMask.miniaturizable | NSWindowStyleMask.titled, 18335 NSBackingStoreType.buffered, 18336 true 18337 ); 18338 18339 SimpleWindow.nativeMapping[cast(void*) window] = this; 18340 18341 window.title = MacString(title).borrow; 18342 18343 auto dg = SDWindowDelegate.alloc.init; 18344 dg.simpleWindow = this; 18345 window.delegate_ = dg; 18346 18347 auto view = SDGraphicsView.alloc.init; 18348 assert(view !is null); 18349 window.contentView = view; 18350 this.view = view; 18351 view.simpleWindow = this; 18352 18353 window.center(); 18354 18355 window.makeKeyAndOrderFront(null); 18356 18357 // no need to make a bitmap on mac since everything is double buffered already 18358 18359 // create area to draw on. 18360 createNewDrawingContext(width, height); 18361 18362 window.setBackgroundColor(NSColor.whiteColor); 18363 } 18364 18365 void createNewDrawingContext(int width, int height) { 18366 // FIXME need to preserve info from the old context too i think... maybe. or at least setNeedsDisplay 18367 if(this.drawingContext) 18368 CGContextRelease(this.drawingContext); 18369 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 18370 this.drawingContext = CGBitmapContextCreate(null, width, height, 8, 4*width, colorSpace, kCGImageAlphaPremultipliedLast |kCGBitmapByteOrder32Big); 18371 CGColorSpaceRelease(colorSpace); 18372 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 18373 auto matrix = CGContextGetTextMatrix(drawingContext); 18374 matrix.c = -matrix.c; 18375 matrix.d = -matrix.d; 18376 CGContextSetTextMatrix(drawingContext, matrix); 18377 18378 } 18379 18380 void dispose() { 18381 closeWindow(); 18382 // window.release(); // closing the window does this automatically i think 18383 } 18384 void closeWindow() { 18385 if(timer) 18386 timer.invalidate(); 18387 window.close(); 18388 } 18389 18390 ScreenPainter getPainter(bool manualInvalidations) { 18391 return ScreenPainter(this, this.window, manualInvalidations); 18392 } 18393 18394 NSWindow window; 18395 NSTimer timer; 18396 NSView view; 18397 CGContextRef drawingContext; 18398 } 18399 } 18400 18401 version(without_opengl) {} else 18402 extern(System) nothrow @nogc { 18403 //enum uint GL_VERSION = 0x1F02; 18404 //const(char)* glGetString (/*GLenum*/uint); 18405 version(X11) { 18406 static if (!SdpyIsUsingIVGLBinds) { 18407 18408 enum GLX_X_RENDERABLE = 0x8012; 18409 enum GLX_DRAWABLE_TYPE = 0x8010; 18410 enum GLX_RENDER_TYPE = 0x8011; 18411 enum GLX_X_VISUAL_TYPE = 0x22; 18412 enum GLX_TRUE_COLOR = 0x8002; 18413 enum GLX_WINDOW_BIT = 0x00000001; 18414 enum GLX_RGBA_BIT = 0x00000001; 18415 enum GLX_COLOR_INDEX_BIT = 0x00000002; 18416 enum GLX_SAMPLE_BUFFERS = 0x186a0; 18417 enum GLX_SAMPLES = 0x186a1; 18418 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18419 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18420 } 18421 18422 // GLX_EXT_swap_control 18423 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 18424 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 18425 18426 //k8: ugly code to prevent warnings when sdpy is compiled into .a 18427 extern(System) { 18428 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 18429 } 18430 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 18431 18432 // this made public so we don't have to get it again and again 18433 public bool glXCreateContextAttribsARB_present () { 18434 if (glXCreateContextAttribsARBFn is cast(void*)1) { 18435 // get it 18436 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 18437 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 18438 } 18439 return (glXCreateContextAttribsARBFn !is null); 18440 } 18441 18442 // this made public so we don't have to get it again and again 18443 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) { 18444 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 18445 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 18446 } 18447 18448 // extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers 18449 extern(C) private __gshared int function(int) glXSwapIntervalMESA; 18450 18451 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 18452 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 18453 if (_glx_swapInterval_fn is null) { 18454 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 18455 if (_glx_swapInterval_fn is null) { 18456 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 18457 return; 18458 } 18459 version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); } 18460 } 18461 18462 if(glXSwapIntervalMESA is null) { 18463 // it seems to require both to actually take effect on many computers 18464 // idk why 18465 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA"); 18466 if(glXSwapIntervalMESA is null) 18467 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1; 18468 } 18469 18470 if(cast(void*) glXSwapIntervalMESA > cast(void*) 1) 18471 glXSwapIntervalMESA(wait ? 1 : 0); 18472 18473 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 18474 } 18475 } else version(Windows) { 18476 static if (!SdpyIsUsingIVGLBinds) { 18477 enum GL_TRUE = 1; 18478 enum GL_FALSE = 0; 18479 18480 public void* glbindGetProcAddress (const(char)* name) { 18481 void* res = wglGetProcAddress(name); 18482 if (res is null) { 18483 /+ 18484 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 18485 import core.sys.windows.windef, core.sys.windows.winbase; 18486 __gshared HINSTANCE dll = null; 18487 if (dll is null) { 18488 dll = LoadLibraryA("opengl32.dll"); 18489 if (dll is null) return null; // <32, but idc 18490 } 18491 res = GetProcAddress(dll, name); 18492 +/ 18493 res = GetProcAddress(gl.libHandle, name); 18494 } 18495 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 18496 return res; 18497 } 18498 } 18499 18500 18501 private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT; 18502 void wglSetVSync(bool wait) { 18503 if(wglSwapIntervalEXT is null) { 18504 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT"); 18505 if(wglSwapIntervalEXT is null) 18506 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1; 18507 } 18508 if(cast(void*) wglSwapIntervalEXT is cast(void*) 1) 18509 return; 18510 18511 wglSwapIntervalEXT(wait ? 1 : 0); 18512 } 18513 18514 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18515 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18516 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 18517 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 18518 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 18519 18520 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 18521 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 18522 18523 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 18524 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 18525 18526 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 18527 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 18528 18529 void wglInitOtherFunctions () { 18530 if (wglCreateContextAttribsARB is null) { 18531 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 18532 } 18533 } 18534 } 18535 18536 static if (!SdpyIsUsingIVGLBinds) { 18537 18538 interface GL { 18539 extern(System) @nogc nothrow: 18540 18541 void glGetIntegerv(int, void*); 18542 void glMatrixMode(int); 18543 void glPushMatrix(); 18544 void glLoadIdentity(); 18545 void glOrtho(double, double, double, double, double, double); 18546 void glFrustum(double, double, double, double, double, double); 18547 18548 void glPopMatrix(); 18549 void glEnable(int); 18550 void glDisable(int); 18551 void glClear(int); 18552 void glBegin(int); 18553 void glVertex2f(float, float); 18554 void glVertex3f(float, float, float); 18555 void glEnd(); 18556 void glColor3b(byte, byte, byte); 18557 void glColor3ub(ubyte, ubyte, ubyte); 18558 void glColor4b(byte, byte, byte, byte); 18559 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 18560 void glColor3i(int, int, int); 18561 void glColor3ui(uint, uint, uint); 18562 void glColor4i(int, int, int, int); 18563 void glColor4ui(uint, uint, uint, uint); 18564 void glColor3f(float, float, float); 18565 void glColor4f(float, float, float, float); 18566 void glTranslatef(float, float, float); 18567 void glScalef(float, float, float); 18568 version(X11) { 18569 void glSecondaryColor3b(byte, byte, byte); 18570 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 18571 void glSecondaryColor3i(int, int, int); 18572 void glSecondaryColor3ui(uint, uint, uint); 18573 void glSecondaryColor3f(float, float, float); 18574 } 18575 18576 void glDrawElements(int, int, int, void*); 18577 18578 void glRotatef(float, float, float, float); 18579 18580 uint glGetError(); 18581 18582 void glDeleteTextures(int, uint*); 18583 18584 18585 void glRasterPos2i(int, int); 18586 void glDrawPixels(int, int, uint, uint, void*); 18587 void glClearColor(float, float, float, float); 18588 18589 18590 void glPixelStorei(uint, int); 18591 18592 void glGenTextures(uint, uint*); 18593 void glBindTexture(int, int); 18594 void glTexParameteri(uint, uint, int); 18595 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18596 void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*); 18597 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 18598 /*GLsizei*/int width, /*GLsizei*/int height, 18599 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 18600 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18601 18602 void glLineWidth(int); 18603 18604 18605 void glTexCoord2f(float, float); 18606 void glVertex2i(int, int); 18607 void glBlendFunc (int, int); 18608 void glDepthFunc (int); 18609 void glViewport(int, int, int, int); 18610 18611 void glClearDepth(double); 18612 18613 void glReadBuffer(uint); 18614 void glReadPixels(int, int, int, int, int, int, void*); 18615 18616 void glFlush(); 18617 void glFinish(); 18618 18619 version(Windows) { 18620 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 18621 HGLRC wglCreateContext(HDC); 18622 HGLRC wglCreateLayerContext(HDC, int); 18623 BOOL wglDeleteContext(HGLRC); 18624 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 18625 HGLRC wglGetCurrentContext(); 18626 HDC wglGetCurrentDC(); 18627 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 18628 PROC wglGetProcAddress(LPCSTR); 18629 BOOL wglMakeCurrent(HDC, HGLRC); 18630 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 18631 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 18632 BOOL wglShareLists(HGLRC, HGLRC); 18633 BOOL wglSwapLayerBuffers(HDC, UINT); 18634 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 18635 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 18636 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18637 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18638 } 18639 18640 } 18641 18642 interface GL3 { 18643 extern(System) @nogc nothrow: 18644 18645 void glGenVertexArrays(GLsizei, GLuint*); 18646 void glBindVertexArray(GLuint); 18647 void glDeleteVertexArrays(GLsizei, const(GLuint)*); 18648 void glGenerateMipmap(GLenum); 18649 void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 18650 void glStencilMask(GLuint); 18651 void glStencilFunc(GLenum, GLint, GLuint); 18652 void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18653 void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18654 GLuint glCreateProgram(); 18655 GLuint glCreateShader(GLenum); 18656 void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 18657 void glCompileShader(GLuint); 18658 void glGetShaderiv(GLuint, GLenum, GLint*); 18659 void glAttachShader(GLuint, GLuint); 18660 void glBindAttribLocation(GLuint, GLuint, const(GLchar)*); 18661 void glLinkProgram(GLuint); 18662 void glGetProgramiv(GLuint, GLenum, GLint*); 18663 void glDeleteProgram(GLuint); 18664 void glDeleteShader(GLuint); 18665 GLint glGetUniformLocation(GLuint, const(GLchar)*); 18666 void glGenBuffers(GLsizei, GLuint*); 18667 18668 void glUniform1f(GLint location, GLfloat v0); 18669 void glUniform2f(GLint location, GLfloat v0, GLfloat v1); 18670 void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); 18671 void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); 18672 void glUniform1i(GLint location, GLint v0); 18673 void glUniform2i(GLint location, GLint v0, GLint v1); 18674 void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2); 18675 void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); 18676 void glUniform1ui(GLint location, GLuint v0); 18677 void glUniform2ui(GLint location, GLuint v0, GLuint v1); 18678 void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2); 18679 void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); 18680 void glUniform1fv(GLint location, GLsizei count, const GLfloat *value); 18681 void glUniform2fv(GLint location, GLsizei count, const GLfloat *value); 18682 void glUniform3fv(GLint location, GLsizei count, const GLfloat *value); 18683 void glUniform4fv(GLint location, GLsizei count, const GLfloat *value); 18684 void glUniform1iv(GLint location, GLsizei count, const GLint *value); 18685 void glUniform2iv(GLint location, GLsizei count, const GLint *value); 18686 void glUniform3iv(GLint location, GLsizei count, const GLint *value); 18687 void glUniform4iv(GLint location, GLsizei count, const GLint *value); 18688 void glUniform1uiv(GLint location, GLsizei count, const GLuint *value); 18689 void glUniform2uiv(GLint location, GLsizei count, const GLuint *value); 18690 void glUniform3uiv(GLint location, GLsizei count, const GLuint *value); 18691 void glUniform4uiv(GLint location, GLsizei count, const GLuint *value); 18692 void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18693 void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18694 void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18695 void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18696 void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18697 void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18698 void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18699 void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18700 void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18701 18702 void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); 18703 void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); 18704 void glDrawArrays(GLenum, GLint, GLsizei); 18705 void glStencilOp(GLenum, GLenum, GLenum); 18706 void glUseProgram(GLuint); 18707 void glCullFace(GLenum); 18708 void glFrontFace(GLenum); 18709 void glActiveTexture(GLenum); 18710 void glBindBuffer(GLenum, GLuint); 18711 void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum); 18712 void glEnableVertexAttribArray(GLuint); 18713 void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 18714 void glUniform1i(GLint, GLint); 18715 void glUniform2fv(GLint, GLsizei, const(GLfloat)*); 18716 void glDisableVertexAttribArray(GLuint); 18717 void glDeleteBuffers(GLsizei, const(GLuint)*); 18718 void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); 18719 void glLogicOp (GLenum opcode); 18720 void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 18721 void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers); 18722 void glGenFramebuffers (GLsizei n, GLuint* framebuffers); 18723 GLenum glCheckFramebufferStatus (GLenum target); 18724 void glBindFramebuffer (GLenum target, GLuint framebuffer); 18725 } 18726 18727 interface GL4 { 18728 extern(System) @nogc nothrow: 18729 18730 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 18731 /*GLsizei*/int width, /*GLsizei*/int height, 18732 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 18733 } 18734 18735 interface GLU { 18736 extern(System) @nogc nothrow: 18737 18738 void gluLookAt(double, double, double, double, double, double, double, double, double); 18739 void gluPerspective(double, double, double, double); 18740 18741 char* gluErrorString(uint); 18742 } 18743 18744 18745 enum GL_RED = 0x1903; 18746 enum GL_ALPHA = 0x1906; 18747 18748 enum uint GL_FRONT = 0x0404; 18749 18750 enum uint GL_BLEND = 0x0be2; 18751 enum uint GL_LEQUAL = 0x0203; 18752 18753 18754 enum uint GL_RGB = 0x1907; 18755 enum uint GL_BGRA = 0x80e1; 18756 enum uint GL_RGBA = 0x1908; 18757 enum uint GL_TEXTURE_2D = 0x0DE1; 18758 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 18759 enum uint GL_NEAREST = 0x2600; 18760 enum uint GL_LINEAR = 0x2601; 18761 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 18762 enum uint GL_TEXTURE_WRAP_S = 0x2802; 18763 enum uint GL_TEXTURE_WRAP_T = 0x2803; 18764 enum uint GL_REPEAT = 0x2901; 18765 enum uint GL_CLAMP = 0x2900; 18766 enum uint GL_CLAMP_TO_EDGE = 0x812F; 18767 enum uint GL_CLAMP_TO_BORDER = 0x812D; 18768 enum uint GL_DECAL = 0x2101; 18769 enum uint GL_MODULATE = 0x2100; 18770 enum uint GL_TEXTURE_ENV = 0x2300; 18771 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 18772 enum uint GL_REPLACE = 0x1E01; 18773 enum uint GL_LIGHTING = 0x0B50; 18774 enum uint GL_DITHER = 0x0BD0; 18775 18776 enum uint GL_NO_ERROR = 0; 18777 18778 18779 18780 enum int GL_VIEWPORT = 0x0BA2; 18781 enum int GL_MODELVIEW = 0x1700; 18782 enum int GL_TEXTURE = 0x1702; 18783 enum int GL_PROJECTION = 0x1701; 18784 enum int GL_DEPTH_TEST = 0x0B71; 18785 18786 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 18787 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 18788 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 18789 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 18790 18791 enum int GL_POINTS = 0x0000; 18792 enum int GL_LINES = 0x0001; 18793 enum int GL_LINE_LOOP = 0x0002; 18794 enum int GL_LINE_STRIP = 0x0003; 18795 enum int GL_TRIANGLES = 0x0004; 18796 enum int GL_TRIANGLE_STRIP = 5; 18797 enum int GL_TRIANGLE_FAN = 6; 18798 enum int GL_QUADS = 7; 18799 enum int GL_QUAD_STRIP = 8; 18800 enum int GL_POLYGON = 9; 18801 18802 alias GLvoid = void; 18803 alias GLboolean = ubyte; 18804 alias GLint = int; 18805 alias GLuint = uint; 18806 alias GLenum = uint; 18807 alias GLchar = char; 18808 alias GLsizei = int; 18809 alias GLfloat = float; 18810 alias GLintptr = size_t; 18811 alias GLsizeiptr = ptrdiff_t; 18812 18813 18814 enum uint GL_INVALID_ENUM = 0x0500; 18815 18816 enum uint GL_ZERO = 0; 18817 enum uint GL_ONE = 1; 18818 18819 enum uint GL_BYTE = 0x1400; 18820 enum uint GL_UNSIGNED_BYTE = 0x1401; 18821 enum uint GL_SHORT = 0x1402; 18822 enum uint GL_UNSIGNED_SHORT = 0x1403; 18823 enum uint GL_INT = 0x1404; 18824 enum uint GL_UNSIGNED_INT = 0x1405; 18825 enum uint GL_FLOAT = 0x1406; 18826 enum uint GL_2_BYTES = 0x1407; 18827 enum uint GL_3_BYTES = 0x1408; 18828 enum uint GL_4_BYTES = 0x1409; 18829 enum uint GL_DOUBLE = 0x140A; 18830 18831 enum uint GL_STREAM_DRAW = 0x88E0; 18832 18833 enum uint GL_CCW = 0x0901; 18834 18835 enum uint GL_STENCIL_TEST = 0x0B90; 18836 enum uint GL_SCISSOR_TEST = 0x0C11; 18837 18838 enum uint GL_EQUAL = 0x0202; 18839 enum uint GL_NOTEQUAL = 0x0205; 18840 18841 enum uint GL_ALWAYS = 0x0207; 18842 enum uint GL_KEEP = 0x1E00; 18843 18844 enum uint GL_INCR = 0x1E02; 18845 18846 enum uint GL_INCR_WRAP = 0x8507; 18847 enum uint GL_DECR_WRAP = 0x8508; 18848 18849 enum uint GL_CULL_FACE = 0x0B44; 18850 enum uint GL_BACK = 0x0405; 18851 18852 enum uint GL_FRAGMENT_SHADER = 0x8B30; 18853 enum uint GL_VERTEX_SHADER = 0x8B31; 18854 18855 enum uint GL_COMPILE_STATUS = 0x8B81; 18856 enum uint GL_LINK_STATUS = 0x8B82; 18857 18858 enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893; 18859 18860 enum uint GL_STATIC_DRAW = 0x88E4; 18861 18862 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 18863 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 18864 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 18865 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 18866 18867 enum uint GL_GENERATE_MIPMAP = 0x8191; 18868 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 18869 18870 enum uint GL_TEXTURE0 = 0x84C0U; 18871 enum uint GL_TEXTURE1 = 0x84C1U; 18872 18873 enum uint GL_ARRAY_BUFFER = 0x8892; 18874 18875 enum uint GL_SRC_COLOR = 0x0300; 18876 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 18877 enum uint GL_SRC_ALPHA = 0x0302; 18878 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 18879 enum uint GL_DST_ALPHA = 0x0304; 18880 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 18881 enum uint GL_DST_COLOR = 0x0306; 18882 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 18883 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 18884 18885 enum uint GL_INVERT = 0x150AU; 18886 18887 enum uint GL_DEPTH_STENCIL = 0x84F9U; 18888 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 18889 18890 enum uint GL_FRAMEBUFFER = 0x8D40U; 18891 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 18892 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 18893 18894 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 18895 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 18896 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 18897 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 18898 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 18899 18900 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 18901 enum uint GL_CLEAR = 0x1500U; 18902 enum uint GL_COPY = 0x1503U; 18903 enum uint GL_XOR = 0x1506U; 18904 18905 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 18906 18907 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 18908 18909 } 18910 } 18911 18912 /++ 18913 History: 18914 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. 18915 +/ 18916 __gshared bool gluSuccessfullyLoaded = true; 18917 18918 version(without_opengl) {} else { 18919 static if(!SdpyIsUsingIVGLBinds) { 18920 version(Windows) { 18921 mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl; 18922 mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu; 18923 } else { 18924 mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl; 18925 mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu; 18926 } 18927 mixin DynamicLoadSupplementalOpenGL!(GL3) gl3; 18928 18929 18930 shared static this() { 18931 gl.loadDynamicLibrary(); 18932 18933 // FIXME: this is NOT actually required and should NOT fail if it is not loaded 18934 // unless those functions are actually used 18935 // go to mark b openGlLibrariesSuccessfullyLoaded = false; 18936 glu.loadDynamicLibrary(); 18937 } 18938 } 18939 } 18940 18941 /++ 18942 Convenience method for converting D arrays to opengl buffer data 18943 18944 I would LOVE to overload it with the original glBufferData, but D won't 18945 let me since glBufferData is a function pointer :( 18946 18947 Added: August 25, 2020 (version 8.5) 18948 +/ 18949 version(without_opengl) {} else 18950 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) { 18951 glBufferData(target, data.length, data.ptr, usage); 18952 } 18953 18954 /+ 18955 /++ 18956 A matrix for simple uses that easily integrates with [OpenGlShader]. 18957 18958 Might not be useful to you since it only as some simple functions and 18959 probably isn't that fast. 18960 18961 Note it uses an inline static array for its storage, so copying it 18962 may be expensive. 18963 +/ 18964 struct BasicMatrix(int columns, int rows, T = float) { 18965 import core.stdc.math; 18966 18967 T[columns * rows] data = 0.0; 18968 18969 /++ 18970 Basic operations that operate *in place*. 18971 +/ 18972 void translate() { 18973 18974 } 18975 18976 /// ditto 18977 void scale() { 18978 18979 } 18980 18981 /// ditto 18982 void rotate() { 18983 18984 } 18985 18986 /++ 18987 18988 +/ 18989 static if(columns == rows) 18990 static BasicMatrix identity() { 18991 BasicMatrix m; 18992 foreach(i; 0 .. columns) 18993 data[0 + i + i * columns] = 1.0; 18994 return m; 18995 } 18996 18997 static BasicMatrix ortho() { 18998 return BasicMatrix.init; 18999 } 19000 } 19001 +/ 19002 19003 /++ 19004 Convenience class for using opengl shaders. 19005 19006 Ensure that you've loaded opengl 3+ and set your active 19007 context before trying to use this. 19008 19009 Added: August 25, 2020 (version 8.5) 19010 +/ 19011 version(without_opengl) {} else 19012 final class OpenGlShader { 19013 private int shaderProgram_; 19014 private @property void shaderProgram(int a) { 19015 shaderProgram_ = a; 19016 } 19017 /// Get the program ID for use in OpenGL functions. 19018 public @property int shaderProgram() { 19019 return shaderProgram_; 19020 } 19021 19022 /++ 19023 19024 +/ 19025 static struct Source { 19026 uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc. 19027 string code; /// 19028 } 19029 19030 /++ 19031 Helper method to just compile some shader code and check for errors 19032 while you do glCreateShader, etc. on the outside yourself. 19033 19034 This just does `glShaderSource` and `glCompileShader` for the given code. 19035 19036 If you the OpenGlShader class constructor, you never need to call this yourself. 19037 +/ 19038 static void compile(int sid, Source code) { 19039 const(char)*[1] buffer; 19040 int[1] lengthBuffer; 19041 19042 buffer[0] = code.code.ptr; 19043 lengthBuffer[0] = cast(int) code.code.length; 19044 19045 glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr); 19046 glCompileShader(sid); 19047 19048 int success; 19049 glGetShaderiv(sid, GL_COMPILE_STATUS, &success); 19050 if(!success) { 19051 char[512] info; 19052 int len; 19053 glGetShaderInfoLog(sid, info.length, &len, info.ptr); 19054 19055 throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]); 19056 } 19057 } 19058 19059 /++ 19060 Calls `glLinkProgram` and throws if error a occurs. 19061 19062 If you the OpenGlShader class constructor, you never need to call this yourself. 19063 +/ 19064 static void link(int shaderProgram) { 19065 glLinkProgram(shaderProgram); 19066 int success; 19067 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 19068 if(!success) { 19069 char[512] info; 19070 int len; 19071 glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr); 19072 19073 throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]); 19074 } 19075 } 19076 19077 /++ 19078 Constructs the shader object by calling `glCreateProgram`, then 19079 compiling each given [Source], and finally, linking them together. 19080 19081 Throws: on compile or link failure. 19082 +/ 19083 this(Source[] codes...) { 19084 shaderProgram = glCreateProgram(); 19085 19086 int[16] shadersBufferStack; 19087 19088 int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 19089 shadersBufferStack[0 .. codes.length] : 19090 new int[](codes.length); 19091 19092 foreach(idx, code; codes) { 19093 shadersBuffer[idx] = glCreateShader(code.type); 19094 19095 compile(shadersBuffer[idx], code); 19096 19097 glAttachShader(shaderProgram, shadersBuffer[idx]); 19098 } 19099 19100 link(shaderProgram); 19101 19102 foreach(s; shadersBuffer) 19103 glDeleteShader(s); 19104 } 19105 19106 /// Calls `glUseProgram(this.shaderProgram)` 19107 void use() { 19108 glUseProgram(this.shaderProgram); 19109 } 19110 19111 /// Deletes the program. 19112 void delete_() { 19113 glDeleteProgram(shaderProgram); 19114 shaderProgram = 0; 19115 } 19116 19117 /++ 19118 [OpenGlShader.uniforms].name gives you one of these. 19119 19120 You can get the id out of it or just assign 19121 +/ 19122 static struct Uniform { 19123 /// the id passed to glUniform* 19124 int id; 19125 19126 /// Assigns the 4 floats. You will probably have to call this via the .opAssign name 19127 void opAssign(float x, float y, float z, float w) { 19128 if(id != -1) 19129 glUniform4f(id, x, y, z, w); 19130 } 19131 19132 void opAssign(float x) { 19133 if(id != -1) 19134 glUniform1f(id, x); 19135 } 19136 19137 void opAssign(float x, float y) { 19138 if(id != -1) 19139 glUniform2f(id, x, y); 19140 } 19141 19142 void opAssign(T)(T t) { 19143 t.glUniform(id); 19144 } 19145 } 19146 19147 static struct UniformsHelper { 19148 OpenGlShader _shader; 19149 19150 @property Uniform opDispatch(string name)() { 19151 auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); 19152 // FIXME: decide what to do here; the exception is liable to be swallowed by the event syste 19153 //if(i == -1) 19154 //throw new Exception("Could not find uniform " ~ name); 19155 return Uniform(i); 19156 } 19157 19158 @property void opDispatch(string name, T)(T t) { 19159 Uniform f = this.opDispatch!name; 19160 t.glUniform(f); 19161 } 19162 } 19163 19164 /++ 19165 Gives access to the uniforms through dot access. 19166 `OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo"); 19167 +/ 19168 @property UniformsHelper uniforms() { return UniformsHelper(this); } 19169 } 19170 19171 version(without_opengl) {} else { 19172 /++ 19173 A static container of experimental types and value constructors for opengl 3+ shaders. 19174 19175 19176 You can declare variables like: 19177 19178 ``` 19179 OGL.vec3f something; 19180 ``` 19181 19182 But generally it would be used with [OpenGlShader]'s uniform helpers like 19183 19184 ``` 19185 shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific 19186 ``` 19187 19188 This is still extremely experimental, not very useful at this point, and thus subject to change at random. 19189 19190 19191 History: 19192 Added December 7, 2021. Not yet stable. 19193 +/ 19194 final class OGL { 19195 static: 19196 19197 private template typeFromSpecifier(string specifier) { 19198 static if(specifier == "f") 19199 alias typeFromSpecifier = GLfloat; 19200 else static if(specifier == "i") 19201 alias typeFromSpecifier = GLint; 19202 else static if(specifier == "ui") 19203 alias typeFromSpecifier = GLuint; 19204 else static assert(0, "I don't know this ogl type suffix " ~ specifier); 19205 } 19206 19207 private template CommonType(T...) { 19208 static if(T.length == 1) 19209 alias CommonType = T[0]; 19210 else static if(is(typeof(true ? T[0].init : T[1].init) C)) 19211 alias CommonType = CommonType!(C, T[2 .. $]); 19212 } 19213 19214 private template typesToSpecifier(T...) { 19215 static if(is(CommonType!T == float)) 19216 enum typesToSpecifier = "f"; 19217 else static if(is(CommonType!T == int)) 19218 enum typesToSpecifier = "i"; 19219 else static if(is(CommonType!T == uint)) 19220 enum typesToSpecifier = "ui"; 19221 else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof); 19222 } 19223 19224 private template genNames(size_t dim, size_t dim2 = 0) { 19225 string helper() { 19226 string s; 19227 if(dim2) { 19228 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;"; 19229 } else { 19230 if(dim > 0) s ~= "type x = 0;"; 19231 if(dim > 1) s ~= "type y = 0;"; 19232 if(dim > 2) s ~= "type z = 0;"; 19233 if(dim > 3) s ~= "type w = 0;"; 19234 } 19235 return s; 19236 } 19237 19238 enum genNames = helper(); 19239 } 19240 19241 // there's vec, arrays of vec, mat, and arrays of mat 19242 template opDispatch(string name) 19243 if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat")) 19244 { 19245 static if(name[4] == 'x') { 19246 enum dimX = cast(int) (name[3] - '0'); 19247 static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]); 19248 19249 enum dimY = cast(int) (name[5] - '0'); 19250 static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]); 19251 19252 enum isArray = name[$ - 1] == 'v'; 19253 enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $]; 19254 alias type = typeFromSpecifier!typeSpecifier; 19255 } else { 19256 enum dim = cast(int) (name[3] - '0'); 19257 static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]); 19258 enum isArray = name[$ - 1] == 'v'; 19259 enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $]; 19260 alias type = typeFromSpecifier!typeSpecifier; 19261 } 19262 19263 align(1) 19264 struct opDispatch { 19265 align(1): 19266 static if(name[4] == 'x') 19267 mixin(genNames!(dimX, dimY)); 19268 else 19269 mixin(genNames!dim); 19270 19271 private void glUniform(OpenGlShader.Uniform assignTo) { 19272 glUniform(assignTo.id); 19273 } 19274 private void glUniform(int assignTo) { 19275 static if(name[4] == 'x') { 19276 // FIXME 19277 pragma(msg, "This matrix uniform helper has never been tested!!!!"); 19278 mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr); 19279 } else 19280 mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof); 19281 } 19282 } 19283 } 19284 19285 auto vec(T...)(T members) { 19286 return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members); 19287 } 19288 } 19289 } 19290 19291 version(linux) { 19292 version(with_eventloop) {} else { 19293 private int epollFd = -1; 19294 void prepareEventLoop() { 19295 if(epollFd != -1) 19296 return; // already initialized, no need to do it again 19297 import ep = core.sys.linux.epoll; 19298 19299 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 19300 if(epollFd == -1) 19301 throw new Exception("epoll create failure"); 19302 } 19303 } 19304 } else version(Posix) { 19305 void prepareEventLoop() {} 19306 } 19307 19308 version(X11) { 19309 import core.stdc.locale : LC_ALL; // rdmd fix 19310 __gshared bool sdx_isUTF8Locale; 19311 19312 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 19313 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 19314 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 19315 // anal magic is here. I (Ketmar) hope you like it. 19316 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 19317 // always return correct unicode symbols. The detection is here 'cause user can change locale 19318 // later. 19319 19320 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 19321 shared static this () { 19322 if(!librariesSuccessfullyLoaded) 19323 return; 19324 19325 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 19326 19327 // this doesn't hurt; it may add some locking, but the speed is still 19328 // allows doing 60 FPS videogames; also, ignore the result, as most 19329 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 19330 // never seen this failing). 19331 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 19332 19333 setlocale(LC_ALL, ""); 19334 // check if out locale is UTF-8 19335 auto lct = setlocale(LC_CTYPE, null); 19336 if (lct is null) { 19337 sdx_isUTF8Locale = false; 19338 } else { 19339 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 19340 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 19341 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 19342 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 19343 { 19344 sdx_isUTF8Locale = true; 19345 break; 19346 } 19347 } 19348 } 19349 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 19350 } 19351 } 19352 19353 class ExperimentalTextComponent2 { 19354 /+ 19355 Stage 1: get it working monospace 19356 Stage 2: use proportional font 19357 Stage 3: allow changes in inline style 19358 Stage 4: allow new fonts and sizes in the middle 19359 Stage 5: optimize gap buffer 19360 Stage 6: optimize layout 19361 Stage 7: word wrap 19362 Stage 8: justification 19363 Stage 9: editing, selection, etc. 19364 19365 Operations: 19366 insert text 19367 overstrike text 19368 select 19369 cut 19370 modify 19371 +/ 19372 19373 /++ 19374 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. 19375 +/ 19376 this(SimpleWindow window) { 19377 this.window = window; 19378 } 19379 19380 private SimpleWindow window; 19381 19382 19383 /++ 19384 When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces 19385 representing the internal parts. The first pass is focused on the x parameter, then the 19386 renderer is responsible for going back to the parts in the current line and calling 19387 adjustDownForAscent to change the y params. 19388 +/ 19389 static interface ComponentRenderHelper { 19390 19391 /+ 19392 When you do an edit, possibly stuff on the same line previously need to move (to adjust 19393 the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs 19394 to move (adjust y to make room for new line) until you get back to the same position, 19395 then you can stop - if one thing is unchanged, nothing after it is changed too. 19396 19397 Word wrap might change this as if can rewrap tons of stuff, but the same idea applies, 19398 once you reach something that is unchanged, you can stop. 19399 +/ 19400 19401 void adjustDownForAscent(int amount); // at the end of the line it needs to do these 19402 19403 int ascent() const; 19404 int descent() const; 19405 19406 int advance() const; 19407 19408 bool endsWithExplititLineBreak() const; 19409 } 19410 19411 static interface RenderResult { 19412 /++ 19413 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. 19414 +/ 19415 void popFront(); 19416 @property bool empty() const; 19417 @property ComponentRenderHelper front() const; 19418 19419 void repositionForNextLine(Point baseline, int availableWidth); 19420 } 19421 19422 static interface ComponentInFlow { 19423 void draw(ScreenPainter painter); 19424 //RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different" 19425 19426 bool startsWithExplicitLineBreak() const; 19427 } 19428 19429 static class TextFlowComponent : ComponentInFlow { 19430 bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true 19431 19432 Color foreground; 19433 Color background; 19434 19435 OperatingSystemFont font; // should NEVER be null 19436 19437 ubyte attributes; // underline, strike through, display on new block 19438 19439 version(Windows) 19440 const(wchar)[] content; 19441 else 19442 const(char)[] content; // this should NEVER have a newline, except at the end 19443 19444 RenderedComponent[] rendered; // entirely controlled by [rerender] 19445 19446 // could prolly put some spacing around it too like margin / padding 19447 19448 this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) 19449 in { assert(font !is null); 19450 assert(!font.isNull); } 19451 do 19452 { 19453 this.foreground = f; 19454 this.background = b; 19455 this.font = font; 19456 19457 this.attributes = attr; 19458 version(Windows) { 19459 auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines; 19460 auto sz = sizeOfConvertedWstring(c, conversionFlags); 19461 auto buffer = new wchar[](sz); 19462 this.content = makeWindowsString(c, buffer, conversionFlags); 19463 } else { 19464 this.content = c.dup; 19465 } 19466 } 19467 19468 void draw(ScreenPainter painter) { 19469 painter.setFont(this.font); 19470 painter.outlineColor = this.foreground; 19471 painter.fillColor = Color.transparent; 19472 foreach(rendered; this.rendered) { 19473 // the component works in term of baseline, 19474 // but the painter works in term of upper left bounding box 19475 // so need to translate that 19476 19477 if(this.background.a) { 19478 painter.fillColor = this.background; 19479 painter.outlineColor = this.background; 19480 19481 painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height)); 19482 19483 painter.outlineColor = this.foreground; 19484 painter.fillColor = Color.transparent; 19485 } 19486 19487 painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice); 19488 19489 // FIXME: strike through, underline, highlight selection, etc. 19490 } 19491 } 19492 } 19493 19494 // I could split the parts into words on render 19495 // for easier word-wrap, each one being an unbreakable "inline-block" 19496 private TextFlowComponent[] parts; 19497 private int needsRerenderFrom; 19498 19499 void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) { 19500 // FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop. 19501 parts ~= new TextFlowComponent(f, b, font, attr, c); 19502 } 19503 19504 static struct RenderedComponent { 19505 int startX; 19506 int startY; 19507 short width; 19508 // 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! 19509 // for individual chars in here you've gotta process on demand 19510 version(Windows) 19511 const(wchar)[] slice; 19512 else 19513 const(char)[] slice; 19514 } 19515 19516 19517 void rerender(Rectangle boundingBox) { 19518 Point baseline = boundingBox.upperLeft; 19519 19520 this.boundingBox.left = boundingBox.left; 19521 this.boundingBox.top = boundingBox.top; 19522 19523 auto remainingParts = parts; 19524 19525 int largestX; 19526 19527 19528 foreach(part; parts) 19529 part.font.prepareContext(window); 19530 scope(exit) 19531 foreach(part; parts) 19532 part.font.releaseContext(); 19533 19534 calculateNextLine: 19535 19536 int nextLineHeight = 0; 19537 int nextBiggestDescent = 0; 19538 19539 foreach(part; remainingParts) { 19540 auto height = part.font.ascent; 19541 if(height > nextLineHeight) 19542 nextLineHeight = height; 19543 if(part.font.descent > nextBiggestDescent) 19544 nextBiggestDescent = part.font.descent; 19545 if(part.content.length && part.content[$-1] == '\n') 19546 break; 19547 } 19548 19549 baseline.y += nextLineHeight; 19550 auto lineStart = baseline; 19551 19552 while(remainingParts.length) { 19553 remainingParts[0].rendered = null; 19554 19555 bool eol; 19556 if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n') 19557 eol = true; 19558 19559 // FIXME: word wrap 19560 auto font = remainingParts[0].font; 19561 auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)]; 19562 auto width = font.stringWidth(slice, window); 19563 remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice); 19564 19565 remainingParts = remainingParts[1 .. $]; 19566 baseline.x += width; 19567 19568 if(eol) { 19569 baseline.y += nextBiggestDescent; 19570 if(baseline.x > largestX) 19571 largestX = baseline.x; 19572 baseline.x = lineStart.x; 19573 goto calculateNextLine; 19574 } 19575 } 19576 19577 if(baseline.x > largestX) 19578 largestX = baseline.x; 19579 19580 this.boundingBox.right = largestX; 19581 this.boundingBox.bottom = baseline.y; 19582 } 19583 19584 // you must call rerender first! 19585 void draw(ScreenPainter painter) { 19586 foreach(part; parts) { 19587 part.draw(painter); 19588 } 19589 } 19590 19591 struct IdentifyResult { 19592 TextFlowComponent part; 19593 int charIndexInPart; 19594 int totalCharIndex = -1; // if this is -1, it just means the end 19595 19596 Rectangle boundingBox; 19597 } 19598 19599 IdentifyResult identify(Point pt, bool exact = false) { 19600 if(parts.length == 0) 19601 return IdentifyResult(null, 0); 19602 19603 if(pt.y < boundingBox.top) { 19604 if(exact) 19605 return IdentifyResult(null, 1); 19606 return IdentifyResult(parts[0], 0); 19607 } 19608 if(pt.y > boundingBox.bottom) { 19609 if(exact) 19610 return IdentifyResult(null, 2); 19611 return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length); 19612 } 19613 19614 int tci = 0; 19615 19616 // I should probably like binary search this or something... 19617 foreach(ref part; parts) { 19618 foreach(rendered; part.rendered) { 19619 auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent); 19620 if(rect.contains(pt)) { 19621 auto x = pt.x - rendered.startX; 19622 auto estimatedIdx = x / part.font.averageWidth; 19623 19624 if(estimatedIdx < 0) 19625 estimatedIdx = 0; 19626 19627 if(estimatedIdx > rendered.slice.length) 19628 estimatedIdx = cast(int) rendered.slice.length; 19629 19630 int idx; 19631 int x1, x2; 19632 if(part.font.isMonospace) { 19633 auto w = part.font.averageWidth; 19634 if(!exact && x > (estimatedIdx + 1) * w) 19635 return IdentifyResult(null, 4); 19636 idx = estimatedIdx; 19637 x1 = idx * w; 19638 x2 = (idx + 1) * w; 19639 } else { 19640 idx = estimatedIdx; 19641 19642 part.font.prepareContext(window); 19643 scope(exit) part.font.releaseContext(); 19644 19645 // int iterations; 19646 19647 while(true) { 19648 // iterations++; 19649 x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0; 19650 x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies. 19651 19652 x1 += rendered.startX; 19653 x2 += rendered.startX; 19654 19655 if(pt.x < x1) { 19656 if(idx == 0) { 19657 if(exact) 19658 return IdentifyResult(null, 6); 19659 else 19660 break; 19661 } 19662 idx--; 19663 } else if(pt.x > x2) { 19664 idx++; 19665 if(idx > rendered.slice.length) { 19666 if(exact) 19667 return IdentifyResult(null, 5); 19668 else 19669 break; 19670 } 19671 } else if(pt.x >= x1 && pt.x <= x2) { 19672 if(idx) 19673 idx--; // point it at the original index 19674 break; // we fit 19675 } 19676 } 19677 19678 // writeln(iterations) 19679 } 19680 19681 19682 return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8? 19683 } 19684 } 19685 tci += cast(int) part.content.length; // FIXME: utf-8? 19686 } 19687 return IdentifyResult(null, 3); 19688 } 19689 19690 Rectangle boundingBox; // only set after [rerender] 19691 19692 // text will be positioned around the exclusion zone 19693 static struct ExclusionZone { 19694 19695 } 19696 19697 ExclusionZone[] exclusionZones; 19698 } 19699 19700 19701 // Don't use this yet. When I'm happy with it, I will move it to the 19702 // regular module namespace. 19703 mixin template ExperimentalTextComponent() { 19704 19705 static: 19706 19707 alias Rectangle = arsd.color.Rectangle; 19708 19709 struct ForegroundColor { 19710 Color color; 19711 alias color this; 19712 19713 this(Color c) { 19714 color = c; 19715 } 19716 19717 this(int r, int g, int b, int a = 255) { 19718 color = Color(r, g, b, a); 19719 } 19720 19721 static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) { 19722 return ForegroundColor(mixin("Color." ~ s)); 19723 } 19724 } 19725 19726 struct BackgroundColor { 19727 Color color; 19728 alias color this; 19729 19730 this(Color c) { 19731 color = c; 19732 } 19733 19734 this(int r, int g, int b, int a = 255) { 19735 color = Color(r, g, b, a); 19736 } 19737 19738 static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) { 19739 return BackgroundColor(mixin("Color." ~ s)); 19740 } 19741 } 19742 19743 static class InlineElement { 19744 string text; 19745 19746 BlockElement containingBlock; 19747 19748 Color color = Color.black; 19749 Color backgroundColor = Color.transparent; 19750 ushort styles; 19751 19752 string font; 19753 int fontSize; 19754 19755 int lineHeight; 19756 19757 void* identifier; 19758 19759 Rectangle boundingBox; 19760 int[] letterXs; // FIXME: maybe i should do bounding boxes for every character 19761 19762 bool isMergeCompatible(InlineElement other) { 19763 return 19764 containingBlock is other.containingBlock && 19765 color == other.color && 19766 backgroundColor == other.backgroundColor && 19767 styles == other.styles && 19768 font == other.font && 19769 fontSize == other.fontSize && 19770 lineHeight == other.lineHeight && 19771 true; 19772 } 19773 19774 int xOfIndex(size_t index) { 19775 if(index < letterXs.length) 19776 return letterXs[index]; 19777 else 19778 return boundingBox.right; 19779 } 19780 19781 InlineElement clone() { 19782 auto ie = new InlineElement(); 19783 ie.tupleof = this.tupleof; 19784 return ie; 19785 } 19786 19787 InlineElement getPreviousInlineElement() { 19788 InlineElement prev = null; 19789 foreach(ie; this.containingBlock.parts) { 19790 if(ie is this) 19791 break; 19792 prev = ie; 19793 } 19794 if(prev is null) { 19795 BlockElement pb; 19796 BlockElement cb = this.containingBlock; 19797 moar: 19798 foreach(ie; this.containingBlock.containingLayout.blocks) { 19799 if(ie is cb) 19800 break; 19801 pb = ie; 19802 } 19803 if(pb is null) 19804 return null; 19805 if(pb.parts.length == 0) { 19806 cb = pb; 19807 goto moar; 19808 } 19809 19810 prev = pb.parts[$-1]; 19811 19812 } 19813 return prev; 19814 } 19815 19816 InlineElement getNextInlineElement() { 19817 InlineElement next = null; 19818 foreach(idx, ie; this.containingBlock.parts) { 19819 if(ie is this) { 19820 if(idx + 1 < this.containingBlock.parts.length) 19821 next = this.containingBlock.parts[idx + 1]; 19822 break; 19823 } 19824 } 19825 if(next is null) { 19826 BlockElement n; 19827 foreach(idx, ie; this.containingBlock.containingLayout.blocks) { 19828 if(ie is this.containingBlock) { 19829 if(idx + 1 < this.containingBlock.containingLayout.blocks.length) 19830 n = this.containingBlock.containingLayout.blocks[idx + 1]; 19831 break; 19832 } 19833 } 19834 if(n is null) 19835 return null; 19836 19837 if(n.parts.length) 19838 next = n.parts[0]; 19839 else {} // FIXME 19840 19841 } 19842 return next; 19843 } 19844 19845 } 19846 19847 // Block elements are used entirely for positioning inline elements, 19848 // which are the things that are actually drawn. 19849 class BlockElement { 19850 InlineElement[] parts; 19851 uint alignment; 19852 19853 int whiteSpace; // pre, pre-wrap, wrap 19854 19855 TextLayout containingLayout; 19856 19857 // inputs 19858 Point where; 19859 Size minimumSize; 19860 Size maximumSize; 19861 Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box. 19862 void* identifier; 19863 19864 Rectangle margin; 19865 Rectangle padding; 19866 19867 // outputs 19868 Rectangle[] boundingBoxes; 19869 } 19870 19871 struct TextIdentifyResult { 19872 InlineElement element; 19873 int offset; 19874 19875 private TextIdentifyResult fixupNewline() { 19876 if(element !is null && offset < element.text.length && element.text[offset] == '\n') { 19877 offset--; 19878 } else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') { 19879 offset--; 19880 } 19881 return this; 19882 } 19883 } 19884 19885 class TextLayout { 19886 BlockElement[] blocks; 19887 Rectangle boundingBox_; 19888 Rectangle boundingBox() { return boundingBox_; } 19889 void boundingBox(Rectangle r) { 19890 if(r != boundingBox_) { 19891 boundingBox_ = r; 19892 layoutInvalidated = true; 19893 } 19894 } 19895 19896 Rectangle contentBoundingBox() { 19897 Rectangle r; 19898 foreach(block; blocks) 19899 foreach(ie; block.parts) { 19900 if(ie.boundingBox.right > r.right) 19901 r.right = ie.boundingBox.right; 19902 if(ie.boundingBox.bottom > r.bottom) 19903 r.bottom = ie.boundingBox.bottom; 19904 } 19905 return r; 19906 } 19907 19908 BlockElement[] getBlocks() { 19909 return blocks; 19910 } 19911 19912 InlineElement[] getTexts() { 19913 InlineElement[] elements; 19914 foreach(block; blocks) 19915 elements ~= block.parts; 19916 return elements; 19917 } 19918 19919 string getPlainText() { 19920 string text; 19921 foreach(block; blocks) 19922 foreach(part; block.parts) 19923 text ~= part.text; 19924 return text; 19925 } 19926 19927 string getHtml() { 19928 return null; // FIXME 19929 } 19930 19931 this(Rectangle boundingBox) { 19932 this.boundingBox = boundingBox; 19933 } 19934 19935 BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) { 19936 auto be = new BlockElement(); 19937 be.containingLayout = this; 19938 if(after is null) 19939 blocks ~= be; 19940 else { 19941 foreach(idx, b; blocks) { 19942 if(b is after.containingBlock) { 19943 blocks = blocks[0 .. idx + 1] ~ be ~ blocks[idx + 1 .. $]; 19944 break; 19945 } 19946 } 19947 } 19948 return be; 19949 } 19950 19951 void clear() { 19952 blocks = null; 19953 selectionStart = selectionEnd = caret = Caret.init; 19954 } 19955 19956 void addText(Args...)(Args args) { 19957 if(blocks.length == 0) 19958 addBlock(); 19959 19960 InlineElement ie = new InlineElement(); 19961 foreach(idx, arg; args) { 19962 static if(is(typeof(arg) == ForegroundColor)) 19963 ie.color = arg; 19964 else static if(is(typeof(arg) == TextFormat)) { 19965 if(arg & 0x8000) // ~TextFormat.something turns it off 19966 ie.styles &= arg; 19967 else 19968 ie.styles |= arg; 19969 } else static if(is(typeof(arg) == string)) { 19970 static if(idx == 0 && args.length > 1) 19971 static assert(0, "Put styles before the string."); 19972 size_t lastLineIndex; 19973 foreach(cidx, char a; arg) { 19974 if(a == '\n') { 19975 ie.text = arg[lastLineIndex .. cidx + 1]; 19976 lastLineIndex = cidx + 1; 19977 ie.containingBlock = blocks[$-1]; 19978 blocks[$-1].parts ~= ie.clone; 19979 ie.text = null; 19980 } else { 19981 19982 } 19983 } 19984 19985 ie.text = arg[lastLineIndex .. $]; 19986 ie.containingBlock = blocks[$-1]; 19987 blocks[$-1].parts ~= ie.clone; 19988 caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length); 19989 } 19990 } 19991 19992 invalidateLayout(); 19993 } 19994 19995 void tryMerge(InlineElement into, InlineElement what) { 19996 if(!into.isMergeCompatible(what)) { 19997 return; // cannot merge, different configs 19998 } 19999 20000 // cool, can merge, bring text together... 20001 into.text ~= what.text; 20002 20003 // and remove what 20004 for(size_t a = 0; a < what.containingBlock.parts.length; a++) { 20005 if(what.containingBlock.parts[a] is what) { 20006 for(size_t i = a; i < what.containingBlock.parts.length - 1; i++) 20007 what.containingBlock.parts[i] = what.containingBlock.parts[i + 1]; 20008 what.containingBlock.parts = what.containingBlock.parts[0 .. $-1]; 20009 20010 } 20011 } 20012 20013 // FIXME: ensure no other carets have a reference to it 20014 } 20015 20016 /// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click. 20017 TextIdentifyResult identify(int x, int y, bool exact = false) { 20018 TextIdentifyResult inexactMatch; 20019 foreach(block; blocks) { 20020 foreach(part; block.parts) { 20021 if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { 20022 20023 // FIXME binary search 20024 int tidx; 20025 int lastX; 20026 foreach_reverse(idxo, lx; part.letterXs) { 20027 int idx = cast(int) idxo; 20028 if(lx <= x) { 20029 if(lastX && lastX - x < x - lx) 20030 tidx = idx + 1; 20031 else 20032 tidx = idx; 20033 break; 20034 } 20035 lastX = lx; 20036 } 20037 20038 return TextIdentifyResult(part, tidx).fixupNewline; 20039 } else if(!exact) { 20040 // we're not in the box, but are we on the same line? 20041 if(y >= part.boundingBox.top && y < part.boundingBox.bottom) 20042 inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length); 20043 } 20044 } 20045 } 20046 20047 if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) 20048 return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline; 20049 20050 return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; 20051 } 20052 20053 void moveCaretToPixelCoordinates(int x, int y) { 20054 auto result = identify(x, y); 20055 caret.inlineElement = result.element; 20056 caret.offset = result.offset; 20057 } 20058 20059 void selectToPixelCoordinates(int x, int y) { 20060 auto result = identify(x, y); 20061 20062 if(y < caretLastDrawnY1) { 20063 // on a previous line, carat is selectionEnd 20064 selectionEnd = caret; 20065 20066 selectionStart = Caret(this, result.element, result.offset); 20067 } else if(y > caretLastDrawnY2) { 20068 // on a later line 20069 selectionStart = caret; 20070 20071 selectionEnd = Caret(this, result.element, result.offset); 20072 } else { 20073 // on the same line... 20074 if(x <= caretLastDrawnX) { 20075 selectionEnd = caret; 20076 selectionStart = Caret(this, result.element, result.offset); 20077 } else { 20078 selectionStart = caret; 20079 selectionEnd = Caret(this, result.element, result.offset); 20080 } 20081 20082 } 20083 } 20084 20085 20086 /// Call this if the inputs change. It will reflow everything 20087 void redoLayout(ScreenPainter painter) { 20088 //painter.setClipRectangle(boundingBox); 20089 auto pos = Point(boundingBox.left, boundingBox.top); 20090 20091 int lastHeight; 20092 void nl() { 20093 pos.x = boundingBox.left; 20094 pos.y += lastHeight; 20095 } 20096 foreach(block; blocks) { 20097 nl(); 20098 foreach(part; block.parts) { 20099 part.letterXs = null; 20100 20101 auto size = painter.textSize(part.text); 20102 version(Windows) 20103 if(part.text.length && part.text[$-1] == '\n') 20104 size.height /= 2; // windows counts the new line at the end, but we don't want that 20105 20106 part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height); 20107 20108 foreach(idx, char c; part.text) { 20109 // FIXME: unicode 20110 part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x; 20111 } 20112 20113 pos.x += size.width; 20114 if(pos.x >= boundingBox.right) { 20115 pos.y += size.height; 20116 pos.x = boundingBox.left; 20117 lastHeight = 0; 20118 } else { 20119 lastHeight = size.height; 20120 } 20121 20122 if(part.text.length && part.text[$-1] == '\n') 20123 nl(); 20124 } 20125 } 20126 20127 layoutInvalidated = false; 20128 } 20129 20130 bool layoutInvalidated = true; 20131 void invalidateLayout() { 20132 layoutInvalidated = true; 20133 } 20134 20135 // FIXME: caret can remain sometimes when inserting 20136 // FIXME: inserting at the beginning once you already have something can eff it up. 20137 void drawInto(ScreenPainter painter, bool focused = false) { 20138 if(layoutInvalidated) 20139 redoLayout(painter); 20140 foreach(block; blocks) { 20141 foreach(part; block.parts) { 20142 painter.outlineColor = part.color; 20143 painter.fillColor = part.backgroundColor; 20144 20145 auto pos = part.boundingBox.upperLeft; 20146 auto size = part.boundingBox.size; 20147 20148 painter.drawText(pos, part.text); 20149 if(part.styles & TextFormat.underline) 20150 painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4)); 20151 if(part.styles & TextFormat.strikethrough) 20152 painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2)); 20153 } 20154 } 20155 20156 // on every redraw, I will force the caret to be 20157 // redrawn too, in order to eliminate perceived lag 20158 // when moving around with the mouse. 20159 eraseCaret(painter); 20160 20161 if(focused) { 20162 highlightSelection(painter); 20163 drawCaret(painter); 20164 } 20165 } 20166 20167 Color selectionXorColor = Color(255, 255, 127); 20168 20169 void highlightSelection(ScreenPainter painter) { 20170 if(selectionStart is selectionEnd) 20171 return; // no selection 20172 20173 if(selectionStart.inlineElement is null) return; 20174 if(selectionEnd.inlineElement is null) return; 20175 20176 assert(selectionStart.inlineElement !is null); 20177 assert(selectionEnd.inlineElement !is null); 20178 20179 painter.rasterOp = RasterOp.xor; 20180 painter.outlineColor = Color.transparent; 20181 painter.fillColor = selectionXorColor; 20182 20183 auto at = selectionStart.inlineElement; 20184 auto atOffset = selectionStart.offset; 20185 bool done; 20186 while(at) { 20187 auto box = at.boundingBox; 20188 if(atOffset < at.letterXs.length) 20189 box.left = at.letterXs[atOffset]; 20190 20191 if(at is selectionEnd.inlineElement) { 20192 if(selectionEnd.offset < at.letterXs.length) 20193 box.right = at.letterXs[selectionEnd.offset]; 20194 done = true; 20195 } 20196 20197 painter.drawRectangle(box.upperLeft, box.width, box.height); 20198 20199 if(done) 20200 break; 20201 20202 at = at.getNextInlineElement(); 20203 atOffset = 0; 20204 } 20205 } 20206 20207 int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2; 20208 bool caretShowingOnScreen = false; 20209 void drawCaret(ScreenPainter painter) { 20210 //painter.setClipRectangle(boundingBox); 20211 int x, y1, y2; 20212 if(caret.inlineElement is null) { 20213 x = boundingBox.left; 20214 y1 = boundingBox.top + 2; 20215 y2 = boundingBox.top + painter.fontHeight; 20216 } else { 20217 x = caret.inlineElement.xOfIndex(caret.offset); 20218 y1 = caret.inlineElement.boundingBox.top + 2; 20219 y2 = caret.inlineElement.boundingBox.bottom - 2; 20220 } 20221 20222 if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2)) 20223 eraseCaret(painter); 20224 20225 painter.pen = Pen(Color.white, 1); 20226 painter.rasterOp = RasterOp.xor; 20227 painter.drawLine( 20228 Point(x, y1), 20229 Point(x, y2) 20230 ); 20231 painter.rasterOp = RasterOp.normal; 20232 caretShowingOnScreen = !caretShowingOnScreen; 20233 20234 if(caretShowingOnScreen) { 20235 caretLastDrawnX = x; 20236 caretLastDrawnY1 = y1; 20237 caretLastDrawnY2 = y2; 20238 } 20239 } 20240 20241 Rectangle caretBoundingBox() { 20242 int x, y1, y2; 20243 if(caret.inlineElement is null) { 20244 x = boundingBox.left; 20245 y1 = boundingBox.top + 2; 20246 y2 = boundingBox.top + 16; 20247 } else { 20248 x = caret.inlineElement.xOfIndex(caret.offset); 20249 y1 = caret.inlineElement.boundingBox.top + 2; 20250 y2 = caret.inlineElement.boundingBox.bottom - 2; 20251 } 20252 20253 return Rectangle(x, y1, x + 1, y2); 20254 } 20255 20256 void eraseCaret(ScreenPainter painter) { 20257 //painter.setClipRectangle(boundingBox); 20258 if(!caretShowingOnScreen) return; 20259 painter.pen = Pen(Color.white, 1); 20260 painter.rasterOp = RasterOp.xor; 20261 painter.drawLine( 20262 Point(caretLastDrawnX, caretLastDrawnY1), 20263 Point(caretLastDrawnX, caretLastDrawnY2) 20264 ); 20265 20266 caretShowingOnScreen = false; 20267 painter.rasterOp = RasterOp.normal; 20268 } 20269 20270 /// Caret movement api 20271 /// These should give the user a logical result based on what they see on screen... 20272 /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) 20273 void moveUp() { 20274 if(caret.inlineElement is null) return; 20275 auto x = caret.inlineElement.xOfIndex(caret.offset); 20276 auto y = caret.inlineElement.boundingBox.top + 2; 20277 20278 y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 20279 if(y < 0) 20280 return; 20281 20282 auto i = identify(x, y); 20283 20284 if(i.element) { 20285 caret.inlineElement = i.element; 20286 caret.offset = i.offset; 20287 } 20288 } 20289 void moveDown() { 20290 if(caret.inlineElement is null) return; 20291 auto x = caret.inlineElement.xOfIndex(caret.offset); 20292 auto y = caret.inlineElement.boundingBox.bottom - 2; 20293 20294 y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 20295 20296 auto i = identify(x, y); 20297 if(i.element) { 20298 caret.inlineElement = i.element; 20299 caret.offset = i.offset; 20300 } 20301 } 20302 void moveLeft() { 20303 if(caret.inlineElement is null) return; 20304 if(caret.offset) 20305 caret.offset--; 20306 else { 20307 auto p = caret.inlineElement.getPreviousInlineElement(); 20308 if(p) { 20309 caret.inlineElement = p; 20310 if(p.text.length && p.text[$-1] == '\n') 20311 caret.offset = cast(int) p.text.length - 1; 20312 else 20313 caret.offset = cast(int) p.text.length; 20314 } 20315 } 20316 } 20317 void moveRight() { 20318 if(caret.inlineElement is null) return; 20319 if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { 20320 caret.offset++; 20321 } else { 20322 auto p = caret.inlineElement.getNextInlineElement(); 20323 if(p) { 20324 caret.inlineElement = p; 20325 caret.offset = 0; 20326 } 20327 } 20328 } 20329 void moveHome() { 20330 if(caret.inlineElement is null) return; 20331 auto x = 0; 20332 auto y = caret.inlineElement.boundingBox.top + 2; 20333 20334 auto i = identify(x, y); 20335 20336 if(i.element) { 20337 caret.inlineElement = i.element; 20338 caret.offset = i.offset; 20339 } 20340 } 20341 void moveEnd() { 20342 if(caret.inlineElement is null) return; 20343 auto x = int.max; 20344 auto y = caret.inlineElement.boundingBox.top + 2; 20345 20346 auto i = identify(x, y); 20347 20348 if(i.element) { 20349 caret.inlineElement = i.element; 20350 caret.offset = i.offset; 20351 } 20352 20353 } 20354 void movePageUp(ref Caret caret) {} 20355 void movePageDown(ref Caret caret) {} 20356 20357 void moveDocumentStart(ref Caret caret) { 20358 if(blocks.length && blocks[0].parts.length) 20359 caret = Caret(this, blocks[0].parts[0], 0); 20360 else 20361 caret = Caret.init; 20362 } 20363 20364 void moveDocumentEnd(ref Caret caret) { 20365 if(blocks.length) { 20366 auto parts = blocks[$-1].parts; 20367 if(parts.length) { 20368 caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length); 20369 } else { 20370 caret = Caret.init; 20371 } 20372 } else 20373 caret = Caret.init; 20374 } 20375 20376 void deleteSelection() { 20377 if(selectionStart is selectionEnd) 20378 return; 20379 20380 if(selectionStart.inlineElement is null) return; 20381 if(selectionEnd.inlineElement is null) return; 20382 20383 assert(selectionStart.inlineElement !is null); 20384 assert(selectionEnd.inlineElement !is null); 20385 20386 auto at = selectionStart.inlineElement; 20387 20388 if(selectionEnd.inlineElement is at) { 20389 // same element, need to chop out 20390 at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $]; 20391 at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $]; 20392 selectionEnd.offset -= selectionEnd.offset - selectionStart.offset; 20393 } else { 20394 // different elements, we can do it with slicing 20395 at.text = at.text[0 .. selectionStart.offset]; 20396 if(selectionStart.offset < at.letterXs.length) 20397 at.letterXs = at.letterXs[0 .. selectionStart.offset]; 20398 20399 at = at.getNextInlineElement(); 20400 20401 while(at) { 20402 if(at is selectionEnd.inlineElement) { 20403 at.text = at.text[selectionEnd.offset .. $]; 20404 if(selectionEnd.offset < at.letterXs.length) 20405 at.letterXs = at.letterXs[selectionEnd.offset .. $]; 20406 selectionEnd.offset = 0; 20407 break; 20408 } else { 20409 auto cfd = at; 20410 cfd.text = null; // delete the whole thing 20411 20412 at = at.getNextInlineElement(); 20413 20414 if(cfd.text.length == 0) { 20415 // and remove cfd 20416 for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) { 20417 if(cfd.containingBlock.parts[a] is cfd) { 20418 for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++) 20419 cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1]; 20420 cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1]; 20421 20422 } 20423 } 20424 } 20425 } 20426 } 20427 } 20428 20429 caret = selectionEnd; 20430 selectNone(); 20431 20432 invalidateLayout(); 20433 20434 } 20435 20436 /// Plain text editing api. These work at the current caret inside the selected inline element. 20437 void insert(in char[] text) { 20438 foreach(dchar ch; text) 20439 insert(ch); 20440 } 20441 /// ditto 20442 void insert(dchar ch) { 20443 20444 bool selectionDeleted = false; 20445 if(selectionStart !is selectionEnd) { 20446 deleteSelection(); 20447 selectionDeleted = true; 20448 } 20449 20450 if(ch == 127) { 20451 delete_(); 20452 return; 20453 } 20454 if(ch == 8) { 20455 if(!selectionDeleted) 20456 backspace(); 20457 return; 20458 } 20459 20460 invalidateLayout(); 20461 20462 if(ch == 13) ch = 10; 20463 auto e = caret.inlineElement; 20464 if(e is null) { 20465 addText("" ~ cast(char) ch) ; // FIXME 20466 return; 20467 } 20468 20469 if(caret.offset == e.text.length) { 20470 e.text ~= cast(char) ch; // FIXME 20471 caret.offset++; 20472 if(ch == 10) { 20473 auto c = caret.inlineElement.clone; 20474 c.text = null; 20475 c.letterXs = null; 20476 insertPartAfter(c,e); 20477 caret = Caret(this, c, 0); 20478 } 20479 } else { 20480 // FIXME cast char sucks 20481 if(ch == 10) { 20482 auto c = caret.inlineElement.clone; 20483 c.text = e.text[caret.offset .. $]; 20484 if(caret.offset < c.letterXs.length) 20485 c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox 20486 e.text = e.text[0 .. caret.offset] ~ cast(char) ch; 20487 if(caret.offset <= e.letterXs.length) { 20488 e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box 20489 } 20490 insertPartAfter(c,e); 20491 caret = Caret(this, c, 0); 20492 } else { 20493 e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $]; 20494 caret.offset++; 20495 } 20496 } 20497 } 20498 20499 void insertPartAfter(InlineElement what, InlineElement where) { 20500 foreach(idx, p; where.containingBlock.parts) { 20501 if(p is where) { 20502 if(idx + 1 == where.containingBlock.parts.length) 20503 where.containingBlock.parts ~= what; 20504 else 20505 where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $]; 20506 return; 20507 } 20508 } 20509 } 20510 20511 void cleanupStructures() { 20512 for(size_t i = 0; i < blocks.length; i++) { 20513 auto block = blocks[i]; 20514 for(size_t a = 0; a < block.parts.length; a++) { 20515 auto part = block.parts[a]; 20516 if(part.text.length == 0) { 20517 for(size_t b = a; b < block.parts.length - 1; b++) 20518 block.parts[b] = block.parts[b+1]; 20519 block.parts = block.parts[0 .. $-1]; 20520 } 20521 } 20522 if(block.parts.length == 0) { 20523 for(size_t a = i; a < blocks.length - 1; a++) 20524 blocks[a] = blocks[a+1]; 20525 blocks = blocks[0 .. $-1]; 20526 } 20527 } 20528 } 20529 20530 void backspace() { 20531 try_again: 20532 auto e = caret.inlineElement; 20533 if(e is null) 20534 return; 20535 if(caret.offset == 0) { 20536 auto prev = e.getPreviousInlineElement(); 20537 if(prev is null) 20538 return; 20539 auto newOffset = cast(int) prev.text.length; 20540 tryMerge(prev, e); 20541 caret.inlineElement = prev; 20542 caret.offset = prev is null ? 0 : newOffset; 20543 20544 goto try_again; 20545 } else if(caret.offset == e.text.length) { 20546 e.text = e.text[0 .. $-1]; 20547 caret.offset--; 20548 } else { 20549 e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $]; 20550 caret.offset--; 20551 } 20552 //cleanupStructures(); 20553 20554 invalidateLayout(); 20555 } 20556 void delete_() { 20557 if(selectionStart !is selectionEnd) 20558 deleteSelection(); 20559 else { 20560 auto before = caret; 20561 moveRight(); 20562 if(caret != before) { 20563 backspace(); 20564 } 20565 } 20566 20567 invalidateLayout(); 20568 } 20569 void overstrike() {} 20570 20571 /// Selection API. See also: caret movement. 20572 void selectAll() { 20573 moveDocumentStart(selectionStart); 20574 moveDocumentEnd(selectionEnd); 20575 } 20576 bool selectNone() { 20577 if(selectionStart != selectionEnd) { 20578 selectionStart = selectionEnd = Caret.init; 20579 return true; 20580 } 20581 return false; 20582 } 20583 20584 /// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements. 20585 /// They will modify the current selection if there is one and will splice one in if needed. 20586 void changeAttributes() {} 20587 20588 20589 /// Text search api. They manipulate the selection and/or caret. 20590 void findText(string text) {} 20591 void findIndex(size_t textIndex) {} 20592 20593 // sample event handlers 20594 20595 void handleEvent(KeyEvent event) { 20596 //if(event.type == KeyEvent.Type.KeyPressed) { 20597 20598 //} 20599 } 20600 20601 void handleEvent(dchar ch) { 20602 20603 } 20604 20605 void handleEvent(MouseEvent event) { 20606 20607 } 20608 20609 bool contentEditable; // can it be edited? 20610 bool contentCaretable; // is there a caret/cursor that moves around in there? 20611 bool contentSelectable; // selectable? 20612 20613 Caret caret; 20614 Caret selectionStart; 20615 Caret selectionEnd; 20616 20617 bool insertMode; 20618 } 20619 20620 struct Caret { 20621 TextLayout layout; 20622 InlineElement inlineElement; 20623 int offset; 20624 } 20625 20626 enum TextFormat : ushort { 20627 // decorations 20628 underline = 1, 20629 strikethrough = 2, 20630 20631 // font selectors 20632 20633 bold = 0x4000 | 1, // weight 700 20634 light = 0x4000 | 2, // weight 300 20635 veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold 20636 // bold | light is really invalid but should give weight 500 20637 // veryBoldOrLight without one of the others should just give the default for the font; it should be ignored. 20638 20639 italic = 0x4000 | 8, 20640 smallcaps = 0x4000 | 16, 20641 } 20642 20643 void* findFont(string family, int weight, TextFormat formats) { 20644 return null; 20645 } 20646 20647 } 20648 20649 /++ 20650 $(PITFALL This is not yet stable and may break in future versions without notice.) 20651 20652 History: 20653 Added February 19, 2021 20654 +/ 20655 /// Group: drag_and_drop 20656 interface DropHandler { 20657 /++ 20658 Called when the drag enters the handler's area. 20659 +/ 20660 DragAndDropAction dragEnter(DropPackage*); 20661 /++ 20662 Called when the drag leaves the handler's area or is 20663 cancelled. You should free your resources when this is called. 20664 +/ 20665 void dragLeave(); 20666 /++ 20667 Called continually as the drag moves over the handler's area. 20668 20669 Returns: feedback to the dragger 20670 +/ 20671 DropParameters dragOver(Point pt); 20672 /++ 20673 The user dropped the data and you should process it now. You can 20674 access the data through the given [DropPackage]. 20675 +/ 20676 void drop(scope DropPackage*); 20677 /++ 20678 Called when the drop is complete. You should free whatever temporary 20679 resources you were using. It is often reasonable to simply forward 20680 this call to [dragLeave]. 20681 +/ 20682 void finish(); 20683 20684 /++ 20685 Parameters returned by [DropHandler.drop]. 20686 +/ 20687 static struct DropParameters { 20688 /++ 20689 Acceptable action over this area. 20690 +/ 20691 DragAndDropAction action; 20692 /++ 20693 Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again. 20694 20695 If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources. 20696 +/ 20697 Rectangle consistentWithin; 20698 } 20699 } 20700 20701 /++ 20702 History: 20703 Added February 19, 2021 20704 +/ 20705 /// Group: drag_and_drop 20706 enum DragAndDropAction { 20707 none = 0, 20708 copy, 20709 move, 20710 link, 20711 ask, 20712 custom 20713 } 20714 20715 /++ 20716 An opaque structure representing dropped data. It contains 20717 private, platform-specific data that your `drop` function 20718 should simply forward. 20719 20720 $(PITFALL This is not yet stable and may break in future versions without notice.) 20721 20722 History: 20723 Added February 19, 2021 20724 +/ 20725 /// Group: drag_and_drop 20726 struct DropPackage { 20727 /++ 20728 Lists the available formats as magic numbers. You should compare these 20729 against looked-up formats (see [DraggableData.getFormatId]) you know you support and can 20730 understand the passed data. 20731 +/ 20732 DraggableData.FormatId[] availableFormats() { 20733 version(X11) { 20734 return xFormats; 20735 } else version(Windows) { 20736 if(pDataObj is null) 20737 return null; 20738 20739 typeof(return) ret; 20740 20741 IEnumFORMATETC ef; 20742 if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) { 20743 FORMATETC fmt; 20744 ULONG fetched; 20745 while(ef.Next(1, &fmt, &fetched) == S_OK) { 20746 if(fetched == 0) 20747 break; 20748 20749 if(fmt.lindex != -1) 20750 continue; 20751 if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT) 20752 continue; 20753 if(!(fmt.tymed & TYMED.TYMED_HGLOBAL)) 20754 continue; 20755 20756 ret ~= fmt.cfFormat; 20757 } 20758 } 20759 20760 return ret; 20761 } else throw new NotYetImplementedException(); 20762 } 20763 20764 /++ 20765 Gets data from the drop and optionally accepts it. 20766 20767 Returns: 20768 void because the data is fed asynchronously through the `dg` parameter. 20769 20770 Params: 20771 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. 20772 20773 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. 20774 20775 Calling `getData` again after accepting a drop is not permitted. 20776 20777 format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format. 20778 20779 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. 20780 20781 Throws: 20782 if `format` was not compatible with the [availableFormats] or if the drop has already been accepted. 20783 20784 History: 20785 Included in first release of [DropPackage]. 20786 +/ 20787 void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) { 20788 version(X11) { 20789 20790 auto display = XDisplayConnection.get(); 20791 auto selectionAtom = GetAtom!"XdndSelection"(display); 20792 auto best = format; 20793 20794 static class X11GetSelectionHandler_Drop : X11GetSelectionHandler { 20795 20796 XDisplay* display; 20797 Atom selectionAtom; 20798 DraggableData.FormatId best; 20799 DraggableData.FormatId format; 20800 void delegate(scope ubyte[] data) dg; 20801 DragAndDropAction acceptedAction; 20802 Window sourceWindow; 20803 SimpleWindow win; 20804 this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) { 20805 this.display = display; 20806 this.win = win; 20807 this.sourceWindow = sourceWindow; 20808 this.format = format; 20809 this.selectionAtom = selectionAtom; 20810 this.best = best; 20811 this.dg = dg; 20812 this.acceptedAction = acceptedAction; 20813 } 20814 20815 20816 mixin X11GetSelectionHandler_Basics; 20817 20818 void handleData(Atom target, in ubyte[] data) { 20819 //if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 20820 20821 dg(cast(ubyte[]) data); 20822 20823 if(acceptedAction != DragAndDropAction.none) { 20824 auto display = XDisplayConnection.get; 20825 20826 XClientMessageEvent xclient; 20827 20828 xclient.type = EventType.ClientMessage; 20829 xclient.window = sourceWindow; 20830 xclient.message_type = GetAtom!"XdndFinished"(display); 20831 xclient.format = 32; 20832 xclient.data.l[0] = win.impl.window; 20833 xclient.data.l[1] = 1; // drop successful 20834 xclient.data.l[2] = dndActionAtom(display, acceptedAction); 20835 20836 XSendEvent( 20837 display, 20838 sourceWindow, 20839 false, 20840 EventMask.NoEventMask, 20841 cast(XEvent*) &xclient 20842 ); 20843 20844 XFlush(display); 20845 } 20846 } 20847 20848 Atom findBestFormat(Atom[] answer) { 20849 Atom best = None; 20850 foreach(option; answer) { 20851 if(option == format) { 20852 best = option; 20853 break; 20854 } 20855 /* 20856 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 20857 best = option; 20858 break; 20859 } else if(option == XA_STRING) { 20860 best = option; 20861 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 20862 best = option; 20863 } 20864 */ 20865 } 20866 return best; 20867 } 20868 } 20869 20870 win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction); 20871 20872 XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp); 20873 20874 } else version(Windows) { 20875 20876 // clean up like DragLeave 20877 // pass effect back up 20878 20879 FORMATETC t; 20880 assert(format >= 0 && format <= ushort.max); 20881 t.cfFormat = cast(ushort) format; 20882 t.lindex = -1; 20883 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 20884 t.tymed = TYMED.TYMED_HGLOBAL; 20885 20886 STGMEDIUM m; 20887 20888 if(pDataObj.GetData(&t, &m) != S_OK) { 20889 // fail 20890 } else { 20891 // succeed, take the data and clean up 20892 20893 // FIXME: ensure it is legit HGLOBAL 20894 auto handle = m.hGlobal; 20895 20896 if(handle) { 20897 auto sz = GlobalSize(handle); 20898 if(auto ptr = cast(ubyte*) GlobalLock(handle)) { 20899 scope(exit) GlobalUnlock(handle); 20900 scope(exit) GlobalFree(handle); 20901 20902 auto data = ptr[0 .. sz]; 20903 20904 dg(data); 20905 } 20906 } 20907 } 20908 } 20909 } 20910 20911 private: 20912 20913 version(X11) { 20914 SimpleWindow win; 20915 Window sourceWindow; 20916 Time dataTimestamp; 20917 20918 Atom[] xFormats; 20919 } 20920 version(Windows) { 20921 IDataObject pDataObj; 20922 } 20923 } 20924 20925 /++ 20926 A generic helper base class for making a drop handler with a preference list of custom types. 20927 This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own 20928 droppers too. 20929 20930 It assumes the whole window it used, but you can subclass to change that. 20931 20932 $(PITFALL This is not yet stable and may break in future versions without notice.) 20933 20934 History: 20935 Added February 19, 2021 20936 +/ 20937 /// Group: drag_and_drop 20938 class GenericDropHandlerBase : DropHandler { 20939 // no fancy state here so no need to do anything here 20940 void finish() { } 20941 void dragLeave() { } 20942 20943 private DragAndDropAction acceptedAction; 20944 private DraggableData.FormatId acceptedFormat; 20945 private void delegate(scope ubyte[]) acceptedHandler; 20946 20947 struct FormatHandler { 20948 DraggableData.FormatId format; 20949 void delegate(scope ubyte[]) handler; 20950 } 20951 20952 protected abstract FormatHandler[] formatHandlers(); 20953 20954 DragAndDropAction dragEnter(DropPackage* pkg) { 20955 debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); } 20956 foreach(fmt; formatHandlers()) 20957 foreach(f; pkg.availableFormats()) 20958 if(f == fmt.format) { 20959 acceptedFormat = f; 20960 acceptedHandler = fmt.handler; 20961 return acceptedAction = DragAndDropAction.copy; 20962 } 20963 return acceptedAction = DragAndDropAction.none; 20964 } 20965 DropParameters dragOver(Point pt) { 20966 return DropParameters(acceptedAction); 20967 } 20968 20969 void drop(scope DropPackage* dropPackage) { 20970 if(!acceptedFormat || acceptedHandler is null) { 20971 debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); } 20972 return; // prolly shouldn't happen anyway... 20973 } 20974 20975 dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler); 20976 } 20977 } 20978 20979 /++ 20980 A simple handler for making your window accept drops of plain text. 20981 20982 $(PITFALL This is not yet stable and may break in future versions without notice.) 20983 20984 History: 20985 Added February 22, 2021 20986 +/ 20987 /// Group: drag_and_drop 20988 class TextDropHandler : GenericDropHandlerBase { 20989 private void delegate(in char[] text) dg; 20990 20991 /++ 20992 20993 +/ 20994 this(void delegate(in char[] text) dg) { 20995 this.dg = dg; 20996 } 20997 20998 protected override FormatHandler[] formatHandlers() { 20999 version(X11) 21000 return [ 21001 FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator), 21002 FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator), 21003 ]; 21004 else version(Windows) 21005 return [ 21006 FormatHandler(CF_UNICODETEXT, &translator), 21007 ]; 21008 else throw new NotYetImplementedException(); 21009 } 21010 21011 private void translator(scope ubyte[] data) { 21012 version(X11) 21013 dg(cast(char[]) data); 21014 else version(Windows) 21015 dg(makeUtf8StringFromWindowsString(cast(wchar[]) data)); 21016 } 21017 } 21018 21019 /++ 21020 A simple handler for making your window accept drops of files, issued to you as file names. 21021 21022 $(PITFALL This is not yet stable and may break in future versions without notice.) 21023 21024 History: 21025 Added February 22, 2021 21026 +/ 21027 /// Group: drag_and_drop 21028 21029 class FilesDropHandler : GenericDropHandlerBase { 21030 private void delegate(in char[][]) dg; 21031 21032 /++ 21033 21034 +/ 21035 this(void delegate(in char[][] fileNames) dg) { 21036 this.dg = dg; 21037 } 21038 21039 protected override FormatHandler[] formatHandlers() { 21040 version(X11) 21041 return [ 21042 FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator), 21043 ]; 21044 else version(Windows) 21045 return [ 21046 FormatHandler(CF_HDROP, &translator), 21047 ]; 21048 else throw new NotYetImplementedException(); 21049 } 21050 21051 private void translator(scope ubyte[] data) { 21052 version(X11) { 21053 char[] listString = cast(char[]) data; 21054 char[][16] buffer; 21055 int count; 21056 char[][] result = buffer[]; 21057 21058 void commit(char[] s) { 21059 if(count == result.length) 21060 result.length += 16; 21061 if(s.length > 7 && s[0 ..7] == "file://") 21062 s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding 21063 result[count++] = s; 21064 } 21065 21066 size_t last; 21067 foreach(idx, char c; listString) { 21068 if(c == '\n') { 21069 commit(listString[last .. idx - 1]); // a \r 21070 last = idx + 1; // a \n 21071 } 21072 } 21073 21074 if(last < listString.length) { 21075 commit(listString[last .. $]); 21076 } 21077 21078 // FIXME: they are uris now, should I translate it to local file names? 21079 // of course the host name is supposed to be there cuz of X rokking... 21080 21081 dg(result[0 .. count]); 21082 } else version(Windows) { 21083 21084 static struct DROPFILES { 21085 DWORD pFiles; 21086 POINT pt; 21087 BOOL fNC; 21088 BOOL fWide; 21089 } 21090 21091 21092 const(char)[][16] buffer; 21093 int count; 21094 const(char)[][] result = buffer[]; 21095 size_t last; 21096 21097 void commitA(in char[] stuff) { 21098 if(count == result.length) 21099 result.length += 16; 21100 result[count++] = stuff; 21101 } 21102 21103 void commitW(in wchar[] stuff) { 21104 commitA(makeUtf8StringFromWindowsString(stuff)); 21105 } 21106 21107 void magic(T)(T chars) { 21108 size_t idx; 21109 while(chars[idx]) { 21110 last = idx; 21111 while(chars[idx]) { 21112 idx++; 21113 } 21114 static if(is(T == char*)) 21115 commitA(chars[last .. idx]); 21116 else 21117 commitW(chars[last .. idx]); 21118 idx++; 21119 } 21120 } 21121 21122 auto df = cast(DROPFILES*) data.ptr; 21123 if(df.fWide) { 21124 wchar* chars = cast(wchar*) (data.ptr + df.pFiles); 21125 magic(chars); 21126 } else { 21127 char* chars = cast(char*) (data.ptr + df.pFiles); 21128 magic(chars); 21129 } 21130 dg(result[0 .. count]); 21131 } 21132 else throw new NotYetImplementedException(); 21133 } 21134 } 21135 21136 /++ 21137 Interface to describe data being dragged. See also [draggable] helper function. 21138 21139 $(PITFALL This is not yet stable and may break in future versions without notice.) 21140 21141 History: 21142 Added February 19, 2021 21143 +/ 21144 interface DraggableData { 21145 version(X11) 21146 alias FormatId = Atom; 21147 else 21148 alias FormatId = uint; 21149 /++ 21150 Gets the platform-specific FormatId associated with the given named format. 21151 21152 This may be a MIME type, but may also be other various strings defined by the 21153 programs you want to interoperate with. 21154 21155 FIXME: sdpy needs to offer data adapter things that look for compatible formats 21156 and convert it to some particular type for you. 21157 +/ 21158 static FormatId getFormatId(string name)() { 21159 version(X11) 21160 return GetAtom!name(XDisplayConnection.get); 21161 else version(Windows) { 21162 static UINT cache; 21163 if(!cache) 21164 cache = RegisterClipboardFormatA(name); 21165 return cache; 21166 } else 21167 throw new NotYetImplementedException(); 21168 } 21169 21170 /++ 21171 Looks up a string to represent the name for the given format, if there is one. 21172 21173 You should avoid using this function because it is slow. It is provided more for 21174 debugging than for primary use. 21175 +/ 21176 static string getFormatName(FormatId format) { 21177 version(X11) { 21178 if(format == 0) 21179 return "None"; 21180 else 21181 return getAtomName(format, XDisplayConnection.get); 21182 } else version(Windows) { 21183 switch(format) { 21184 case CF_UNICODETEXT: return "CF_UNICODETEXT"; 21185 case CF_DIBV5: return "CF_DIBV5"; 21186 case CF_RIFF: return "CF_RIFF"; 21187 case CF_WAVE: return "CF_WAVE"; 21188 case CF_HDROP: return "CF_HDROP"; 21189 default: 21190 char[1024] name; 21191 auto count = GetClipboardFormatNameA(format, name.ptr, name.length); 21192 return name[0 .. count].idup; 21193 } 21194 } else throw new NotYetImplementedException(); 21195 } 21196 21197 FormatId[] availableFormats(); 21198 // Return the slice of data you filled, empty slice if done. 21199 // this is to support the incremental thing 21200 ubyte[] getData(FormatId format, return scope ubyte[] data); 21201 21202 size_t dataLength(FormatId format); 21203 } 21204 21205 /++ 21206 $(PITFALL This is not yet stable and may break in future versions without notice.) 21207 21208 History: 21209 Added February 19, 2021 21210 +/ 21211 DraggableData draggable(string s) { 21212 version(X11) 21213 return new class X11SetSelectionHandler_Text, DraggableData { 21214 this() { 21215 super(s); 21216 } 21217 21218 override FormatId[] availableFormats() { 21219 return X11SetSelectionHandler_Text.availableFormats(); 21220 } 21221 21222 override ubyte[] getData(FormatId format, return scope ubyte[] data) { 21223 return X11SetSelectionHandler_Text.getData(format, data); 21224 } 21225 21226 size_t dataLength(FormatId format) { 21227 return s.length; 21228 } 21229 }; 21230 else version(Windows) 21231 return new class DraggableData { 21232 FormatId[] availableFormats() { 21233 return [CF_UNICODETEXT]; 21234 } 21235 21236 ubyte[] getData(FormatId format, return scope ubyte[] data) { 21237 return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 21238 } 21239 21240 size_t dataLength(FormatId format) { 21241 return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof; 21242 } 21243 }; 21244 else 21245 throw new NotYetImplementedException(); 21246 } 21247 21248 /++ 21249 $(PITFALL This is not yet stable and may break in future versions without notice.) 21250 21251 History: 21252 Added February 19, 2021 21253 +/ 21254 /// Group: drag_and_drop 21255 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) 21256 in { 21257 assert(window !is null); 21258 assert(handler !is null); 21259 } 21260 do 21261 { 21262 version(X11) { 21263 auto sh = cast(X11SetSelectionHandler) handler; 21264 if(sh is null) { 21265 // gotta make my own adapter. 21266 sh = new class X11SetSelectionHandler { 21267 mixin X11SetSelectionHandler_Basics; 21268 21269 Atom[] availableFormats() { return handler.availableFormats(); } 21270 ubyte[] getData(Atom format, return scope ubyte[] data) { 21271 return handler.getData(format, data); 21272 } 21273 21274 // since the drop selection is only ever used once it isn't important 21275 // to reset it. 21276 void done() {} 21277 }; 21278 } 21279 return doDragDropX11(window, sh, action); 21280 } else version(Windows) { 21281 return doDragDropWindows(window, handler, action); 21282 } else throw new NotYetImplementedException(); 21283 } 21284 21285 version(Windows) 21286 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) { 21287 IDataObject obj = new class IDataObject { 21288 ULONG refCount; 21289 ULONG AddRef() { 21290 return ++refCount; 21291 } 21292 ULONG Release() { 21293 return --refCount; 21294 } 21295 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21296 if (IID_IUnknown == *riid) { 21297 *ppv = cast(void*) cast(IUnknown) this; 21298 } 21299 else if (IID_IDataObject == *riid) { 21300 *ppv = cast(void*) cast(IDataObject) this; 21301 } 21302 else { 21303 *ppv = null; 21304 return E_NOINTERFACE; 21305 } 21306 21307 AddRef(); 21308 return NOERROR; 21309 } 21310 21311 HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) { 21312 // writeln("Advise"); 21313 return E_NOTIMPL; 21314 } 21315 HRESULT DUnadvise(DWORD dwConnection) { 21316 return E_NOTIMPL; 21317 } 21318 HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) { 21319 // writeln("EnumDAdvise"); 21320 return OLE_E_ADVISENOTSUPPORTED; 21321 } 21322 // tell what formats it supports 21323 21324 FORMATETC[] types; 21325 this() { 21326 FORMATETC t; 21327 foreach(ty; handler.availableFormats()) { 21328 assert(ty <= ushort.max && ty >= 0); 21329 t.cfFormat = cast(ushort) ty; 21330 t.lindex = -1; 21331 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 21332 t.tymed = TYMED.TYMED_HGLOBAL; 21333 } 21334 types ~= t; 21335 } 21336 HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) { 21337 if(dwDirection == DATADIR.DATADIR_GET) { 21338 *ppenumFormatEtc = new class IEnumFORMATETC { 21339 ULONG refCount; 21340 ULONG AddRef() { 21341 return ++refCount; 21342 } 21343 ULONG Release() { 21344 return --refCount; 21345 } 21346 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21347 if (IID_IUnknown == *riid) { 21348 *ppv = cast(void*) cast(IUnknown) this; 21349 } 21350 else if (IID_IEnumFORMATETC == *riid) { 21351 *ppv = cast(void*) cast(IEnumFORMATETC) this; 21352 } 21353 else { 21354 *ppv = null; 21355 return E_NOINTERFACE; 21356 } 21357 21358 AddRef(); 21359 return NOERROR; 21360 } 21361 21362 21363 int pos; 21364 this() { 21365 pos = 0; 21366 } 21367 21368 HRESULT Clone(IEnumFORMATETC* ppenum) { 21369 // writeln("clone"); 21370 return E_NOTIMPL; // FIXME 21371 } 21372 21373 // Caller is responsible for freeing memory 21374 HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) { 21375 // fetched may be null if celt is one 21376 if(celt != 1) 21377 return E_NOTIMPL; // FIXME 21378 21379 if(celt + pos > types.length) 21380 return S_FALSE; 21381 21382 *rgelt = types[pos++]; 21383 21384 if(pceltFetched !is null) 21385 *pceltFetched = 1; 21386 21387 // writeln("ok celt ", celt); 21388 return S_OK; 21389 } 21390 21391 HRESULT Reset() { 21392 pos = 0; 21393 return S_OK; 21394 } 21395 21396 HRESULT Skip(ULONG celt) { 21397 if(celt + pos <= types.length) { 21398 pos += celt; 21399 return S_OK; 21400 } 21401 return S_FALSE; 21402 } 21403 }; 21404 21405 return S_OK; 21406 } else 21407 return E_NOTIMPL; 21408 } 21409 // given a format, return the format you'd prefer to use cuz it is identical 21410 HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) { 21411 // FIXME: prolly could be better but meh 21412 // writeln("gcf: ", *pformatectIn); 21413 *pformatetcOut = *pformatectIn; 21414 return S_OK; 21415 } 21416 HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21417 foreach(ty; types) { 21418 if(ty == *pformatetcIn) { 21419 auto format = ty.cfFormat; 21420 // writeln("A: ", *pformatetcIn, "\nB: ", ty); 21421 STGMEDIUM medium; 21422 medium.tymed = TYMED.TYMED_HGLOBAL; 21423 21424 auto sz = handler.dataLength(format); 21425 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz); 21426 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 21427 if(auto data = cast(wchar*) GlobalLock(handle)) { 21428 auto slice = data[0 .. sz]; 21429 scope(exit) 21430 GlobalUnlock(handle); 21431 21432 handler.getData(format, cast(ubyte[]) slice[]); 21433 } 21434 21435 21436 medium.hGlobal = handle; // FIXME 21437 *pmedium = medium; 21438 return S_OK; 21439 } 21440 } 21441 return DV_E_FORMATETC; 21442 } 21443 HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21444 // writeln("GDH: ", *pformatetcIn); 21445 return E_NOTIMPL; // FIXME 21446 } 21447 HRESULT QueryGetData(FORMATETC* pformatetc) { 21448 auto search = *pformatetc; 21449 search.tymed &= TYMED.TYMED_HGLOBAL; 21450 foreach(ty; types) 21451 if(ty == search) { 21452 // writeln("QueryGetData ", search, " ", types[0]); 21453 return S_OK; 21454 } 21455 if(pformatetc.cfFormat==CF_UNICODETEXT) { 21456 //writeln("QueryGetData FALSE ", search, " ", types[0]); 21457 } 21458 return S_FALSE; 21459 } 21460 HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) { 21461 // writeln("SetData: "); 21462 return E_NOTIMPL; 21463 } 21464 }; 21465 21466 21467 IDropSource src = new class IDropSource { 21468 ULONG refCount; 21469 ULONG AddRef() { 21470 return ++refCount; 21471 } 21472 ULONG Release() { 21473 return --refCount; 21474 } 21475 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21476 if (IID_IUnknown == *riid) { 21477 *ppv = cast(void*) cast(IUnknown) this; 21478 } 21479 else if (IID_IDropSource == *riid) { 21480 *ppv = cast(void*) cast(IDropSource) this; 21481 } 21482 else { 21483 *ppv = null; 21484 return E_NOINTERFACE; 21485 } 21486 21487 AddRef(); 21488 return NOERROR; 21489 } 21490 21491 int QueryContinueDrag(int fEscapePressed, uint grfKeyState) { 21492 if(fEscapePressed) 21493 return DRAGDROP_S_CANCEL; 21494 if(!(grfKeyState & MK_LBUTTON)) 21495 return DRAGDROP_S_DROP; 21496 return S_OK; 21497 } 21498 21499 int GiveFeedback(uint dwEffect) { 21500 return DRAGDROP_S_USEDEFAULTCURSORS; 21501 } 21502 }; 21503 21504 DWORD effect; 21505 21506 if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect."); 21507 21508 DROPEFFECT de = win32DragAndDropAction(action); 21509 21510 // I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time 21511 // but still prolly a FIXME 21512 21513 auto ret = DoDragDrop(obj, src, de, &effect); 21514 /+ 21515 if(ret == DRAGDROP_S_DROP) 21516 writeln("drop ", effect); 21517 else if(ret == DRAGDROP_S_CANCEL) 21518 writeln("cancel"); 21519 else if(ret == S_OK) 21520 writeln("ok"); 21521 else writeln(ret); 21522 +/ 21523 21524 return ret; 21525 } 21526 21527 version(Windows) 21528 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) { 21529 DROPEFFECT de; 21530 21531 with(DragAndDropAction) 21532 with(DROPEFFECT) 21533 final switch(action) { 21534 case none: de = DROPEFFECT_NONE; break; 21535 case copy: de = DROPEFFECT_COPY; break; 21536 case move: de = DROPEFFECT_MOVE; break; 21537 case link: de = DROPEFFECT_LINK; break; 21538 case ask: throw new Exception("ask not implemented yet"); 21539 case custom: throw new Exception("custom not implemented yet"); 21540 } 21541 21542 return de; 21543 } 21544 21545 21546 /++ 21547 History: 21548 Added February 19, 2021 21549 +/ 21550 /// Group: drag_and_drop 21551 void enableDragAndDrop(SimpleWindow window, DropHandler handler) { 21552 version(X11) { 21553 auto display = XDisplayConnection.get; 21554 21555 Atom atom = 5; // right??? 21556 21557 XChangeProperty( 21558 display, 21559 window.impl.window, 21560 GetAtom!"XdndAware"(display), 21561 XA_ATOM, 21562 32 /* bits */, 21563 PropModeReplace, 21564 &atom, 21565 1); 21566 21567 window.dropHandler = handler; 21568 } else version(Windows) { 21569 21570 initDnd(); 21571 21572 auto dropTarget = new class (handler) IDropTarget { 21573 DropHandler handler; 21574 this(DropHandler handler) { 21575 this.handler = handler; 21576 } 21577 ULONG refCount; 21578 ULONG AddRef() { 21579 return ++refCount; 21580 } 21581 ULONG Release() { 21582 return --refCount; 21583 } 21584 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21585 if (IID_IUnknown == *riid) { 21586 *ppv = cast(void*) cast(IUnknown) this; 21587 } 21588 else if (IID_IDropTarget == *riid) { 21589 *ppv = cast(void*) cast(IDropTarget) this; 21590 } 21591 else { 21592 *ppv = null; 21593 return E_NOINTERFACE; 21594 } 21595 21596 AddRef(); 21597 return NOERROR; 21598 } 21599 21600 21601 // /////////////////// 21602 21603 HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21604 DropPackage dropPackage = DropPackage(pDataObj); 21605 *pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage)); 21606 return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter 21607 } 21608 21609 HRESULT DragLeave() { 21610 handler.dragLeave(); 21611 // release the IDataObject if needed 21612 return S_OK; 21613 } 21614 21615 HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21616 auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates 21617 21618 *pdwEffect = win32DragAndDropAction(res.action); 21619 // same as DragEnter basically 21620 return S_OK; 21621 } 21622 21623 HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21624 DropPackage pkg = DropPackage(pDataObj); 21625 handler.drop(&pkg); 21626 21627 return S_OK; 21628 } 21629 }; 21630 // Windows can hold on to the handler and try to call it 21631 // during which time the GC can't see it. so important to 21632 // manually manage this. At some point i'll FIXME and make 21633 // all my com instances manually managed since they supposed 21634 // to respect the refcount. 21635 import core.memory; 21636 GC.addRoot(cast(void*) dropTarget); 21637 21638 if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK) 21639 throw new WindowsApiException("RegisterDragDrop", GetLastError()); 21640 21641 window.dropHandler = handler; 21642 } else throw new NotYetImplementedException(); 21643 } 21644 21645 21646 21647 static if(UsingSimpledisplayX11) { 21648 21649 enum _NET_WM_STATE_ADD = 1; 21650 enum _NET_WM_STATE_REMOVE = 0; 21651 enum _NET_WM_STATE_TOGGLE = 2; 21652 21653 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases. 21654 void demandAttention(SimpleWindow window, bool needs = true) { 21655 demandAttention(window.impl.window, needs); 21656 } 21657 21658 /// ditto 21659 void demandAttention(Window window, bool needs = true) { 21660 setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs); 21661 } 21662 21663 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) { 21664 auto display = XDisplayConnection.get(); 21665 if(atom == None) 21666 return; // non-failure error 21667 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 21668 21669 XClientMessageEvent xclient; 21670 21671 xclient.type = EventType.ClientMessage; 21672 xclient.window = window; 21673 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 21674 xclient.format = 32; 21675 xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 21676 xclient.data.l[1] = atom; 21677 xclient.data.l[2] = atom2; 21678 xclient.data.l[3] = 1; 21679 // [3] == source. 0 == unknown, 1 == app, 2 == else 21680 21681 XSendEvent( 21682 display, 21683 RootWindow(display, DefaultScreen(display)), 21684 false, 21685 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 21686 cast(XEvent*) &xclient 21687 ); 21688 21689 /+ 21690 XChangeProperty( 21691 display, 21692 window.impl.window, 21693 GetAtom!"_NET_WM_STATE"(display), 21694 XA_ATOM, 21695 32 /* bits */, 21696 PropModeAppend, 21697 &atom, 21698 1); 21699 +/ 21700 } 21701 21702 private Atom dndActionAtom(Display* display, DragAndDropAction action) { 21703 Atom actionAtom; 21704 with(DragAndDropAction) 21705 final switch(action) { 21706 case none: actionAtom = None; break; 21707 case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break; 21708 case move: actionAtom = GetAtom!"XdndActionMove"(display); break; 21709 case link: actionAtom = GetAtom!"XdndActionLink"(display); break; 21710 case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break; 21711 case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break; 21712 } 21713 21714 return actionAtom; 21715 } 21716 21717 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) { 21718 // FIXME: I need to show user feedback somehow. 21719 auto display = XDisplayConnection.get; 21720 21721 auto actionAtom = dndActionAtom(display, action); 21722 assert(actionAtom, "Don't use action none to accept a drop"); 21723 21724 setX11Selection!"XdndSelection"(window, handler, null); 21725 21726 auto oldKeyHandler = window.handleKeyEvent; 21727 scope(exit) window.handleKeyEvent = oldKeyHandler; 21728 21729 auto oldCharHandler = window.handleCharEvent; 21730 scope(exit) window.handleCharEvent = oldCharHandler; 21731 21732 auto oldMouseHandler = window.handleMouseEvent; 21733 scope(exit) window.handleMouseEvent = oldMouseHandler; 21734 21735 Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child 21736 21737 import core.sys.posix.sys.time; 21738 timeval tv; 21739 gettimeofday(&tv, null); 21740 21741 Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; 21742 21743 Time lastMouseTimestamp; 21744 21745 bool dnding = true; 21746 Window lastIn = None; 21747 21748 void leave() { 21749 if(lastIn == None) 21750 return; 21751 21752 XEvent ev; 21753 ev.xclient.type = EventType.ClientMessage; 21754 ev.xclient.window = lastIn; 21755 ev.xclient.message_type = GetAtom!("XdndLeave", true)(display); 21756 ev.xclient.format = 32; 21757 ev.xclient.data.l[0] = window.impl.window; 21758 21759 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21760 XFlush(display); 21761 21762 lastIn = None; 21763 } 21764 21765 void enter(Window w) { 21766 assert(lastIn == None); 21767 21768 lastIn = w; 21769 21770 XEvent ev; 21771 ev.xclient.type = EventType.ClientMessage; 21772 ev.xclient.window = lastIn; 21773 ev.xclient.message_type = GetAtom!("XdndEnter", true)(display); 21774 ev.xclient.format = 32; 21775 ev.xclient.data.l[0] = window.impl.window; 21776 ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types 21777 21778 auto types = handler.availableFormats(); 21779 assert(types.length > 0); 21780 21781 ev.xclient.data.l[2] = types[0]; 21782 if(types.length > 1) 21783 ev.xclient.data.l[3] = types[1]; 21784 if(types.length > 2) 21785 ev.xclient.data.l[4] = types[2]; 21786 21787 // FIXME: other types?!?!? and make sure we skip TARGETS 21788 21789 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21790 XFlush(display); 21791 } 21792 21793 void position(int rootX, int rootY) { 21794 assert(lastIn != None); 21795 21796 XEvent ev; 21797 ev.xclient.type = EventType.ClientMessage; 21798 ev.xclient.window = lastIn; 21799 ev.xclient.message_type = GetAtom!("XdndPosition", true)(display); 21800 ev.xclient.format = 32; 21801 ev.xclient.data.l[0] = window.impl.window; 21802 ev.xclient.data.l[1] = 0; // reserved 21803 ev.xclient.data.l[2] = (rootX << 16) | rootY; 21804 ev.xclient.data.l[3] = dataTimestamp; 21805 ev.xclient.data.l[4] = actionAtom; 21806 21807 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21808 XFlush(display); 21809 21810 } 21811 21812 void drop() { 21813 XEvent ev; 21814 ev.xclient.type = EventType.ClientMessage; 21815 ev.xclient.window = lastIn; 21816 ev.xclient.message_type = GetAtom!("XdndDrop", true)(display); 21817 ev.xclient.format = 32; 21818 ev.xclient.data.l[0] = window.impl.window; 21819 ev.xclient.data.l[1] = 0; // reserved 21820 ev.xclient.data.l[2] = dataTimestamp; 21821 21822 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21823 XFlush(display); 21824 21825 lastIn = None; 21826 dnding = false; 21827 } 21828 21829 // fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler 21830 // but idk if i should... 21831 21832 window.setEventHandlers( 21833 delegate(KeyEvent ev) { 21834 if(ev.pressed == true && ev.key == Key.Escape) { 21835 // cancel 21836 dnding = false; 21837 } 21838 }, 21839 delegate(MouseEvent ev) { 21840 if(ev.timestamp < lastMouseTimestamp) 21841 return; 21842 21843 lastMouseTimestamp = ev.timestamp; 21844 21845 if(ev.type == MouseEventType.motion) { 21846 auto display = XDisplayConnection.get; 21847 auto root = RootWindow(display, DefaultScreen(display)); 21848 21849 Window topWindow; 21850 int rootX, rootY; 21851 21852 XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow); 21853 21854 if(topWindow == None) 21855 return; 21856 21857 top: 21858 if(auto result = topWindow in eligibility) { 21859 auto dropWindow = *result; 21860 if(dropWindow == None) { 21861 leave(); 21862 return; 21863 } 21864 21865 if(dropWindow != lastIn) { 21866 leave(); 21867 enter(dropWindow); 21868 position(rootX, rootY); 21869 } else { 21870 position(rootX, rootY); 21871 } 21872 } else { 21873 // determine eligibility 21874 auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM); 21875 if(data.length == 1) { 21876 // in case there is no WM or it isn't reparenting 21877 eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh 21878 } else { 21879 21880 Window tryScanChildren(Window search, int maxRecurse) { 21881 // could be reparenting window manager, so gotta check the next few children too 21882 Window child; 21883 int x; 21884 int y; 21885 XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child); 21886 21887 if(child == None) 21888 return None; 21889 auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM); 21890 if(data.length == 1) { 21891 return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh 21892 } else { 21893 if(maxRecurse) 21894 return tryScanChildren(child, maxRecurse - 1); 21895 else 21896 return None; 21897 } 21898 21899 } 21900 21901 // if a WM puts more than 3 layers on it, like wtf is it doing, screw that. 21902 auto topResult = tryScanChildren(topWindow, 3); 21903 // it is easy to have a false negative due to the mouse going over a WM 21904 // child window like the close button if separate from the frame... so I 21905 // can't really cache negatives, :( 21906 if(topResult != None) { 21907 eligibility[topWindow] = topResult; 21908 goto top; // reload to do the positioning iff eligibility changed lest we endless loop 21909 } 21910 } 21911 21912 } 21913 21914 } else if(ev.type == MouseEventType.buttonReleased) { 21915 drop(); 21916 dnding = false; 21917 } 21918 } 21919 ); 21920 21921 window.grabInput(); 21922 scope(exit) 21923 window.releaseInputGrab(); 21924 21925 21926 EventLoop.get.run(() => dnding); 21927 21928 return 0; 21929 } 21930 21931 /// X-specific 21932 TrueColorImage getWindowNetWmIcon(Window window) { 21933 try { 21934 auto display = XDisplayConnection.get; 21935 21936 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 21937 21938 if (data.length > arch_ulong.sizeof * 2) { 21939 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 21940 // these are an array of rgba images that we have to convert into pixmaps ourself 21941 21942 int width = cast(int) meta[0]; 21943 int height = cast(int) meta[1]; 21944 21945 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 21946 21947 static if(arch_ulong.sizeof == 4) { 21948 bytes = bytes[0 .. width * height * 4]; 21949 alias imageData = bytes; 21950 } else static if(arch_ulong.sizeof == 8) { 21951 bytes = bytes[0 .. width * height * 8]; 21952 auto imageData = new ubyte[](4 * width * height); 21953 } else static assert(0); 21954 21955 21956 21957 // this returns ARGB. Remember it is little-endian so 21958 // we have BGRA 21959 // our thing uses RGBA, which in little endian, is ABGR 21960 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 21961 auto r = bytes[idx + 2]; 21962 auto g = bytes[idx + 1]; 21963 auto b = bytes[idx + 0]; 21964 auto a = bytes[idx + 3]; 21965 21966 imageData[idx2 + 0] = r; 21967 imageData[idx2 + 1] = g; 21968 imageData[idx2 + 2] = b; 21969 imageData[idx2 + 3] = a; 21970 } 21971 21972 return new TrueColorImage(width, height, imageData); 21973 } 21974 21975 return null; 21976 } catch(Exception e) { 21977 return null; 21978 } 21979 } 21980 21981 } /* UsingSimpledisplayX11 */ 21982 21983 21984 void loadBinNameToWindowClassName () { 21985 import core.stdc.stdlib : realloc; 21986 version(linux) { 21987 // args[0] MAY be empty, so we'll just use this 21988 import core.sys.posix.unistd : readlink; 21989 char[1024] ebuf = void; // 1KB should be enough for everyone! 21990 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 21991 if (len < 1) return; 21992 } else /*version(Windows)*/ { 21993 import core.runtime : Runtime; 21994 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 21995 auto ebuf = Runtime.args[0]; 21996 auto len = ebuf.length; 21997 } 21998 auto pos = len; 21999 while (pos > 0 && ebuf[pos-1] != '/') --pos; 22000 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 22001 if (sdpyWindowClassStr is null) return; // oops 22002 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 22003 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 22004 } 22005 22006 /++ 22007 An interface representing a font that is drawn with custom facilities. 22008 22009 You might want [OperatingSystemFont] instead, which represents 22010 a font loaded and drawn by functions native to the operating system. 22011 22012 WARNING: I might still change this. 22013 +/ 22014 interface DrawableFont : MeasurableFont { 22015 /++ 22016 Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string. 22017 22018 Implementations must use the painter's fillColor to draw a rectangle behind the string, 22019 then use the outlineColor to draw the string. It might alpha composite if there's a transparent 22020 fill color, but that's up to the implementation. 22021 +/ 22022 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 22023 22024 /++ 22025 Requests that the given string is added to the image cache. You should only do this rarely, but 22026 if you have a string that you know will be used over and over again, adding it to a cache can 22027 improve things (assuming the implementation actually has a cache; it is also valid for an implementation 22028 to implement this as a do-nothing method). 22029 +/ 22030 void cacheString(SimpleWindow window, Color foreground, Color background, string text); 22031 } 22032 22033 /++ 22034 Loads a true type font using [arsd.ttf] that can be drawn as images on windows 22035 through a [ScreenPainter]. That module must be compiled in if you choose to use this function. 22036 22037 You should also consider [OperatingSystemFont], which loads and draws a font with 22038 facilities native to the user's operating system. You might also consider 22039 [arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind 22040 of game, as they have their own ways to draw text too. 22041 22042 Be warned: this can be slow, especially on remote connections to the X server, since 22043 it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface 22044 offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to 22045 experiment in your specific case. 22046 22047 Please note that the return type of [DrawableFont] also includes an implementation of 22048 [MeasurableFont]. 22049 +/ 22050 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 22051 import arsd.ttf; 22052 static class ArsdTtfFont : DrawableFont { 22053 TtfFont font; 22054 int size; 22055 this(in ubyte[] data, int size) { 22056 font = TtfFont(data); 22057 this.size = size; 22058 22059 22060 auto scale = stbtt_ScaleForPixelHeight(&font.font, size); 22061 int ascent_, descent_, line_gap; 22062 stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap); 22063 22064 int advance, lsb; 22065 stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb); 22066 xWidth = cast(int) (advance * scale); 22067 stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb); 22068 MWidth = cast(int) (advance * scale); 22069 } 22070 22071 private int ascent_; 22072 private int descent_; 22073 private int xWidth; 22074 private int MWidth; 22075 22076 bool isMonospace() { 22077 return xWidth == MWidth; 22078 } 22079 int averageWidth() { 22080 return xWidth; 22081 } 22082 int height() { 22083 return size; 22084 } 22085 int ascent() { 22086 return ascent_; 22087 } 22088 int descent() { 22089 return descent_; 22090 } 22091 22092 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 22093 int width, height; 22094 font.getStringSize(s, size, width, height); 22095 return width; 22096 } 22097 22098 22099 22100 Sprite[string] cache; 22101 22102 void cacheString(SimpleWindow window, Color foreground, Color background, string text) { 22103 auto sprite = new Sprite(window, stringToImage(foreground, background, text)); 22104 cache[text] = sprite; 22105 } 22106 22107 Image stringToImage(Color fg, Color bg, in char[] text) { 22108 int width, height; 22109 auto data = font.renderString(text, size, width, height); 22110 auto image = new TrueColorImage(width, height); 22111 int pos = 0; 22112 foreach(y; 0 .. height) 22113 foreach(x; 0 .. width) { 22114 fg.a = data[0]; 22115 bg.a = 255; 22116 auto color = alphaBlend(fg, bg); 22117 image.imageData.bytes[pos++] = color.r; 22118 image.imageData.bytes[pos++] = color.g; 22119 image.imageData.bytes[pos++] = color.b; 22120 image.imageData.bytes[pos++] = data[0]; 22121 data = data[1 .. $]; 22122 } 22123 assert(data.length == 0); 22124 22125 return Image.fromMemoryImage(image); 22126 } 22127 22128 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 22129 Sprite sprite = (text in cache) ? *(text in cache) : null; 22130 22131 auto fg = painter.impl._outlineColor; 22132 auto bg = painter.impl._fillColor; 22133 22134 if(sprite !is null) { 22135 auto w = cast(SimpleWindow) painter.window; 22136 assert(w !is null); 22137 22138 sprite.drawAt(painter, upperLeft); 22139 } else { 22140 painter.drawImage(upperLeft, stringToImage(fg, bg, text)); 22141 } 22142 } 22143 } 22144 22145 return new ArsdTtfFont(data, size); 22146 } 22147 22148 class NotYetImplementedException : Exception { 22149 this(string file = __FILE__, size_t line = __LINE__) { 22150 super("Not yet implemented", file, line); 22151 } 22152 } 22153 22154 /// 22155 __gshared bool librariesSuccessfullyLoaded = true; 22156 /// 22157 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 22158 22159 private mixin template DynamicLoadSupplementalOpenGL(Iface) { 22160 mixin(staticForeachReplacement!Iface); 22161 22162 void loadDynamicLibrary() @nogc { 22163 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 22164 } 22165 22166 void loadDynamicLibraryForReal() { 22167 foreach(name; __traits(derivedMembers, Iface)) { 22168 mixin("alias tmp = " ~ name ~ ";"); 22169 tmp = cast(typeof(tmp)) glbindGetProcAddress(name); 22170 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL"); 22171 } 22172 } 22173 } 22174 22175 private const(char)[] staticForeachReplacement(Iface)() pure { 22176 /* 22177 // just this for gdc 9.... 22178 // when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease 22179 22180 static foreach(name; __traits(derivedMembers, Iface)) 22181 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 22182 */ 22183 22184 char[] code = new char[](__traits(derivedMembers, Iface).length * 64); 22185 size_t pos; 22186 22187 void append(in char[] what) { 22188 if(pos + what.length > code.length) 22189 code.length = (code.length * 3) / 2; 22190 code[pos .. pos + what.length] = what[]; 22191 pos += what.length; 22192 } 22193 22194 foreach(name; __traits(derivedMembers, Iface)) { 22195 append(`__gshared typeof(&__traits(getMember, Iface, "`); 22196 append(name); 22197 append(`")) `); 22198 append(name); 22199 append(";"); 22200 } 22201 22202 return code[0 .. pos]; 22203 } 22204 22205 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) { 22206 mixin(staticForeachReplacement!Iface); 22207 22208 private __gshared void* libHandle; 22209 private __gshared bool attempted; 22210 22211 void loadDynamicLibrary() @nogc { 22212 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 22213 } 22214 22215 bool loadAttempted() { 22216 return attempted; 22217 } 22218 bool loadSuccessful() { 22219 return libHandle !is null; 22220 } 22221 22222 void loadDynamicLibraryForReal() { 22223 attempted = true; 22224 version(Posix) { 22225 import core.sys.posix.dlfcn; 22226 version(OSX) { 22227 version(X11) 22228 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 22229 else 22230 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 22231 } else { 22232 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 22233 if(libHandle is null) 22234 libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW); 22235 } 22236 22237 static void* loadsym(void* l, const char* name) { 22238 import core.stdc.stdlib; 22239 if(l is null) 22240 return &abort; 22241 return dlsym(l, name); 22242 } 22243 } else version(Windows) { 22244 import core.sys.windows.winbase; 22245 libHandle = LoadLibrary(library ~ ".dll"); 22246 static void* loadsym(void* l, const char* name) { 22247 import core.stdc.stdlib; 22248 if(l is null) 22249 return &abort; 22250 return GetProcAddress(l, name); 22251 } 22252 } 22253 if(libHandle is null) { 22254 success = false; 22255 //throw new Exception("load failure of library " ~ library); 22256 } 22257 foreach(name; __traits(derivedMembers, Iface)) { 22258 mixin("alias tmp = " ~ name ~ ";"); 22259 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 22260 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 22261 } 22262 } 22263 22264 void unloadDynamicLibrary() { 22265 version(Posix) { 22266 import core.sys.posix.dlfcn; 22267 dlclose(libHandle); 22268 } else version(Windows) { 22269 import core.sys.windows.winbase; 22270 FreeLibrary(libHandle); 22271 } 22272 foreach(name; __traits(derivedMembers, Iface)) 22273 mixin(name ~ " = null;"); 22274 } 22275 } 22276 22277 /+ 22278 The GC can be called from any thread, and a lot of cleanup must be done 22279 on the gui thread. Since the GC can interrupt any locks - including being 22280 triggered inside a critical section - it is vital to avoid deadlocks to get 22281 these functions called from the right place. 22282 22283 If the buffer overflows, things are going to get leaked. I'm kinda ok with that 22284 right now. 22285 22286 The cleanup function is run when the event loop gets around to it, which is just 22287 whenever there's something there after it has been woken up for other work. It does 22288 NOT wake up the loop itself - can't risk doing that from inside the GC in another thread. 22289 (Well actually it might be ok but i don't wanna mess with it right now.) 22290 +/ 22291 private struct CleanupQueue { 22292 import core.stdc.stdlib; 22293 22294 void queue(alias func, T...)(T args) { 22295 static struct Args { 22296 T args; 22297 } 22298 static struct RealJob { 22299 Job j; 22300 Args a; 22301 } 22302 static void call(Job* data) { 22303 auto rj = cast(RealJob*) data; 22304 func(rj.a.args); 22305 } 22306 22307 RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof); 22308 thing.j.call = &call; 22309 thing.a.args = args; 22310 22311 buffer[tail++] = cast(Job*) thing; 22312 22313 // FIXME: set overflowed 22314 } 22315 22316 void process() { 22317 const tail = this.tail; 22318 22319 while(tail != head) { 22320 Job* job = cast(Job*) buffer[head++]; 22321 job.call(job); 22322 free(job); 22323 } 22324 22325 if(overflowed) 22326 throw new Exception("cleanup overflowed"); 22327 } 22328 22329 private: 22330 22331 ubyte tail; // must ONLY be written by queue 22332 ubyte head; // must ONLY be written by process 22333 bool overflowed; 22334 22335 static struct Job { 22336 void function(Job*) call; 22337 } 22338 22339 void*[256] buffer; 22340 } 22341 private __gshared CleanupQueue cleanupQueue; 22342 22343 version(X11) 22344 /++ 22345 Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"]. 22346 22347 $(WARNING 22348 This function is exempted from stability guarantees. 22349 ) 22350 +/ 22351 float customScalingFactorForMonitor(int monitorNumber) { 22352 import core.stdc.stdlib; 22353 auto val = getenv("ARSD_SCALING_FACTOR"); 22354 22355 // FIXME: maybe we should assume a default nbased on the dpi thing if this isn't given 22356 if(val is null) 22357 return 1.0; 22358 22359 char[16] buffer = 0; 22360 int pos; 22361 22362 const(char)* at = val; 22363 22364 foreach(item; 0 .. monitorNumber + 1) { 22365 if(*at == 0) 22366 break; // reuse the last number when we at the end of the string 22367 pos = 0; 22368 while(pos + 1 < buffer.length && *at && *at != ';') { 22369 buffer[pos++] = *at; 22370 at++; 22371 } 22372 if(*at) 22373 at++; // skip the semicolon 22374 buffer[pos] = 0; 22375 } 22376 22377 //sdpyPrintDebugString(buffer[0 .. pos]); 22378 22379 import core.stdc.math; 22380 auto f = atof(buffer.ptr); 22381 22382 if(f <= 0.0 || isnan(f) || isinf(f)) 22383 return 1.0; 22384 22385 return f; 22386 } 22387 22388 void guiAbortProcess(string msg) { 22389 import core.stdc.stdlib; 22390 version(Windows) { 22391 WCharzBuffer t = WCharzBuffer(msg); 22392 MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0); 22393 } else { 22394 import core.stdc.stdio; 22395 fwrite(msg.ptr, 1, msg.length, stderr); 22396 msg = "\n"; 22397 fwrite(msg.ptr, 1, msg.length, stderr); 22398 fflush(stderr); 22399 } 22400 22401 abort(); 22402 } 22403 22404 private int minInternal(int a, int b) { 22405 return (a < b) ? a : b; 22406 } 22407 22408 private alias scriptable = arsd_jsvar_compatible;